未命名 Pj8MLDedit icon

创建者:
邓朝元
Fork(复制)
下载
嵌入
BUG反馈
index.html
index.html
            
            <!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ProDraw Studio - 企业级画图软件</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }

        :root {
            --primary: #6366f1;
            --primary-dark: #4f46e5;
            --primary-light: #a5b4fc;
            --secondary: #1e293b;
            --bg-dark: #0f172a;
            --bg-panel: #1e293b;
            --bg-card: #334155;
            --text-primary: #f8fafc;
            --text-secondary: #94a3b8;
            --border: #475569;
            --success: #10b981;
            --warning: #f59e0b;
            --danger: #ef4444;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            background: var(--bg-dark);
            color: var(--text-primary);
            overflow: hidden;
            height: 100vh;
            display: flex;
            flex-direction: column;
        }

        /* 专注模式样式 */
        body.focus-mode .header,
        body.focus-mode .toolbar,
        body.focus-mode .left-panel,
        body.focus-mode .right-panel,
        body.focus-mode .status-bar {
            display: none !important;
        }
        body.focus-mode .canvas-area {
            border-radius: 0;
            background: #000;
        }

        .header {
            background: var(--secondary);
            padding: 8px 20px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            border-bottom: 1px solid var(--border);
            z-index: 100;
            transition: all 0.3s ease;
        }

        .logo { display: flex; align-items: center; gap: 10px; }
        .logo-icon {
            width: 32px; height: 32px;
            background: linear-gradient(135deg, var(--primary), #8b5cf6);
            border-radius: 8px;
            display: flex; align-items: center; justify-content: center;
            font-weight: bold; font-size: 16px;
        }
        .logo-text {
            font-size: 18px; font-weight: 700;
            background: linear-gradient(135deg, var(--primary-light), #c4b5fd);
            -webkit-background-clip: text; -webkit-text-fill-color: transparent;
        }

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

        .header-btn {
            padding: 6px 14px; border: 1px solid var(--border);
            background: var(--bg-card); color: var(--text-primary);
            border-radius: 6px; cursor: pointer; font-size: 13px;
            display: flex; align-items: center; gap: 6px; transition: all 0.2s;
        }
        .header-btn:hover { background: var(--primary); border-color: var(--primary); }
        .header-btn.primary { background: var(--primary); border-color: var(--primary); }
        .header-btn.primary:hover { background: var(--primary-dark); }

        .main-container { display: flex; flex: 1; overflow: hidden; }

        .toolbar {
            width: 56px; background: var(--secondary);
            border-right: 1px solid var(--border);
            display: flex; flex-direction: column; align-items: center;
            padding: 12px 0; gap: 4px; transition: all 0.3s ease;
        }

        .tool-btn {
            width: 40px; height: 40px; border: none; background: transparent;
            color: var(--text-secondary); border-radius: 8px; cursor: pointer;
            display: flex; align-items: center; justify-content: center;
            transition: all 0.2s; font-size: 18px;
        }
        .tool-btn:hover { background: var(--bg-card); color: var(--text-primary); }
        .tool-btn.active { background: var(--primary); color: white; }
        .tool-separator { width: 30px; height: 1px; background: var(--border); margin: 6px 0; }

        .left-panel {
            width: 260px; background: var(--bg-panel);
            border-right: 1px solid var(--border);
            overflow-y: auto; padding: 16px;
            transition: width 0.3s ease, padding 0.3s ease, opacity 0.3s ease;
        }
        .left-panel.collapsed {
            width: 0; padding: 0; opacity: 0; border-right: none; overflow: hidden;
        }

        .right-panel {
            width: 240px; background: var(--bg-panel);
            border-left: 1px solid var(--border);
            padding: 16px; overflow-y: auto;
            transition: width 0.3s ease, padding 0.3s ease, opacity 0.3s ease;
        }
        .right-panel.collapsed {
            width: 0; padding: 0; opacity: 0; border-left: none; overflow: hidden;
        }

        .panel-section { margin-bottom: 20px; }
        .panel-title {
            font-size: 11px; font-weight: 600; text-transform: uppercase;
            letter-spacing: 1px; color: var(--text-secondary); margin-bottom: 10px;
        }

        .color-presets { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; margin-bottom: 10px; }
        .color-preset {
            width: 24px; height: 24px; border-radius: 4px; cursor: pointer;
            border: 2px solid transparent; transition: all 0.2s;
        }
        .color-preset:hover { transform: scale(1.2); border-color: white; }
        .color-preset.active { border-color: white; box-shadow: 0 0 8px rgba(255,255,255,0.5); }
        /* 白色预设特殊处理,防止在白底上看不见 */
        .color-preset.white-preset {
            border: 1px solid #94a3b8;
            box-shadow: inset 0 0 3px rgba(0,0,0,0.1);
        }

        .color-input-wrapper { display: flex; gap: 8px; align-items: center; }
        .color-input { width: 40px; height: 32px; border: none; border-radius: 6px; cursor: pointer; background: none; }
        .color-input::-webkit-color-swatch-wrapper { padding: 0; }
        .color-input::-webkit-color-swatch { border: 2px solid var(--border); border-radius: 6px; }
        .color-hex {
            flex: 1; background: var(--bg-card); border: 1px solid var(--border);
            color: var(--text-primary); padding: 6px 10px; border-radius: 6px;
            font-size: 12px; font-family: monospace;
        }

        .slider-group { margin-bottom: 12px; }
        .slider-label {
            display: flex; justify-content: space-between; align-items: center;
            margin-bottom: 6px; font-size: 12px; color: var(--text-secondary);
        }
        .slider-value {
            background: var(--bg-card); padding: 2px 8px; border-radius: 4px;
            font-size: 11px; font-family: monospace;
        }

        input[type="range"] {
            width: 100%; height: 4px; -webkit-appearance: none;
            background: var(--bg-card); border-radius: 2px; outline: none;
        }
        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none; width: 14px; height: 14px;
            background: var(--primary); border-radius: 50%; cursor: pointer;
            transition: transform 0.2s;
        }
        input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.3); }

        .input-group { display: flex; gap: 8px; margin-bottom: 8px; }
        .input-field { flex: 1; }
        .input-field label { display: block; font-size: 11px; color: var(--text-secondary); margin-bottom: 4px; }
        .input-field input, .input-field select {
            width: 100%; background: var(--bg-card); border: 1px solid var(--border);
            color: var(--text-primary); padding: 6px 10px; border-radius: 6px; font-size: 12px;
        }
        .input-field input:focus, .input-field select:focus { outline: none; border-color: var(--primary); }

        .canvas-area {
            flex: 1; display: flex; align-items: center; justify-content: center;
            background: #1a1a2e; position: relative; overflow: hidden;
            transition: all 0.3s ease;
        }
        .canvas-container {
            position: relative; box-shadow: 0 20px 60px rgba(0,0,0,0.5);
            transition: transform 0.2s ease;
        }
        #drawingCanvas { display: block; cursor: crosshair; background: white; }

        .export-section {
            background: var(--bg-card); border-radius: 8px; padding: 14px; margin-bottom: 12px;
        }
        .export-title {
            font-size: 13px; font-weight: 600; margin-bottom: 10px;
            display: flex; align-items: center; gap: 6px;
        }

        .btn {
            width: 100%; padding: 10px; border: none; border-radius: 8px;
            cursor: pointer; font-size: 13px; font-weight: 500;
            display: flex; align-items: center; justify-content: center;
            gap: 8px; transition: all 0.2s; margin-bottom: 8px;
        }
        .btn-primary { background: var(--primary); color: white; }
        .btn-primary:hover { background: var(--primary-dark); }
        .btn-secondary { background: var(--bg-panel); color: var(--text-primary); border: 1px solid var(--border); }
        .btn-secondary:hover { border-color: var(--primary); }

        .history-list { max-height: 200px; overflow-y: auto; }
        .history-item {
            padding: 6px 10px; font-size: 11px; color: var(--text-secondary);
            border-left: 2px solid transparent; margin-bottom: 2px;
            border-radius: 4px; cursor: pointer; transition: all 0.2s;
        }
        .history-item:hover { background: rgba(99,102,241,0.1); }
        .history-item.current {
            background: rgba(99, 102, 241, 0.2);
            border-left-color: var(--primary); color: var(--text-primary);
        }

        .zoom-controls {
            position: absolute; bottom: 16px; right: 16px;
            display: flex; gap: 4px; background: var(--bg-panel);
            padding: 4px; border-radius: 8px; border: 1px solid var(--border);
        }
        .zoom-btn {
            width: 32px; height: 32px; border: none; background: transparent;
            color: var(--text-primary); border-radius: 6px; cursor: pointer;
            display: flex; align-items: center; justify-content: center; font-size: 14px;
        }
        .zoom-btn:hover { background: var(--bg-card); }
        .zoom-level { padding: 0 10px; display: flex; align-items: center; font-size: 12px; color: var(--text-secondary); }

        .status-bar {
            background: var(--secondary); padding: 4px 16px;
            display: flex; align-items: center; justify-content: space-between;
            font-size: 11px; color: var(--text-secondary); border-top: 1px solid var(--border);
        }
        .status-left, .status-right { display: flex; align-items: center; gap: 16px; }
        .status-dot {
            width: 6px; height: 6px; background: var(--success);
            border-radius: 50%; display: inline-block; margin-right: 6px;
        }

        .modal-overlay {
            position: fixed; top: 0; left: 0; right: 0; bottom: 0;
            background: rgba(0,0,0,0.7); display: none;
            align-items: center; justify-content: center; z-index: 1000;
        }
        .modal-overlay.active { display: flex; }
        .modal {
            background: var(--bg-panel); border-radius: 12px; padding: 24px;
            width: 400px; max-width: 90%; border: 1px solid var(--border);
        }
        .modal-title { font-size: 18px; font-weight: 600; margin-bottom: 16px; }
        .modal-actions { display: flex; gap: 8px; margin-top: 20px; justify-content: flex-end; }
        .modal-btn {
            padding: 8px 16px; border: 1px solid var(--border);
            background: var(--bg-card); color: var(--text-primary);
            border-radius: 6px; cursor: pointer; font-size: 13px;
        }
        .modal-btn.primary { background: var(--primary); border-color: var(--primary); }

        .toast-container { position: fixed; top: 60px; right: 20px; z-index: 2000; }
        .toast {
            background: var(--bg-panel); border: 1px solid var(--border);
            border-radius: 8px; padding: 12px 16px; margin-bottom: 8px;
            display: flex; align-items: center; gap: 10px;
            animation: slideIn 0.3s ease; min-width: 250px;
        }
        .toast.success { border-left: 3px solid var(--success); }
        .toast.error { border-left: 3px solid var(--danger); }
        .toast.info { border-left: 3px solid var(--primary); }

        @keyframes slideIn {
            from { transform: translateX(100%); opacity: 0; }
            to { transform: translateX(0); opacity: 1; }
        }

        .brush-preview {
            width: 100%; height: 60px; background: var(--bg-card);
            border-radius: 8px; display: flex; align-items: center;
            justify-content: center; margin-bottom: 10px;
        }
        .brush-dot { border-radius: 50%; background: var(--primary); transition: all 0.2s; }

        ::-webkit-scrollbar { width: 6px; }
        ::-webkit-scrollbar-track { background: transparent; }
        ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
        ::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); }
    </style>
</head>
<body>
    <div class="header">
        <div class="logo">
            <div class="logo-icon">P</div>
            <span class="logo-text">ProDraw Studio</span>
        </div>
        <div class="header-actions">
            <button class="header-btn" id="leftToggleBtn" onclick="togglePanel('left')" title="折叠/展开左侧面板">◀ 左</button>
            <button class="header-btn" id="rightToggleBtn" onclick="togglePanel('right')" title="折叠/展开右侧面板">右 ▶</button>
            <div style="width:1px;height:20px;background:var(--border);margin:0 4px;"></div>
            <button class="header-btn" onclick="createNewCanvas()">📄 新建</button>
            <button class="header-btn" onclick="document.getElementById('uploadInput').click()">📁 上传</button>
            <input type="file" id="uploadInput" accept="image/*" style="display:none" onchange="uploadImage(event)">
            <button class="header-btn" onclick="showExportModal()">💾 导出</button>
            <button class="header-btn primary" onclick="quickSave()">⬇️ 快速保存</button>
        </div>
    </div>

    <div class="main-container">
        <div class="toolbar">
            <button class="tool-btn active" data-tool="pen" title="画笔 (B)">✏️</button>
            <button class="tool-btn" data-tool="brush" title="毛笔 (P)">🖌️</button>
            <button class="tool-btn" data-tool="eraser" title="橡皮擦 (E)">🧹</button>
            <div class="tool-separator"></div>
            <button class="tool-btn" data-tool="line" title="直线 (L)">📏</button>
            <button class="tool-btn" data-tool="rectangle" title="矩形 (R)">⬜</button>
            <button class="tool-btn" data-tool="circle" title="圆形 (C)">⭕</button>
            <button class="tool-btn" data-tool="triangle" title="三角形">🔺</button>
            <div class="tool-separator"></div>
            <button class="tool-btn" data-tool="fill" title="填充 (G)">🪣</button>
            <button class="tool-btn" data-tool="text" title="文字 (T)">📝</button>
            <button class="tool-btn" data-tool="eyedropper" title="取色器 (I)">💉</button>
            <div class="tool-separator"></div>
            <button class="tool-btn" onclick="undo()" title="撤销 (Ctrl+Z)">↩️</button>
            <button class="tool-btn" onclick="redo()" title="重做 (Ctrl+Y)">↪️</button>
            <div class="tool-separator"></div>
            <button class="tool-btn" onclick="clearCanvas()" title="清空画布">🗑️</button>
        </div>

        <div class="left-panel" id="leftPanel">
            <div class="panel-section">
                <div class="panel-title">🎨 颜色</div>
                <div class="color-presets" id="colorPresets"></div>
                <div class="color-input-wrapper">
                    <input type="color" class="color-input" id="colorPicker" value="#000000">
                    <input type="text" class="color-hex" id="colorHex" value="#000000" maxlength="7">
                </div>
            </div>

            <div class="panel-section">
                <div class="panel-title">🖊️ 画笔设置</div>
                <div class="brush-preview">
                    <div class="brush-dot" id="brushPreview"></div>
                </div>
                <div class="slider-group">
                    <div class="slider-label">
                        <span>粗细</span>
                        <span class="slider-value" id="sizeValue">5px</span>
                    </div>
                    <input type="range" id="brushSize" min="1" max="100" value="5">
                </div>
                <div class="slider-group">
                    <div class="slider-label">
                        <span>透明度</span>
                        <span class="slider-value" id="opacityValue">100%</span>
                    </div>
                    <input type="range" id="brushOpacity" min="1" max="100" value="100">
                </div>
            </div>

            <div class="panel-section">
                <div class="panel-title">📐 画布设置</div>
                <div class="input-group">
                    <div class="input-field">
                        <label>宽度 (px)</label>
                        <input type="number" id="canvasWidth" value="1200">
                    </div>
                    <div class="input-field">
                        <label>高度 (px)</label>
                        <input type="number" id="canvasHeight" value="800">
                    </div>
                </div>
                <div class="input-group">
                    <div class="input-field">
                        <label>背景颜色</label>
                        <input type="color" id="bgColor" value="#ffffff" style="width:100%;height:30px;border:none;border-radius:6px;cursor:pointer;">
                    </div>
                </div>
                <button class="btn btn-secondary" onclick="resizeCanvas()">应用尺寸</button>
                <div style="margin-top:8px;">
                    <div class="input-field">
                        <label>预设尺寸</label>
                        <select id="presetSize" onchange="applyPresetSize()">
                            <option value="">自定义</option>
                            <option value="1920x1080">1920×1080 (全高清)</option>
                            <option value="1280x720">1280×720 (高清)</option>
                            <option value="800x600">800×600</option>
                            <option value="1080x1080">1080×1080 (Instagram)</option>
                            <option value="1080x1920">1080×1920 (手机壁纸)</option>
                            <option value="2480x3508">A4 (300dpi)</option>
                        </select>
                    </div>
                </div>
            </div>

            <div class="panel-section">
                <div class="panel-title">⬛ 形状设置</div>
                <div class="slider-group">
                    <div class="slider-label">
                        <span>边框宽度</span>
                        <span class="slider-value" id="strokeValue">2px</span>
                    </div>
                    <input type="range" id="shapeStroke" min="1" max="20" value="2">
                </div>
                <label style="display:flex;align-items:center;gap:8px;font-size:12px;color:var(--text-secondary);cursor:pointer;">
                    <input type="checkbox" id="shapeFill"> 填充形状
                </label>
            </div>
        </div>

        <div class="canvas-area" id="canvasArea">
            <div class="canvas-container" id="canvasContainer">
                <canvas id="drawingCanvas" width="1200" height="800"></canvas>
            </div>
            <div class="zoom-controls">
                <button class="zoom-btn" onclick="zoomOut()">−</button>
                <span class="zoom-level" id="zoomLevel">100%</span>
                <button class="zoom-btn" onclick="zoomIn()">+</button>
                <button class="zoom-btn" onclick="resetZoom()">⟲</button>
            </div>
        </div>

        <div class="right-panel" id="rightPanel">
            <div class="export-section">
                <div class="export-title">💾 导出设置</div>
                <div class="input-field" style="margin-bottom:10px;">
                    <label>导出格式</label>
                    <select id="exportFormat">
                        <option value="png">PNG (无损)</option>
                        <option value="jpeg">JPEG (压缩)</option>
                        <option value="webp">WebP (推荐)</option>
                    </select>
                </div>
                <div class="slider-group" id="qualitySlider" style="display:none;">
                    <div class="slider-label">
                        <span>压缩质量</span>
                        <span class="slider-value" id="qualityValue">92%</span>
                    </div>
                    <input type="range" id="exportQuality" min="1" max="100" value="92">
                </div>
                <div class="input-field" style="margin-bottom:10px;">
                    <label>文件名</label>
                    <input type="text" id="exportFilename" value="my-drawing">
                </div>
                <button class="btn btn-primary" onclick="exportImage()">⬇️ 下载图片</button>
            </div>

            <div class="export-section">
                <div class="export-title">⚡ 快捷操作</div>
                <button class="btn btn-secondary" onclick="flipHorizontal()">↔️ 水平翻转</button>
                <button class="btn btn-secondary" onclick="flipVertical()">↕️ 垂直翻转</button>
                <button class="btn btn-secondary" onclick="rotateCanvas()">🔄 旋转90°</button>
                <button class="btn btn-secondary" onclick="invertColors()">🌓 反色</button>
                <button class="btn btn-secondary" onclick="grayscale()">⬛ 灰度</button>
            </div>

            <div class="export-section">
                <div class="export-title">📜 操作历史</div>
                <div class="history-list" id="historyList">
                    <div class="history-item current">初始画布</div>
                </div>
            </div>

            <div class="export-section">
                <div class="export-title">ℹ️ 画布信息</div>
                <div style="font-size:11px;color:var(--text-secondary);line-height:1.8;">
                    <div>尺寸: <span id="infoSize">1200 × 800</span></div>
                    <div>操作次数: <span id="infoOps">0</span></div>
                    <div>文件大小(预估): <span id="infoFileSize">-</span></div>
                </div>
            </div>
        </div>
    </div>

    <div class="status-bar">
        <div class="status-left">
            <span><span class="status-dot"></span>就绪</span>
            <span>工具: <strong id="currentTool">画笔</strong></span>
            <span>坐标: <strong id="cursorPos">0, 0</strong></span>
        </div>
        <div class="status-right">
            <span>缩放: <strong id="statusZoom">100%</strong></span>
            <span>快捷键: Tab专注模式 B画笔 E橡皮 Ctrl+Z撤销</span>
        </div>
    </div>

    <!-- 导出模态框 -->
    <div class="modal-overlay" id="exportModal">
        <div class="modal">
            <div class="modal-title">💾 高级导出</div>
            <div class="input-field" style="margin-bottom:12px;">
                <label>文件格式</label>
                <select id="modalFormat">
                    <option value="png">PNG - 无损压缩,支持透明</option>
                    <option value="jpeg">JPEG - 高压缩率,不支持透明</option>
                    <option value="webp">WebP - 最佳平衡,支持透明</option>
                </select>
            </div>
            <div class="slider-group" id="modalQualityGroup">
                <div class="slider-label">
                    <span>图片质量 / 压缩率</span>
                    <span class="slider-value" id="modalQualityValue">92%</span>
                </div>
                <input type="range" id="modalQuality" min="1" max="100" value="92">
            </div>
            <div class="input-field" style="margin-bottom:12px;">
                <label>文件名</label>
                <input type="text" id="modalFilename" value="my-drawing">
            </div>
            <div class="input-field" style="margin-bottom:12px;">
                <label>缩放比例</label>
                <select id="modalScale">
                    <option value="1">100% (原始尺寸)</option>
                    <option value="0.75">75%</option>
                    <option value="0.5">50%</option>
                    <option value="0.25">25%</option>
                    <option value="2">200%</option>
                </select>
            </div>
            <div class="modal-actions">
                <button class="modal-btn" onclick="closeExportModal()">取消</button>
                <button class="modal-btn primary" onclick="modalExport()">导出下载</button>
            </div>
        </div>
    </div>

    <!-- 文字模态框 -->
    <div class="modal-overlay" id="textModal">
        <div class="modal">
            <div class="modal-title">📝 添加文字</div>
            <div class="input-field" style="margin-bottom:12px;">
                <label>文字内容</label>
                <input type="text" id="textContent" placeholder="输入文字...">
            </div>
            <div class="input-group">
                <div class="input-field">
                    <label>字体大小</label>
                    <input type="number" id="textSize" value="24" min="8" max="200">
                </div>
                <div class="input-field">
                    <label>字体</label>
                    <select id="textFont">
                        <option value="Arial">Arial</option>
                        <option value="Microsoft YaHei">微软雅黑</option>
                        <option value="SimSun">宋体</option>
                        <option value="KaiTi">楷体</option>
                    </select>
                </div>
            </div>
            <div class="input-group">
                <div class="input-field">
                    <label>样式</label>
                    <select id="textStyle">
                        <option value="normal">正常</option>
                        <option value="bold">粗体</option>
                        <option value="italic">斜体</option>
                    </select>
                </div>
                <div class="input-field">
                    <label>颜色</label>
                    <input type="color" id="textColor" value="#000000" style="width:100%;height:32px;border:none;border-radius:6px;cursor:pointer;">
                </div>
            </div>
            <div class="modal-actions">
                <button class="modal-btn" onclick="closeTextModal()">取消</button>
                <button class="modal-btn primary" onclick="addText()">添加</button>
            </div>
        </div>
    </div>

    <div class="toast-container" id="toastContainer"></div>

    <script>
        const state = {
            tool: 'pen', color: '#000000', brushSize: 5, opacity: 100,
            shapeStroke: 2, shapeFill: false, isDrawing: false,
            lastX: 0, lastY: 0, startX: 0, startY: 0,
            zoom: 1, history: [], historyIndex: -1, maxHistory: 50,
            bgColor: '#ffffff', snapshot: null, textX: 0, textY: 0
        };

        const canvas = document.getElementById('drawingCanvas');
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = state.bgColor;
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        function init() {
            initColors();
            saveState('初始画布');
            updateBrushPreview();
            setupEventListeners();
        }

        function initColors() {
            // 将白色放在首位,并添加特殊 class 防止在白底上隐形
            const colors = [
                '#ffffff', '#000000', '#434343', '#666666', '#999999', '#b7b7b7', '#cccccc', '#d9d9d9',
                '#980000', '#ff0000', '#ff9900', '#ffff00', '#00ff00', '#00ffff', '#4a86e8', '#0000ff',
                '#9900ff', '#ff00ff', '#e6b8af', '#f4cccc', '#fce5cd', '#fff2cc', '#d9ead3', '#d0e0e3',
                '#c9daf8', '#cfe2f3', '#d9d2e9', '#ead1dc', '#dd7e6b', '#ea9999', '#f9cb9c', '#ffe599',
                '#b6d7a8', '#a2c4c9', '#a4c2f4', '#9fc5e8', '#b4a7d6', '#d5a6bd', '#cc4125', '#e06666'
            ];
            const container = document.getElementById('colorPresets');
            colors.forEach(color => {
                const el = document.createElement('div');
                el.className = 'color-preset' + (color === '#ffffff' ? ' white-preset' : '');
                el.style.background = color;
                el.onclick = () => setColor(color);
                container.appendChild(el);
            });
        }

        function setColor(color) {
            state.color = color;
            document.getElementById('colorPicker').value = color;
            document.getElementById('colorHex').value = color;
            updateBrushPreview();
        }

        function setupEventListeners() {
            document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
                btn.addEventListener('click', () => {
                    document.querySelectorAll('.tool-btn[data-tool]').forEach(b => b.classList.remove('active'));
                    btn.classList.add('active');
                    state.tool = btn.dataset.tool;
                    updateStatus();
                });
            });

            document.getElementById('colorPicker').addEventListener('input', (e) => setColor(e.target.value));
            document.getElementById('colorHex').addEventListener('change', (e) => {
                let val = e.target.value;
                if (!val.startsWith('#')) val = '#' + val;
                if (/^#[0-9A-Fa-f]{6}$/.test(val)) setColor(val);
            });

            document.getElementById('brushSize').addEventListener('input', (e) => {
                state.brushSize = parseInt(e.target.value);
                document.getElementById('sizeValue').textContent = state.brushSize + 'px';
                updateBrushPreview();
            });

            document.getElementById('brushOpacity').addEventListener('input', (e) => {
                state.opacity = parseInt(e.target.value);
                document.getElementById('opacityValue').textContent = state.opacity + '%';
            });

            document.getElementById('shapeStroke').addEventListener('input', (e) => {
                state.shapeStroke = parseInt(e.target.value);
                document.getElementById('strokeValue').textContent = state.shapeStroke + 'px';
            });

            document.getElementById('shapeFill').addEventListener('change', (e) => {
                state.shapeFill = e.target.checked;
            });

            document.getElementById('exportFormat').addEventListener('change', (e) => {
                document.getElementById('qualitySlider').style.display = 
                    (e.target.value === 'jpeg' || e.target.value === 'webp') ? 'block' : 'none';
            });

            document.getElementById('exportQuality').addEventListener('input', (e) => {
                document.getElementById('qualityValue').textContent = e.target.value + '%';
            });

            canvas.addEventListener('mousedown', startDrawing);
            canvas.addEventListener('mousemove', draw);
            canvas.addEventListener('mouseup', stopDrawing);
            canvas.addEventListener('mouseout', stopDrawing);
            canvas.addEventListener('touchstart', handleTouch, {passive: false});
            canvas.addEventListener('touchmove', handleTouch, {passive: false});
            canvas.addEventListener('touchend', stopDrawing);
            document.addEventListener('keydown', handleKeyboard);

            canvas.addEventListener('mousemove', (e) => {
                const rect = canvas.getBoundingClientRect();
                const x = Math.round((e.clientX - rect.left) / state.zoom);
                const y = Math.round((e.clientY - rect.top) / state.zoom);
                document.getElementById('cursorPos').textContent = `${x}, ${y}`;
            });
        }

        function startDrawing(e) {
            state.isDrawing = true;
            const pos = getPos(e);
            state.lastX = pos.x; state.lastY = pos.y;
            state.startX = pos.x; state.startY = pos.y;

            if (state.tool === 'fill') {
                floodFill(Math.round(pos.x), Math.round(pos.y), state.color);
                saveState('填充');
                state.isDrawing = false;
                return;
            }

            if (state.tool === 'eyedropper') {
                pickColor(Math.round(pos.x), Math.round(pos.y));
                state.isDrawing = false;
                return;
            }

            if (state.tool === 'text') {
                state.textX = pos.x; state.textY = pos.y;
                document.getElementById('textModal').classList.add('active');
                state.isDrawing = false;
                return;
            }

            if (['line', 'rectangle', 'circle', 'triangle'].includes(state.tool)) {
                state.snapshot = ctx.getImageData(0, 0, canvas.width, canvas.height);
            }

            if (state.tool === 'pen' || state.tool === 'brush' || state.tool === 'eraser') {
                ctx.beginPath();
                ctx.moveTo(pos.x, pos.y);
            }
        }

        function draw(e) {
            if (!state.isDrawing) return;
            const pos = getPos(e);

            if (state.tool === 'pen') {
                ctx.globalAlpha = state.opacity / 100;
                ctx.strokeStyle = state.color;
                ctx.lineWidth = state.brushSize;
                ctx.lineCap = 'round'; ctx.lineJoin = 'round';
                ctx.lineTo(pos.x, pos.y);
                ctx.stroke();
            } else if (state.tool === 'brush') {
                ctx.globalAlpha = state.opacity / 100;
                ctx.strokeStyle = state.color;
                ctx.lineWidth = state.brushSize * 2;
                ctx.lineCap = 'round'; ctx.lineJoin = 'round';
                ctx.shadowBlur = state.brushSize / 2;
                ctx.shadowColor = state.color;
                ctx.lineTo(pos.x, pos.y);
                ctx.stroke();
                ctx.shadowBlur = 0;
            } else if (state.tool === 'eraser') {
                ctx.globalAlpha = 1;
                ctx.strokeStyle = state.bgColor;
                ctx.lineWidth = state.brushSize * 2;
                ctx.lineCap = 'round'; ctx.lineJoin = 'round';
                ctx.lineTo(pos.x, pos.y);
                ctx.stroke();
            } else if (['line', 'rectangle', 'circle', 'triangle'].includes(state.tool)) {
                if (state.snapshot) ctx.putImageData(state.snapshot, 0, 0);
                drawShape(state.tool, state.startX, state.startY, pos.x, pos.y);
            }

            state.lastX = pos.x; state.lastY = pos.y;
        }

        function stopDrawing(e) {
            if (!state.isDrawing) return;
            state.isDrawing = false;

            if (state.tool === 'pen' || state.tool === 'brush') {
                ctx.closePath();
                saveState('绘制 - ' + state.tool);
            } else if (state.tool === 'eraser') {
                ctx.closePath();
                saveState('擦除');
            } else if (['line', 'rectangle', 'circle', 'triangle'].includes(state.tool)) {
                if (state.snapshot) ctx.putImageData(state.snapshot, 0, 0);
                const pos = e ? getPos(e) : { x: state.lastX, y: state.lastY };
                drawShape(state.tool, state.startX, state.startY, pos.x, pos.y);
                saveState('形状 - ' + state.tool);
            }

            ctx.globalAlpha = 1;
            state.snapshot = null;
            updateInfo();
        }

        function drawShape(type, x1, y1, x2, y2) {
            ctx.globalAlpha = state.opacity / 100;
            ctx.strokeStyle = state.color;
            ctx.fillStyle = state.color;
            ctx.lineWidth = state.shapeStroke;
            ctx.lineCap = 'round'; ctx.lineJoin = 'round';
            ctx.beginPath();

            switch(type) {
                case 'line':
                    ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); break;
                case 'rectangle':
                    if (state.shapeFill) ctx.fillRect(x1, y1, x2-x1, y2-y1);
                    ctx.strokeRect(x1, y1, x2-x1, y2-y1); break;
                case 'circle':
                    const rx = Math.abs(x2 - x1) / 2;
                    const ry = Math.abs(y2 - y1) / 2;
                    const cx = Math.min(x1, x2) + rx;
                    const cy = Math.min(y1, y2) + ry;
                    ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
                    if (state.shapeFill) ctx.fill();
                    ctx.stroke(); break;
                case 'triangle':
                    ctx.moveTo((x1 + x2) / 2, y1);
                    ctx.lineTo(x1, y2); ctx.lineTo(x2, y2);
                    ctx.closePath();
                    if (state.shapeFill) ctx.fill();
                    ctx.stroke(); break;
            }
        }

        function getPos(e) {
            const rect = canvas.getBoundingClientRect();
            let clientX, clientY;
            if (e.touches) {
                clientX = e.touches[0].clientX;
                clientY = e.touches[0].clientY;
            } else {
                clientX = e.clientX;
                clientY = e.clientY;
            }
            return {
                x: (clientX - rect.left) / state.zoom,
                y: (clientY - rect.top) / state.zoom
            };
        }

        function handleTouch(e) {
            e.preventDefault();
            const touch = e.touches[0];
            const mouseEvent = new MouseEvent(e.type === 'touchstart' ? 'mousedown' : 'mousemove', {
                clientX: touch.clientX, clientY: touch.clientY
            });
            canvas.dispatchEvent(mouseEvent);
        }

        function floodFill(startX, startY, fillColor) {
            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            const data = imageData.data;
            const width = canvas.width;
            const height = canvas.height;
            const startIdx = (startY * width + startX) * 4;
            const startR = data[startIdx], startG = data[startIdx+1], startB = data[startIdx+2], startA = data[startIdx+3];
            const hex = fillColor.replace('#', '');
            const fillR = parseInt(hex.substr(0,2),16);
            const fillG = parseInt(hex.substr(2,2),16);
            const fillB = parseInt(hex.substr(4,2),16);
            if (startR === fillR && startG === fillG && startB === fillB) return;
            
            const stack = [[startX, startY]];
            const tolerance = 30;
            let pixelsProcessed = 0;
            const maxPixels = width * height; // 防止超大画布卡死
            
            function matchColor(idx) {
                return Math.abs(data[idx]-startR)<=tolerance && Math.abs(data[idx+1]-startG)<=tolerance &&
                       Math.abs(data[idx+2]-startB)<=tolerance && Math.abs(data[idx+3]-startA)<=tolerance;
            }
            
            while (stack.length > 0 && pixelsProcessed < maxPixels) {
                const [x, y] = stack.pop();
                const idx = (y * width + x) * 4;
                if (x<0 || x>=width || y<0 || y>=height) continue;
                if (!matchColor(idx)) continue;
                
                data[idx]=fillR; data[idx+1]=fillG; data[idx+2]=fillB; data[idx+3]=255;
                pixelsProcessed++;
                
                stack.push([x+1,y], [x-1,y], [x,y+1], [x,y-1]);
            }
            ctx.putImageData(imageData, 0, 0);
        }

        function pickColor(x, y) {
            const pixel = ctx.getImageData(x, y, 1, 1).data;
            const hex = '#' + [pixel[0],pixel[1],pixel[2]].map(v=>v.toString(16).padStart(2,'0')).join('');
            setColor(hex);
            showToast('已拾取颜色: ' + hex, 'info');
        }

        function saveState(action) {
            if (state.historyIndex < state.history.length - 1) {
                state.history = state.history.slice(0, state.historyIndex + 1);
            }
            state.history.push({ data: canvas.toDataURL('image/png'), action, timestamp: Date.now() });
            if (state.history.length > state.maxHistory) state.history.shift();
            state.historyIndex = state.history.length - 1;
            updateHistoryUI();
            updateInfo();
        }

        function undo() {
            if (state.historyIndex > 0) {
                state.historyIndex--;
                restoreState(state.historyIndex);
                showToast('撤销: ' + state.history[state.historyIndex+1].action, 'info');
            } else {
                showToast('没有更多可撤销的操作', 'error');
            }
        }

        function redo() {
            if (state.historyIndex < state.history.length - 1) {
                state.historyIndex++;
                restoreState(state.historyIndex);
                showToast('重做: ' + state.history[state.historyIndex].action, 'info');
            } else {
                showToast('没有更多可重做的操作', 'error');
            }
        }

        function restoreState(index) {
            const img = new Image();
            img.onload = () => {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.drawImage(img, 0, 0);
                updateHistoryUI();
            };
            img.src = state.history[index].data;
        }

        function updateHistoryUI() {
            const container = document.getElementById('historyList');
            container.innerHTML = '';
            state.history.forEach((item, i) => {
                const el = document.createElement('div');
                el.className = 'history-item' + (i === state.historyIndex ? ' current' : '');
                el.textContent = `${i+1}. ${item.action}`;
                el.onclick = () => { state.historyIndex = i; restoreState(i); };
                container.appendChild(el);
            });
            container.scrollTop = container.scrollHeight;
            document.getElementById('infoOps').textContent = state.history.length - 1;
        }

        function clearCanvas() {
            if (confirm('确定要清空画布吗?此操作不可恢复。')) {
                ctx.fillStyle = state.bgColor;
                ctx.fillRect(0, 0, canvas.width, canvas.height);
                saveState('清空画布');
                showToast('画布已清空', 'info');
            }
        }

        function resizeCanvas() {
            const w = parseInt(document.getElementById('canvasWidth').value);
            const h = parseInt(document.getElementById('canvasHeight').value);
            if (w<1||h<1||w>4000||h>4000) { showToast('尺寸必须在 1-4000 像素之间', 'error'); return; }
            const tempCanvas = document.createElement('canvas');
            tempCanvas.width = canvas.width; tempCanvas.height = canvas.height;
            tempCanvas.getContext('2d').drawImage(canvas, 0, 0);
            canvas.width = w; canvas.height = h;
            state.bgColor = document.getElementById('bgColor').value;
            ctx.fillStyle = state.bgColor;
            ctx.fillRect(0, 0, w, h);
            ctx.drawImage(tempCanvas, 0, 0);
            document.getElementById('infoSize').textContent = `${w} × ${h}`;
            saveState(`调整尺寸 ${w}×${h}`);
            showToast(`画布已调整为 ${w} × ${h}`, 'success');
        }

        function applyPresetSize() {
            const val = document.getElementById('presetSize').value;
            if (val) {
                const [w, h] = val.split('x');
                document.getElementById('canvasWidth').value = w;
                document.getElementById('canvasHeight').value = h;
                resizeCanvas();
            }
        }

        function createNewCanvas() {
            if (confirm('创建新画布将丢失当前工作,确定继续吗?')) {
                ctx.fillStyle = document.getElementById('bgColor').value;
                ctx.fillRect(0, 0, canvas.width, canvas.height);
                state.history = []; state.historyIndex = -1;
                saveState('新建画布');
                showToast('已创建新画布', 'success');
            }
        }

        function uploadImage(event) {
            const file = event.target.files[0];
            if (!file) return;
            const reader = new FileReader();
            reader.onload = (e) => {
                const img = new Image();
                img.onload = () => {
                    canvas.width = img.width; canvas.height = img.height;
                    document.getElementById('canvasWidth').value = img.width;
                    document.getElementById('canvasHeight').value = img.height;
                    ctx.drawImage(img, 0, 0);
                    saveState('上传图片: ' + file.name);
                    showToast('图片已加载: ' + file.name, 'success');
                    document.getElementById('infoSize').textContent = `${img.width} × ${img.height}`;
                    resetZoom();
                };
                img.src = e.target.result;
            };
            reader.readAsDataURL(file);
            event.target.value = '';
        }

        function showExportModal() { document.getElementById('exportModal').classList.add('active'); }
        function closeExportModal() { document.getElementById('exportModal').classList.remove('active'); }

        document.getElementById('modalFormat').addEventListener('change', (e) => {
            document.getElementById('modalQualityGroup').style.display = e.target.value === 'png' ? 'none' : 'block';
        });
        document.getElementById('modalQuality').addEventListener('input', (e) => {
            document.getElementById('modalQualityValue').textContent = e.target.value + '%';
        });

        function modalExport() {
            const format = document.getElementById('modalFormat').value;
            const quality = parseInt(document.getElementById('modalQuality').value) / 100;
            const filename = document.getElementById('modalFilename').value || 'drawing';
            const scale = parseFloat(document.getElementById('modalScale').value);
            exportWithSettings(format, quality, filename, scale);
            closeExportModal();
        }

        function exportImage() {
            const format = document.getElementById('exportFormat').value;
            const quality = parseInt(document.getElementById('exportQuality').value) / 100;
            const filename = document.getElementById('exportFilename').value || 'drawing';
            exportWithSettings(format, quality, filename, 1);
        }

        function exportWithSettings(format, quality, filename, scale) {
            let exportCanvas = canvas;
            if (scale !== 1) {
                exportCanvas = document.createElement('canvas');
                exportCanvas.width = canvas.width * scale;
                exportCanvas.height = canvas.height * scale;
                exportCanvas.getContext('2d').drawImage(canvas, 0, 0, exportCanvas.width, exportCanvas.height);
            }
            let mimeType = 'image/png', ext = 'png';
            switch(format) {
                case 'jpeg': mimeType = 'image/jpeg'; ext = 'jpg'; break;
                case 'webp': mimeType = 'image/webp'; ext = 'webp'; break;
            }
            const dataURL = exportCanvas.toDataURL(mimeType, quality);
            const link = document.createElement('a');
            link.download = `${filename}.${ext}`;
            link.href = dataURL;
            link.click();
            const fileSize = Math.round((dataURL.length * 3) / 4 / 1024);
            showToast(`已导出: ${filename}.${ext} (~${fileSize}KB)`, 'success');
        }

        function quickSave() { exportImage(); }

        function flipHorizontal() {
            const t = document.createElement('canvas');
            t.width = canvas.width; t.height = canvas.height;
            const tc = t.getContext('2d');
            tc.translate(canvas.width, 0); tc.scale(-1, 1);
            tc.drawImage(canvas, 0, 0);
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(t, 0, 0);
            saveState('水平翻转');
            showToast('已水平翻转', 'success');
        }

        function flipVertical() {
            const t = document.createElement('canvas');
            t.width = canvas.width; t.height = canvas.height;
            const tc = t.getContext('2d');
            tc.translate(0, canvas.height); tc.scale(1, -1);
            tc.drawImage(canvas, 0, 0);
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(t, 0, 0);
            saveState('垂直翻转');
            showToast('已垂直翻转', 'success');
        }

        function rotateCanvas() {
            const t = document.createElement('canvas');
            t.width = canvas.height; t.height = canvas.width;
            const tc = t.getContext('2d');
            tc.translate(t.width/2, t.height/2);
            tc.rotate(Math.PI/2);
            tc.drawImage(canvas, -canvas.width/2, -canvas.height/2);
            canvas.width = t.width; canvas.height = t.height;
            ctx.drawImage(t, 0, 0);
            document.getElementById('canvasWidth').value = canvas.width;
            document.getElementById('canvasHeight').value = canvas.height;
            document.getElementById('infoSize').textContent = `${canvas.width} × ${canvas.height}`;
            saveState('旋转90°');
            showToast('已旋转90°', 'success');
        }

        function invertColors() {
            const id = ctx.getImageData(0, 0, canvas.width, canvas.height);
            const d = id.data;
            for (let i=0; i<d.length; i+=4) { d[i]=255-d[i]; d[i+1]=255-d[i+1]; d[i+2]=255-d[i+2]; }
            ctx.putImageData(id, 0, 0);
            saveState('反色');
            showToast('已应用反色效果', 'success');
        }

        function grayscale() {
            const id = ctx.getImageData(0, 0, canvas.width, canvas.height);
            const d = id.data;
            for (let i=0; i<d.length; i+=4) {
                const g = d[i]*0.299 + d[i+1]*0.587 + d[i+2]*0.114;
                d[i]=g; d[i+1]=g; d[i+2]=g;
            }
            ctx.putImageData(id, 0, 0);
            saveState('灰度');
            showToast('已应用灰度效果', 'success');
        }

        function closeTextModal() { document.getElementById('textModal').classList.remove('active'); }

        function addText() {
            const text = document.getElementById('textContent').value;
            if (!text) return;
            const size = document.getElementById('textSize').value;
            const font = document.getElementById('textFont').value;
            const style = document.getElementById('textStyle').value;
            const color = document.getElementById('textColor').value;
            ctx.fillStyle = color;
            ctx.font = `${style} ${size}px ${font}`;
            ctx.fillText(text, state.textX, state.textY);
            saveState('添加文字: ' + text);
            closeTextModal();
            showToast('文字已添加', 'success');
        }

        function zoomIn() { state.zoom = Math.min(state.zoom + 0.1, 5); applyZoom(); }
        function zoomOut() { state.zoom = Math.max(state.zoom - 0.1, 0.1); applyZoom(); }
        function resetZoom() { state.zoom = 1; applyZoom(); }

        function applyZoom() {
            canvas.style.transform = `scale(${state.zoom})`;
            canvas.style.transformOrigin = 'center center';
            const percent = Math.round(state.zoom * 100);
            document.getElementById('zoomLevel').textContent = percent + '%';
            document.getElementById('statusZoom').textContent = percent + '%';
        }

        // 核心优化:完善画笔及全局撤销逻辑
        function handleKeyboard(e) {
            // Tab 键专注模式切换
            if (e.key === 'Tab') {
                e.preventDefault();
                toggleFocusMode();
                return;
            }

            if (e.ctrlKey || e.metaKey) {
                switch(e.key.toLowerCase()) {
                    case 'z':
                        e.preventDefault();
                        // 优化:如果正在绘制中按撤销,先取消当前笔画,再执行撤销
                        if (state.isDrawing) cancelCurrentStroke();
                        if (e.shiftKey) redo(); else undo();
                        break;
                    case 'y':
                        e.preventDefault();
                        if (state.isDrawing) cancelCurrentStroke();
                        redo();
                        break;
                    case 's':
                        e.preventDefault();
                        quickSave();
                        break;
                }
            } else {
                switch(e.key.toLowerCase()) {
                    case 'b': selectTool('pen'); break;
                    case 'p': selectTool('brush'); break;
                    case 'e': selectTool('eraser'); break;
                    case 'l': selectTool('line'); break;
                    case 'r': selectTool('rectangle'); break;
                    case 'c': selectTool('circle'); break;
                    case 'g': selectTool('fill'); break;
                    case 't': selectTool('text'); break;
                    case 'i': selectTool('eyedropper'); break;
                }
            }
        }

        // 新增:取消当前未完成的笔画(用于绘制中途撤销)
        function cancelCurrentStroke() {
            state.isDrawing = false;
            if (state.snapshot) {
                ctx.putImageData(state.snapshot, 0, 0);
                state.snapshot = null;
            }
            ctx.closePath();
            ctx.globalAlpha = 1;
        }

        // 新增:面板折叠/展开功能
        function togglePanel(side) {
            const panel = document.getElementById(side + 'Panel');
            const btn = document.getElementById(side + 'ToggleBtn');
            panel.classList.toggle('collapsed');
            
            if (panel.classList.contains('collapsed')) {
                btn.textContent = side === 'left' ? '▶' : '◀';
                btn.title = '展开' + (side === 'left' ? '左侧' : '右侧') + '面板';
            } else {
                btn.textContent = side === 'left' ? '◀ 左' : '右 ▶';
                btn.title = '折叠' + (side === 'left' ? '左侧' : '右侧') + '面板';
            }
        }

        // 新增:专注模式切换
        let isFocusMode = false;
        function toggleFocusMode() {
            isFocusMode = !isFocusMode;
            document.body.classList.toggle('focus-mode', isFocusMode);
            showToast(isFocusMode ? '已进入专注模式 (按 Tab 退出)' : '已退出专注模式', 'info');
        }

        function selectTool(tool) {
            state.tool = tool;
            document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
                btn.classList.toggle('active', btn.dataset.tool === tool);
            });
            updateStatus();
        }

        function updateBrushPreview() {
            const preview = document.getElementById('brushPreview');
            const size = Math.max(4, Math.min(state.brushSize, 50));
            preview.style.width = size + 'px';
            preview.style.height = size + 'px';
            preview.style.background = state.color;
        }

        function updateStatus() {
            const names = {pen:'画笔',brush:'毛笔',eraser:'橡皮擦',line:'直线',rectangle:'矩形',circle:'圆形',triangle:'三角形',fill:'填充',text:'文字',eyedropper:'取色器'};
            document.getElementById('currentTool').textContent = names[state.tool] || state.tool;
        }

        function updateInfo() {
            document.getElementById('infoSize').textContent = `${canvas.width} × ${canvas.height}`;
            const data = canvas.toDataURL('image/png');
            const size = Math.round((data.length * 3) / 4 / 1024);
            document.getElementById('infoFileSize').textContent = `~${size}KB`;
        }

        function showToast(message, type = 'info') {
            const container = document.getElementById('toastContainer');
            const toast = document.createElement('div');
            toast.className = `toast ${type}`;
            const icons = {success:'✅', error:'❌', info:'ℹ️'};
            toast.innerHTML = `<span>${icons[type]||'ℹ️'}</span><span>${message}</span>`;
            container.appendChild(toast);
            setTimeout(() => {
                toast.style.opacity = '0';
                toast.style.transform = 'translateX(100%)';
                toast.style.transition = 'all 0.3s ease';
                setTimeout(() => toast.remove(), 300);
            }, 3000);
        }

        init();
    </script>
</body>
</html>
        
编辑器加载中
预览
控制台