Typingerz Ritual Hook

Typingerz通信掌握儀式 - 全通信網羅版(ソケット競合修正版)

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Typingerz Ritual Hook
// @namespace    http://tampermonkey.net/
// @version      3.5
// @description  Typingerz通信掌握儀式 - 全通信網羅版(ソケット競合修正版)
// @match        https://typingerz.com/battlemenu/*
// @grant        none
// @license MIT
// ==/UserScript==


(function() {
    'use strict';

    if (document.getElementById('th-panel')) {
        console.log('[TH] UIが既に存在するため中断します。');
        return;
    }

    console.log('%c[TH] 初期化開始...', 'color: #00ff00; font-weight: bold; font-size: 14px;');

    if (!window.TH) {

    // メイン機能オブジェクト
    window.TH = {
        _intervals: [],
        playerNum: 1, // デフォルトプレイヤー番号

        // 常に最新のソケットを取得する関数
        _getActiveSocket: function() {
            let socket = null;

            if (typeof io !== 'undefined') {
                // 方法1: Socket.IOマネージャーから最新のソケットを取得
                if (io.managers) {
                    for (let url in io.managers) {
                        const manager = io.managers[url];
                        if (manager && manager.nsps) {
                            for (let nsp in manager.nsps) {
                                const candidateSocket = manager.nsps[nsp];
                                // 接続されているソケットを優先
                                if (candidateSocket && candidateSocket.connected) {
                                    socket = candidateSocket;
                                    break;
                                }
                                // 接続されていなくても、存在するソケットを保持
                                if (!socket && candidateSocket) {
                                    socket = candidateSocket;
                                }
                            }
                        }
                        if (socket && socket.connected) break;
                    }
                }
            }

            return socket;
        },

        // 安全な送信(常に最新のソケットを使用)
        _emit: function(event, data) {
            return new Promise((resolve, reject) => {
                try {
                    // 送信前に必ず最新のソケットを取得
                    const currentSocket = this._getActiveSocket();

                    if (!currentSocket) {
                        console.error('[TH] Socket未検出。ページをリロードしてください。');
                        reject(new Error('Socket未検出'));
                        return;
                    }

                    if (!currentSocket.connected) {
                        console.warn('[TH] Socket未接続。再接続試行中...');
                        currentSocket.connect();
                        setTimeout(() => {
                            if (currentSocket.connected) {
                                currentSocket.emit(event, data);
                                console.log(`[TH] 再接続後送信成功: ${event}`);
                                resolve(true);
                            } else {
                                reject(new Error('再接続失敗'));
                            }
                        }, 1000);
                    } else {
                        currentSocket.emit(event, data);
                        resolve(true);
                    }
                } catch(e) {
                    console.error('[TH] 送信エラー:', e);
                    reject(e);
                }
            });
        },

        // ==================== バトル関連 ====================

        // ダメージ送信(修正版)
        damage: async function(target, amount = 100) {
            if (typeof target !== 'number') {
                console.error('[TH] 使い方: TH.damage(1 or 2, ダメージ数)');
                return false;
            }

            try {
                if (target === 1) {
                    // プレイヤー1にダメージ
                    await this._emit('damage', amount);
                    console.log(`%c[TH] ✓ プレイヤー1にダメージ: ${amount}`, 'color: #ff6b6b;');
                } else if (target === 2) {
                    // プレイヤー2にダメージ
                    await this._emit('cdamage', amount);
                    console.log(`%c[TH] ✓ プレイヤー2にダメージ: ${amount}`, 'color: #ff6b6b;');
                } else {
                    console.error('[TH] ターゲットは 1 または 2 を指定してください');
                    return false;
                }
                return true;
            } catch(e) {
                console.error('[TH] ✗ ダメージ送信失敗:', e.message);
                return false;
            }
        },

        // ペナルティダメージ(修正版)
        penalty: async function(target, amount = 50) {
            if (typeof target !== 'number') {
                console.error('[TH] 使い方: TH.penalty(1 or 2, ペナルティ数)');
                return false;
            }

            try {
                if (target === 1) {
                    // プレイヤー1にペナルティ
                    await this._emit('penaDamage', amount);
                    console.log(`%c[TH] ✓ プレイヤー1にペナルティ: ${amount}`, 'color: #ff6348;');
                } else if (target === 2) {
                    // プレイヤー2にペナルティ
                    await this._emit('cpenaDamage', amount);
                    console.log(`%c[TH] ✓ プレイヤー2にペナルティ: ${amount}`, 'color: #ff6348;');
                } else {
                    console.error('[TH] ターゲットは 1 または 2 を指定してください');
                    return false;
                }
                return true;
            } catch(e) {
                console.error('[TH] ✗ ペナルティ送信失敗:', e.message);
                return false;
            }
        },

        // ミス送信
        miss: async function(count = 1) {
            try {
                await this._emit('miss', count);
                console.log(`%c[TH] ✓ ミス送信: ${count}回`, 'color: #95afc0;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ ミス送信失敗:', e.message);
                return false;
            }
        },

        // 必殺技発動
        hadou: async function(hadouID = 1, missCount = 0, playerNum = null) {
            try {
                await this._emit('hadouOn', {
                    hadoumiss: missCount,
                    hadouID: hadouID,
                    playerNum: playerNum || this.playerNum,
                    hadouImageStop: 0
                });
                console.log(`%c[TH] ✓ 必殺技発動: ID=${hadouID}, Miss=${missCount}`, 'color: #ffd93d;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ 必殺技発動失敗:', e.message);
                return false;
            }
        },

        // ハンデ送信
        sendHandi: async function(handiP, handiHP, handiDamageRate) {
            try {
                await this._emit('sendHandi', {
                    handiP: handiP,
                    handiHP: handiHP,
                    handiDamageRate: handiDamageRate
                });
                console.log(`%c[TH] ✓ ハンデ送信: P=${handiP}, HP=${handiHP}`, 'color: #a29bfe;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ ハンデ送信失敗:', e.message);
                return false;
            }
        },

        // ==================== プレイヤー情報 ====================

        // プレイヤー参加
        joinBattle: async function(userData = {}) {
            const defaultData = {
                pn: 1,
                monsterID: 1,
                tabletemp: [],
                str: 10,
                vit: 10,
                dex: 10,
                hp: 100,
                lv: 1,
                typingerRank: 'H',
                nickname: 'Player',
                speed: 100,
                handiflag: 0,
                userid: 'test123',
                renshou: 0,
                chatOnOff: 'on'
            };

            const data = Object.assign({}, defaultData, userData);

            try {
                await this._emit('st_koukan', data);
                console.log(`%c[TH] ✓ プレイヤー情報送信`, 'color: #4ecdc4;', data);
                return true;
            } catch(e) {
                console.error('[TH] ✗ プレイヤー情報送信失敗:', e.message);
                return false;
            }
        },

        // レーティング交換
        sendRating: async function(rating = 1500, rd = 350, volatility = 0.06, nickname = 'Player', rank = 'H') {
            try {
                await this._emit('ratingKoukan', {
                    rating: rating,
                    rd: rd,
                    volatility: volatility,
                    nickname: nickname,
                    rank: rank
                });
                console.log(`%c[TH] ✓ レーティング送信: ${rating}`, 'color: #ffd93d;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ レーティング送信失敗:', e.message);
                return false;
            }
        },

        // 結果交換
        sendResult: async function(speed, seikakusei, penaltyNum, machigaisuu) {
            try {
                await this._emit('kekkaKoukan', {
                    speed: speed || 0,
                    seikakusei: seikakusei || 0,
                    penaltyNum: penaltyNum || 0,
                    machigaisuu: machigaisuu || 0
                });
                console.log(`%c[TH] ✓ 結果送信完了`, 'color: #00ff00;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ 結果送信失敗:', e.message);
                return false;
            }
        },

        // ==================== チャット関連 ====================

        // チャット送信
        chat: async function(message) {
            try {
                await this._emit('chat_send', message);
                console.log(`%c[TH] ✓ チャット: "${message}"`, 'color: #4ecdc4;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ チャット送信失敗:', e.message);
                return false;
            }
        },

        // チャットOFF送信
        chatOff: async function() {
            try {
                await this._emit('chatoff_send');
                console.log(`%c[TH] ✓ チャットOFF通知送信`, 'color: #95afc0;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ チャットOFF送信失敗:', e.message);
                return false;
            }
        },

        // ==================== 接続・マッチング ====================

        // マッチング参加
        joinMatch: async function(handiflag = 0, userid = 'test123', preuserid = [], friendPass = null, speed = 100, limitNum = 0) {
            try {
                await this._emit('setRandom', {
                    handiflag: handiflag,
                    userid: userid,
                    preuserid: preuserid,
                    friendPass: friendPass,
                    speed: speed,
                    limitNum: limitNum
                });
                console.log(`%c[TH] ✓ マッチング参加`, 'color: #a29bfe;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ マッチング参加失敗:', e.message);
                return false;
            }
        },

        // レアキーボード使用通知
        rareKeyboard: async function() {
            try {
                await this._emit('rareKbdOn');
                console.log(`%c[TH] ✓ レアキーボード通知`, 'color: #ffd93d;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ レアキーボード通知失敗:', e.message);
                return false;
            }
        },

        // バトル開始
        startBattle: async function(playerNum) {
            try {
                await this._emit('battleStart', playerNum);
                console.log(`%c[TH] ✓ バトル開始: プレイヤー${playerNum}`, 'color: #00ff00; font-weight: bold;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ バトル開始失敗:', e.message);
                return false;
            }
        },

        // 再戦終了
        endBattleAgain: async function() {
            try {
                await this._emit('battleAgainEnd');
                console.log(`%c[TH] ✓ 再戦終了通知`, 'color: #95afc0;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ 再戦終了失敗:', e.message);
                return false;
            }
        },

        // 再戦要求
        requestBattleAgain: async function() {
            try {
                await this._emit('battleAgain');
                console.log(`%c[TH] ✓ 再戦要求送信`, 'color: #ffd93d;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ 再戦要求失敗:', e.message);
                return false;
            }
        },

        // 接続確認
        setConnection: async function() {
            try {
                await this._emit('setuzokuConf');
                console.log(`%c[TH] ✓ 接続確認送信`, 'color: #4ecdc4;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ 接続確認失敗:', e.message);
                return false;
            }
        },

        // 初期化要求
        reset: async function() {
            try {
                await this._emit('shokika');
                console.log(`%c[TH] ✓ 初期化要求送信`, 'color: #ff6348;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ 初期化失敗:', e.message);
                return false;
            }
        },

        // 自分の接続切断通知
        disconnectSelf: async function(typingend) {
            try {
                await this._emit('jibunSetuzokugire', typingend);
                console.log(`%c[TH] ✓ 切断通知送信`, 'color: #ff6348;');
                return true;
            } catch(e) {
                console.error('[TH] ✗ 切断通知失敗:', e.message);
                return false;
            }
        },

        // ==================== 自動化機能 ====================

        // 連続攻撃
        autoAttack: function(target, damage = 50, interval = 100, count = 10) {
            if (typeof target !== 'number' || (target !== 1 && target !== 2)) {
                console.error('[TH] 使い方: TH.autoAttack(1 or 2, ダメージ, 間隔ms, 回数)');
                return;
            }

            console.log(`%c[TH] 連続攻撃開始: P${target}に${damage}dmg × ${count}回`, 'color: #ff6b6b; font-weight: bold;');

            let i = 0;
            const attackInterval = setInterval(async () => {
                if (i >= count) {
                    clearInterval(attackInterval);
                    console.log('%c[TH] 連続攻撃完了', 'color: #00ff00; font-weight: bold;');
                    return;
                }

                await this.damage(target, damage);
                i++;
            }, interval);

            this._intervals.push(attackInterval);
            return attackInterval;
        },

        // 連続ペナルティ
        autoPenalty: function(target, amount = 50, interval = 100, count = 10) {
            if (typeof target !== 'number' || (target !== 1 && target !== 2)) {
                console.error('[TH] 使い方: TH.autoPenalty(1 or 2, ペナルティ, 間隔ms, 回数)');
                return;
            }

            console.log(`%c[TH] 連続ペナルティ開始: P${target}に${amount} × ${count}回`, 'color: #ff6348; font-weight: bold;');

            let i = 0;
            const penaltyInterval = setInterval(async () => {
                if (i >= count) {
                    clearInterval(penaltyInterval);
                    console.log('%c[TH] 連続ペナルティ完了', 'color: #00ff00; font-weight: bold;');
                    return;
                }

                await this.penalty(target, amount);
                i++;
            }, interval);

            this._intervals.push(penaltyInterval);
            return penaltyInterval;
        },

        // チャットスパム
        spamChat: function(message, interval = 1000, count = 5) {
            console.log(`%c[TH] チャットスパム開始: "${message}" × ${count}回`, 'color: #4ecdc4;');

            let i = 0;
            const chatInterval = setInterval(async () => {
                if (i >= count) {
                    clearInterval(chatInterval);
                    console.log('%c[TH] チャットスパム完了', 'color: #00ff00;');
                    return;
                }

                await this.chat(message);
                i++;
            }, interval);

            this._intervals.push(chatInterval);
            return chatInterval;
        },

        // すべての自動処理停止
        stopAll: function() {
            this._intervals.forEach(id => clearInterval(id));
            this._intervals = [];
            console.log('%c[TH] すべての自動処理を停止しました', 'color: #ffaa00; font-weight: bold;');
        },

        // ==================== 監視機能 ====================

        // イベント監視
        monitor: function(enable = true) {
            const events = [
                // 受信イベント
                'damage_from_server', 'cdamage_from_server',
                'penaDamage_from_server', 'cpenaDamage_from_server',
                'chat_from_server_jibun', 'chat_from_server_aite',
                'chatOff_from_server',
                'miss_from_server', 'hadouOn_from_server',
                'playerNum', 'getPlayer', 'playerJoin',
                'battleStart', 'winFlag', 'kekkaKoukan',
                'ratingGet', 'sendHandi',
                'battleAgainEnd', 'setuzokuOK',
                'shokika', 'jibunSetuzokugire',
                'rareKbdOn',
                // 接続イベント
                'connect', 'disconnect', 'connect_error',
                'reconnect', 'reconnect_attempt'
            ];

            const socket = this._getActiveSocket();
            if (!socket) {
                console.error('[TH] Socket未検出のため監視不可');
                return;
            }

            if (enable) {
                events.forEach(event => {
                    socket.on(event, (data) => {
                        console.log(`%c[EVENT] ${event}`, 'color: #a29bfe; font-weight: bold;', data);
                    });
                });
                console.log('%c[TH] イベント監視開始(全イベント)', 'color: #00ff00; font-weight: bold;');
            } else {
                events.forEach(event => socket.off(event));
                console.log('%c[TH] イベント監視停止', 'color: #ffaa00;');
            }
        },

        // 接続状態確認
        status: function() {
            const socket = this._getActiveSocket();
            const connected = socket && socket.connected;
            const info = `
╔═══════════════════════════════════╗
║        接続状態                    ║
╚═══════════════════════════════════╝
Socket: ${socket ? '✓ 存在' : '✗ なし'}
接続: ${connected ? '✓ 接続中' : '✗ 切断中'}
URL: ${socket && socket.io ? socket.io.uri : '不明'}
デフォルトプレイヤー番号: ${this.playerNum}
`;
            console.log(connected ? `%c${info}` : `%c${info}`, connected ? 'color: #00ff00;' : 'color: #ff0000;');
            return connected;
        },

        // ==================== ヘルプ ====================

        help: function() {
            console.log(`%c
╔═══════════════════════════════════════════════╗
║     Typingerz 完全版ハックツール v3.1         ║
║     (ソケット競合完全修正版)                   ║
╚═══════════════════════════════════════════════╝
`, 'color: #00ff00; font-weight: bold; font-size: 16px;');

            console.log(`%c
【バトル攻撃】
TH.damage(1, 100)          - プレイヤー1に100ダメージ
TH.damage(2, 100)          - プレイヤー2に100ダメージ
TH.penalty(1, 50)          - プレイヤー1に50ペナルティ
TH.penalty(2, 50)          - プレイヤー2に50ペナルティ
TH.miss(5)                 - 5回ミス送信
TH.hadou(1, 0)             - 必殺技ID1を発動

【プレイヤー情報】
TH.joinBattle({...})       - バトル参加(詳細はコード参照)
TH.sendRating(1500,...)    - レーティング送信
TH.sendResult(...)         - 結果送信

【チャット】
TH.chat("message")         - チャット送信
TH.chatOff()               - チャットOFF通知
TH.spamChat("hi", 1000, 5) - チャットスパム

【接続・マッチング】
TH.joinMatch()             - マッチング参加
TH.startBattle(1)          - バトル開始
TH.requestBattleAgain()    - 再戦要求
TH.endBattleAgain()        - 再戦終了
TH.setConnection()         - 接続確認
TH.reset()                 - 初期化
TH.disconnectSelf(0)       - 切断通知

【自動化】
TH.autoAttack(2,100,50,20) - P2に100dmgを50ms間隔で20回
TH.autoPenalty(1,50,100,10)- P1に50ペナを100ms間隔で10回
TH.stopAll()               - すべての自動処理停止

【監視・確認】
TH.monitor(true)           - 全イベント監視ON
TH.monitor(false)          - 監視OFF
TH.status()                - 接続状態確認

【使用例】
await TH.damage(2, 200)    // 相手(P2)に200ダメージ
await TH.penalty(1, 100)   // 自分(P1)に100ペナルティ
TH.autoAttack(2, 100, 50, 20)  // 相手に連続攻撃
TH.chat("gg")              // チャット
TH.monitor(true)           // 監視開始

【v3.1の変更点】
✓ 常に最新のSocketを自動取得(競合問題完全解決)
✓ 送信前に毎回ソケット状態を確認
✓ 日本語コメントで文字化け防止
`, 'color: #4ecdc4;');
        }
    };

    // エイリアス
    window.th = window.TH;

    // 起動完了
    console.log(`%c
╔════════════════════════════════════════════════╗
║                                                ║
║        準備完了!TH.help() でヘルプ表示       ║
║                                                ║
╚════════════════════════════════════════════════╝
`, 'color: #00ff00; font-weight: bold; font-size: 16px;');

    TH.help();
    TH.status();
    }

        // テンプレートデータ
    const templates = [
        { code: 'TH.damage(1, 100)', desc: 'プレイヤー1に100ダメージ' },
        { code: 'TH.damage(2, 100)', desc: 'プレイヤー2に100ダメージ' },
        { code: 'TH.penalty(1, 50)', desc: 'プレイヤー1に50ペナルティ' },
        { code: 'TH.penalty(1,9999999999999999999)', desc: '最強☆' },
        { code: 'TH.miss(5)', desc: '5回ミス送信' },
        { code: 'TH.hadou(1, 0)', desc: '必殺技ID1を発動' },
        { code: 'TH.chat("message")', desc: 'チャット送信' },
        { code: 'TH.chatOff()', desc: 'チャットOFF通知' },
        { code: 'TH.autoAttack(2, 100, 50, 20)', desc: 'P2に100dmgを50ms間隔で20回' },
        { code: 'TH.autoPenalty(1, 50, 100, 10)', desc: 'P1に50ペナを100ms間隔で10回' },
        { code: 'TH.spamChat("hi", 1000, 5)', desc: 'チャットスパム' },
        { code: 'TH.stopAll()', desc: 'すべての自動処理停止' },
        { code: 'TH.joinBattle({})', desc: 'バトル参加' },
        { code: 'TH.sendRating(1500)', desc: 'レーティング送信' },
        { code: 'TH.sendResult()', desc: '結果送信' },
        { code: 'TH.joinMatch()', desc: 'マッチング参加' },
        { code: 'TH.startBattle(1)', desc: 'バトル開始' },
        { code: 'TH.requestBattleAgain()', desc: '再戦要求' },
        { code: 'TH.endBattleAgain()', desc: '再戦終了' },
        { code: 'TH.setConnection()', desc: '接続確認' },
        { code: 'TH.reset()', desc: '初期化' },
        { code: 'TH.disconnectSelf(0)', desc: '切断通知' },
        { code: 'TH.monitor(true)', desc: '全イベント監視ON' },
        { code: 'TH.monitor(false)', desc: '監視OFF' },
        { code: 'TH.status()', desc: '接続状態確認' }
    ];

    // スタイル追加
    const style = document.createElement('style');
    style.textContent = `
        #th-panel {
            position: fixed;
            left: 15px;
            top: 260px;
            right: auto;
            width: 320px;
            min-width: 320px;
            min-height: 250px;
            background: linear-gradient(145deg, rgba(25, 15, 45, 0.95) 0%, rgba(15, 10, 35, 0.98) 100%);
            backdrop-filter: blur(20px);
            border: 1px solid rgba(138, 116, 249, 0.3);
            border-radius: 20px;
            box-shadow:
                0 8px 32px rgba(0, 0, 0, 0.6),
                0 0 80px rgba(138, 116, 249, 0.15),
                inset 0 0 60px rgba(138, 116, 249, 0.05);
            z-index: 999999;
            font-family: 'Noto Sans JP', sans-serif;
            display: flex;
            flex-direction: column;
            overflow: hidden;
            animation: panelFadeIn 0.5s ease-out;
        }

        @keyframes panelFadeIn {
            from {
                opacity: 0;
                transform: translateY(-20px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        #th-panel-header {
            background: linear-gradient(135deg, rgba(138, 116, 249, 0.2) 0%, rgba(98, 76, 209, 0.15) 100%);
            padding: 16px 20px;
            cursor: move;
            border-bottom: 1px solid rgba(138, 116, 249, 0.2);
            display: flex;
            justify-content: space-between;
            align-items: center;
            backdrop-filter: blur(10px);
        }

        #th-panel-title {
            color: #c9b8ff;
            font-weight: 500;
            font-size: 15px;
            letter-spacing: 1.5px;
            text-shadow: 0 0 20px rgba(138, 116, 249, 0.5);
            display: flex;
            align-items: center;
            gap: 8px;
        }

        #th-panel-title::before {
            content: '✨';
            animation: sparkle 2s ease-in-out infinite;
        }

        @keyframes sparkle {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }

        #th-panel-close {
            background: linear-gradient(135deg, rgba(255, 107, 107, 0.2) 0%, rgba(255, 71, 87, 0.3) 100%);
            border: 1px solid rgba(255, 107, 107, 0.4);
            color: #ffb3ba;
            width: 30px;
            height: 30px;
            border-radius: 50%;
            cursor: pointer;
            font-size: 18px;
            line-height: 1;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        #th-panel-close:hover {
            background: linear-gradient(135deg, rgba(255, 107, 107, 0.4) 0%, rgba(255, 71, 87, 0.5) 100%);
            transform: rotate(90deg);
            box-shadow: 0 0 20px rgba(255, 107, 107, 0.4);
        }

        #th-panel-content {
            padding: 20px;
            flex: 1;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            gap: 14px;
        }

        #th-panel-content::-webkit-scrollbar {
            width: 8px;
        }

        #th-panel-content::-webkit-scrollbar-track {
            background: rgba(138, 116, 249, 0.05);
            border-radius: 10px;
        }

        #th-panel-content::-webkit-scrollbar-thumb {
            background: linear-gradient(180deg, rgba(138, 116, 249, 0.3) 0%, rgba(98, 76, 209, 0.3) 100%);
            border-radius: 10px;
        }

        #th-panel-content::-webkit-scrollbar-thumb:hover {
            background: linear-gradient(180deg, rgba(138, 116, 249, 0.5) 0%, rgba(98, 76, 209, 0.5) 100%);
        }

        #th-input-wrapper {
            position: relative;
        }

        #th-input {
            width: 100%;
            padding: 14px 16px;
            background: rgba(15, 10, 35, 0.6);
            border: 1px solid rgba(138, 116, 249, 0.3);
            border-radius: 12px;
            color: #c9b8ff;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            box-sizing: border-box;
            transition: all 0.3s ease;
            box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.3);
        }

        #th-input:focus {
            outline: none;
            border-color: rgba(138, 116, 249, 0.6);
            background: rgba(15, 10, 35, 0.8);
            box-shadow:
                inset 0 2px 10px rgba(0, 0, 0, 0.3),
                0 0 20px rgba(138, 116, 249, 0.3);
        }

        #th-input::placeholder {
            color: rgba(201, 184, 255, 0.4);
        }

        #th-buttons {
            display: flex;
            gap: 12px;
        }

        .th-btn {
            flex: 1;
            padding: 12px 16px;
            border: none;
            border-radius: 12px;
            cursor: pointer;
            font-weight: 500;
            font-size: 13px;
            transition: all 0.3s ease;
            font-family: 'Noto Sans JP', sans-serif;
            position: relative;
            overflow: hidden;
        }

        .th-btn::before {
            content: '';
            position: absolute;
            top: 50%;
            left: 50%;
            width: 0;
            height: 0;
            border-radius: 50%;
            background: rgba(255, 255, 255, 0.2);
            transform: translate(-50%, -50%);
            transition: width 0.6s, height 0.6s;
        }

        .th-btn:hover::before {
            width: 300px;
            height: 300px;
        }

        #th-execute {
            background: linear-gradient(135deg, rgba(138, 249, 200, 0.3) 0%, rgba(98, 209, 160, 0.4) 100%);
            color: #b8ffd9;
            border: 1px solid rgba(138, 249, 200, 0.4);
            box-shadow: 0 4px 15px rgba(138, 249, 200, 0.2);
        }

        #th-execute:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 25px rgba(138, 249, 200, 0.4);
            border-color: rgba(138, 249, 200, 0.6);
        }

        #th-template-toggle {
            background: linear-gradient(135deg, rgba(138, 180, 249, 0.3) 0%, rgba(98, 140, 209, 0.4) 100%);
            color: #b8d9ff;
            border: 1px solid rgba(138, 180, 249, 0.4);
            box-shadow: 0 4px 15px rgba(138, 180, 249, 0.2);
        }

        #th-template-toggle:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 25px rgba(138, 180, 249, 0.4);
            border-color: rgba(138, 180, 249, 0.6);
        }

        #th-templates {
            max-height: 0;
            overflow: hidden;
            transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1);
        }

        #th-templates.show {
            max-height: 450px;
            overflow-y: auto;
        }

        #th-templates::-webkit-scrollbar {
            width: 6px;
        }

        #th-templates::-webkit-scrollbar-track {
            background: rgba(138, 116, 249, 0.05);
            border-radius: 10px;
        }

        #th-templates::-webkit-scrollbar-thumb {
            background: linear-gradient(180deg, rgba(138, 116, 249, 0.3) 0%, rgba(98, 76, 209, 0.3) 100%);
            border-radius: 10px;
        }

        .th-template-item {
            padding: 12px 16px;
            background: rgba(15, 10, 35, 0.4);
            border: 1px solid rgba(138, 116, 249, 0.2);
            border-radius: 10px;
            margin-bottom: 8px;
            cursor: pointer;
            transition: all 0.3s ease;
            position: relative;
            color: #c9b8ff;
            font-size: 12px;
            font-family: 'Courier New', monospace;
        }

        .th-template-item:hover {
            background: rgba(138, 116, 249, 0.15);
            border-color: rgba(138, 116, 249, 0.5);
            transform: translateX(5px);
            box-shadow: 0 4px 20px rgba(138, 116, 249, 0.2);
        }

        .th-template-tooltip {
            position: fixed;
            background: linear-gradient(135deg, rgba(25, 15, 45, 0.98) 0%, rgba(35, 25, 55, 0.98) 100%);
            color: #e0d4ff;
            padding: 10px 16px;
            border-radius: 10px;
            font-size: 12px;
            font-family: 'Noto Sans JP', sans-serif;
            white-space: nowrap;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.3s ease;
            border: 1px solid rgba(138, 116, 249, 0.4);
            z-index: 1000000;
            box-shadow:
                0 8px 32px rgba(0, 0, 0, 0.6),
                0 0 30px rgba(138, 116, 249, 0.3);
            backdrop-filter: blur(10px);
        }

        .th-template-item:hover .th-template-tooltip {
            opacity: 1;
        }

        .th-template-tooltip::before {
            content: '';
            position: absolute;
            left: -8px;
            top: 50%;
            transform: translateY(-50%);
            border-right: 8px solid rgba(138, 116, 249, 0.4);
            border-top: 6px solid transparent;
            border-bottom: 6px solid transparent;
        }

        .th-template-tooltip::after {
            content: '';
            position: absolute;
            left: -7px;
            top: 50%;
            transform: translateY(-50%);
            border-right: 7px solid rgba(25, 15, 45, 0.98);
            border-top: 5px solid transparent;
            border-bottom: 5px solid transparent;
        }

        #th-resize-handle {
            position: absolute;
            bottom: 0;
            right: 0;
            width: 24px;
            height: 24px;
            cursor: nwse-resize;
            background: linear-gradient(135deg, transparent 45%, rgba(138, 116, 249, 0.3) 50%);
            border-bottom-right-radius: 20px;
            transition: all 0.3s ease;
        }

        #th-resize-handle:hover {
            background: linear-gradient(135deg, transparent 45%, rgba(138, 116, 249, 0.6) 50%);
        }

        #th-output {
            background: rgba(15, 10, 35, 0.6);
            border: 1px solid rgba(138, 116, 249, 0.2);
            border-radius: 12px;
            padding: 14px;
            color: #c9b8ff;
            font-size: 11px;
            font-family: 'Courier New', monospace;
            max-height: 180px;
            overflow-y: auto;
            white-space: pre-wrap;
            word-wrap: break-word;
            box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.3);
            line-height: 1.6;
        }

        #th-output::-webkit-scrollbar {
            width: 6px;
        }

        #th-output::-webkit-scrollbar-track {
            background: rgba(138, 116, 249, 0.05);
            border-radius: 10px;
        }

        #th-output::-webkit-scrollbar-thumb {
            background: linear-gradient(180deg, rgba(138, 116, 249, 0.3) 0%, rgba(98, 76, 209, 0.3) 100%);
            border-radius: 10px;
        }

        #th-output:empty::before {
            content: '実行結果がここに表示されます...';
            color: rgba(201, 184, 255, 0.3);
            font-style: italic;
        }
    `;
    document.head.appendChild(style);

    // パネルHTML作成
    const panel = document.createElement('div');
    panel.id = 'th-panel';
    panel.innerHTML = `
        <div id="th-panel-header">
            <span id="th-panel-title">TH Control Panel</span>
            <button id="th-panel-close">×</button>
        </div>
        <div id="th-panel-content">
 <div id="th-input-wrapper">
    <textarea id="th-input" placeholder="TH.damage(1, 100)" rows="5" style="resize:vertical;width:100%;"></textarea>
  </div>
            <div id="th-buttons">
                <button class="th-btn" id="th-execute">▶ 実行</button>
                <button class="th-btn" id="th-template-toggle">📋 テンプレート</button>
            </div>
            <div id="th-templates"></div>
            <div id="th-output"></div>
        </div>
        <div id="th-resize-handle"></div>
    `;
    document.body.appendChild(panel);

    // 要素取得
    const input = document.getElementById('th-input');
    const executeBtn = document.getElementById('th-execute');
    const templateToggle = document.getElementById('th-template-toggle');
    const templatesDiv = document.getElementById('th-templates');
    const output = document.getElementById('th-output');
    const closeBtn = document.getElementById('th-panel-close');
    const header = document.getElementById('th-panel-header');
    const resizeHandle = document.getElementById('th-resize-handle');

    // ツールチップ要素を作成
    const tooltip = document.createElement('div');
    tooltip.className = 'th-template-tooltip';
    document.body.appendChild(tooltip);

    // テンプレート表示生成
    templates.forEach(template => {
        const item = document.createElement('div');
        item.className = 'th-template-item';
        item.textContent = template.code;

        // マウスオーバーでツールチップ表示
        item.addEventListener('mouseenter', (e) => {
            const rect = item.getBoundingClientRect();
            tooltip.textContent = template.desc;
            tooltip.style.left = (rect.right + 15) + 'px';
            tooltip.style.top = (rect.top + rect.height / 2) + 'px';
            tooltip.style.transform = 'translateY(-50%)';
            tooltip.style.opacity = '1';
        });

        item.addEventListener('mouseleave', () => {
            tooltip.style.opacity = '0';
        });

        item.addEventListener('click', () => {
            input.value = template.code;
            input.focus();
            tooltip.style.opacity = '0';
        });

        templatesDiv.appendChild(item);
    });

    // テンプレート表示切替
    let templatesVisible = false;
    templateToggle.addEventListener('click', () => {
        templatesVisible = !templatesVisible;
        if (templatesVisible) {
            templatesDiv.classList.add('show');
        } else {
            templatesDiv.classList.remove('show');
            tooltip.style.opacity = '0';
        }
    });

    // コード実行
    async function executeCode() {
        const code = input.value.trim();
        if (!code) return;

        output.textContent = `> ${code}\n`;

        try {
            const result = await eval(code);
            output.textContent += `✓ 実行成功\n`;
            if (result !== undefined) {
                output.textContent += `結果: ${JSON.stringify(result)}\n`;
            }
        } catch (error) {
            output.textContent += `✗ エラー: ${error.message}\n`;
        }

        output.scrollTop = output.scrollHeight;
    }

    executeBtn.addEventListener('click', executeCode);

    input.addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            executeCode();
        }
    });

    // 閉じるボタン
    closeBtn.addEventListener('click', () => {
        panel.remove(); // 要素をぶっ壊す
        tooltip.remove(); // ツールチップもついでにぶっ壊す
    });

    // ドラッグ移動
    let isDragging = false;
    let currentX;
    let currentY;
    let initialX;
    let initialY;

    header.addEventListener('mousedown', (e) => {
        isDragging = true;
        initialX = e.clientX - panel.offsetLeft;
        initialY = e.clientY - panel.offsetTop;
        tooltip.style.opacity = '0';
    });

    document.addEventListener('mousemove', (e) => {
        if (isDragging) {
            e.preventDefault();
            currentX = e.clientX - initialX;
            currentY = e.clientY - initialY;
            panel.style.left = currentX + 'px';
            panel.style.top = currentY + 'px';
            panel.style.right = 'auto';
        }
    });

    document.addEventListener('mouseup', () => {
        isDragging = false;
    });

    // リサイズ機能
    let isResizing = false;
    let startWidth;
    let startHeight;
    let startX;
    let startY;

    resizeHandle.addEventListener('mousedown', (e) => {
        isResizing = true;
        startWidth = panel.offsetWidth;
        startHeight = panel.offsetHeight;
        startX = e.clientX;
        startY = e.clientY;
        tooltip.style.opacity = '0';
        e.stopPropagation();
    });

    document.addEventListener('mousemove', (e) => {
        if (isResizing) {
            const width = startWidth + (e.clientX - startX);
            const height = startHeight + (e.clientY - startY);

            if (width >= 320) {
                panel.style.width = width + 'px';
            }
            if (height >= 250) {
                panel.style.height = height + 'px';
            }
        }
    });

    document.addEventListener('mouseup', () => {
        isResizing = false;
    });

    console.log('%c✨ [TH UI] パネル読み込み完了!', 'color: #c9b8ff; font-weight: bold; font-size: 14px;');

})();