tvvj.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Fursuit Pro V3 控制器</title>
<style>
    :root {
        --primary: #4a90e2;
        --success: #2ecc71;
        --danger: #e74c3c;
        --dark: #34495e;
        --bg: #f4f7f6;
    }
    body {
        font-family: -apple-system, BlinkMacSystemFont, sans-serif;
        background: var(--bg);
        margin: 0; padding: 15px; color: #333;
        display: flex; flex-direction: column; align-items: center;
    }
    .container { width: 100%; max-width: 500px; }
    .header {
        background: white; padding: 20px; border-radius: 16px;
        box-shadow: 0 4px 15px rgba(0,0,0,0.05); margin-bottom: 20px; text-align: center;
    }
    h1 { margin: 0 0 10px 0; font-size: 1.4rem; color: var(--dark); }
    #status { font-size: 0.9rem; color: #7f8c8d; margin-bottom: 15px; font-weight: bold;}
    
    .card {
        background: white; padding: 20px; border-radius: 16px;
        box-shadow: 0 4px 15px rgba(0,0,0,0.05); margin-bottom: 20px;
    }
    h2 { font-size: 1.1rem; border-left: 4px solid var(--primary); padding-left: 10px; margin: 0 0 15px 0; color: var(--dark); }

    button {
        width: 100%; padding: 14px; border: none; border-radius: 10px;
        font-size: 1rem; font-weight: 600; cursor: pointer;
        transition: transform 0.1s;
        margin-bottom: 10px; color: white; background: var(--primary);
        display: flex; align-items: center; justify-content: center; gap: 8px;
    }
    button:active { transform: scale(0.96); }
    
    .btn-red { background: var(--danger); }
    .btn-green { background: var(--success); }
    .btn-outline { background: white; border: 2px solid var(--primary); color: var(--primary); }
    
    .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
    .grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; }
    
    .slider-container { margin: 15px 0; }
    input[type=range] { width: 100%; height: 8px; background: #ddd; border-radius: 5px; -webkit-appearance: none; }
    input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 24px; height: 24px; background: var(--primary); border-radius: 50%; border: 2px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
    
    .preset-row { display: flex; gap: 8px; margin-bottom: 10px; align-items: center; }
    .preset-input-name { flex: 2; padding: 8px; border: 1px solid #ddd; border-radius: 6px; }
    .preset-input-val { width: 55px; padding: 8px; border: 1px solid #ddd; border-radius: 6px; text-align: center; }
    .preset-btn-save { width: auto; background: #95a5a6; padding: 8px 12px; margin: 0; }

    .switch-row { display: flex; justify-content: space-between; align-items: center; }
    .switch { position: relative; width: 50px; height: 28px; }
    .switch input { opacity: 0; width: 0; height: 0; }
    .slider-toggle { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px; transition: .4s; }
    .slider-toggle:before { position: absolute; content: ""; height: 20px; width: 20px; left: 4px; bottom: 4px; background-color: white; border-radius: 50%; transition: .4s; }
    input:checked + .slider-toggle { background-color: var(--success); }
    input:checked + .slider-toggle:before { transform: translateX(22px); }

    .lang-btn { position: fixed; top: 15px; right: 15px; width: auto; padding: 5px 10px; background: white; border: 1px solid #ccc; font-size: 0.8rem; z-index: 100; }
</style>
</head>
<body>

<button class="lang-btn" onclick="toggleLang()">中/EN</button>

<div class="container">
    <div class="header">
        <h1 data-zh="🦊 Fursuit 智能控制 V3" data-en="🦊 Fursuit Control V3">🦊 Fursuit 智能控制 V3</h1>
        <div id="status">🔴 未连接</div>
        <button id="connectBtn" onclick="connectBLE()" style="background: var(--dark);">🔌 点击搜索设备</button>
    </div>

    <div id="panel" style="opacity: 0.4; pointer-events: none;">
        <div class="card">
            <h2 data-zh="👁️ 实时调节" data-en="👁️ Real-time">👁️ 实时调节</h2>
            <div class="slider-container">
                <div style="display:flex; justify-content:space-between; margin-bottom:5px;">
                    <span data-zh="双眼开合度" data-en="Aperture">双眼开合度</span>
                    <span id="angVal">90°</span>
                </div>
                <input type="range" min="20" max="100" value="90" 
                       oninput="updateVal('angVal', this.value)" 
                       onchange="send('ANG', this.value)">
            </div>
            
            <div class="grid-3">
                <button class="btn-outline" onclick="send('BLINK', 1)" data-zh="左快" data-en="L-F">左快</button>
                <button onclick="send('BLINK', 0)" data-zh="双快" data-en="Both">双快</button>
                <button class="btn-outline" onclick="send('BLINK', 2)" data-zh="右快" data-en="R-F">右快</button>
            </div>
        </div>

        <div class="card">
            <h2 data-zh="⏳ 慢动作眨眼" data-en="⏳ Slow Blink">⏳ 慢动作眨眼</h2>
            <div style="display:flex; justify-content:space-between; margin-bottom:5px;">
                <span data-zh="眨眼耗时" data-en="Duration">眨眼耗时</span>
                <span id="durVal" style="color:var(--primary); font-weight:bold;">1.0s</span>
            </div>
            <input type="range" min="500" max="5000" step="100" value="1000" 
                   oninput="updateDurDisplay(this.value)" 
                   onchange="send('DUR', this.value)">
            
            <div class="grid-3" style="margin-top:15px">
                <button class="btn-outline" onclick="send('SLOW', 1)" data-zh="左慢" data-en="L-S">左慢</button>
                <button class="btn-outline" onclick="send('SLOW', 0)" data-zh="双慢" data-en="Both">双慢</button>
                <button class="btn-outline" onclick="send('SLOW', 2)" data-zh="右慢" data-en="R-S">右慢</button>
            </div>
        </div>

        <div class="card">
            <h2 data-zh="🤖 预设与自动化" data-en="🤖 Presets">🤖 预设与自动化</h2>
            <div class="switch-row" style="margin-bottom: 15px;">
                <span data-zh="自动随机眨眼" data-en="Auto Blink">自动随机眨眼</span>
                <label class="switch"><input type="checkbox" onchange="send('AUTO', this.checked?1:0)"><span class="slider-toggle"></span></label>
            </div>

            <div class="preset-row">
                <input type="text" id="p1_name" class="preset-input-name" placeholder="预设1">
                <input type="number" id="p1_val" class="preset-input-val" value="90">
                <button class="preset-btn-save" onclick="savePreset(1)">💾</button>
                <button style="width:auto; flex:1; margin:0;" onclick="loadPreset(1)">应用</button>
            </div>
            <div class="preset-row">
                <input type="text" id="p2_name" class="preset-input-name" placeholder="预设2">
                <input type="number" id="p2_val" class="preset-input-val" value="60">
                <button class="preset-btn-save" onclick="savePreset(2)">💾</button>
                <button style="width:auto; flex:1; margin:0;" onclick="loadPreset(2)">应用</button>
            </div>
        </div>

        <div class="card">
            <h2 data-zh="🔒 锁定控制" data-en="🔒 Locking">🔒 锁定控制</h2>
            <div class="grid-2">
                <button class="btn-red" onclick="send('CLOSE', 0)" data-zh="全闭锁定" data-en="Lock Close">全闭锁定</button>
                <button class="btn-green" onclick="send('OPEN', 0)" data-zh="全开/释放" data-en="Release">全开/释放</button>
            </div>
        </div>
    </div>
</div>

<script>
    const SERVICE_UUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b";
    const CHAR_UUID = "beb5483e-36e1-4688-b7f5-ea07361b26a8";
    let bleChar;
    let currentLang = 'zh';

    window.onload = () => {
        for(let i=1; i<=2; i++){
            if(localStorage.getItem('v3_p'+i+'n')) document.getElementById('p'+i+'_name').value = localStorage.getItem('v3_p'+i+'n');
            if(localStorage.getItem('v3_p'+i+'v')) document.getElementById('p'+i+'_val').value = localStorage.getItem('v3_p'+i+'v');
        }
    };

    function updateVal(id, val) { document.getElementById(id).innerText = val + '°'; }
    function updateDurDisplay(val) { document.getElementById('durVal').innerText = (val/1000).toFixed(1) + 's'; }

    async function connectBLE() {
        try {
            const device = await navigator.bluetooth.requestDevice({
                filters: [{ name: 'Fursuit-Pro-V3' }],
                optionalServices: [SERVICE_UUID]
            });
            const server = await device.gatt.connect();
            const service = await server.getPrimaryService(SERVICE_UUID);
            bleChar = await service.getCharacteristic(CHAR_UUID);
            
            document.getElementById('status').innerText = "✅ 已连接 V3";
            document.getElementById('status').style.color = "#2ecc71";
            document.getElementById('panel').style.opacity = "1";
            document.getElementById('panel').style.pointerEvents = "auto";
            document.getElementById('connectBtn').style.display = "none";
            
            device.addEventListener('gattserverdisconnected', () => { location.reload(); });
        } catch (e) { console.log(e); }
    }

    // 发送指令函数
    function send(cmd, val) {
        if (!bleChar) return;
        const encoder = new TextEncoder();
        const data = cmd + ":" + val;
        bleChar.writeValue(encoder.encode(data)).catch(err => console.log(err));
    }

    function savePreset(id) {
        const name = document.getElementById('p'+id+'_name').value;
        const val = document.getElementById('p'+id+'_val').value;
        localStorage.setItem('v3_p'+id+'n', name);
        localStorage.setItem('v3_p'+id+'v', val);
        alert("已保存");
    }

    function loadPreset(id) {
        send('PRESET_LOAD', id);
        const val = document.getElementById('p'+id+'_val').value;
        document.getElementById('angVal').innerText = val + '°';
        document.querySelector('input[type=range]').value = val;
    }

    function toggleLang() {
        currentLang = currentLang === 'zh' ? 'en' : 'zh';
        document.querySelectorAll('[data-zh]').forEach(el => {
            el.innerText = el.getAttribute('data-' + currentLang);
        });
    }
</script>
</body>
</html>
Download
pasted.sh