Greasy Fork

Greasy Fork is available in English.

mock-monkey

浏览器本地接口 Mock 的 Tampermonkey 脚本

当前为 2026-03-11 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         mock-monkey
// @namespace    https://github.com/ikaven1024/
// @version      1.0.0
// @description  浏览器本地接口 Mock 的 Tampermonkey 脚本
// @description:en  A browser-native API mocking Tampermonkey script that requires no backend
// @author       ikaven1024
// @license      MIT
// @homepageURL  https://github.com/ikaven1024/mock-monkey
// @supportURL   https://github.com/ikaven1024/mock-monkey/issues
// @match        *://*/*
// @grant        none
// @run-at       document-end
// @icon         data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEwMCIgaGVpZ2h0PSIxMTAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogPGc+CiAgPHRpdGxlPkxheWVyIDE8L3RpdGxlPgogIDxwYXRoIGQ9Im03MzQsNjNjLTUuMTU2LDguOTE5MiAtMTMuMjUsMTYuNDY5NCAtMjEsMjMuMTMwNGMtMjIuMTMzLDE5LjAyMTYgLTQ4LjYwNSwzMC45NTE2IC03NywzNy4wNzk2Yy00MC45NTksOC44MzkgLTgyLjUzNSw2LjA0IC0xMjQsOC44NzljLTMyLjk5NSwyLjI2IC02Ny4wMjEsOS45NzcgLTk4LDIxLjQ0MWMtODMuNTA0LDMwLjkgLTE1Mi4zNzEsOTYuMjg5IC0xOTAuMjE5LDE3Ni40N2MtOS4yMzEsMTkuNTU1IC0xNi4zMDQsNDAuOTQyIC0yMS4xMyw2MmMtMS41ODksNi45MzMgLTMuMDM5LDEzLjk4NSAtNC4yMTYsMjFjLTAuNTEyLDMuMDUgLTAuMzc1LDcuMDEyIC0yLjM3Myw5LjU2NmMtMy4zODMsNC4zMjIgLTEyLjI3LDIuMTQgLTE3LjA2MiwyLjYwNGMtMTMuNzQ1LDEuMzMgLTI4LjExLDUuNzQyIC00MSwxMC41MTFjLTM0LjIxNywxMi42NjIgLTY0LjQ5NTQsMzcuODI1IC04NS41NzI1LDY3LjMxOWMtNDAuOTQxNCw1Ny4yOSAtNDcuNTc4MzgsMTM3LjE1MyAtMTguMTE4MSwyMDFjMTQuMTkzMSwzMC43NiAzNy4xNjg3LDU3LjkwNCA2NC42OTA2LDc3LjU3M2MyMi4wNCwxNS43NSA0OC4zMDQsMjguOTcxIDc1LDM0LjQyM2MyMS44OTEsNC40NzEgNDMuNzg1LDQuMDA0IDY2LDQuMDA0Yy0yLjEzOSwtMTEuNTIgMCwtMjQuMjM2IDAsLTM2YzAsLTIzLjM2MSAtMC41ODcsLTQ2LjYyMyAtMC4wMTUsLTcwYzAuNjIsLTI1LjMgMC4wMTUsLTUwLjY5MiAwLjAxNSwtNzZjMCwtMTUuNjk4IC0wLjYzMywtMzEuNDk5IDIuMjYxLC00N2M2LjExLC0zMi43MzUgMjIuNzQ3LC02Mi4wMjEgNDYuNzM5LC04NC45NjFjNTAuMjQ1LC00OC4wNDEgMTMxLjQ3MywtNTYuNDUxIDE4OSwtMTYuMDAzYzIyLjUyMiwxNS44MzYgNDAuMzUzLDM4LjAwNyA1MS43NjksNjIuOTY0YzYuODg0LDE1LjA0OSAxMy41MTMsMzMuMzE5IDE0LjIzMSw1MGMyLjU1NywtNC44MDggMi40ODUsLTEwLjcyOSAzLjY2NSwtMTZjMS43MywtNy43MjUgNC4zMiwtMTUuNTY0IDcuMDMsLTIzYzguMTc1LC0yMi40MjYgMjIuODgsLTQzLjY3OSA0MC4zMDUsLTU5LjkxMWM0My4zMTgsLTQwLjM1MSAxMDkuOSwtNTEuNDA0IDE2NCwtMjYuOTk0YzIxLjgzNCw5Ljg1MSA0MC44NDYsMjUuMjQ5IDU1LjgsNDMuOTA1YzcuNDY2LDkuMzE0IDE0LjczLDE5LjE2NiAxOS44OCwzMWM4LjUxNSwxNy45MTQgMTQuMjYsMzcuMjIyIDE2LjE1LDU3YzEuOTA5LDE5Ljk3NCAwLjE3LDQwLjkzIDAuMTcsNjFsMCwxNTJjMTcuNDM0LDAgMzUuNzU0LDAuMzY3IDUzLC0yLjI5NmM0MS4yNTUsLTYuMzcyIDgyLjQyMSwtMjcuMzA0IDExMS45MSwtNTYuNzkzYzYwLjg4LC02MC44OCA3OS4xNCwtMTYxLjYyOCAzNS43NSwtMjM3LjkxMWMtMTguMTYsLTMxLjkzMyAtNDQuNzMsLTU4LjkxMiAtNzYuNjYsLTc3LjEyN2MtMjQuODI3LC0xNC4xNjUgLTUyLjU4NiwtMjEuODM4IC04MSwtMjMuNzg0Yy05LjQyMywtMC42NDUgLTE4LjY0NCwwLjcxOCAtMjgsMC45MTFjLTAuODg1LC0yMC4zMTYgLTcuNTg3LC00MS45MzcgLTE0LjMwOSwtNjFjLTE2LjkwMiwtNDcuOTMzIC00My42ODMsLTkwLjk5NCAtNzkuNjkxLC0xMjdjLTEwLjY1MiwtMTAuNjUxIC0yMS45NzMsLTIwLjQxNSAtMzQsLTI5LjQ3NWMtNy4wMTIsLTUuMjgxIC0xMy44NzksLTExLjAyMSAtMjIsLTE0LjUyNWMzLjkzMSwtMTIuMjkyIDUuNjc3LC0yNS4yOTIgNy43NTQsLTM4YzMuMzc1LC0yMC42NSA2LjI2LC00MS4yNCA4Ljk2NCwtNjJjMS4yNTUsLTkuNjI3MSAxLjAyMiwtMTkuNTMzNiAzLjI4MiwtMjlsLTIsMG0tMzUwLDUxMWMxLjY0NCw4Ljg1NSAxLDE4LjAyMyAxLDI3bDAsNDRsMCwxMzljMzEuNjgzLDAgNjQuNDc3LC0zLjk4NCA5NiwwbDAsLTIxMGwtOTcsMG0yMjUsMjEwbDcxLDBsMTcsMGMyLjIzNiwtMC4wMDUgNS41MDgsMC40NjggNy4zOTcsLTEuMDI4YzMuNDcsLTIuNzQ4IDAuOTQxLC0xMC4zNDEgMC42OTIsLTEzLjk3MmMtMC44MzcsLTEyLjIyNCAtMC4wODksLTI0Ljc0NiAtMC4wODksLTM3bDAsLTE1OGwtNzAsMGwtMTgsMGMtMi4yMTgsMC4wMDUgLTUuNTI4LC0wLjQ3OCAtNy4zOTMsMS4wMjhjLTIuODM4LDIuMjkxIC0wLjYyMyw5Ljc0OCAtMC42MDgsMTIuOTcyYzAuMDYxLDEyLjY2NiAwLjAwMSwyNS4zMzQgMC4wMDEsMzhsMCwxNTh6IiBmaWxsPSIjOEQ1NTI0IiBpZD0ic3ZnXzIiLz4KIDwvZz4KCjwvc3ZnPg==
// ==/UserScript==


          (function() {
            var _this = this;
            /*! mockjs 14-12-2015 16:19:19 */
/*! src/mock-prefix.js */
/*!
    Mock - 模拟请求 & 模拟数据
    https://github.com/nuysoft/Mock
    墨智 [email protected]
*/
(function(undefined) {
    var Mock = {
        version: "0.1.11",
        _mocked: {}
    };
    /*! src/util.js */
    var Util = function() {
        var Util = {};
        Util.extend = function extend() {
            var target = arguments[0] || {}, i = 1, length = arguments.length, options, name, src, copy, clone;
            if (length === 1) {
                target = this;
                i = 0;
            }
            for (;i < length; i++) {
                options = arguments[i];
                if (!options) continue;
                for (name in options) {
                    src = target[name];
                    copy = options[name];
                    if (target === copy) continue;
                    if (copy === undefined) continue;
                    if (Util.isArray(copy) || Util.isObject(copy)) {
                        if (Util.isArray(copy)) clone = src && Util.isArray(src) ? src : [];
                        if (Util.isObject(copy)) clone = src && Util.isObject(src) ? src : {};
                        target[name] = Util.extend(clone, copy);
                    } else {
                        target[name] = copy;
                    }
                }
            }
            return target;
        };
        Util.each = function each(obj, iterator, context) {
            var i, key;
            if (this.type(obj) === "number") {
                for (i = 0; i < obj; i++) {
                    iterator(i, i);
                }
            } else if (obj.length === +obj.length) {
                for (i = 0; i < obj.length; i++) {
                    if (iterator.call(context, obj[i], i, obj) === false) break;
                }
            } else {
                for (key in obj) {
                    if (iterator.call(context, obj[key], key, obj) === false) break;
                }
            }
        };
        Util.type = function type(obj) {
            return obj === null || obj === undefined ? String(obj) : Object.prototype.toString.call(obj).match(/\[object (\w+)\]/)[1].toLowerCase();
        };
        Util.each("String Object Array RegExp Function".split(" "), function(value) {
            Util["is" + value] = function(obj) {
                return Util.type(obj) === value.toLowerCase();
            };
        });
        Util.isObjectOrArray = function(value) {
            return Util.isObject(value) || Util.isArray(value);
        };
        Util.isNumeric = function(value) {
            return !isNaN(parseFloat(value)) && isFinite(value);
        };
        Util.keys = function(obj) {
            var keys = [];
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) keys.push(key);
            }
            return keys;
        };
        Util.values = function(obj) {
            var values = [];
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) values.push(obj[key]);
            }
            return values;
        };
        Util.heredoc = function heredoc(fn) {
            return fn.toString().replace(/^[^\/]+\/\*!?/, "").replace(/\*\/[^\/]+$/, "").replace(/^[\s\xA0]+/, "").replace(/[\s\xA0]+$/, "");
        };
        Util.noop = function() {};
        return Util;
    }();
    /*! src/random.js */
    var Random = function() {
        var Random = {
            extend: Util.extend
        };
        Random.extend({
            "boolean": function(min, max, cur) {
                if (cur !== undefined) {
                    min = typeof min !== "undefined" && !isNaN(min) ? parseInt(min, 10) : 1;
                    max = typeof max !== "undefined" && !isNaN(max) ? parseInt(max, 10) : 1;
                    return Math.random() > 1 / (min + max) * min ? !cur : cur;
                }
                return Math.random() >= .5;
            },
            bool: function(min, max, cur) {
                return this.boolean(min, max, cur);
            },
            natural: function(min, max) {
                min = typeof min !== "undefined" ? parseInt(min, 10) : 0;
                max = typeof max !== "undefined" ? parseInt(max, 10) : 9007199254740992;
                return Math.round(Math.random() * (max - min)) + min;
            },
            integer: function(min, max) {
                min = typeof min !== "undefined" ? parseInt(min, 10) : -9007199254740992;
                max = typeof max !== "undefined" ? parseInt(max, 10) : 9007199254740992;
                return Math.round(Math.random() * (max - min)) + min;
            },
            "int": function(min, max) {
                return this.integer(min, max);
            },
            "float": function(min, max, dmin, dmax) {
                dmin = dmin === undefined ? 0 : dmin;
                dmin = Math.max(Math.min(dmin, 17), 0);
                dmax = dmax === undefined ? 17 : dmax;
                dmax = Math.max(Math.min(dmax, 17), 0);
                var ret = this.integer(min, max) + ".";
                for (var i = 0, dcount = this.natural(dmin, dmax); i < dcount; i++) {
                    ret += this.character("number");
                }
                return parseFloat(ret, 10);
            },
            character: function(pool) {
                var pools = {
                    lower: "abcdefghijklmnopqrstuvwxyz",
                    upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
                    number: "0123456789",
                    symbol: "!@#$%^&*()[]"
                };
                pools.alpha = pools.lower + pools.upper;
                pools["undefined"] = pools.lower + pools.upper + pools.number + pools.symbol;
                pool = pools[("" + pool).toLowerCase()] || pool;
                return pool.charAt(Random.natural(0, pool.length - 1));
            },
            "char": function(pool) {
                return this.character(pool);
            },
            string: function(pool, min, max) {
                var length;
                if (arguments.length === 3) {
                    length = Random.natural(min, max);
                }
                if (arguments.length === 2) {
                    if (typeof arguments[0] === "string") {
                        length = min;
                    } else {
                        length = Random.natural(pool, min);
                        pool = undefined;
                    }
                }
                if (arguments.length === 1) {
                    length = pool;
                    pool = undefined;
                }
                if (arguments.length === 0) {
                    length = Random.natural(3, 7);
                }
                var text = "";
                for (var i = 0; i < length; i++) {
                    text += Random.character(pool);
                }
                return text;
            },
            str: function(pool, min, max) {
                return this.string(pool, min, max);
            },
            range: function(start, stop, step) {
                if (arguments.length <= 1) {
                    stop = start || 0;
                    start = 0;
                }
                step = arguments[2] || 1;
                start = +start, stop = +stop, step = +step;
                var len = Math.max(Math.ceil((stop - start) / step), 0);
                var idx = 0;
                var range = new Array(len);
                while (idx < len) {
                    range[idx++] = start;
                    start += step;
                }
                return range;
            }
        });
        Random.extend({
            patternLetters: {
                yyyy: "getFullYear",
                yy: function(date) {
                    return ("" + date.getFullYear()).slice(2);
                },
                y: "yy",
                MM: function(date) {
                    var m = date.getMonth() + 1;
                    return m < 10 ? "0" + m : m;
                },
                M: function(date) {
                    return date.getMonth() + 1;
                },
                dd: function(date) {
                    var d = date.getDate();
                    return d < 10 ? "0" + d : d;
                },
                d: "getDate",
                HH: function(date) {
                    var h = date.getHours();
                    return h < 10 ? "0" + h : h;
                },
                H: "getHours",
                hh: function(date) {
                    var h = date.getHours() % 12;
                    return h < 10 ? "0" + h : h;
                },
                h: function(date) {
                    return date.getHours() % 12;
                },
                mm: function(date) {
                    var m = date.getMinutes();
                    return m < 10 ? "0" + m : m;
                },
                m: "getMinutes",
                ss: function(date) {
                    var s = date.getSeconds();
                    return s < 10 ? "0" + s : s;
                },
                s: "getSeconds",
                SS: function(date) {
                    var ms = date.getMilliseconds();
                    return ms < 10 && "00" + ms || ms < 100 && "0" + ms || ms;
                },
                S: "getMilliseconds",
                A: function(date) {
                    return date.getHours() < 12 ? "AM" : "PM";
                },
                a: function(date) {
                    return date.getHours() < 12 ? "am" : "pm";
                },
                T: "getTime"
            }
        });
        Random.extend({
            rformat: new RegExp(function() {
                var re = [];
                for (var i in Random.patternLetters) re.push(i);
                return "(" + re.join("|") + ")";
            }(), "g"),
            format: function(date, format) {
                var patternLetters = Random.patternLetters, rformat = Random.rformat;
                return format.replace(rformat, function($0, flag) {
                    return typeof patternLetters[flag] === "function" ? patternLetters[flag](date) : patternLetters[flag] in patternLetters ? arguments.callee($0, patternLetters[flag]) : date[patternLetters[flag]]();
                });
            },
            randomDate: function(min, max) {
                min = min === undefined ? new Date(0) : min;
                max = max === undefined ? new Date() : max;
                return new Date(Math.random() * (max.getTime() - min.getTime()));
            },
            date: function(format) {
                format = format || "yyyy-MM-dd";
                return this.format(this.randomDate(), format);
            },
            time: function(format) {
                format = format || "HH:mm:ss";
                return this.format(this.randomDate(), format);
            },
            datetime: function(format) {
                format = format || "yyyy-MM-dd HH:mm:ss";
                return this.format(this.randomDate(), format);
            },
            now: function(unit, format) {
                if (arguments.length === 1) {
                    if (!/year|month|week|day|hour|minute|second|week/.test(unit)) {
                        format = unit;
                        unit = "";
                    }
                }
                unit = (unit || "").toLowerCase();
                format = format || "yyyy-MM-dd HH:mm:ss";
                var date = new Date();
                switch (unit) {
                  case "year":
                    date.setMonth(0);

                  case "month":
                    date.setDate(1);

                  case "week":
                  case "day":
                    date.setHours(0);

                  case "hour":
                    date.setMinutes(0);

                  case "minute":
                    date.setSeconds(0);

                  case "second":
                    date.setMilliseconds(0);
                }
                switch (unit) {
                  case "week":
                    date.setDate(date.getDate() - date.getDay());
                }
                return this.format(date, format);
            }
        });
        Random.extend({
            ad_size: [ "300x250", "250x250", "240x400", "336x280", "180x150", "720x300", "468x60", "234x60", "88x31", "120x90", "120x60", "120x240", "125x125", "728x90", "160x600", "120x600", "300x600" ],
            screen_size: [ "320x200", "320x240", "640x480", "800x480", "800x480", "1024x600", "1024x768", "1280x800", "1440x900", "1920x1200", "2560x1600" ],
            video_size: [ "720x480", "768x576", "1280x720", "1920x1080" ],
            image: function(size, background, foreground, format, text) {
                if (arguments.length === 4) {
                    text = format;
                    format = undefined;
                }
                if (arguments.length === 3) {
                    text = foreground;
                    foreground = undefined;
                }
                if (!size) size = this.pick(this.ad_size);
                if (background && ~background.indexOf("#")) background = background.slice(1);
                if (foreground && ~foreground.indexOf("#")) foreground = foreground.slice(1);
                return "http://dummyimage.com/" + size + (background ? "/" + background : "") + (foreground ? "/" + foreground : "") + (format ? "." + format : "") + (text ? "&text=" + text : "");
            },
            img: function() {
                return this.image.apply(this, arguments);
            }
        });
        Random.extend({
            brandColors: {
                "4ormat": "#fb0a2a",
                "500px": "#02adea",
                "About.me (blue)": "#00405d",
                "About.me (yellow)": "#ffcc33",
                Addvocate: "#ff6138",
                Adobe: "#ff0000",
                Aim: "#fcd20b",
                Amazon: "#e47911",
                Android: "#a4c639",
                "Angie's List": "#7fbb00",
                AOL: "#0060a3",
                Atlassian: "#003366",
                Behance: "#053eff",
                "Big Cartel": "#97b538",
                bitly: "#ee6123",
                Blogger: "#fc4f08",
                Boeing: "#0039a6",
                "Booking.com": "#003580",
                Carbonmade: "#613854",
                Cheddar: "#ff7243",
                "Code School": "#3d4944",
                Delicious: "#205cc0",
                Dell: "#3287c1",
                Designmoo: "#e54a4f",
                Deviantart: "#4e6252",
                "Designer News": "#2d72da",
                Devour: "#fd0001",
                DEWALT: "#febd17",
                "Disqus (blue)": "#59a3fc",
                "Disqus (orange)": "#db7132",
                Dribbble: "#ea4c89",
                Dropbox: "#3d9ae8",
                Drupal: "#0c76ab",
                Dunked: "#2a323a",
                eBay: "#89c507",
                Ember: "#f05e1b",
                Engadget: "#00bdf6",
                Envato: "#528036",
                Etsy: "#eb6d20",
                Evernote: "#5ba525",
                "Fab.com": "#dd0017",
                Facebook: "#3b5998",
                Firefox: "#e66000",
                "Flickr (blue)": "#0063dc",
                "Flickr (pink)": "#ff0084",
                Forrst: "#5b9a68",
                Foursquare: "#25a0ca",
                Garmin: "#007cc3",
                GetGlue: "#2d75a2",
                Gimmebar: "#f70078",
                GitHub: "#171515",
                "Google Blue": "#0140ca",
                "Google Green": "#16a61e",
                "Google Red": "#dd1812",
                "Google Yellow": "#fcca03",
                "Google+": "#dd4b39",
                Grooveshark: "#f77f00",
                Groupon: "#82b548",
                "Hacker News": "#ff6600",
                HelloWallet: "#0085ca",
                "Heroku (light)": "#c7c5e6",
                "Heroku (dark)": "#6567a5",
                HootSuite: "#003366",
                Houzz: "#73ba37",
                HTML5: "#ec6231",
                IKEA: "#ffcc33",
                IMDb: "#f3ce13",
                Instagram: "#3f729b",
                Intel: "#0071c5",
                Intuit: "#365ebf",
                Kickstarter: "#76cc1e",
                kippt: "#e03500",
                Kodery: "#00af81",
                LastFM: "#c3000d",
                LinkedIn: "#0e76a8",
                Livestream: "#cf0005",
                Lumo: "#576396",
                Mixpanel: "#a086d3",
                Meetup: "#e51937",
                Nokia: "#183693",
                NVIDIA: "#76b900",
                Opera: "#cc0f16",
                Path: "#e41f11",
                "PayPal (dark)": "#1e477a",
                "PayPal (light)": "#3b7bbf",
                Pinboard: "#0000e6",
                Pinterest: "#c8232c",
                PlayStation: "#665cbe",
                Pocket: "#ee4056",
                Prezi: "#318bff",
                Pusha: "#0f71b4",
                Quora: "#a82400",
                "QUOTE.fm": "#66ceff",
                Rdio: "#008fd5",
                Readability: "#9c0000",
                "Red Hat": "#cc0000",
                Resource: "#7eb400",
                Rockpack: "#0ba6ab",
                Roon: "#62b0d9",
                RSS: "#ee802f",
                Salesforce: "#1798c1",
                Samsung: "#0c4da2",
                Shopify: "#96bf48",
                Skype: "#00aff0",
                Snagajob: "#f47a20",
                Softonic: "#008ace",
                SoundCloud: "#ff7700",
                "Space Box": "#f86960",
                Spotify: "#81b71a",
                Sprint: "#fee100",
                Squarespace: "#121212",
                StackOverflow: "#ef8236",
                Staples: "#cc0000",
                "Status Chart": "#d7584f",
                Stripe: "#008cdd",
                StudyBlue: "#00afe1",
                StumbleUpon: "#f74425",
                "T-Mobile": "#ea0a8e",
                Technorati: "#40a800",
                "The Next Web": "#ef4423",
                Treehouse: "#5cb868",
                Trulia: "#5eab1f",
                Tumblr: "#34526f",
                "Twitch.tv": "#6441a5",
                Twitter: "#00acee",
                TYPO3: "#ff8700",
                Ubuntu: "#dd4814",
                Ustream: "#3388ff",
                Verizon: "#ef1d1d",
                Vimeo: "#86c9ef",
                Vine: "#00a478",
                Virb: "#06afd8",
                "Virgin Media": "#cc0000",
                Wooga: "#5b009c",
                "WordPress (blue)": "#21759b",
                "WordPress (orange)": "#d54e21",
                "WordPress (grey)": "#464646",
                Wunderlist: "#2b88d9",
                XBOX: "#9bc848",
                XING: "#126567",
                "Yahoo!": "#720e9e",
                Yandex: "#ffcc00",
                Yelp: "#c41200",
                YouTube: "#c4302b",
                Zalongo: "#5498dc",
                Zendesk: "#78a300",
                Zerply: "#9dcc7a",
                Zootool: "#5e8b1d"
            },
            brands: function() {
                var brands = [];
                for (var b in this.brandColors) {
                    brands.push(b);
                }
                return brands;
            },
            dataImage: function(size, text) {
                var canvas = typeof document !== "undefined" && document.createElement("canvas"), ctx = canvas && canvas.getContext && canvas.getContext("2d");
                if (!canvas || !ctx) return "";
                if (!size) size = this.pick(this.ad_size);
                text = text !== undefined ? text : size;
                size = size.split("x");
                var width = parseInt(size[0], 10), height = parseInt(size[1], 10), background = this.brandColors[this.pick(this.brands())], foreground = "#FFF", text_height = 14, font = "sans-serif";
                canvas.width = width;
                canvas.height = height;
                ctx.textAlign = "center";
                ctx.textBaseline = "middle";
                ctx.fillStyle = background;
                ctx.fillRect(0, 0, width, height);
                ctx.fillStyle = foreground;
                ctx.font = "bold " + text_height + "px " + font;
                ctx.fillText(text, width / 2, height / 2, width);
                return canvas.toDataURL("image/png");
            }
        });
        Random.extend({
            color: function() {
                var colour = Math.floor(Math.random() * (16 * 16 * 16 * 16 * 16 * 16 - 1)).toString(16);
                colour = "#" + ("000000" + colour).slice(-6);
                return colour;
            }
        });
        Random.extend({
            capitalize: function(word) {
                return (word + "").charAt(0).toUpperCase() + (word + "").substr(1);
            },
            upper: function(str) {
                return (str + "").toUpperCase();
            },
            lower: function(str) {
                return (str + "").toLowerCase();
            },
            pick: function(arr) {
                arr = arr || [];
                return arr[this.natural(0, arr.length - 1)];
            },
            shuffle: function(arr) {
                arr = arr || [];
                var old = arr.slice(0), result = [], index = 0, length = old.length;
                for (var i = 0; i < length; i++) {
                    index = this.natural(0, old.length - 1);
                    result.push(old[index]);
                    old.splice(index, 1);
                }
                return result;
            }
        });
        Random.extend({
            paragraph: function(min, max) {
                var len;
                if (arguments.length === 0) len = Random.natural(3, 7);
                if (arguments.length === 1) len = max = min;
                if (arguments.length === 2) {
                    min = parseInt(min, 10);
                    max = parseInt(max, 10);
                    len = Random.natural(min, max);
                }
                var arr = [];
                for (var i = 0; i < len; i++) {
                    arr.push(Random.sentence());
                }
                return arr.join(" ");
            },
            sentence: function(min, max) {
                var len;
                if (arguments.length === 0) len = Random.natural(12, 18);
                if (arguments.length === 1) len = max = min;
                if (arguments.length === 2) {
                    min = parseInt(min, 10);
                    max = parseInt(max, 10);
                    len = Random.natural(min, max);
                }
                var arr = [];
                for (var i = 0; i < len; i++) {
                    arr.push(Random.word());
                }
                return Random.capitalize(arr.join(" ")) + ".";
            },
            word: function(min, max) {
                var len;
                if (arguments.length === 0) len = Random.natural(3, 10);
                if (arguments.length === 1) len = max = min;
                if (arguments.length === 2) {
                    min = parseInt(min, 10);
                    max = parseInt(max, 10);
                    len = Random.natural(min, max);
                }
                var result = "";
                for (var i = 0; i < len; i++) {
                    result += Random.character("lower");
                }
                return result;
            },
            title: function(min, max) {
                var len, result = [];
                if (arguments.length === 0) len = Random.natural(3, 7);
                if (arguments.length === 1) len = max = min;
                if (arguments.length === 2) {
                    min = parseInt(min, 10);
                    max = parseInt(max, 10);
                    len = Random.natural(min, max);
                }
                for (var i = 0; i < len; i++) {
                    result.push(this.capitalize(this.word()));
                }
                return result.join(" ");
            }
        });
        Random.extend({
            first: function() {
                var names = [ "James", "John", "Robert", "Michael", "William", "David", "Richard", "Charles", "Joseph", "Thomas", "Christopher", "Daniel", "Paul", "Mark", "Donald", "George", "Kenneth", "Steven", "Edward", "Brian", "Ronald", "Anthony", "Kevin", "Jason", "Matthew", "Gary", "Timothy", "Jose", "Larry", "Jeffrey", "Frank", "Scott", "Eric" ].concat([ "Mary", "Patricia", "Linda", "Barbara", "Elizabeth", "Jennifer", "Maria", "Susan", "Margaret", "Dorothy", "Lisa", "Nancy", "Karen", "Betty", "Helen", "Sandra", "Donna", "Carol", "Ruth", "Sharon", "Michelle", "Laura", "Sarah", "Kimberly", "Deborah", "Jessica", "Shirley", "Cynthia", "Angela", "Melissa", "Brenda", "Amy", "Anna" ]);
                return this.pick(names);
            },
            last: function() {
                var names = [ "Smith", "Johnson", "Williams", "Brown", "Jones", "Miller", "Davis", "Garcia", "Rodriguez", "Wilson", "Martinez", "Anderson", "Taylor", "Thomas", "Hernandez", "Moore", "Martin", "Jackson", "Thompson", "White", "Lopez", "Lee", "Gonzalez", "Harris", "Clark", "Lewis", "Robinson", "Walker", "Perez", "Hall", "Young", "Allen" ];
                return this.pick(names);
            },
            name: function(middle) {
                return this.first() + " " + (middle ? this.first() + " " : "") + this.last();
            },
            chineseName: function(count) {
                var surnames = "赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐".split("");
                var forenames = "贵福生龙元全国胜学祥才发武新利清飞彬富顺信子杰涛昌成康星光天达安岩中茂进林有坚和彪博绍功松善厚庆磊民友裕河哲江超浩亮政谦亨奇固之轮翰朗伯宏言若鸣朋斌梁栋维启克伦翔旭鹏月莺媛艳瑞凡佳嘉琼勤珍贞莉桂娣叶璧璐娅琦晶妍茜秋珊莎锦黛青倩婷姣婉娴瑾颖露瑶怡婵雁蓓".split("");
                if (typeof count !== "number") {
                    count = Math.random() > .66 ? 2 : 3;
                }
                var surname = this.pick(surnames);
                var forename = "";
                count = count - 1;
                for (var i = 0; i < count; i++) {
                    forename += this.pick(forenames);
                }
                return surname + forename;
            },
            cname: function(count) {
                return this.chineseName(count);
            }
        });
        Random.extend({
            url: function() {
                return "http://" + this.domain() + "/" + this.word();
            },
            domain: function(tld) {
                return this.word() + "." + (tld || this.tld());
            },
            email: function(domain) {
                return this.character("lower") + "." + this.last().toLowerCase() + "@" + this.last().toLowerCase() + "." + this.tld();
            },
            ip: function() {
                return this.natural(0, 255) + "." + this.natural(0, 255) + "." + this.natural(0, 255) + "." + this.natural(0, 255);
            },
            tlds: [ "com", "org", "edu", "gov", "co.uk", "net", "io" ],
            tld: function() {
                return this.pick(this.tlds);
            }
        });
        Random.extend({
            areas: [ "东北", "华北", "华东", "华中", "华南", "西南", "西北" ],
            area: function() {
                return this.pick(this.areas);
            },
            regions: [ "110000 北京市", "120000 天津市", "130000 河北省", "140000 山西省", "150000 内蒙古自治区", "210000 辽宁省", "220000 吉林省", "230000 黑龙江省", "310000 上海市", "320000 江苏省", "330000 浙江省", "340000 安徽省", "350000 福建省", "360000 江西省", "370000 山东省", "410000 河南省", "420000 湖北省", "430000 湖南省", "440000 广东省", "450000 广西壮族自治区", "460000 海南省", "500000 重庆市", "510000 四川省", "520000 贵州省", "530000 云南省", "540000 西藏自治区", "610000 陕西省", "620000 甘肃省", "630000 青海省", "640000 宁夏回族自治区", "650000 新疆维吾尔自治区", "650000 新疆维吾尔自治区", "710000 台湾省", "810000 香港特别行政区", "820000 澳门特别行政区" ],
            region: function() {
                return this.pick(this.regions).split(" ")[1];
            },
            address: function() {},
            city: function() {},
            phone: function() {},
            areacode: function() {},
            street: function() {},
            street_suffixes: function() {},
            street_suffix: function() {},
            states: function() {},
            state: function() {},
            zip: function(len) {
                var zip = "";
                for (var i = 0; i < (len || 6); i++) zip += this.natural(0, 9);
                return zip;
            }
        });
        Random.extend({
            todo: function() {
                return "todo";
            }
        });
        Random.extend({
            d4: function() {
                return this.natural(1, 4);
            },
            d6: function() {
                return this.natural(1, 6);
            },
            d8: function() {
                return this.natural(1, 8);
            },
            d12: function() {
                return this.natural(1, 12);
            },
            d20: function() {
                return this.natural(1, 20);
            },
            d100: function() {
                return this.natural(1, 100);
            },
            guid: function() {
                var pool = "ABCDEF1234567890", guid = this.string(pool, 8) + "-" + this.string(pool, 4) + "-" + this.string(pool, 4) + "-" + this.string(pool, 4) + "-" + this.string(pool, 12);
                return guid;
            },
            id: function() {
                var id, sum = 0, rank = [ "7", "9", "10", "5", "8", "4", "2", "1", "6", "3", "7", "9", "10", "5", "8", "4", "2" ], last = [ "1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2" ];
                id = this.pick(this.regions).split(" ")[0] + this.date("yyyyMMdd") + this.string("number", 3);
                for (var i = 0; i < id.length; i++) {
                    sum += id[i] * rank[i];
                }
                id += last[sum % 11];
                return id;
            },
            autoIncrementInteger: 0,
            increment: function(step) {
                return this.autoIncrementInteger += +step || 1;
            },
            inc: function(step) {
                return this.increment(step);
            }
        });
        return Random;
    }();
    /*! src/mock.js */
    var rkey = /(.+)\|(?:\+(\d+)|([\+\-]?\d+-?[\+\-]?\d*)?(?:\.(\d+-?\d*))?)/, rrange = /([\+\-]?\d+)-?([\+\-]?\d+)?/, rplaceholder = /\\*@([^@#%&()\?\s\/\.]+)(?:\((.*?)\))?/g;
    Mock.extend = Util.extend;
    Mock.mock = function(rurl, rtype, template) {
        if (arguments.length === 1) {
            return Handle.gen(rurl);
        }
        if (arguments.length === 2) {
            template = rtype;
            rtype = undefined;
        }
        Mock._mocked[rurl + (rtype || "")] = {
            rurl: rurl,
            rtype: rtype,
            template: template
        };
        return Mock;
    };
    var Handle = {
        extend: Util.extend
    };
    Handle.rule = function(name) {
        name = (name || "") + "";
        var parameters = (name || "").match(rkey), range = parameters && parameters[3] && parameters[3].match(rrange), min = range && parseInt(range[1], 10), max = range && parseInt(range[2], 10), count = range ? !range[2] && parseInt(range[1], 10) || Random.integer(min, max) : 1, decimal = parameters && parameters[4] && parameters[4].match(rrange), dmin = decimal && parseInt(decimal[1], 10), dmax = decimal && parseInt(decimal[2], 10), dcount = decimal ? !decimal[2] && parseInt(decimal[1], 10) || Random.integer(dmin, dmax) : 0, point = parameters && parameters[4];
        return {
            parameters: parameters,
            range: range,
            min: min,
            max: max,
            count: count,
            decimal: decimal,
            dmin: dmin,
            dmax: dmax,
            dcount: dcount,
            point: point
        };
    };
    Handle.gen = function(template, name, context) {
        name = name = (name || "") + "";
        context = context || {};
        context = {
            path: context.path || [],
            templatePath: context.templatePath || [],
            currentContext: context.currentContext,
            templateCurrentContext: context.templateCurrentContext || template,
            root: context.root,
            templateRoot: context.templateRoot
        };
        var rule = Handle.rule(name);
        var type = Util.type(template);
        if (Handle[type]) {
            return Handle[type]({
                type: type,
                template: template,
                name: name,
                parsedName: name ? name.replace(rkey, "$1") : name,
                rule: rule,
                context: context
            });
        }
        return template;
    };
    Handle.extend({
        array: function(options) {
            var result = [], i, j;
            if (!options.rule.parameters) {
                for (i = 0; i < options.template.length; i++) {
                    options.context.path.push(i);
                    result.push(Handle.gen(options.template[i], i, {
                        currentContext: result,
                        templateCurrentContext: options.template,
                        path: options.context.path
                    }));
                    options.context.path.pop();
                }
            } else {
                if (options.rule.count === 1 && options.template.length > 1) {
                    options.context.path.push(options.name);
                    result = Random.pick(Handle.gen(options.template, undefined, {
                        currentContext: result,
                        templateCurrentContext: options.template,
                        path: options.context.path
                    }));
                    options.context.path.pop();
                } else {
                    for (i = 0; i < options.rule.count; i++) {
                        j = 0;
                        do {
                            result.push(Handle.gen(options.template[j++]));
                        } while (j < options.template.length);
                    }
                }
            }
            return result;
        },
        object: function(options) {
            var result = {}, keys, fnKeys, key, parsedKey, inc, i;
            if (options.rule.min) {
                keys = Util.keys(options.template);
                keys = Random.shuffle(keys);
                keys = keys.slice(0, options.rule.count);
                for (i = 0; i < keys.length; i++) {
                    key = keys[i];
                    parsedKey = key.replace(rkey, "$1");
                    options.context.path.push(parsedKey);
                    result[parsedKey] = Handle.gen(options.template[key], key, {
                        currentContext: result,
                        templateCurrentContext: options.template,
                        path: options.context.path
                    });
                    options.context.path.pop();
                }
            } else {
                keys = [];
                fnKeys = [];
                for (key in options.template) {
                    (typeof options.template[key] === "function" ? fnKeys : keys).push(key);
                }
                keys = keys.concat(fnKeys);
                for (i = 0; i < keys.length; i++) {
                    key = keys[i];
                    parsedKey = key.replace(rkey, "$1");
                    options.context.path.push(parsedKey);
                    result[parsedKey] = Handle.gen(options.template[key], key, {
                        currentContext: result,
                        templateCurrentContext: options.template,
                        path: options.context.path
                    });
                    options.context.path.pop();
                    inc = key.match(rkey);
                    if (inc && inc[2] && Util.type(options.template[key]) === "number") {
                        options.template[key] += parseInt(inc[2], 10);
                    }
                }
            }
            return result;
        },
        number: function(options) {
            var result, parts, i;
            if (options.rule.point) {
                options.template += "";
                parts = options.template.split(".");
                parts[0] = options.rule.range ? options.rule.count : parts[0];
                parts[1] = (parts[1] || "").slice(0, options.rule.dcount);
                for (i = 0; parts[1].length < options.rule.dcount; i++) {
                    parts[1] += Random.character("number");
                }
                result = parseFloat(parts.join("."), 10);
            } else {
                result = options.rule.range && !options.rule.parameters[2] ? options.rule.count : options.template;
            }
            return result;
        },
        "boolean": function(options) {
            var result;
            result = options.rule.parameters ? Random.bool(options.rule.min, options.rule.max, options.template) : options.template;
            return result;
        },
        string: function(options) {
            var result = "", i, placeholders, ph, phed;
            if (options.template.length) {
                for (i = 0; i < options.rule.count; i++) {
                    result += options.template;
                }
                placeholders = result.match(rplaceholder) || [];
                for (i = 0; i < placeholders.length; i++) {
                    ph = placeholders[i];
                    if (/^\\/.test(ph)) {
                        placeholders.splice(i--, 1);
                        continue;
                    }
                    phed = Handle.placeholder(ph, options.context.currentContext, options.context.templateCurrentContext);
                    if (placeholders.length === 1 && ph === result && typeof phed !== typeof result) {
                        result = phed;
                        break;
                    }
                    result = result.replace(ph, phed);
                }
            } else {
                result = options.rule.range ? Random.string(options.rule.count) : options.template;
            }
            return result;
        },
        "function": function(options) {
            return options.template.call(options.context.currentContext);
        }
    });
    Handle.extend({
        _all: function() {
            var re = {};
            for (var key in Random) re[key.toLowerCase()] = key;
            return re;
        },
        placeholder: function(placeholder, obj, templateContext) {
            rplaceholder.exec("");
            var parts = rplaceholder.exec(placeholder), key = parts && parts[1], lkey = key && key.toLowerCase(), okey = this._all()[lkey], params = parts && parts[2] || "";
            try {
                params = eval("(function(){ return [].splice.call(arguments, 0 ) })(" + params + ")");
            } catch (error) {
                params = parts[2].split(/,\s*/);
            }
            if (obj && key in obj) return obj[key];
            if (templateContext && typeof templateContext === "object" && key in templateContext && placeholder !== templateContext[key]) {
                templateContext[key] = Handle.gen(templateContext[key], key, {
                    currentContext: obj,
                    templateCurrentContext: templateContext
                });
                return templateContext[key];
            }
            if (!(key in Random) && !(lkey in Random) && !(okey in Random)) return placeholder;
            for (var i = 0; i < params.length; i++) {
                rplaceholder.exec("");
                if (rplaceholder.test(params[i])) {
                    params[i] = Handle.placeholder(params[i], obj);
                }
            }
            var handle = Random[key] || Random[lkey] || Random[okey];
            switch (Util.type(handle)) {
              case "array":
                return Random.pick(handle);

              case "function":
                var re = handle.apply(Random, params);
                if (re === undefined) re = "";
                return re;
            }
        }
    });
    /*! src/mockjax.js */
    function find(options) {
        for (var sUrlType in Mock._mocked) {
            var item = Mock._mocked[sUrlType];
            if ((!item.rurl || match(item.rurl, options.url)) && (!item.rtype || match(item.rtype, options.type.toLowerCase()))) {
                return item;
            }
        }
        function match(expected, actual) {
            if (Util.type(expected) === "string") {
                return expected === actual;
            }
            if (Util.type(expected) === "regexp") {
                return expected.test(actual);
            }
        }
    }
    function convert(item, options) {
        return Util.isFunction(item.template) ? item.template(options) : Mock.mock(item.template);
    }
    Mock.mockjax = function mockjax(jQuery) {
        function mockxhr() {
            return {
                readyState: 4,
                status: 200,
                statusText: "",
                open: jQuery.noop,
                send: function() {
                    if (this.onload) this.onload();
                },
                setRequestHeader: jQuery.noop,
                getAllResponseHeaders: jQuery.noop,
                getResponseHeader: jQuery.noop,
                statusCode: jQuery.noop,
                abort: jQuery.noop
            };
        }
        function prefilter(options, originalOptions, jqXHR) {
            var item = find(options);
            if (item) {
                options.dataFilter = options.converters["text json"] = options.converters["text jsonp"] = options.converters["text script"] = options.converters["script json"] = function() {
                    return convert(item, options);
                };
                options.xhr = mockxhr;
                if (originalOptions.dataType !== "script") return "json";
            }
        }
        jQuery.ajaxPrefilter("json jsonp script", prefilter);
        return Mock;
    };
    if (typeof jQuery != "undefined") Mock.mockjax(jQuery);
    if (typeof Zepto != "undefined") {
        Mock.mockjax = function(Zepto) {
            var __original_ajax = Zepto.ajax;
            var xhr = {
                readyState: 4,
                responseText: "",
                responseXML: null,
                state: 2,
                status: 200,
                statusText: "success",
                timeoutTimer: null
            };
            Zepto.ajax = function(options) {
                var item = find(options);
                if (item) {
                    var data = convert(item, options);
                    if (options.success) options.success(data, xhr, options);
                    if (options.complete) options.complete(xhr.status, xhr, options);
                    return xhr;
                }
                return __original_ajax.call(Zepto, options);
            };
        };
        Mock.mockjax(Zepto);
    }
    if (typeof KISSY != "undefined" && KISSY.add) {
        Mock.mockjax = function mockjax(KISSY) {
            var _original_ajax = KISSY.io;
            var xhr = {
                readyState: 4,
                responseText: "",
                responseXML: null,
                state: 2,
                status: 200,
                statusText: "success",
                timeoutTimer: null
            };
            KISSY.io = function(options) {
                var item = find(options);
                if (item) {
                    var data = Mock.mock(item.template);
                    if (options.success) options.success(data, xhr, options);
                    if (options.complete) options.complete(xhr.status, xhr, options);
                    return xhr;
                }
                return _original_ajax.apply(this, arguments);
            };
            for (var name in _original_ajax) {
                KISSY.io[name] = _original_ajax[name];
            }
        };
    }
    /*! src/expose.js */
    Mock.Util = Util;
    Mock.Random = Random;
    Mock.heredoc = Util.heredoc;
    if (typeof module === "object" && module.exports) {
        module.exports = Mock;
    } else if (typeof define === "function" && define.amd) {
        define("mock", [], function() {
            return Mock;
        });
        define("mockjs", [], function() {
            return Mock;
        });
    } else if (typeof define === "function" && define.cmd) {
        define(function() {
            return Mock;
        });
    }
    this.Mock = Mock;
    this.Random = Random;
    if (typeof KISSY != "undefined") {
        Util.each([ "mock", "components/mock/", "mock/dist/mock", "gallery/Mock/0.1.9/" ], function register(name) {
            KISSY.add(name, function(S) {
                Mock.mockjax(S);
                return Mock;
            }, {
                requires: [ "ajax" ]
            });
        });
    }
    /*! src/mock4tpl.js */
    (function(undefined) {
        var Mock4Tpl = {
            version: "0.0.1"
        };
        if (!this.Mock) module.exports = Mock4Tpl;
        Mock.tpl = function(input, options, helpers, partials) {
            return Mock4Tpl.mock(input, options, helpers, partials);
        };
        Mock.parse = function(input) {
            return Handlebars.parse(input);
        };
        Mock4Tpl.mock = function(input, options, helpers, partials) {
            helpers = helpers ? Util.extend({}, helpers, Handlebars.helpers) : Handlebars.helpers;
            partials = partials ? Util.extend({}, partials, Handlebars.partials) : Handlebars.partials;
            return Handle.gen(input, null, options, helpers, partials);
        };
        var Handle = {
            debug: Mock4Tpl.debug || false,
            extend: Util.extend
        };
        Handle.gen = function(node, context, options, helpers, partials) {
            if (Util.isString(node)) {
                var ast = Handlebars.parse(node);
                options = Handle.parseOptions(node, options);
                var data = Handle.gen(ast, context, options, helpers, partials);
                return data;
            }
            context = context || [ {} ];
            options = options || {};
            if (this[node.type] === Util.noop) return;
            options.__path = options.__path || [];
            if (Mock4Tpl.debug || Handle.debug) {
                console.log();
                console.group("[" + node.type + "]", JSON.stringify(node));
                console.log("[options]", options.__path.length, JSON.stringify(options));
            }
            var preLength = options.__path.length;
            this[node.type](node, context, options, helpers, partials);
            options.__path.splice(preLength);
            if (Mock4Tpl.debug || Handle.debug) {
                console.groupEnd();
            }
            return context[context.length - 1];
        };
        Handle.parseOptions = function(input, options) {
            var rComment = /<!--\s*\n*Mock\s*\n*([\w\W]+?)\s*\n*-->/g;
            var comments = input.match(rComment), ret = {}, i, ma, option;
            for (i = 0; comments && i < comments.length; i++) {
                rComment.lastIndex = 0;
                ma = rComment.exec(comments[i]);
                if (ma) {
                    option = new Function("return " + ma[1]);
                    option = option();
                    Util.extend(ret, option);
                }
            }
            return Util.extend(ret, options);
        };
        Handle.val = function(name, options, context, def) {
            if (name !== options.__path[options.__path.length - 1]) throw new Error(name + "!==" + options.__path);
            if (Mock4Tpl.debug || Handle.debug) console.log("[options]", name, options.__path);
            if (def !== undefined) def = Mock.mock(def);
            if (options) {
                var mocked = Mock.mock(options);
                if (Util.isString(mocked)) return mocked;
                if (name in mocked) {
                    return mocked[name];
                }
            }
            if (Util.isArray(context[0])) return {};
            return def !== undefined ? def : name || Random.word();
        };
        Handle.program = function(node, context, options, helpers, partials) {
            for (var i = 0; i < node.statements.length; i++) {
                this.gen(node.statements[i], context, options, helpers, partials);
            }
        };
        Handle.mustache = function(node, context, options, helpers, partials) {
            var i, currentContext = context[0], contextLength = context.length;
            if (Util.type(currentContext) === "array") {
                currentContext.push({});
                currentContext = currentContext[currentContext.length - 1];
                context.unshift(currentContext);
            }
            if (node.isHelper || helpers && helpers[node.id.string]) {
                if (node.params.length === 0) {} else {
                    for (i = 0; i < node.params.length; i++) {
                        this.gen(node.params[i], context, options, helpers, partials);
                    }
                }
                if (node.hash) this.gen(node.hash, context, options, helpers, partials);
            } else {
                this.gen(node.id, context, options, helpers, partials);
            }
            if (context.length > contextLength) context.splice(0, context.length - contextLength);
        };
        Handle.block = function(node, context, options, helpers, partials) {
            var parts = node.mustache.id.parts, i, len, cur, val, type, currentContext = context[0], contextLength = context.length;
            if (node.inverse) {}
            if (node.mustache.isHelper || helpers && helpers[node.mustache.id.string]) {
                type = parts[0];
                val = (Helpers[type] || Helpers.custom).apply(this, arguments);
                currentContext = context[0];
            } else {
                for (i = 0; i < parts.length; i++) {
                    options.__path.push(parts[i]);
                    cur = parts[i];
                    val = this.val(cur, options, context, {});
                    currentContext[cur] = Util.isArray(val) && [] || val;
                    type = Util.type(currentContext[cur]);
                    if (type === "object" || type === "array") {
                        currentContext = currentContext[cur];
                        context.unshift(currentContext);
                    }
                }
            }
            if (node.program) {
                if (Util.type(currentContext) === "array") {
                    len = val.length || Random.integer(3, 7);
                    for (i = 0; i < len; i++) {
                        currentContext.push(typeof val[i] !== "undefined" ? val[i] : {});
                        options.__path.push("[]");
                        context.unshift(currentContext[currentContext.length - 1]);
                        this.gen(node.program, context, options, helpers, partials);
                        options.__path.pop();
                        context.shift();
                    }
                } else this.gen(node.program, context, options, helpers, partials);
            }
            if (context.length > contextLength) context.splice(0, context.length - contextLength);
        };
        Handle.hash = function(node, context, options, helpers, partials) {
            var pairs = node.pairs, pair, i, j;
            for (i = 0; i < pairs.length; i++) {
                pair = pairs[i];
                for (j = 1; j < pair.length; j++) {
                    this.gen(pair[j], context, options, helpers, partials);
                }
            }
        };
        Handle.ID = function(node, context, options) {
            var parts = node.parts, i, len, cur, prev, def, val, type, valType, preOptions, currentContext = context[node.depth], contextLength = context.length;
            if (Util.isArray(currentContext)) currentContext = context[node.depth + 1];
            if (!parts.length) {} else {
                for (i = 0, len = parts.length; i < len; i++) {
                    options.__path.push(parts[i]);
                    cur = parts[i];
                    prev = parts[i - 1];
                    preOptions = options[prev];
                    def = i === len - 1 ? currentContext[cur] : {};
                    val = this.val(cur, options, context, def);
                    type = Util.type(currentContext[cur]);
                    valType = Util.type(val);
                    if (type === "undefined") {
                        if (i < len - 1 && valType !== "object" && valType !== "array") {
                            currentContext[cur] = {};
                        } else {
                            currentContext[cur] = Util.isArray(val) && [] || val;
                        }
                    } else {
                        if (i < len - 1 && type !== "object" && type !== "array") {
                            currentContext[cur] = Util.isArray(val) && [] || {};
                        }
                    }
                    type = Util.type(currentContext[cur]);
                    if (type === "object" || type === "array") {
                        currentContext = currentContext[cur];
                        context.unshift(currentContext);
                    }
                }
            }
            if (context.length > contextLength) context.splice(0, context.length - contextLength);
        };
        Handle.partial = function(node, context, options, helpers, partials) {
            var name = node.partialName.name, partial = partials && partials[name], contextLength = context.length;
            if (partial) Handle.gen(partial, context, options, helpers, partials);
            if (context.length > contextLength) context.splice(0, context.length - contextLength);
        };
        Handle.content = Util.noop;
        Handle.PARTIAL_NAME = Util.noop;
        Handle.DATA = Util.noop;
        Handle.STRING = Util.noop;
        Handle.INTEGER = Util.noop;
        Handle.BOOLEAN = Util.noop;
        Handle.comment = Util.noop;
        var Helpers = {};
        Helpers.each = function(node, context, options) {
            var i, len, cur, val, parts, def, type, currentContext = context[0];
            parts = node.mustache.params[0].parts;
            for (i = 0, len = parts.length; i < len; i++) {
                options.__path.push(parts[i]);
                cur = parts[i];
                def = i === len - 1 ? [] : {};
                val = this.val(cur, options, context, def);
                currentContext[cur] = Util.isArray(val) && [] || val;
                type = Util.type(currentContext[cur]);
                if (type === "object" || type === "array") {
                    currentContext = currentContext[cur];
                    context.unshift(currentContext);
                }
            }
            return val;
        };
        Helpers["if"] = Helpers.unless = function(node, context, options) {
            var params = node.mustache.params, i, j, cur, val, parts, def, type, currentContext = context[0];
            for (i = 0; i < params.length; i++) {
                parts = params[i].parts;
                for (j = 0; j < parts.length; j++) {
                    if (i === 0) options.__path.push(parts[j]);
                    cur = parts[j];
                    def = j === parts.length - 1 ? "@BOOL(2,1,true)" : {};
                    val = this.val(cur, options, context, def);
                    if (j === parts.length - 1) {
                        val = val === "true" ? true : val === "false" ? false : val;
                    }
                    currentContext[cur] = Util.isArray(val) ? [] : val;
                    type = Util.type(currentContext[cur]);
                    if (type === "object" || type === "array") {
                        currentContext = currentContext[cur];
                        context.unshift(currentContext);
                    }
                }
            }
            return val;
        };
        Helpers["with"] = function(node, context, options) {
            var i, cur, val, parts, def, currentContext = context[0];
            parts = node.mustache.params[0].parts;
            for (i = 0; i < parts.length; i++) {
                options.__path.push(parts[i]);
                cur = parts[i];
                def = {};
                val = this.val(cur, options, context, def);
                currentContext = currentContext[cur] = val;
                context.unshift(currentContext);
            }
            return val;
        };
        Helpers.log = function() {};
        Helpers.custom = function(node, context, options) {
            var i, len, cur, val, parts, def, type, currentContext = context[0];
            if (node.mustache.params.length === 0) {
                return;
                options.__path.push(node.mustache.id.string);
                cur = node.mustache.id.string;
                def = "@BOOL(2,1,true)";
                val = this.val(cur, options, context, def);
                currentContext[cur] = Util.isArray(val) && [] || val;
                type = Util.type(currentContext[cur]);
                if (type === "object" || type === "array") {
                    currentContext = currentContext[cur];
                    context.unshift(currentContext);
                }
            } else {
                parts = node.mustache.params[0].parts;
                for (i = 0, len = parts.length; i < len; i++) {
                    options.__path.push(parts[i]);
                    cur = parts[i];
                    def = i === len - 1 ? [] : {};
                    val = this.val(cur, options, context, def);
                    currentContext[cur] = Util.isArray(val) && [] || val;
                    type = Util.type(currentContext[cur]);
                    if (type === "object" || type === "array") {
                        currentContext = currentContext[cur];
                        context.unshift(currentContext);
                    }
                }
            }
            return val;
        };
    }).call(this);
    /*! src/mock4xtpl.js */
    (function(undefined) {
        if (typeof KISSY === "undefined") return;
        var Mock4XTpl = {
            debug: false
        };
        var XTemplate;
        KISSY.use("xtemplate", function(S, T) {
            XTemplate = T;
        });
        if (!this.Mock) module.exports = Mock4XTpl;
        Mock.xtpl = function(input, options, helpers, partials) {
            return Mock4XTpl.mock(input, options, helpers, partials);
        };
        Mock.xparse = function(input) {
            return XTemplate.compiler.parse(input);
        };
        Mock4XTpl.mock = function(input, options, helpers, partials) {
            helpers = helpers ? Util.extend({}, helpers, XTemplate.RunTime.commands) : XTemplate.RunTime.commands;
            partials = partials ? Util.extend({}, partials, XTemplate.RunTime.subTpls) : XTemplate.RunTime.subTpls;
            return this.gen(input, null, options, helpers, partials, {});
        };
        Mock4XTpl.parse = function(input) {
            return XTemplate.compiler.parse(input);
        };
        Mock4XTpl.gen = function(node, context, options, helpers, partials, other) {
            if (typeof node === "string") {
                if (Mock4XTpl.debug) {
                    console.log("[tpl    ]\n", node);
                }
                var ast = this.parse(node);
                options = this.parseOptions(node, options);
                var data = this.gen(ast, context, options, helpers, partials, other);
                return data;
            }
            context = context || [ {} ];
            options = options || {};
            node.type = node.type;
            if (this[node.type] === Util.noop) return;
            options.__path = options.__path || [];
            if (Mock4XTpl.debug) {
                console.log();
                console.group("[" + node.type + "]", JSON.stringify(node));
                console.log("[context]", "[before]", context.length, JSON.stringify(context));
                console.log("[options]", "[before]", options.__path.length, JSON.stringify(options));
                console.log("[other  ]", "[before]", JSON.stringify(other));
            }
            var preLength = options.__path.length;
            this[node.type](node, context, options, helpers, partials, other);
            if (Mock4XTpl.debug) {
                console.log("[__path ]", "[after ]", options.__path);
            }
            if (!other.hold || typeof other.hold === "function" && !other.hold(node, options, context)) {
                options.__path.splice(preLength);
            }
            if (Mock4XTpl.debug) {
                console.log("[context]", "[after ]", context.length, JSON.stringify(context));
                console.groupEnd();
            }
            return context[context.length - 1];
        };
        Mock4XTpl.parseOptions = function(input, options) {
            var rComment = /<!--\s*\n*Mock\s*\n*([\w\W]+?)\s*\n*-->/g;
            var comments = input.match(rComment), ret = {}, i, ma, option;
            for (i = 0; comments && i < comments.length; i++) {
                rComment.lastIndex = 0;
                ma = rComment.exec(comments[i]);
                if (ma) {
                    option = new Function("return " + ma[1]);
                    option = option();
                    Util.extend(ret, option);
                }
            }
            return Util.extend(ret, options);
        };
        Mock4XTpl.parseVal = function(expr, object) {
            function queryArray(prop, context) {
                if (typeof context === "object" && prop in context) return [ context[prop] ];
                var ret = [];
                for (var i = 0; i < context.length; i++) {
                    ret.push.apply(ret, query(prop, [ context[i] ]));
                }
                return ret;
            }
            function queryObject(prop, context) {
                if (typeof context === "object" && prop in context) return [ context[prop] ];
                var ret = [];
                for (var key in context) {
                    ret.push.apply(ret, query(prop, [ context[key] ]));
                }
                return ret;
            }
            function query(prop, set) {
                var ret = [];
                for (var i = 0; i < set.length; i++) {
                    if (typeof set[i] !== "object") continue;
                    if (prop in set[i]) ret.push(set[i][prop]); else {
                        ret.push.apply(ret, Util.isArray(set[i]) ? queryArray(prop, set[i]) : queryObject(prop, set[i]));
                    }
                }
                return ret;
            }
            function parse(expr, context) {
                var parts = typeof expr === "string" ? expr.split(".") : expr.slice(0), set = [ context ];
                while (parts.length) {
                    set = query(parts.shift(), set);
                }
                return set;
            }
            return parse(expr, object);
        };
        Mock4XTpl.val = function(name, options, context, def) {
            if (name !== options.__path[options.__path.length - 1]) throw new Error(name + "!==" + options.__path);
            if (def !== undefined) def = Mock.mock(def);
            if (options) {
                var mocked = Mock.mock(options);
                if (Util.isString(mocked)) return mocked;
                var ret = Mock4XTpl.parseVal(options.__path, mocked);
                if (ret.length > 0) return ret[0];
                if (name in mocked) {
                    return mocked[name];
                }
            }
            if (Util.isArray(context[0])) return {};
            return def !== undefined ? def : name;
        };
        Mock4XTpl.program = function(node, context, options, helpers, partials, other) {
            for (var i = 0; i < node.statements.length; i++) {
                this.gen(node.statements[i], context, options, helpers, partials, other);
            }
            for (var j = 0; node.inverse && j < node.inverse.length; j++) {
                this.gen(node.inverse[j], context, options, helpers, partials, other);
            }
        };
        Mock4XTpl.block = function(node, context, options, helpers, partials, other) {
            var contextLength = context.length;
            this.gen(node.tpl, context, options, helpers, partials, Util.extend({}, other, {
                def: {},
                hold: true
            }));
            var currentContext = context[0], mocked, i, len;
            if (Util.type(currentContext) === "array") {
                mocked = this.val(options.__path[options.__path.length - 1], options, context);
                len = mocked && mocked.length || Random.integer(3, 7);
                for (i = 0; i < len; i++) {
                    currentContext.push(mocked && mocked[i] !== undefined ? mocked[i] : {});
                    options.__path.push(i);
                    context.unshift(currentContext[currentContext.length - 1]);
                    this.gen(node.program, context, options, helpers, partials, other);
                    options.__path.pop();
                    context.shift();
                }
            } else this.gen(node.program, context, options, helpers, partials, other);
            if (!other.hold || typeof other.hold === "function" && !other.hold(node, options, context)) {
                context.splice(0, context.length - contextLength);
            }
        };
        Mock4XTpl.tpl = function(node, context, options, helpers, partials, other) {
            if (node.params && node.params.length) {
                other = Util.extend({}, other, {
                    def: {
                        each: [],
                        "if": "@BOOL(2,1,true)",
                        unless: "@BOOL(2,1,false)",
                        "with": {}
                    }[node.path.string],
                    hold: {
                        each: true,
                        "if": function(_, __, ___, name, value) {
                            return typeof value === "object";
                        },
                        unless: function(_, __, ___, name, value) {
                            return typeof value === "object";
                        },
                        "with": true,
                        include: false
                    }[node.path.string]
                });
                for (var i = 0, input; i < node.params.length; i++) {
                    if (node.path.string === "include") {
                        input = partials && partials[node.params[i].value];
                    } else input = node.params[i];
                    if (input) this.gen(input, context, options, helpers, partials, other);
                }
                if (node.hash) {
                    this.gen(node.hash, context, options, helpers, partials, other);
                }
            } else {
                this.gen(node.path, context, options, helpers, partials, other);
            }
        };
        Mock4XTpl.tplExpression = function(node, context, options, helpers, partials, other) {
            this.gen(node.expression, context, options, helpers, partials, other);
        };
        Mock4XTpl.content = Util.noop;
        Mock4XTpl.unaryExpression = Util.noop;
        Mock4XTpl.multiplicativeExpression = Mock4XTpl.additiveExpression = function(node, context, options, helpers, partials, other) {
            this.gen(node.op1, context, options, helpers, partials, Util.extend({}, other, {
                def: function() {
                    return node.op2.type === "number" ? node.op2.value.indexOf(".") > -1 ? Random.float(-Math.pow(10, 10), Math.pow(10, 10), 1, Math.pow(10, 6)) : Random.integer() : undefined;
                }()
            }));
            this.gen(node.op2, context, options, helpers, partials, Util.extend({}, other, {
                def: function() {
                    return node.op1.type === "number" ? node.op1.value.indexOf(".") > -1 ? Random.float(-Math.pow(10, 10), Math.pow(10, 10), 1, Math.pow(10, 6)) : Random.integer() : undefined;
                }()
            }));
        };
        Mock4XTpl.relationalExpression = function(node, context, options, helpers, partials, other) {
            this.gen(node.op1, context, options, helpers, partials, other);
            this.gen(node.op2, context, options, helpers, partials, other);
        };
        Mock4XTpl.equalityExpression = Util.noop;
        Mock4XTpl.conditionalAndExpression = Util.noop;
        Mock4XTpl.conditionalOrExpression = Util.noop;
        Mock4XTpl.string = Util.noop;
        Mock4XTpl.number = Util.noop;
        Mock4XTpl.boolean = Util.noop;
        Mock4XTpl.hash = function(node, context, options, helpers, partials, other) {
            var pairs = node.value, key;
            for (key in pairs) {
                this.gen(pairs[key], context, options, helpers, partials, other);
            }
        };
        Mock4XTpl.id = function(node, context, options, helpers, partials, other) {
            var contextLength = context.length;
            var parts = node.parts, currentContext = context[node.depth], i, len, cur, def, val;
            function fix(currentContext, index, length, name, val) {
                var type = Util.type(currentContext[name]), valType = Util.type(val);
                val = val === "true" ? true : val === "false" ? false : val;
                if (type === "undefined") {
                    if (index < length - 1 && !Util.isObjectOrArray(val)) {
                        currentContext[name] = {};
                    } else {
                        currentContext[name] = Util.isArray(val) && [] || val;
                    }
                } else {
                    if (index < length - 1 && type !== "object" && type !== "array") {
                        currentContext[name] = Util.isArray(val) && [] || {};
                    } else {
                        if (type !== "object" && type !== "array" && valType !== "object" && valType !== "array") {
                            currentContext[name] = val;
                        }
                    }
                }
                return currentContext[name];
            }
            if (Util.isArray(currentContext)) currentContext = context[node.depth + 1];
            for (i = 0, len = parts.length; i < len; i++) {
                if (i === 0 && parts[i] === "this") continue;
                if (/^(xindex|xcount|xkey)$/.test(parts[i])) continue;
                if (i === 0 && len === 1 && parts[i] in helpers) continue;
                options.__path.push(parts[i]);
                cur = parts[i];
                def = i === len - 1 ? other.def !== undefined ? other.def : context[0][cur] : {};
                val = this.val(cur, options, context, def);
                if (Mock4XTpl.debug) {
                    console.log("[def    ]", JSON.stringify(def));
                    console.log("[val    ]", JSON.stringify(val));
                }
                val = fix(currentContext, i, len, cur, val);
                if (Util.isObjectOrArray(currentContext[cur])) {
                    context.unshift(currentContext = currentContext[cur]);
                }
            }
            if (!other.hold || typeof other.hold === "function" && !other.hold(node, options, context, cur, val)) {
                context.splice(0, context.length - contextLength);
            }
        };
    }).call(this);
}).call(this);
          }).call(window);
        
(function() {
  "use strict";
  class MockManager {
    constructor() {
      this.rules =  new Map();
      this.storageKey = "mock-monkey-rules";
      this.loadFromStorage();
    }
    /**
     * Add Mock rule
     */
    add(params) {
      const id = this.generateId();
      const rule = {
        id,
        pattern: params.pattern,
        response: params.response,
        options: params.options || {},
        enabled: true,
        createdAt: Date.now()
      };
      this.rules.set(id, rule);
      this.saveToStorage();
      console.log(`[MockMonkey] Rule added: ${this.patternToString(params.pattern)}`);
      return rule;
    }
    /**
     * Update Mock rule
     */
    update(id, updates) {
      const rule = this.rules.get(id);
      if (!rule) return false;
      const updated = { ...rule, ...updates };
      this.rules.set(id, updated);
      this.saveToStorage();
      return true;
    }
    /**
     * Remove Mock rule
     */
    remove(id) {
      const result = this.rules.delete(id);
      if (result) {
        this.saveToStorage();
      }
      return result;
    }
    /**
     * Remove rule by pattern
     */
    removeByPattern(pattern) {
      const patternStr = this.patternToString(pattern);
      for (const [id, rule] of this.rules) {
        if (this.patternToString(rule.pattern) === patternStr) {
          return this.remove(id);
        }
      }
      return false;
    }
    /**
     * Clear all rules
     */
    clear() {
      this.rules.clear();
      this.saveToStorage();
    }
    /**
     * Get all rules
     */
    getAll() {
      return Array.from(this.rules.values());
    }
    /**
     * Get single rule
     */
    get(id) {
      return this.rules.get(id);
    }
    /**
     * Find matching Mock rule
     */
    findMatch(url) {
      for (const rule of this.rules.values()) {
        if (!rule.enabled) continue;
        if (rule.pattern instanceof RegExp) {
          if (rule.pattern.test(url)) return rule;
        } else if (url.includes(rule.pattern)) {
          return rule;
        }
      }
      return null;
    }
    /**
     * Enable/disable rule
     */
    toggle(id) {
      const rule = this.rules.get(id);
      if (!rule) return false;
      rule.enabled = !rule.enabled;
      this.saveToStorage();
      return rule.enabled;
    }
    /**
     * Generate unique ID
     */
    generateId() {
      return `rule_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    }
    /**
     * Convert pattern to string
     */
    patternToString(pattern) {
      return pattern instanceof RegExp ? pattern.toString() : pattern;
    }
    /**
     * Save to localStorage
     */
    saveToStorage() {
      try {
        const data = Array.from(this.rules.entries());
        localStorage.setItem(this.storageKey, JSON.stringify(data));
      } catch (e) {
        console.warn("[MockMonkey] Failed to save rules:", e);
      }
    }
    /**
     * Load from localStorage
     */
    loadFromStorage() {
      try {
        const stored = localStorage.getItem(this.storageKey);
        if (!stored) return;
        const data = JSON.parse(stored);
        for (const [id, rule] of data) {
          if (typeof rule.pattern === "string" && rule.pattern.startsWith("/")) {
            try {
              const match = rule.pattern.match(/^\/(.+)\/([gimuy]*)$/);
              if (match) {
                rule.pattern = new RegExp(match[1], match[2]);
              }
            } catch (e) {
            }
          }
          this.rules.set(id, rule);
        }
        console.log(`[MockMonkey] Loaded ${this.rules.size} rules`);
      } catch (e) {
        console.warn("[MockMonkey] Failed to load rules:", e);
      }
    }
  }
  class RequestRecorder {
    constructor() {
      this.requests = [];
      this.maxRecords = 500;
      this.listeners =  new Set();
    }
    /**
     * Add request record
     */
    addRequest(request) {
      this.requests.unshift(request);
      if (this.requests.length > this.maxRecords) {
        this.requests = this.requests.slice(0, this.maxRecords);
      }
      this.notifyListeners();
    }
    /**
     * Update request record (for updating response info, etc.)
     */
    updateRequest(id, updates) {
      const index = this.requests.findIndex((r) => r.id === id);
      if (index !== -1) {
        this.requests[index] = { ...this.requests[index], ...updates };
        this.notifyListeners();
      }
    }
    /**
     * Get all request records
     */
    getRequests() {
      return [...this.requests];
    }
    /**
     * Clear all records
     */
    clear() {
      this.requests = [];
      this.notifyListeners();
    }
    /**
     * Subscribe to request changes
     */
    subscribe(listener) {
      this.listeners.add(listener);
      return () => {
        this.listeners.delete(listener);
      };
    }
    /**
     * Notify all listeners
     */
    notifyListeners() {
      this.listeners.forEach((listener) => listener([...this.requests]));
    }
    /**
     * Generate unique ID
     */
    static generateId() {
      return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    }
  }
  class Interceptor {
    constructor(manager, recorder) {
      this.manager = manager;
      this.recorder = recorder;
      this.xhrOpen = XMLHttpRequest.prototype.open;
      this.xhrSend = XMLHttpRequest.prototype.send;
      this.originalFetch = window.fetch.bind(window);
    }
    /**
     * Convert relative URL to full URL
     */
    normalizeUrl(url) {
      try {
        if (url.startsWith("http://") || url.startsWith("https://")) {
          return url;
        }
        return new URL(url, window.location.href).href;
      } catch {
        return url;
      }
    }
    /**
     * Start interception
     */
    start() {
      this.interceptXHR();
      this.interceptFetch();
      console.log("[MockMonkey] 拦截器已启动");
    }
    /**
     * Stop interception
     */
    stop() {
      XMLHttpRequest.prototype.open = this.xhrOpen;
      XMLHttpRequest.prototype.send = this.xhrSend;
      window.fetch = this.originalFetch;
      console.log("[MockMonkey] 拦截器已停止");
    }
    /**
     * Intercept XMLHttpRequest
     */
    interceptXHR() {
      const self = this;
      XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
        this._mockMethod = method;
        this._mockUrl = url.toString();
        this._mockRequestId = RequestRecorder.generateId();
        this._mockRequestTime = Date.now();
        return self.xhrOpen.call(this, method, url, async ?? true, username, password);
      };
      XMLHttpRequest.prototype.send = function(body) {
        const xhr = this;
        const rawUrl = xhr._mockUrl;
        const method = xhr._mockMethod;
        const requestId = xhr._mockRequestId;
        const requestTime = xhr._mockRequestTime;
        const url = self.normalizeUrl(rawUrl);
        self.recorder.addRequest({
          id: requestId,
          method,
          url,
          body: body?.toString(),
          type: "XHR",
          mocked: false,
          timestamp: requestTime
        });
        const rule = self.manager.findMatch(url);
        if (rule) {
          console.log(`[MockMonkey] XHR 拦截: ${method} ${url}`);
          self.recorder.updateRequest(requestId, {
            mocked: true,
            ruleId: rule.id,
            status: rule.options.status || 200,
            response: rule.response,
            duration: rule.options.delay || 0
          });
          self.mockXHR(this, rule, requestId);
          return;
        }
        const originalOnReadyStateChange = xhr.onreadystatechange;
        xhr.onreadystatechange = function(ev) {
          if (xhr.readyState === XMLHttpRequest.DONE) {
            const duration = Date.now() - requestTime;
            self.recorder.updateRequest(requestId, {
              status: xhr.status,
              duration
            });
          }
          if (originalOnReadyStateChange) {
            return originalOnReadyStateChange.call(this, ev);
          }
        };
        const originalOnLoad = xhr.onload;
        xhr.onload = function(ev) {
          const duration = Date.now() - requestTime;
          let response;
          try {
            response = JSON.parse(xhr.responseText);
          } catch {
            response = xhr.responseText;
          }
          self.recorder.updateRequest(requestId, {
            status: xhr.status,
            response,
            duration
          });
          if (originalOnLoad) {
            return originalOnLoad.call(this, ev);
          }
        };
        return self.xhrSend.call(this, body);
      };
    }
    /**
     * Intercept Fetch
     */
    interceptFetch() {
      const self = this;
      window.fetch = function(input, init) {
        const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
        const url = self.normalizeUrl(rawUrl);
        const requestId = RequestRecorder.generateId();
        const requestTime = Date.now();
        const method = init?.method || "GET";
        self.recorder.addRequest({
          id: requestId,
          method,
          url,
          body: init?.body?.toString(),
          type: "Fetch",
          mocked: false,
          timestamp: requestTime
        });
        const rule = self.manager.findMatch(url);
        if (rule) {
          console.log(`[MockMonkey] Fetch 拦截: ${method} ${url}`);
          self.recorder.updateRequest(requestId, {
            mocked: true,
            ruleId: rule.id,
            status: rule.options.status || 200,
            response: rule.response,
            duration: rule.options.delay || 0
          });
          return self.mockFetch(rule, requestId);
        }
        return self.originalFetch(input, init).then((response) => {
          const duration = Date.now() - requestTime;
          const clonedResponse = response.clone();
          clonedResponse.json().catch(() => clonedResponse.text()).then((data) => {
            self.recorder.updateRequest(requestId, {
              status: response.status,
              response: data,
              duration
            });
          }).catch(() => {
            self.recorder.updateRequest(requestId, {
              status: response.status,
              duration
            });
          });
          return response;
        });
      };
    }
    /**
     * Mock XHR response
     */
    mockXHR(xhr, rule, requestId) {
      const delay = rule.options.delay || 0;
      const requestTime = Date.now();
      setTimeout(() => {
        const duration = Date.now() - requestTime;
        let mockResponse = rule.response;
        if (typeof window !== "undefined" && window.Mock) {
          try {
            const originalResponse = JSON.stringify(rule.response);
            mockResponse = window.Mock.mock(rule.response);
            const mockResponseStr = JSON.stringify(mockResponse);
            if (originalResponse !== mockResponseStr) {
              console.log("[MockMonkey] Mock.js 已解析模板");
            }
          } catch (e) {
            console.warn("[MockMonkey] Mock.js 解析失败,使用原始响应:", e);
            mockResponse = rule.response;
          }
        } else {
          console.warn("[MockMonkey] Mock.js 未加载,占位符将不会被替换");
        }
        Object.defineProperty(xhr, "readyState", {
          value: 4,
          writable: false,
          configurable: true
        });
        Object.defineProperty(xhr, "status", {
          value: rule.options.status || 200,
          writable: false,
          configurable: true
        });
        const responseText = JSON.stringify(mockResponse);
        Object.defineProperty(xhr, "responseText", {
          value: responseText,
          writable: false,
          configurable: true
        });
        Object.defineProperty(xhr, "response", {
          value: responseText,
          writable: false,
          configurable: true
        });
        this.recorder.updateRequest(requestId, { duration, response: mockResponse });
        const isSuccess = (rule.options.status || 200) >= 200 && (rule.options.status || 200) < 300;
        const eventType = isSuccess ? "load" : "error";
        xhr.dispatchEvent(new Event(eventType));
        xhr.dispatchEvent(new Event("loadend"));
        if (xhr.onreadystatechange) {
          xhr.onreadystatechange(new Event("readystatechange"));
        }
      }, delay);
    }
    /**
     * Mock Fetch response
     */
    mockFetch(rule, requestId) {
      return new Promise((resolve) => {
        const delay = rule.options.delay || 0;
        const requestTime = Date.now();
        setTimeout(() => {
          const duration = Date.now() - requestTime;
          const headers = rule.options.headers || { "Content-Type": "application/json" };
          let mockResponse = rule.response;
          if (typeof window !== "undefined" && window.Mock) {
            try {
              const originalResponse = JSON.stringify(rule.response);
              mockResponse = window.Mock.mock(rule.response);
              const mockResponseStr = JSON.stringify(mockResponse);
              if (originalResponse !== mockResponseStr) {
                console.log("[MockMonkey] Mock.js 已解析模板");
              }
            } catch (e) {
              console.warn("[MockMonkey] Mock.js 解析失败,使用原始响应:", e);
              mockResponse = rule.response;
            }
          } else {
            console.warn("[MockMonkey] Mock.js 未加载,占位符将不会被替换");
          }
          this.recorder.updateRequest(requestId, { duration, response: mockResponse });
          resolve(
            new Response(JSON.stringify(mockResponse), {
              status: rule.options.status || 200,
              headers
            })
          );
        }, delay);
      });
    }
  }
  const translations = {
    zh: {
      common: {
        add: "添加",
        edit: "编辑",
        delete: "删除",
        enable: "启用",
        disable: "禁用",
        cancel: "取消",
        save: "保存",
        details: "详情",
        confirmDelete: "确定要删除这条规则吗?"
      },
      tabs: {
        rules: "规则",
        add: "添加",
        network: "网络"
      },
      rules: {
        count: "条规则",
        export: "导出",
        import: "导入",
        empty: "暂无 Mock 规则",
        startConfig: '点击<span class="mm-link" data-action="go-to-add">"添加规则"</span>开始配置',
        status: "状态",
        delay: "延迟"
      },
      form: {
        urlPattern: "URL 模式 *",
        urlPatternHint: "支持字符串或正则表达式(格式:/pattern/flags)",
        responseData: "响应数据 (JSON) *",
        responseDataPlaceholder: '{"code": 200, "data": {}}',
        delay: "延迟 (ms)",
        status: "状态码",
        addRule: "添加规则",
        saveRule: "保存规则",
        cancelEdit: "取消",
        importError: "导入文件格式错误:必须是数组",
        jsonError: "响应数据 JSON 格式错误",
        regexError: "正则表达式格式错误"
      },
      network: {
        count: "条请求",
        clear: "清空",
        empty: "暂无网络请求",
        emptyHint: "发起请求后会在此显示",
        createMock: "创建 Mock 规则",
        responseData: "响应数据"
      }
    },
    en: {
      common: {
        add: "Add",
        edit: "Edit",
        delete: "Delete",
        enable: "Enable",
        disable: "Disable",
        cancel: "Cancel",
        save: "Save",
        details: "Details",
        confirmDelete: "Are you sure you want to delete this rule?"
      },
      tabs: {
        rules: "Rules",
        add: "Add",
        network: "Network"
      },
      rules: {
        count: "rules",
        export: "Export",
        import: "Import",
        empty: "No Mock rules yet",
        startConfig: 'Click <span class="mm-link" data-action="go-to-add">"Add Rule"</span> to start',
        status: "Status",
        delay: "Delay"
      },
      form: {
        urlPattern: "URL Pattern *",
        urlPatternHint: "Support string or regex (format: /pattern/flags)",
        responseData: "Response Data (JSON) *",
        responseDataPlaceholder: '{"code": 200, "data": {}}',
        delay: "Delay (ms)",
        status: "Status Code",
        addRule: "Add Rule",
        saveRule: "Save Rule",
        cancelEdit: "Cancel",
        importError: "Import file format error: must be an array",
        jsonError: "Response data JSON format error",
        regexError: "Regex format error"
      },
      network: {
        count: "requests",
        clear: "Clear",
        empty: "No network requests yet",
        emptyHint: "Requests will appear here",
        createMock: "Create Mock",
        responseData: "Response Data"
      }
    }
  };
  class I18n {
    constructor() {
      this.STORAGE_KEY = "mock-monkey-language";
      this._language = this.loadLanguage();
    }
    /**
     * Get singleton instance
     */
    static getInstance() {
      if (!I18n.instance) {
        I18n.instance = new I18n();
      }
      return I18n.instance;
    }
    /**
     * Get current language
     */
    getLanguage() {
      return this._language;
    }
    /**
     * Set language and save to localStorage
     */
    setLanguage(lang) {
      this._language = lang;
      this.saveLanguage(lang);
    }
    /**
     * Toggle language between zh and en
     */
    toggleLanguage() {
      this._language = this._language === "zh" ? "en" : "zh";
      this.saveLanguage(this._language);
    }
    /**
     * Get translation text by key path
     * Supports nested key path like 'common.add', 'tabs.rules'
     */
    t(key) {
      const keys = key.split(".");
      let value = translations[this._language];
      for (const k of keys) {
        if (value && typeof value === "object" && k in value) {
          value = value[k];
        } else {
          console.warn(`[MockMonkey i18n] Translation key not found: ${key}`);
          return key;
        }
      }
      return typeof value === "string" ? value : key;
    }
    /**
     * Load language from localStorage
     */
    loadLanguage() {
      try {
        const saved = localStorage.getItem(this.STORAGE_KEY);
        if (saved === "zh" || saved === "en") {
          return saved;
        }
      } catch (e) {
        console.warn("[MockMonkey i18n] Failed to load language from localStorage:", e);
      }
      return "zh";
    }
    /**
     * Save language to localStorage
     */
    saveLanguage(lang) {
      try {
        localStorage.setItem(this.STORAGE_KEY, lang);
      } catch (e) {
        console.warn("[MockMonkey i18n] Failed to save language to localStorage:", e);
      }
    }
  }
  class Panel {
    // i18n instance
    constructor(onAddRule, onUpdateRule, onCreateFromRequest) {
      this.onAddRule = onAddRule;
      this.onUpdateRule = onUpdateRule;
      this.onCreateFromRequest = onCreateFromRequest;
      this.container = null;
      this.shadowRoot = null;
      this.isVisible = false;
      this.networkRequests = [];
      this.toggleButton = null;
      this.isDragging = false;
      this.hasMoved = false;
      this.dragStartTime = 0;
      this.dragOffset = { x: 0, y: 0 };
      this.buttonPosition = { x: 20, y: 20 };
      this.currentRules = [];
      this.panelElement = null;
      this.isPanelDragging = false;
      this.panelHasMoved = false;
      this.panelDragStartTime = 0;
      this.panelDragOffset = { x: 0, y: 0 };
      this.panelPosition = null;
      this.editingRuleId = null;
      this.handleMouseMove = (e) => {
        if (!this.toggleButton) return;
        const btn = this.toggleButton;
        const rect = btn.getBoundingClientRect();
        let newX = e.clientX - this.dragOffset.x;
        let newY = e.clientY - this.dragOffset.y;
        const currentRight = window.innerWidth - rect.left - rect.width;
        const currentBottom = window.innerHeight - rect.top - rect.height;
        const newRight = window.innerWidth - newX - rect.width;
        const newBottom = window.innerHeight - newY - rect.height;
        if (Math.abs(newRight - currentRight) > 3 || Math.abs(newBottom - currentBottom) > 3) {
          this.hasMoved = true;
          this.isDragging = true;
        }
        const maxX = window.innerWidth - rect.width;
        const maxY = window.innerHeight - rect.height;
        newX = Math.max(0, Math.min(newX, maxX));
        newY = Math.max(0, Math.min(newY, maxY));
        btn.style.left = `${newX}px`;
        btn.style.top = `${newY}px`;
        btn.style.right = "auto";
        btn.style.bottom = "auto";
      };
      this.handleMouseUp = () => {
        if (!this.toggleButton) return;
        const btn = this.toggleButton;
        btn.style.transition = "all 0.2s";
        document.removeEventListener("mousemove", this.handleMouseMove);
        document.removeEventListener("mouseup", this.handleMouseUp);
        setTimeout(() => {
          this.isDragging = false;
        }, 100);
        this.saveButtonPosition();
      };
      this.handlePanelMouseMove = (e) => {
        if (!this.panelElement) return;
        const panel = this.panelElement;
        let newX = e.clientX - this.panelDragOffset.x;
        let newY = e.clientY - this.panelDragOffset.y;
        const rect = panel.getBoundingClientRect();
        if (Math.abs(newX - rect.left) > 3 || Math.abs(newY - rect.top) > 3) {
          this.panelHasMoved = true;
          this.isPanelDragging = true;
        }
        const maxX = window.innerWidth - rect.width;
        const maxY = window.innerHeight - rect.height;
        newX = Math.max(0, Math.min(newX, maxX));
        newY = Math.max(0, Math.min(newY, maxY));
        panel.style.left = `${newX}px`;
        panel.style.top = `${newY}px`;
        panel.style.transform = "none";
        this.panelPosition = { left: newX, top: newY };
      };
      this.handlePanelMouseUp = () => {
        if (!this.panelElement) return;
        const panel = this.panelElement;
        panel.style.transition = "";
        document.removeEventListener("mousemove", this.handlePanelMouseMove);
        document.removeEventListener("mouseup", this.handlePanelMouseUp);
        setTimeout(() => {
          this.isPanelDragging = false;
        }, 100);
        this.savePanelPosition();
      };
      this.i18n = I18n.getInstance();
      this.loadButtonPosition();
    }
    /**
     * Initialize panel
     */
    init() {
      this.container = document.createElement("div");
      this.container.id = "mock-monkey-container";
      this.shadowRoot = this.container.attachShadow({ mode: "open" });
      this.attachStyles();
      this.createContent();
      this.bindEvents();
      this.ensureBody().then(() => {
        if (document.body && this.container) {
          document.body.appendChild(this.container);
          console.log("[MockMonkey] 面板容器已添加到页面");
        } else {
          console.error("[MockMonkey] document.body 仍然不存在");
        }
        this.createToggleButton();
      });
    }
    /**
     * Create container
     */
    createContainer() {
    }
    /**
     * Attach styles
     */
    attachStyles() {
      if (!this.shadowRoot) return;
      const style = document.createElement("style");
      style.textContent = this.getStyles();
      this.shadowRoot.appendChild(style);
    }
    /**
     * Create panel content
     */
    createContent() {
      if (!this.shadowRoot) return;
      const panel = document.createElement("div");
      panel.className = "mm-panel";
      this.loadPanelPosition();
      if (this.panelPosition) {
        panel.style.left = `${this.panelPosition.left}px`;
        panel.style.top = `${this.panelPosition.top}px`;
        panel.style.transform = "none";
      }
      this.panelElement = panel;
      panel.innerHTML = `
      <div class="mm-header" data-drag-handle="panel">
        <h2 class="mm-title">MockMonkey</h2>
        <div class="mm-header-actions">
          <button class="mm-lang-btn" data-action="toggle-lang" title="${this.i18n.getLanguage() === "zh" ? "Switch to English" : "切换中文"}">${this.i18n.getLanguage() === "zh" ? "EN" : "中"}</button>
          <button class="mm-close-btn" data-action="close">×</button>
        </div>
      </div>

      <div class="mm-tabs">
        <button class="mm-tab mm-tab--active" data-tab="rules">${this.i18n.t("tabs.rules")}</button>
        <button class="mm-tab" data-tab="add" data-tab-label>${this.i18n.t("tabs.add")}</button>
        <button class="mm-tab" data-tab="requests">${this.i18n.t("tabs.network")}</button>
      </div>

      <div class="mm-content">
        <div class="mm-tab-content mm-tab-content--active" data-content="rules">
          <div class="mm-rules-header">
            <span class="mm-rules-count">0 ${this.i18n.t("rules.count")}</span>
            <button class="mm-btn mm-btn--small" data-action="export">${this.i18n.t("rules.export")}</button>
            <button class="mm-btn mm-btn--small" data-action="import">${this.i18n.t("rules.import")}</button>
          </div>
          <div class="mm-rules-list" data-rules-list></div>
        </div>

        <div class="mm-tab-content" data-content="add">
          <form class="mm-form" data-action="add-rule">
            <input type="hidden" name="editing-id" value="">
            <div class="mm-form-group">
              <label class="mm-label">${this.i18n.t("form.urlPattern")}</label>
              <input class="mm-input" name="pattern" placeholder="/api/user" required>
              <span class="mm-hint">${this.i18n.t("form.urlPatternHint")}</span>
            </div>

            <div class="mm-form-group">
              <label class="mm-label">${this.i18n.t("form.responseData")}</label>
              <textarea class="mm-textarea" name="response" rows="6" placeholder='${this.i18n.t("form.responseDataPlaceholder")}' required></textarea>
            </div>

            <div class="mm-form-row">
              <div class="mm-form-group">
                <label class="mm-label">${this.i18n.t("form.delay")}</label>
                <input class="mm-input" type="number" name="delay" value="0" min="0">
              </div>
              <div class="mm-form-group">
                <label class="mm-label">${this.i18n.t("form.status")}</label>
                <input class="mm-input" type="number" name="status" value="200" min="100" max="599">
              </div>
            </div>

            <div class="mm-form-actions">
              <button type="button" class="mm-btn" data-action="cancel-edit" style="display: none;">${this.i18n.t("form.cancelEdit")}</button>
              <button type="submit" class="mm-btn mm-btn--primary" data-submit-btn>${this.i18n.t("form.addRule")}</button>
            </div>
          </form>
        </div>

        <div class="mm-tab-content" data-content="requests">
          <div class="mm-rules-header">
            <span class="mm-rules-count">0 ${this.i18n.t("network.count")}</span>
            <button class="mm-btn mm-btn--small" data-action="clear-requests">${this.i18n.t("network.clear")}</button>
          </div>
          <div class="mm-requests-list" data-requests-list></div>
        </div>
      </div>

      <input type="file" class="mm-hidden" data-action="import-file" accept=".json">
    `;
      this.shadowRoot.appendChild(panel);
    }
    /**
     * Create toggle button
     */
    createToggleButton() {
      this.ensureBody().then(() => {
        const btn = document.createElement("button");
        btn.className = "mm-toggle-btn";
        btn.innerHTML = `<svg width="32" height="32" viewBox="0 0 1100 1100" xmlns="http://www.w3.org/2000/svg"><path d="m734,63c-5.156,8.9192 -13.25,16.4694 -21,23.1304c-22.133,19.0216 -48.605,30.9516 -77,37.0796c-40.959,8.839 -82.535,6.04 -124,8.879c-32.995,2.26 -67.021,9.977 -98,21.441c-83.504,30.9 -152.371,96.289 -190.219,176.47c-9.231,19.555 -16.304,40.942 -21.13,62c-1.589,6.933 -3.039,13.985 -4.216,21c-0.512,3.05 -0.375,7.012 -2.373,9.566c-3.383,4.322 -12.27,2.14 -17.062,2.604c-13.745,1.33 -28.11,5.742 -41,10.511c-34.217,12.662 -64.4954,37.825 -85.5725,67.319c-40.9414,57.29 -47.57838,137.153 -18.1181,201c14.1931,30.76 37.1687,57.904 64.6906,77.573c22.04,15.75 48.304,28.971 75,34.423c21.891,4.471 43.785,4.004 66,4.004c-2.139,-11.52 0,-24.236 0,-36c0,-23.361 -0.587,-46.623 -0.015,-70c0.62,-25.3 0.015,-50.692 0.015,-76c0,-15.698 -0.633,-31.499 2.261,-47c6.11,-32.735 22.747,-62.021 46.739,-84.961c50.245,-48.041 131.473,-56.451 189,-16.003c22.522,15.836 40.353,38.007 51.769,62.964c6.884,15.049 13.513,33.319 14.231,50c2.557,-4.808 2.485,-10.729 3.665,-16c1.73,-7.725 4.32,-15.564 7.03,-23c8.175,-22.426 22.88,-43.679 40.305,-59.911c43.318,-40.351 109.9,-51.404 164,-26.994c21.834,9.851 40.846,25.249 55.8,43.905c7.466,9.314 14.73,19.166 19.88,30c8.515,17.914 14.26,37.222 16.15,57c1.909,19.974 0.17,40.93 0.17,61l0,152c17.434,0 35.754,0.367 53,-2.296c41.255,-6.372 82.421,-27.304 111.91,-56.793c60.88,-60.88 79.14,-161.628 35.75,-237.911c-18.16,-31.933 -44.73,-58.912 -76.66,-77.127c-24.827,-14.165 -52.586,-21.838 -81,-23.784c-9.423,-0.645 -18.644,0.718 -28,0.911c-0.885,-20.316 -7.587,-41.937 -14.309,-61c-16.902,-47.933 -43.683,-90.994 -79.691,-127c-10.652,-10.651 -21.973,-20.415 -34,-29.475c-7.012,-5.281 -13.879,-11.021 -22,-14.525c3.931,-12.292 5.677,-25.292 7.754,-38c3.375,-20.65 6.26,-41.24 8.964,-62c1.255,-9.6271 1.022,-19.5336 3.282,-29l-2,0m-350,511c1.644,8.855 1,18.023 1,27l0,44l0,139c31.683,0 64.477,-3.984 96,0l0,-210l-97,0m225,210l71,0l17,0c2.236,-0.005 5.508,0.468 7.397,-1.028c3.47,-2.748 0.941,-10.341 0.692,-13.972c-0.837,-12.224 -0.089,-24.746 -0.089,-37l0,-158l-70,0l-18,0c-2.218,0.005 -5.528,-0.478 -7.393,1.028c-2.838,2.291 -0.623,9.748 -0.608,12.972c0.061,12.666 0.001,25.334 0.001,38l0,158z" fill="#8D5524"/></svg>`;
        btn.title = "MockMonkey";
        btn.style.cssText = `position: fixed; bottom: ${this.buttonPosition.y}px; right: ${this.buttonPosition.x}px; width: 50px; height: 50px; border-radius: 50%; background: #f5f5f5; border: none; cursor: move; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); z-index: 999998; display: flex; align-items: center; justify-content: center; padding: 9px; user-select: none;`;
        this.toggleButton = btn;
        btn.addEventListener("click", (e) => {
          if (this.isDragging) return;
          e.preventDefault();
          e.stopPropagation();
          console.log("[MockMonkey] 按钮被点击");
          this.toggle();
        });
        this.bindDragEvents(btn);
        document.body.appendChild(btn);
        console.log("[MockMonkey] 切换按钮已添加到页面");
      });
    }
    /**
     * Ensure body element exists
     */
    ensureBody() {
      return new Promise((resolve) => {
        if (document.body) {
          resolve();
        } else {
          const checkBody = () => {
            if (document.body) {
              resolve();
            } else {
              setTimeout(checkBody, 10);
            }
          };
          checkBody();
        }
      });
    }
    /**
     * Bind drag events
     */
    bindDragEvents(btn) {
      btn.addEventListener("mousedown", (e) => {
        if (e.button !== 0) return;
        this.dragStartTime = Date.now();
        this.hasMoved = false;
        this.isDragging = false;
        const rect = btn.getBoundingClientRect();
        this.dragOffset.x = e.clientX - rect.left;
        this.dragOffset.y = e.clientY - rect.top;
        document.addEventListener("mousemove", this.handleMouseMove);
        document.addEventListener("mouseup", this.handleMouseUp);
        btn.style.transition = "none";
      });
    }
    /**
     * Save button position to localStorage
     */
    saveButtonPosition() {
      if (!this.toggleButton) return;
      const rect = this.toggleButton.getBoundingClientRect();
      const right = window.innerWidth - rect.left - rect.width;
      const bottom = window.innerHeight - rect.top - rect.height;
      this.buttonPosition = { x: Math.round(right), y: Math.round(bottom) };
      try {
        localStorage.setItem("mock-monkey-button-position", JSON.stringify(this.buttonPosition));
        console.log("[MockMonkey] 按钮位置已保存:", this.buttonPosition);
      } catch (e) {
        console.warn("[MockMonkey] 保存按钮位置失败:", e);
      }
    }
    /**
     * Load button position from localStorage
     */
    loadButtonPosition() {
      try {
        const saved = localStorage.getItem("mock-monkey-button-position");
        if (saved) {
          const position = JSON.parse(saved);
          if (typeof position.x === "number" && typeof position.y === "number" && position.x >= 0 && position.y >= 0) {
            this.buttonPosition = position;
            console.log("[MockMonkey] 按钮位置已加载:", this.buttonPosition);
          }
        }
      } catch (e) {
        console.warn("[MockMonkey] 加载按钮位置失败:", e);
      }
    }
    /**
     * Bind panel drag events
     */
    bindPanelDragEvents() {
      if (!this.shadowRoot) return;
      const dragHandle = this.shadowRoot.querySelector('[data-drag-handle="panel"]');
      if (!dragHandle) return;
      dragHandle.addEventListener("mousedown", (e) => {
        const mouseEvent = e;
        if (mouseEvent.button !== 0) return;
        if (mouseEvent.target.closest('[data-action="close"]')) return;
        this.panelDragStartTime = Date.now();
        this.panelHasMoved = false;
        this.isPanelDragging = false;
        const panel = this.panelElement;
        if (!panel) return;
        const rect = panel.getBoundingClientRect();
        this.panelDragOffset.x = mouseEvent.clientX - rect.left;
        this.panelDragOffset.y = mouseEvent.clientY - rect.top;
        document.addEventListener("mousemove", this.handlePanelMouseMove);
        document.addEventListener("mouseup", this.handlePanelMouseUp);
        panel.style.transition = "none";
        e.preventDefault();
      });
    }
    /**
     * Save panel position to localStorage
     */
    savePanelPosition() {
      if (!this.panelPosition) return;
      try {
        localStorage.setItem("mock-monkey-panel-position", JSON.stringify(this.panelPosition));
        console.log("[MockMonkey] 面板位置已保存:", this.panelPosition);
      } catch (e) {
        console.warn("[MockMonkey] 保存面板位置失败:", e);
      }
    }
    /**
     * Load panel position from localStorage
     */
    loadPanelPosition() {
      try {
        const saved = localStorage.getItem("mock-monkey-panel-position");
        if (saved) {
          const position = JSON.parse(saved);
          if (typeof position.left === "number" && typeof position.top === "number" && position.left >= 0 && position.top >= 0) {
            this.panelPosition = position;
            console.log("[MockMonkey] 面板位置已加载:", this.panelPosition);
          }
        }
      } catch (e) {
        console.warn("[MockMonkey] 加载面板位置失败:", e);
      }
    }
    /**
     * Export rules
     */
    exportRules() {
      try {
        const exportData = this.currentRules.map((rule) => ({
          pattern: rule.patternStr,
          response: rule.response,
          enabled: rule.enabled,
          delay: rule.delay,
          status: rule.status
        }));
        const blob = new Blob([JSON.stringify(exportData, null, 2)], {
          type: "application/json"
        });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = `mock-monkey-rules-${( new Date()).toISOString().slice(0, 10)}.json`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
        console.log("[MockMonkey] 规则导出成功:", exportData.length, "条");
      } catch (e) {
        console.error("[MockMonkey] 导出规则失败:", e);
      }
    }
    /**
     * Import rules
     */
    importRules() {
      const fileInput = this.shadowRoot?.querySelector('[data-action="import-file"]');
      if (fileInput) {
        fileInput.value = "";
        fileInput.click();
      }
    }
    /**
     * Handle import file
     */
    handleImportFile(e) {
      const input = e.currentTarget;
      const file = input.files?.[0];
      if (!file) return;
      const reader = new FileReader();
      reader.onload = (event) => {
        try {
          const content = event.target?.result;
          const importedRules = JSON.parse(content);
          if (!Array.isArray(importedRules)) {
            throw new Error(this.i18n.t("form.importError"));
          }
          let successCount = 0;
          importedRules.forEach((ruleData) => {
            let parsedPattern = ruleData.pattern;
            if (ruleData.pattern.startsWith("/")) {
              try {
                const match = ruleData.pattern.match(/^\/(.+)\/([gimuy]*)$/);
                if (match) {
                  parsedPattern = new RegExp(match[1], match[2]);
                }
              } catch (err) {
                console.warn("[MockMonkey] 跳过无效规则:", ruleData.pattern);
                return;
              }
            }
            this.onAddRule({
              pattern: parsedPattern,
              response: ruleData.response,
              options: {
                delay: ruleData.delay ?? 0,
                status: ruleData.status ?? 200
              }
            });
            successCount++;
          });
          console.log(`[MockMonkey] 成功导入 ${successCount} 条规则`);
          input.value = "";
        } catch (e2) {
          console.error("[MockMonkey] 导入规则失败:", e2);
          alert("导入失败:" + e2.message);
        }
      };
      reader.readAsText(file);
    }
    /**
     * Bind events
     */
    bindEvents() {
      if (!this.shadowRoot) return;
      this.shadowRoot.querySelector('[data-action="close"]')?.addEventListener("click", () => this.hide());
      this.shadowRoot.querySelector('[data-action="toggle-lang"]')?.addEventListener("click", () => {
        this.toggleLanguage();
      });
      this.shadowRoot.querySelectorAll(".mm-tab").forEach((tab) => {
        tab.addEventListener("click", (e) => {
          const target = e.currentTarget;
          const tabName = target.dataset.tab;
          if (tabName) this.switchTab(tabName);
        });
      });
      this.shadowRoot.querySelector('[data-action="clear-requests"]')?.addEventListener("click", () => {
        this.updateNetworkRequests([]);
      });
      this.shadowRoot.querySelector('[data-action="export"]')?.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        this.exportRules();
        e.currentTarget.blur();
      });
      this.shadowRoot.querySelector('[data-action="import"]')?.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        this.importRules();
        e.currentTarget.blur();
      });
      this.shadowRoot.querySelector('[data-action="import-file"]')?.addEventListener("change", (e) => {
        this.handleImportFile(e);
      });
      this.shadowRoot.querySelector('[data-action="add-rule"]')?.addEventListener("submit", (e) => {
        e.preventDefault();
        this.handleAddRule(e);
      });
      this.shadowRoot.querySelector('[data-action="cancel-edit"]')?.addEventListener("click", () => {
        this.cancelEdit();
      });
      const panel = this.shadowRoot.querySelector(".mm-panel");
      if (panel) {
        panel.addEventListener("wheel", (e) => {
          e.stopPropagation();
        }, { capture: true, passive: true });
      }
      this.bindPanelDragEvents();
    }
    /**
     * Switch tab
     */
    switchTab(tabName) {
      if (!this.shadowRoot) return;
      this.shadowRoot.querySelectorAll(".mm-tab").forEach((tab) => {
        const isActive = tab.dataset.tab === tabName;
        tab.classList.toggle("mm-tab--active", isActive);
      });
      this.shadowRoot.querySelectorAll(".mm-tab-content").forEach((content) => {
        const isActive = content.dataset.content === tabName;
        content.classList.toggle("mm-tab-content--active", isActive);
      });
    }
    /**
     * Toggle language and update UI
     */
    toggleLanguage() {
      this.i18n.toggleLanguage();
      this.updateLanguage();
    }
    /**
     * Update all UI text with current language
     */
    updateLanguage() {
      if (!this.shadowRoot) return;
      const langBtn = this.shadowRoot.querySelector('[data-action="toggle-lang"]');
      if (langBtn) {
        const lang = this.i18n.getLanguage();
        langBtn.textContent = lang === "zh" ? "EN" : "中";
        langBtn.title = lang === "zh" ? "Switch to English" : "切换中文";
      }
      this.shadowRoot.querySelectorAll(".mm-tab").forEach((tab) => {
        const tabName = tab.dataset.tab;
        if (tabName === "rules") {
          tab.textContent = this.i18n.t("tabs.rules");
        } else if (tabName === "add") {
          const isEditing = this.editingRuleId !== null;
          tab.textContent = isEditing ? this.i18n.t("common.edit") : this.i18n.t("tabs.add");
        } else if (tabName === "requests") {
          tab.textContent = this.i18n.t("tabs.network");
        }
      });
      const urlPatternLabel = this.shadowRoot.querySelector(".mm-form-group:first-child .mm-label");
      if (urlPatternLabel) urlPatternLabel.textContent = this.i18n.t("form.urlPattern");
      const urlPatternHint = this.shadowRoot.querySelector(".mm-form-group:first-child .mm-hint");
      if (urlPatternHint) urlPatternHint.textContent = this.i18n.t("form.urlPatternHint");
      const responseLabel = this.shadowRoot.querySelector(".mm-form-group:nth-child(2) .mm-label");
      if (responseLabel) responseLabel.textContent = this.i18n.t("form.responseData");
      const delayLabel = this.shadowRoot.querySelector(".mm-form-row .mm-form-group:first-child .mm-label");
      if (delayLabel) delayLabel.textContent = this.i18n.t("form.delay");
      const statusLabel = this.shadowRoot.querySelector(".mm-form-row .mm-form-group:last-child .mm-label");
      if (statusLabel) statusLabel.textContent = this.i18n.t("form.status");
      const cancelBtn = this.shadowRoot.querySelector('[data-action="cancel-edit"]');
      if (cancelBtn) cancelBtn.textContent = this.i18n.t("form.cancelEdit");
      const submitBtn = this.shadowRoot.querySelector("[data-submit-btn]");
      if (submitBtn) {
        submitBtn.textContent = this.editingRuleId ? this.i18n.t("form.saveRule") : this.i18n.t("form.addRule");
      }
      const exportBtn = this.shadowRoot.querySelector('[data-action="export"]');
      if (exportBtn) exportBtn.textContent = this.i18n.t("rules.export");
      const importBtn = this.shadowRoot.querySelector('[data-action="import"]');
      if (importBtn) importBtn.textContent = this.i18n.t("rules.import");
      const clearRequestsBtn = this.shadowRoot.querySelector('[data-action="clear-requests"]');
      if (clearRequestsBtn) clearRequestsBtn.textContent = this.i18n.t("network.clear");
      this.updateRules(this.currentRules);
      this.updateNetworkRequests(this.networkRequests);
    }
    /**
     * Handle add rule
     */
    handleAddRule(e) {
      const form = e.currentTarget;
      const formData = new FormData(form);
      const pattern = formData.get("pattern");
      const responseStr = formData.get("response");
      const delay = parseInt(formData.get("delay")) || 0;
      const status = parseInt(formData.get("status")) || 200;
      const editingId = formData.get("editing-id");
      let parsedPattern = pattern;
      if (pattern.startsWith("/")) {
        try {
          const match = pattern.match(/^\/(.+)\/([gimuy]*)$/);
          if (match) {
            parsedPattern = new RegExp(match[1], match[2]);
          }
        } catch (err) {
          alert(this.i18n.t("form.regexError"));
          return;
        }
      }
      let response;
      try {
        response = JSON.parse(responseStr);
      } catch (err) {
        alert(this.i18n.t("form.jsonError"));
        return;
      }
      const ruleData = {
        pattern: parsedPattern,
        response,
        options: { delay, status }
      };
      if (editingId) {
        this.onUpdateRule?.(editingId, ruleData);
      } else {
        this.onAddRule(ruleData);
      }
      this.cancelEdit();
      form.reset();
      this.switchTab("rules");
    }
    /**
     * Cancel edit mode
     */
    cancelEdit() {
      this.editingRuleId = null;
      if (!this.shadowRoot) return;
      const form = this.shadowRoot.querySelector('[data-action="add-rule"]');
      const submitBtn = this.shadowRoot.querySelector("[data-submit-btn]");
      const cancelBtn = this.shadowRoot.querySelector('[data-action="cancel-edit"]');
      const tabLabel = this.shadowRoot.querySelector("[data-tab-label]");
      const editingIdInput = this.shadowRoot.querySelector('[name="editing-id"]');
      if (form) form.reset();
      if (submitBtn) submitBtn.textContent = this.i18n.t("form.addRule");
      if (cancelBtn) cancelBtn.style.display = "none";
      if (tabLabel) tabLabel.textContent = this.i18n.t("tabs.add");
      if (editingIdInput) editingIdInput.value = "";
    }
    /**
     * Enter edit mode
     */
    enterEditMode(rule) {
      this.editingRuleId = rule.id;
      if (!this.shadowRoot) return;
      const form = this.shadowRoot.querySelector('[data-action="add-rule"]');
      const submitBtn = this.shadowRoot.querySelector("[data-submit-btn]");
      const cancelBtn = this.shadowRoot.querySelector('[data-action="cancel-edit"]');
      const tabLabel = this.shadowRoot.querySelector("[data-tab-label]");
      const editingIdInput = this.shadowRoot.querySelector('[name="editing-id"]');
      const patternInput = this.shadowRoot.querySelector('[name="pattern"]');
      const responseInput = this.shadowRoot.querySelector('[name="response"]');
      const delayInput = this.shadowRoot.querySelector('[name="delay"]');
      const statusInput = this.shadowRoot.querySelector('[name="status"]');
      if (patternInput) patternInput.value = rule.patternStr;
      if (responseInput) responseInput.value = JSON.stringify(rule.response, null, 2);
      if (delayInput) delayInput.value = rule.delay.toString();
      if (statusInput) statusInput.value = rule.status.toString();
      if (editingIdInput) editingIdInput.value = rule.id;
      if (submitBtn) submitBtn.textContent = this.i18n.t("form.saveRule");
      if (cancelBtn) cancelBtn.style.display = "";
      if (tabLabel) tabLabel.textContent = this.i18n.t("common.edit");
      this.switchTab("add");
    }
    /**
     * Update rules list
     */
    updateRules(rules) {
      this.currentRules = rules;
      if (!this.shadowRoot) return;
      const listContainer = this.shadowRoot.querySelector("[data-rules-list]");
      const countEl = this.shadowRoot.querySelector(".mm-rules-count");
      if (!listContainer) return;
      if (countEl) {
        countEl.textContent = `${rules.length} ${this.i18n.t("rules.count")}`;
      }
      if (rules.length === 0) {
        listContainer.innerHTML = `
        <div class="mm-empty">
          <p>${this.i18n.t("rules.empty")}</p>
          <p class="mm-hint">${this.i18n.t("rules.startConfig")}</p>
        </div>
      `;
        listContainer.querySelector('[data-action="go-to-add"]')?.addEventListener("click", () => {
          this.switchTab("add");
        });
        return;
      }
      listContainer.innerHTML = rules.map(
        (rule) => `
      <div class="mm-rule-item ${rule.enabled ? "" : "mm-rule-item--disabled"}">
        <div class="mm-rule-header">
          <span class="mm-rule-pattern" title="${this.escapeHtmlAttr(rule.patternStr)}">${this.escapeHtml(rule.patternStr)}</span>
          <div class="mm-rule-actions">
            <button class="mm-btn-icon" data-action="toggle" data-id="${rule.id}" title="${rule.enabled ? this.i18n.t("common.disable") : this.i18n.t("common.enable")}">
              ${rule.enabled ? "🟢" : "⚫"}
            </button>
            <button class="mm-btn-icon" data-action="edit" data-id="${rule.id}" title="${this.i18n.t("common.edit")}">✏️</button>
            <button class="mm-btn-icon" data-action="delete" data-id="${rule.id}" title="${this.i18n.t("common.delete")}">🗑️</button>
          </div>
        </div>
        <details class="mm-rule-details">
          <summary class="mm-rule-summary">${this.i18n.t("common.details")}</summary>
          <div class="mm-rule-meta">
            <span>${this.i18n.t("rules.status")}: ${rule.status}</span>
            <span>${this.i18n.t("rules.delay")}: ${rule.delay}ms</span>
          </div>
          <pre class="mm-rule-response">${this.escapeHtml(JSON.stringify(rule.response, null, 2))}</pre>
        </details>
      </div>
    `
      ).join("");
      listContainer.querySelectorAll('[data-action="toggle"]').forEach((btn) => {
        btn.addEventListener("click", (e) => {
          const id = e.currentTarget.dataset.id;
          if (id) this.onToggleRule(id);
        });
      });
      listContainer.querySelectorAll('[data-action="edit"]').forEach((btn) => {
        btn.addEventListener("click", (e) => {
          const id = e.currentTarget.dataset.id;
          if (id) {
            const rule = rules.find((r) => r.id === id);
            if (rule) this.enterEditMode(rule);
          }
        });
      });
      listContainer.querySelectorAll('[data-action="delete"]').forEach((btn) => {
        btn.addEventListener("click", (e) => {
          const id = e.currentTarget.dataset.id;
          if (id) this.onDeleteRule(id);
        });
      });
    }
    /**
     * Update network request list
     */
    updateNetworkRequests(requests) {
      this.networkRequests = requests;
      this.requestsById = new Map(requests.map((r) => [r.id, r]));
      if (!this.shadowRoot) return;
      const listContainer = this.shadowRoot.querySelector("[data-requests-list]");
      const countEl = this.shadowRoot.querySelector('[data-content="requests"] .mm-rules-count');
      if (!listContainer) return;
      if (countEl) {
        countEl.textContent = `${requests.length} ${this.i18n.t("network.count")}`;
      }
      if (requests.length === 0) {
        listContainer.innerHTML = `
        <div class="mm-empty">
          <p>${this.i18n.t("network.empty")}</p>
          <p class="mm-hint">${this.i18n.t("network.emptyHint")}</p>
        </div>
      `;
        return;
      }
      listContainer.innerHTML = requests.map(
        (req) => `
      <div class="mm-request-item ${req.mocked ? "mm-request-item--mocked" : ""}" data-request-id="${req.id}">
        <div class="mm-request-header">
          <span class="mm-request-method" data-method="${req.method}">${req.method}</span>
          <span class="mm-request-url" title="${this.escapeHtmlAttr(req.url)}">${this.escapeHtml(this.truncateUrl(req.url))}</span>
          <span class="mm-request-type">${req.type}</span>
          ${req.mocked ? '<span class="mm-badge mm-badge--mocked">MOCK</span>' : ""}
        </div>
        <div class="mm-request-meta">
          <span class="mm-request-status" data-status="${req.status ? Math.floor(req.status / 100).toString() : ""}">${req.status ?? "PENDING"}</span>
          <span class="mm-request-duration">${req.duration ? `${req.duration}ms` : "-"}</span>
          <span class="mm-request-time">${new Date(req.timestamp).toLocaleTimeString()}</span>
          <button class="mm-btn mm-btn--small mm-btn-create-mock" data-action="create-mock" data-request-id="${req.id}" title="${this.i18n.t("network.createMock")}">
            + Mock
          </button>
        </div>
        ${req.response !== void 0 ? `
          <details class="mm-request-details">
            <summary class="mm-request-summary">${this.i18n.t("network.responseData")}</summary>
            <pre class="mm-request-response">${this.escapeHtml(JSON.stringify(req.response, null, 2))}</pre>
          </details>
        ` : ""}
      </div>
    `
      ).join("");
      listContainer.querySelectorAll('[data-action="create-mock"]').forEach((btn) => {
        btn.addEventListener("click", (e) => {
          const id = e.currentTarget.dataset.requestId;
          if (id) this.handleCreateFromRequest(id);
        });
      });
    }
    /**
     * 显示面板
     */
    show() {
      console.log("[MockMonkey] show() 被调用", { container: !!this.container, shadowRoot: !!this.shadowRoot });
      if (this.container) {
        this.isVisible = true;
        this.container.classList.add("mm-panel--visible");
        console.log("[MockMonkey] 已添加 mm-panel--visible 类");
        const panel = this.shadowRoot?.querySelector(".mm-panel");
        if (panel) {
          panel.style.display = "flex";
          panel.style.opacity = "1";
          panel.style.pointerEvents = "auto";
          console.log("[MockMonkey] 面板样式已应用");
        } else {
          console.error("[MockMonkey] 找不到 .mm-panel 元素");
        }
      }
    }
    /**
     * 隐藏面板
     */
    hide() {
      console.log("[MockMonkey] hide() 被调用");
      if (this.container) {
        this.isVisible = false;
        this.container.classList.remove("mm-panel--visible");
        const panel = this.shadowRoot?.querySelector(".mm-panel");
        if (panel) {
          panel.style.opacity = "0";
          panel.style.pointerEvents = "none";
        }
      }
    }
    /**
     * 切换显示状态
     */
    toggle() {
      console.log("[MockMonkey] toggle() 被调用", { isVisible: this.isVisible });
      if (this.isVisible) {
        this.hide();
      } else {
        this.show();
      }
    }
    /**
     * Create Mock rule from network request
     */
    handleCreateFromRequest(requestId) {
      const requestsById = this.requestsById;
      const request = requestsById?.get(requestId);
      if (!request) return;
      if (this.onCreateFromRequest) {
        this.onCreateFromRequest(request);
      }
      if (!this.shadowRoot) return;
      const patternInput = this.shadowRoot.querySelector('[name="pattern"]');
      const responseInput = this.shadowRoot.querySelector('[name="response"]');
      const statusInput = this.shadowRoot.querySelector('[name="status"]');
      if (patternInput) {
        patternInput.value = request.url;
      }
      if (responseInput && request.response !== void 0) {
        responseInput.value = JSON.stringify(request.response, null, 2);
      }
      if (statusInput && request.status) {
        statusInput.value = request.status.toString();
      }
      this.switchTab("add");
    }
    /**
     * HTML escape
     */
    escapeHtml(text) {
      const div = document.createElement("div");
      div.textContent = text;
      return div.innerHTML;
    }
    /**
     * HTML attribute escape (escape double quotes)
     */
    escapeHtmlAttr(text) {
      return this.escapeHtml(text).replace(/"/g, "&quot;");
    }
    /**
     * Truncate URL, display domain + path + partial query params
     */
    truncateUrl(url, maxLength = 100) {
      try {
        const urlObj = new URL(url);
        const baseUrl = `${urlObj.origin}${urlObj.pathname}`;
        if (urlObj.search) {
          const searchParams = new URLSearchParams(urlObj.search);
          const params = [];
          let currentLength = baseUrl.length + 1;
          for (const [key, value] of searchParams.entries()) {
            const paramStr = `${key}=${value}`;
            if (currentLength + paramStr.length + 1 > maxLength) {
              break;
            }
            params.push(paramStr);
            currentLength += paramStr.length + 1;
          }
          const queryString = params.length > 0 ? "?" + params.join("&") : "?";
          if (params.length < Array.from(searchParams.entries()).length) {
            return baseUrl + queryString + "...";
          }
          return baseUrl + queryString;
        }
        return baseUrl;
      } catch {
        if (url.length > maxLength) {
          return url.substring(0, maxLength - 3) + "...";
        }
        return url;
      }
    }
    /**
     * Get styles
     */
    getStyles() {
      return `
      :host {
        all: initial;
      }

      .mm-panel {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 600px;
        max-width: 90vw;
        max-height: 80vh;
        background: #fff;
        border-radius: 12px;
        box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
        display: flex;
        flex-direction: column;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        font-size: 14px;
        color: #333;
        z-index: 999999;
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.2s;
      }

      .mm-panel--visible {
        opacity: 1;
        pointer-events: auto;
      }

      .mm-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 16px 20px;
        border-bottom: 1px solid #e0e0e0;
        cursor: move;
        user-select: none;
      }

      .mm-title {
        margin: 0;
        font-size: 18px;
        font-weight: 600;
      }

      .mm-close-btn {
        background: none;
        border: none;
        font-size: 24px;
        cursor: pointer;
        padding: 0;
        width: 32px;
        height: 32px;
        border-radius: 4px;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .mm-close-btn:hover {
        background: #f5f5f5;
      }

      .mm-header-actions {
        display: flex;
        align-items: center;
        gap: 8px;
      }

      .mm-lang-btn {
        background: #f5f5f5;
        border: 1px solid #d1d5db;
        border-radius: 6px;
        padding: 6px 12px;
        font-size: 12px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s;
        min-width: 45px;
        text-align: center;
      }

      .mm-lang-btn:hover {
        background: #e5e7eb;
        border-color: #9ca3af;
      }

      .mm-tabs {
        display: flex;
        border-bottom: 1px solid #e0e0e0;
      }

      .mm-tab {
        flex: 1;
        padding: 12px;
        background: none;
        border: none;
        border-bottom: 2px solid transparent;
        cursor: pointer;
        font-size: 14px;
        font-weight: 500;
        color: #666;
        transition: all 0.2s;
      }

      .mm-tab:hover {
        color: #333;
        background: #fafafa;
      }

      .mm-tab--active {
        color: #4f46e5;
        border-bottom-color: #4f46e5;
      }

      .mm-content {
        flex: 1;
        overflow: hidden;
        display: flex;
        flex-direction: column;
      }

      .mm-tab-content {
        display: none;
        flex: 1;
        overflow-y: auto;
        padding: 20px;
        overscroll-behavior: contain;
      }

      .mm-tab-content--active {
        display: block;
      }

      .mm-rules-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 16px;
      }

      .mm-rules-count {
        font-weight: 500;
        color: #666;
      }

      .mm-rules-list {
        display: flex;
        flex-direction: column;
        gap: 12px;
      }

      .mm-empty {
        text-align: center;
        padding: 40px 20px;
        color: #999;
      }

      .mm-rule-item {
        background: #f9fafb;
        border: 1px solid #e5e7eb;
        border-radius: 8px;
        padding: 12px;
        transition: all 0.2s;
      }

      .mm-rule-item:hover {
        border-color: #d1d5db;
      }

      .mm-rule-item--disabled {
        opacity: 0.6;
      }

      .mm-rule-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 8px;
      }

      .mm-rule-pattern {
        flex: 1;
        min-width: 0;
        font-weight: 500;
        color: #4f46e5;
        font-family: 'Monaco', 'Menlo', monospace;
        font-size: 13px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }

      .mm-rule-actions {
        display: flex;
        gap: 4px;
      }

      .mm-rule-details {
        margin-top: 8px;
      }

      .mm-rule-summary {
        cursor: pointer;
        font-size: 12px;
        color: #6b7280;
        user-select: none;
        padding: 4px 0;
        list-style: none;
      }

      .mm-rule-summary::-webkit-details-marker {
        display: none;
      }

      .mm-rule-summary::before {
        content: '▶';
        display: inline-block;
        margin-right: 6px;
        transition: transform 0.2s;
        font-size: 10px;
      }

      details[open] > .mm-rule-summary::before {
        transform: rotate(90deg);
      }

      .mm-rule-meta {
        display: flex;
        gap: 12px;
        font-size: 12px;
        color: #6b7280;
        margin-bottom: 8px;
        margin-top: 8px;
      }

      .mm-rule-response {
        margin: 0;
        padding: 8px 12px;
        background: #fff;
        border-radius: 4px;
        font-size: 12px;
        font-family: 'Monaco', 'Menlo', monospace;
        color: #374151;
        overflow-x: auto;
        max-height: 150px;
        overflow-y: auto;
      }

      .mm-form-group {
        margin-bottom: 16px;
      }

      .mm-label {
        display: block;
        margin-bottom: 6px;
        font-weight: 500;
        color: #374151;
      }

      .mm-input,
      .mm-textarea {
        width: 100%;
        padding: 10px 12px;
        border: 1px solid #d1d5db;
        border-radius: 6px;
        font-size: 14px;
        font-family: inherit;
        box-sizing: border-box;
        transition: border-color 0.2s;
      }

      .mm-input:focus,
      .mm-textarea:focus {
        outline: none;
        border-color: #4f46e5;
        box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
      }

      .mm-textarea {
        font-family: 'Monaco', 'Menlo', monospace;
        font-size: 13px;
        resize: vertical;
      }

      .mm-hint {
        display: block;
        margin-top: 4px;
        font-size: 12px;
        color: #6b7280;
      }

      .mm-link {
        color: #4f46e5;
        cursor: pointer;
        text-decoration: underline;
      }

      .mm-link:hover {
        color: #4338ca;
      }

      .mm-form-row {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 12px;
      }

      .mm-form-actions {
        display: flex;
        justify-content: flex-end;
        padding-top: 8px;
      }

      .mm-btn {
        padding: 10px 16px;
        border: 1px solid #d1d5db;
        border-radius: 6px;
        background: #fff;
        font-size: 14px;
        font-weight: 500;
        cursor: pointer;
        transition: all 0.2s;
      }

      .mm-btn:hover {
        background: #f9fafb;
        border-color: #9ca3af;
      }

      .mm-btn--primary {
        background: #4f46e5;
        color: #fff;
        border-color: #4f46e5;
      }

      .mm-btn--primary:hover {
        background: #4338ca;
        border-color: #4338ca;
      }

      .mm-btn--small {
        padding: 6px 12px;
        font-size: 12px;
      }

      .mm-btn-create-mock {
        margin-left: auto;
        padding: 4px 10px;
        font-size: 11px;
        background: #4f46e5;
        color: #fff;
        border-color: #4f46e5;
        white-space: nowrap;
      }

      .mm-btn-create-mock:hover {
        background: #4338ca;
        border-color: #4338ca;
      }

      .mm-btn-icon {
        background: none;
        border: none;
        font-size: 16px;
        cursor: pointer;
        padding: 4px 8px;
        border-radius: 4px;
        opacity: 0.7;
        transition: opacity 0.2s;
      }

      .mm-btn-icon:hover {
        opacity: 1;
        background: rgba(0, 0, 0, 0.05);
      }

      .mm-toggle-btn {
        position: fixed;
        bottom: 20px;
        right: 20px;
        width: 50px;
        height: 50px;
        border-radius: 50%;
        background: #4f46e5;
        border: none;
        font-size: 24px;
        cursor: pointer;
        box-shadow: 0 4px 12px rgba(79, 70, 229, 0.4);
        z-index: 999998;
        transition: transform 0.2s, box-shadow 0.2s;
      }

      .mm-toggle-btn:hover {
        transform: scale(1.1);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
      }

      .mm-requests-list {
        display: flex;
        flex-direction: column;
        gap: 8px;
      }

      .mm-request-item {
        background: #f9fafb;
        border: 1px solid #e5e7eb;
        border-radius: 8px;
        padding: 12px;
        font-size: 13px;
        transition: all 0.2s;
      }

      .mm-request-item:hover {
        border-color: #d1d5db;
      }

      .mm-request-item--mocked {
        background: #f0fdf4;
        border-color: #86efac;
      }

      .mm-request-header {
        display: flex;
        align-items: center;
        gap: 8px;
        margin-bottom: 6px;
        flex-wrap: wrap;
      }

      .mm-request-method {
        font-weight: 600;
        font-size: 11px;
        padding: 2px 6px;
        border-radius: 4px;
        background: #e5e7eb;
        color: #374151;
        min-width: 45px;
        text-align: center;
      }

      .mm-request-method[data-method="GET"] {
        background: #dbeafe;
        color: #1d4ed8;
      }

      .mm-request-method[data-method="POST"] {
        background: #dcfce7;
        color: #16a34a;
      }

      .mm-request-method[data-method="PUT"] {
        background: #fef3c7;
        color: #d97706;
      }

      .mm-request-method[data-method="DELETE"] {
        background: #fee2e2;
        color: #dc2626;
      }

      .mm-request-url {
        flex: 1;
        min-width: 0;
        font-family: 'Monaco', 'Menlo', monospace;
        font-size: 12px;
        color: #374151;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }

      .mm-request-type {
        font-size: 10px;
        padding: 2px 6px;
        border-radius: 4px;
        background: #e5e7eb;
        color: #6b7280;
        font-weight: 500;
      }

      .mm-badge {
        font-size: 10px;
        padding: 2px 6px;
        border-radius: 4px;
        font-weight: 500;
      }

      .mm-badge--mocked {
        background: #22c55e;
        color: #fff;
      }

      .mm-request-meta {
        display: flex;
        gap: 12px;
        font-size: 11px;
        color: #6b7280;
      }

      .mm-request-status {
        font-weight: 500;
      }

      .mm-request-status[data-status="2"] {
        color: #16a34a;
      }

      .mm-request-status[data-status="3"] {
        color: #d97706;
      }

      .mm-request-status[data-status="4"],
      .mm-request-status[data-status="5"] {
        color: #dc2626;
      }

      .mm-request-duration {
        font-family: 'Monaco', 'Menlo', monospace;
      }

      .mm-request-details {
        margin-top: 8px;
      }

      .mm-request-summary {
        cursor: pointer;
        font-size: 11px;
        color: #6b7280;
        user-select: none;
        padding: 4px 0;
      }

      .mm-request-summary:hover {
        color: #374151;
      }

      .mm-request-response {
        margin: 4px 0 0 0;
        padding: 8px 12px;
        background: #fff;
        border-radius: 4px;
        font-size: 11px;
        font-family: 'Monaco', 'Menlo', monospace;
        color: #374151;
        overflow-x: auto;
        max-height: 200px;
        overflow-y: auto;
      }

      .mm-hidden {
        display: none;
      }
    `;
    }
  }
  class PanelWithCallbacks extends Panel {
    constructor(onAddRule, callbacks, onCreateFromRequest) {
      super(onAddRule, callbacks.onEdit, onCreateFromRequest);
      this.callbacks = callbacks;
    }
    onToggleRule(id) {
      this.callbacks.onToggle(id);
    }
    onEditRule(id, rule) {
      this.callbacks.onEdit(id, rule);
    }
    onDeleteRule(id) {
      this.callbacks.onDelete(id);
    }
  }
  class MockMonkey {
    constructor() {
      this.recorder = new RequestRecorder();
      this.manager = new MockManager();
      this.interceptor = new Interceptor(this.manager, this.recorder);
      this.i18n = I18n.getInstance();
      this.panel = new PanelWithCallbacks(
        (rule) => this.handleAddRule(rule),
        {
          onToggle: (id) => this.handleToggleRule(id),
          onEdit: (id, rule) => this.handleEditRule(id, rule),
          onDelete: (id) => this.handleDeleteRule(id)
        }
      );
      this.recorder.subscribe((requests) => {
        this.panel.updateNetworkRequests(requests);
      });
    }
    /**
     * Get singleton instance
     */
    static getInstance() {
      if (!MockMonkey.instance) {
        MockMonkey.instance = new MockMonkey();
      }
      return MockMonkey.instance;
    }
    /**
     * Start MockMonkey
     */
    start() {
      if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", () => this.init());
      } else {
        this.init();
      }
    }
    /**
     * Initialize
     */
    init() {
      this.interceptor.start();
      this.panel.init();
      this.updateRulesList();
      console.log("[MockMonkey] Started! Click the 🐵 button in the bottom right to open the management panel");
    }
    /**
     * Add rule
     */
    handleAddRule(rule) {
      this.manager.add(rule);
      this.updateRulesList();
      console.log("[MockMonkey] Rule added");
    }
    /**
     * Toggle rule status
     */
    handleToggleRule(id) {
      const enabled = this.manager.toggle(id);
      this.updateRulesList();
      console.log(`[MockMonkey] Rule ${enabled ? "enabled" : "disabled"}`);
    }
    /**
     * Edit rule
     */
    handleEditRule(id, rule) {
      const success = this.manager.update(id, {
        pattern: rule.pattern,
        response: rule.response,
        options: rule.options
      });
      if (success) {
        this.updateRulesList();
        console.log("[MockMonkey] Rule updated");
      } else {
        console.error("[MockMonkey] Rule update failed: rule not found");
      }
    }
    /**
     * Delete rule
     */
    handleDeleteRule(id) {
      if (confirm(`[MockMonkey] ${this.i18n.t("common.confirmDelete")}`)) {
        this.manager.remove(id);
        this.updateRulesList();
        console.log("[MockMonkey] Rule deleted");
      }
    }
    /**
     * Update rules list
     */
    updateRulesList() {
      const rules = this.manager.getAll().map((rule) => ({
        id: rule.id,
        patternStr: rule.pattern instanceof RegExp ? rule.pattern.toString() : rule.pattern,
        response: rule.response,
        enabled: rule.enabled,
        delay: rule.options.delay || 0,
        status: rule.options.status || 200
      }));
      this.panel.updateRules(rules);
    }
  }
  MockMonkey.getInstance().start();
  window.mockMonkey = {
    add: (pattern, response, options) => {
      MockMonkey.getInstance()["manager"].add({ pattern, response, options });
      MockMonkey.getInstance()["updateRulesList"]();
    },
    remove: (pattern) => {
      MockMonkey.getInstance()["manager"].removeByPattern(pattern);
      MockMonkey.getInstance()["updateRulesList"]();
    },
    clear: () => {
      MockMonkey.getInstance()["manager"].clear();
      MockMonkey.getInstance()["updateRulesList"]();
    },
    list: () => {
      const manager = MockMonkey.getInstance()["manager"];
      console.log("[MockMonkey] Current rules:");
      manager.getAll().forEach((rule) => {
        console.log(`  ${rule.enabled ? "✓" : "✗"} ${rule.pattern}`, rule);
      });
    },
    listRequests: () => {
      const recorder = MockMonkey.getInstance()["recorder"];
      console.log("[MockMonkey] Network request records:");
      recorder.getRequests().forEach((req) => {
        console.log(`  ${req.mocked ? "🟢 MOCK" : "⚪ REAL"} ${req.method} ${req.url}`, req);
      });
    },
    clearRequests: () => {
      const recorder = MockMonkey.getInstance()["recorder"];
      recorder.clear();
      console.log("[MockMonkey] Network request records cleared");
    },
    manager: MockMonkey.getInstance()["manager"],
    recorder: MockMonkey.getInstance()["recorder"]
  };
})();