duqing2026 commited on
Commit
96b445e
·
1 Parent(s): 10c714c

升级优化

Browse files
Files changed (5) hide show
  1. README.md +3 -2
  2. app.py +8 -0
  3. static/script.js +208 -0
  4. static/style.css +37 -0
  5. templates/index.html +37 -155
README.md CHANGED
@@ -15,14 +15,15 @@ Code Snapshot Studio 是一个专为开发者设计的代码截图生成工具
15
 
16
  ## ✨ 核心功能
17
 
18
- - **多语言支持**:支持 Python, JavaScript, Go, Rust, Java 等多种主流编程语言的语法高亮。
19
  - **主题切换**:内置 Tomorrow (Dark), Okaidia, Solarized Light, Twilight 等多种经典配色主题。
20
  - **高度定制**:
21
  - 调整内边距 (Padding)
22
- - 切换背景风格 (渐变色/纯色)
23
  - 窗口控件开关 (Mac 风格红绿灯)
24
  - 行号显示开关
25
  - 自定义窗口标题
 
26
  - **高清导出**:基于 HTML2Canvas,一键导出 2x 分辨率的高清 PNG 图片。
27
  - **实时预览**:所见即所得的编辑体验。
28
 
 
15
 
16
  ## ✨ 核心功能
17
 
18
+ - **多语言支持**:支持 Python, JavaScript, Go, Rust, Java 等多种主流编程语言的语法高亮,并内置丰富代码示例。
19
  - **主题切换**:内置 Tomorrow (Dark), Okaidia, Solarized Light, Twilight 等多种经典配色主题。
20
  - **高度定制**:
21
  - 调整内边距 (Padding)
22
+ - 切换多种精美背景风格 (渐变色/纯色)
23
  - 窗口控件开关 (Mac 风格红绿灯)
24
  - 行号显示开关
25
  - 自定义窗口标题
26
+ - **移动端适配**:响应式设计,支持手机端操作。
27
  - **高清导出**:基于 HTML2Canvas,一键导出 2x 分辨率的高清 PNG 图片。
28
  - **实时预览**:所见即所得的编辑体验。
29
 
app.py CHANGED
@@ -11,5 +11,13 @@ def index():
11
  def send_static(path):
12
  return send_from_directory('static', path)
13
 
 
 
 
 
 
 
 
 
14
  if __name__ == '__main__':
15
  app.run(host='0.0.0.0', port=7860)
 
11
  def send_static(path):
12
  return send_from_directory('static', path)
13
 
14
+ @app.errorhandler(404)
15
+ def page_not_found(e):
16
+ return "Page Not Found", 404
17
+
18
+ @app.errorhandler(500)
19
+ def internal_server_error(e):
20
+ return "Internal Server Error", 500
21
+
22
  if __name__ == '__main__':
23
  app.run(host='0.0.0.0', port=7860)
static/script.js ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { createApp, ref, onMounted, watch, nextTick } = Vue;
2
+
3
+ const codeSnippets = {
4
+ python: `def hello_world():
5
+ print("Hello, Trae!")
6
+ return True
7
+
8
+ # Build something amazing
9
+ if __name__ == "__main__":
10
+ hello_world()`,
11
+ javascript: `function greet(name) {
12
+ return \`Hello, \${name}!\`;
13
+ }
14
+
15
+ // Welcome to Code Snapshot Studio
16
+ console.log(greet('Developer'));`,
17
+ typescript: `interface User {
18
+ id: number;
19
+ name: string;
20
+ }
21
+
22
+ const user: User = {
23
+ id: 1,
24
+ name: "Trae User"
25
+ };
26
+
27
+ console.log(user);`,
28
+ html: `<!DOCTYPE html>
29
+ <html lang="en">
30
+ <body>
31
+ <h1>Hello World</h1>
32
+ <p>Welcome to my website.</p>
33
+ </body>
34
+ </html>`,
35
+ css: `.container {
36
+ display: flex;
37
+ justify-content: center;
38
+ align-items: center;
39
+ height: 100vh;
40
+ background: #f0f0f0;
41
+ }`,
42
+ java: `public class Main {
43
+ public static void main(String[] args) {
44
+ System.out.println("Hello, World!");
45
+ }
46
+ }`,
47
+ go: `package main
48
+
49
+ import "fmt"
50
+
51
+ func main() {
52
+ fmt.Println("Hello, Go!")
53
+ }`,
54
+ rust: `fn main() {
55
+ println!("Hello, Rust!");
56
+ }`,
57
+ sql: `SELECT id, username, email
58
+ FROM users
59
+ WHERE status = 'active'
60
+ ORDER BY created_at DESC;`,
61
+ bash: `#!/bin/bash
62
+
63
+ echo "Starting deployment..."
64
+ npm install
65
+ npm run build
66
+ echo "Done!"`,
67
+ json: `{
68
+ "name": "code-snapshot-studio",
69
+ "version": "1.0.0",
70
+ "private": true
71
+ }`
72
+ };
73
+
74
+ createApp({
75
+ setup() {
76
+ const code = ref(codeSnippets.python);
77
+ const language = ref('python');
78
+ const theme = ref('tomorrow');
79
+ const padding = ref(64);
80
+ const showWindowControls = ref(true);
81
+ const showLineNumbers = ref(true);
82
+ const showShadow = ref(true);
83
+ const windowTitle = ref('main.py');
84
+ const snapshotCard = ref(null);
85
+ const codeBlock = ref(null);
86
+ const isExporting = ref(false);
87
+ const showSidebar = ref(true); // For mobile toggle
88
+
89
+ // Background presets
90
+ const backgrounds = [
91
+ { name: 'Gradient 1', value: 'linear-gradient(135deg, #FF9D6C 0%, #BB4E75 100%)' },
92
+ { name: 'Gradient 2', value: 'linear-gradient(135deg, #85FFBD 0%, #FFFB7D 100%)' },
93
+ { name: 'Gradient 3', value: 'linear-gradient(135deg, #8EC5FC 0%, #E0C3FC 100%)' },
94
+ { name: 'Gradient 4', value: 'linear-gradient(135deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%)' },
95
+ { name: 'Solid Dark', value: '#1e293b' },
96
+ { name: 'Solid Light', value: '#f8fafc' },
97
+ { name: 'Mesh Gradient', value: 'radial-gradient(at 40% 20%, hsla(28,100%,74%,1) 0px, transparent 50%), radial-gradient(at 80% 0%, hsla(189,100%,56%,1) 0px, transparent 50%), radial-gradient(at 0% 50%, hsla(355,100%,93%,1) 0px, transparent 50%)' }
98
+ ];
99
+ const currentBg = ref(backgrounds[3]);
100
+
101
+ const themeBgColor = ref('#2d2d2d'); // Default for Tomorrow
102
+
103
+ const changeTheme = () => {
104
+ const link = document.getElementById('prism-theme');
105
+ const baseUrl = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism';
106
+ if (theme.value === 'default') {
107
+ link.href = `${baseUrl}.min.css`;
108
+ themeBgColor.value = '#f5f2f0';
109
+ } else {
110
+ link.href = `${baseUrl}-${theme.value}.min.css`;
111
+ // Update bg color approximation
112
+ if(theme.value === 'tomorrow') themeBgColor.value = '#2d2d2d';
113
+ if(theme.value === 'okaidia') themeBgColor.value = '#272822';
114
+ if(theme.value === 'solarizedlight') themeBgColor.value = '#fdf6e3';
115
+ if(theme.value === 'twilight') themeBgColor.value = '#141414';
116
+ }
117
+ };
118
+
119
+ const refreshHighlight = () => {
120
+ nextTick(() => {
121
+ if (window.Prism) {
122
+ Prism.highlightAll();
123
+ }
124
+ });
125
+ };
126
+
127
+ // Auto-load snippet when language changes (if code is empty or matches another snippet)
128
+ const onLanguageChange = () => {
129
+ // Optional: You could ask user or just automatically switch if it's default
130
+ // For now, let's just refresh highlight
131
+ refreshHighlight();
132
+ };
133
+
134
+ const loadExample = () => {
135
+ if (codeSnippets[language.value]) {
136
+ code.value = codeSnippets[language.value];
137
+ }
138
+ };
139
+
140
+ watch([code, language, showLineNumbers], () => {
141
+ refreshHighlight();
142
+ });
143
+
144
+ onMounted(() => {
145
+ refreshHighlight();
146
+ // Check screen size for initial sidebar state
147
+ if (window.innerWidth < 1024) {
148
+ showSidebar.value = false;
149
+ }
150
+ });
151
+
152
+ const exportImage = async () => {
153
+ if (!snapshotCard.value || isExporting.value) return;
154
+
155
+ isExporting.value = true;
156
+ try {
157
+ // Wait for any renders
158
+ await nextTick();
159
+
160
+ const canvas = await html2canvas(snapshotCard.value, {
161
+ scale: 2, // Retina support
162
+ useCORS: true,
163
+ backgroundColor: null,
164
+ logging: false,
165
+ allowTaint: true
166
+ });
167
+
168
+ const link = document.createElement('a');
169
+ link.download = `code-snapshot-${Date.now()}.png`;
170
+ link.href = canvas.toDataURL('image/png');
171
+ link.click();
172
+ } catch (err) {
173
+ console.error('Export failed:', err);
174
+ alert('导出失败,请重试');
175
+ } finally {
176
+ isExporting.value = false;
177
+ }
178
+ };
179
+
180
+ const toggleSidebar = () => {
181
+ showSidebar.value = !showSidebar.value;
182
+ };
183
+
184
+ return {
185
+ code,
186
+ language,
187
+ theme,
188
+ padding,
189
+ showWindowControls,
190
+ showLineNumbers,
191
+ showShadow,
192
+ windowTitle,
193
+ backgrounds,
194
+ currentBg,
195
+ snapshotCard,
196
+ codeBlock,
197
+ exportImage,
198
+ changeTheme,
199
+ refreshHighlight,
200
+ themeBgColor,
201
+ onLanguageChange,
202
+ loadExample,
203
+ isExporting,
204
+ showSidebar,
205
+ toggleSidebar
206
+ };
207
+ }
208
+ }).mount('#app');
static/style.css ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
3
+ background-color: #f3f4f6;
4
+ }
5
+
6
+ .preview-container {
7
+ transition: all 0.3s ease;
8
+ overflow: hidden;
9
+ }
10
+
11
+ /* Custom Scrollbar */
12
+ ::-webkit-scrollbar {
13
+ width: 8px;
14
+ height: 8px;
15
+ }
16
+
17
+ ::-webkit-scrollbar-track {
18
+ background: transparent;
19
+ }
20
+
21
+ ::-webkit-scrollbar-thumb {
22
+ background: #cbd5e1;
23
+ border-radius: 4px;
24
+ }
25
+
26
+ ::-webkit-scrollbar-thumb:hover {
27
+ background: #94a3b8;
28
+ }
29
+
30
+ .code-font {
31
+ font-family: 'Fira Code', 'Consolas', 'Monaco', 'Courier New', monospace;
32
+ }
33
+
34
+ /* Mobile Sidebar Toggle Transition */
35
+ .sidebar-transition {
36
+ transition: transform 0.3s ease-in-out;
37
+ }
templates/index.html CHANGED
@@ -13,57 +13,39 @@
13
  <link id="prism-theme" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
14
  <!-- Icons -->
15
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
16
- <style>
17
- body {
18
- font-family: 'Inter', system-ui, -apple-system, sans-serif;
19
- background-color: #f3f4f6;
20
- }
21
- .preview-container {
22
- transition: all 0.3s ease;
23
- overflow: hidden; /* Ensure content doesn't spill out */
24
- }
25
- /* Custom Scrollbar */
26
- ::-webkit-scrollbar {
27
- width: 8px;
28
- height: 8px;
29
- }
30
- ::-webkit-scrollbar-track {
31
- background: transparent;
32
- }
33
- ::-webkit-scrollbar-thumb {
34
- background: #cbd5e1;
35
- border-radius: 4px;
36
- }
37
- ::-webkit-scrollbar-thumb:hover {
38
- background: #94a3b8;
39
- }
40
- .code-font {
41
- font-family: 'Fira Code', 'Consolas', 'Monaco', 'Courier New', monospace;
42
- }
43
- </style>
44
  </head>
45
  <body class="h-screen flex flex-col overflow-hidden">
46
  <div id="app" class="flex-1 flex flex-col h-full">
47
  <!-- Header -->
48
- <header class="bg-white border-b border-gray-200 px-6 py-3 flex items-center justify-between shadow-sm z-10">
49
  <div class="flex items-center gap-3">
 
 
 
50
  <div class="w-8 h-8 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-lg flex items-center justify-center text-white font-bold">
51
  <i class="fa-solid fa-camera"></i>
52
  </div>
53
- <h1 class="text-xl font-bold text-gray-800 tracking-tight">Code Snapshot Studio</h1>
54
  </div>
55
  <div class="flex items-center gap-4">
56
- <button @click="exportImage" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg font-medium transition-colors flex items-center gap-2 shadow-sm">
57
- <i class="fa-solid fa-download"></i>
58
- <span>导出 PNG</span>
 
 
59
  </button>
60
  </div>
61
  </header>
62
 
63
  <!-- Main Content -->
64
- <main class="flex-1 flex overflow-hidden">
 
 
 
65
  <!-- Sidebar Controls -->
66
- <aside class="w-80 bg-white border-r border-gray-200 overflow-y-auto p-6 flex flex-col gap-6 shadow-[4px_0_24px_rgba(0,0,0,0.02)] z-10">
67
 
68
  <!-- Code Input -->
69
  <div class="flex flex-col gap-2">
@@ -74,19 +56,24 @@
74
  <!-- Language & Theme -->
75
  <div class="grid grid-cols-2 gap-4">
76
  <div class="flex flex-col gap-2">
77
- <label class="text-sm font-semibold text-gray-700">语言</label>
78
- <select v-model="language" @change="refreshHighlight" class="w-full p-2 border border-gray-300 rounded-lg text-sm bg-white focus:ring-2 focus:ring-indigo-500 outline-none">
 
 
 
 
 
79
  <option value="javascript">JavaScript</option>
 
80
  <option value="python">Python</option>
81
- <option value="html">HTML</option>
82
- <option value="css">CSS</option>
83
  <option value="java">Java</option>
84
  <option value="go">Go</option>
85
  <option value="rust">Rust</option>
 
 
86
  <option value="sql">SQL</option>
87
  <option value="bash">Bash</option>
88
  <option value="json">JSON</option>
89
- <option value="typescript">TypeScript</option>
90
  </select>
91
  </div>
92
  <div class="flex flex-col gap-2">
@@ -108,7 +95,8 @@
108
  <button v-for="(bg, index) in backgrounds" :key="index"
109
  @click="currentBg = bg"
110
  :class="['w-8 h-8 rounded-full border-2 transition-all', currentBg.value === bg.value ? 'border-indigo-600 scale-110 shadow-md' : 'border-transparent hover:scale-105']"
111
- :style="{ background: bg.value }">
 
112
  </button>
113
  </div>
114
  </div>
@@ -142,18 +130,18 @@
142
  </aside>
143
 
144
  <!-- Preview Area -->
145
- <section class="flex-1 bg-gray-100 flex items-center justify-center p-8 overflow-auto relative">
146
  <!-- Grid Background Pattern -->
147
  <div class="absolute inset-0 opacity-[0.03]"
148
  style="background-image: radial-gradient(#000 1px, transparent 1px); background-size: 20px 20px;">
149
  </div>
150
 
151
  <!-- The Snapshot Card -->
152
- <div ref="snapshotCard" class="preview-container relative flex items-center justify-center min-w-[400px]"
153
  :style="{ background: currentBg.value, padding: padding + 'px' }">
154
 
155
  <!-- Window -->
156
- <div class="bg-[#1e1e1e] rounded-xl overflow-hidden min-w-[400px] max-w-4xl w-auto"
157
  :class="{'shadow-2xl': showShadow, 'shadow-none': !showShadow}"
158
  :style="{ backgroundColor: themeBgColor }">
159
 
@@ -166,13 +154,13 @@
166
  <div class="w-3 h-3 rounded-full bg-[#27c93f] border border-[#1aab29]"></div>
167
  </div>
168
  <!-- Title -->
169
- <div class="w-full text-center text-xs font-medium text-gray-400 font-sans select-none">
170
  {{ windowTitle }}
171
  </div>
172
  </div>
173
 
174
  <!-- Code Content -->
175
- <div class="p-0 overflow-hidden">
176
  <pre class="!m-0 !p-6 !bg-transparent text-sm leading-relaxed outline-none code-font"
177
  :class="{'line-numbers': showLineNumbers}"><code :class="'language-' + language" ref="codeBlock">{{ code }}</code></pre>
178
  </div>
@@ -182,113 +170,7 @@
182
  </main>
183
  </div>
184
 
185
- <script>
186
- const { createApp, ref, onMounted, watch, nextTick } = Vue;
187
-
188
- createApp({
189
- setup() {
190
- const code = ref(`def hello_world():
191
- print("Hello, Trae!")
192
- return True
193
-
194
- # Build something amazing
195
- if __name__ == "__main__":
196
- hello_world()`);
197
- const language = ref('python');
198
- const theme = ref('tomorrow');
199
- const padding = ref(64);
200
- const showWindowControls = ref(true);
201
- const showLineNumbers = ref(true);
202
- const showShadow = ref(true);
203
- const windowTitle = ref('main.py');
204
- const snapshotCard = ref(null);
205
- const codeBlock = ref(null);
206
-
207
- // Background presets
208
- const backgrounds = [
209
- { name: 'Gradient 1', value: 'linear-gradient(135deg, #FF9D6C 0%, #BB4E75 100%)' },
210
- { name: 'Gradient 2', value: 'linear-gradient(135deg, #85FFBD 0%, #FFFB7D 100%)' },
211
- { name: 'Gradient 3', value: 'linear-gradient(135deg, #8EC5FC 0%, #E0C3FC 100%)' },
212
- { name: 'Gradient 4', value: 'linear-gradient(135deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%)' },
213
- { name: 'Solid Dark', value: '#1e293b' },
214
- ];
215
- const currentBg = ref(backgrounds[3]);
216
-
217
- const themeBgColor = ref('#2d2d2d'); // Default for Tomorrow
218
-
219
- const changeTheme = () => {
220
- const link = document.getElementById('prism-theme');
221
- const baseUrl = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism';
222
- if (theme.value === 'default') {
223
- link.href = `${baseUrl}.min.css`;
224
- themeBgColor.value = '#f5f2f0';
225
- } else {
226
- link.href = `${baseUrl}-${theme.value}.min.css`;
227
- // Update bg color approximation
228
- if(theme.value === 'tomorrow') themeBgColor.value = '#2d2d2d';
229
- if(theme.value === 'okaidia') themeBgColor.value = '#272822';
230
- if(theme.value === 'solarizedlight') themeBgColor.value = '#fdf6e3';
231
- if(theme.value === 'twilight') themeBgColor.value = '#141414';
232
- }
233
- };
234
-
235
- const refreshHighlight = () => {
236
- nextTick(() => {
237
- if (window.Prism) {
238
- Prism.highlightAll();
239
- }
240
- });
241
- };
242
-
243
- watch([code, language, showLineNumbers], () => {
244
- refreshHighlight();
245
- });
246
-
247
- onMounted(() => {
248
- refreshHighlight();
249
- });
250
-
251
- const exportImage = async () => {
252
- if (!snapshotCard.value) return;
253
-
254
- try {
255
- const canvas = await html2canvas(snapshotCard.value, {
256
- scale: 2, // Retina support
257
- useCORS: true,
258
- backgroundColor: null,
259
- logging: false
260
- });
261
-
262
- const link = document.createElement('a');
263
- link.download = `code-snapshot-${Date.now()}.png`;
264
- link.href = canvas.toDataURL('image/png');
265
- link.click();
266
- } catch (err) {
267
- console.error('Export failed:', err);
268
- alert('导出失败,请重试');
269
- }
270
- };
271
-
272
- return {
273
- code,
274
- language,
275
- theme,
276
- padding,
277
- showWindowControls,
278
- showLineNumbers,
279
- showShadow,
280
- windowTitle,
281
- backgrounds,
282
- currentBg,
283
- snapshotCard,
284
- codeBlock,
285
- exportImage,
286
- changeTheme,
287
- refreshHighlight,
288
- themeBgColor
289
- };
290
- }
291
- }).mount('#app');
292
- </script>
293
  </body>
294
- </html>
 
13
  <link id="prism-theme" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
14
  <!-- Icons -->
15
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
16
+ <!-- Custom Styles -->
17
+ <link rel="stylesheet" href="/static/style.css">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  </head>
19
  <body class="h-screen flex flex-col overflow-hidden">
20
  <div id="app" class="flex-1 flex flex-col h-full">
21
  <!-- Header -->
22
+ <header class="bg-white border-b border-gray-200 px-4 lg:px-6 py-3 flex items-center justify-between shadow-sm z-30">
23
  <div class="flex items-center gap-3">
24
+ <button @click="toggleSidebar" class="lg:hidden text-gray-600 hover:text-indigo-600 transition-colors">
25
+ <i class="fa-solid fa-bars text-xl"></i>
26
+ </button>
27
  <div class="w-8 h-8 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-lg flex items-center justify-center text-white font-bold">
28
  <i class="fa-solid fa-camera"></i>
29
  </div>
30
+ <h1 class="text-lg lg:text-xl font-bold text-gray-800 tracking-tight truncate">Code Snapshot Studio</h1>
31
  </div>
32
  <div class="flex items-center gap-4">
33
+ <button @click="exportImage" :disabled="isExporting"
34
+ class="bg-indigo-600 hover:bg-indigo-700 disabled:bg-indigo-400 text-white px-4 py-2 rounded-lg font-medium transition-colors flex items-center gap-2 shadow-sm whitespace-nowrap">
35
+ <i v-if="!isExporting" class="fa-solid fa-download"></i>
36
+ <i v-else class="fa-solid fa-spinner fa-spin"></i>
37
+ <span>{{ isExporting ? '导出中...' : '导出 PNG' }}</span>
38
  </button>
39
  </div>
40
  </header>
41
 
42
  <!-- Main Content -->
43
+ <main class="flex-1 flex overflow-hidden relative">
44
+ <!-- Mobile Backdrop -->
45
+ <div v-if="showSidebar" @click="showSidebar = false" class="fixed inset-0 bg-black/20 z-20 lg:hidden backdrop-blur-sm transition-opacity"></div>
46
+
47
  <!-- Sidebar Controls -->
48
+ <aside :class="['w-80 bg-white border-r border-gray-200 overflow-y-auto p-6 flex flex-col gap-6 shadow-[4px_0_24px_rgba(0,0,0,0.02)] z-30 absolute inset-y-0 left-0 lg:static sidebar-transition transform', showSidebar ? 'translate-x-0' : '-translate-x-full lg:translate-x-0']">
49
 
50
  <!-- Code Input -->
51
  <div class="flex flex-col gap-2">
 
56
  <!-- Language & Theme -->
57
  <div class="grid grid-cols-2 gap-4">
58
  <div class="flex flex-col gap-2">
59
+ <label class="text-sm font-semibold text-gray-700 flex justify-between items-center">
60
+ 语言
61
+ <button @click="loadExample" class="text-xs text-indigo-600 hover:text-indigo-800 font-normal hover:underline" title="加载示例代码">
62
+ 示例
63
+ </button>
64
+ </label>
65
+ <select v-model="language" @change="onLanguageChange" class="w-full p-2 border border-gray-300 rounded-lg text-sm bg-white focus:ring-2 focus:ring-indigo-500 outline-none">
66
  <option value="javascript">JavaScript</option>
67
+ <option value="typescript">TypeScript</option>
68
  <option value="python">Python</option>
 
 
69
  <option value="java">Java</option>
70
  <option value="go">Go</option>
71
  <option value="rust">Rust</option>
72
+ <option value="html">HTML</option>
73
+ <option value="css">CSS</option>
74
  <option value="sql">SQL</option>
75
  <option value="bash">Bash</option>
76
  <option value="json">JSON</option>
 
77
  </select>
78
  </div>
79
  <div class="flex flex-col gap-2">
 
95
  <button v-for="(bg, index) in backgrounds" :key="index"
96
  @click="currentBg = bg"
97
  :class="['w-8 h-8 rounded-full border-2 transition-all', currentBg.value === bg.value ? 'border-indigo-600 scale-110 shadow-md' : 'border-transparent hover:scale-105']"
98
+ :style="{ background: bg.value }"
99
+ :title="bg.name">
100
  </button>
101
  </div>
102
  </div>
 
130
  </aside>
131
 
132
  <!-- Preview Area -->
133
+ <section class="flex-1 bg-gray-100 flex items-center justify-center p-4 lg:p-8 overflow-auto relative w-full">
134
  <!-- Grid Background Pattern -->
135
  <div class="absolute inset-0 opacity-[0.03]"
136
  style="background-image: radial-gradient(#000 1px, transparent 1px); background-size: 20px 20px;">
137
  </div>
138
 
139
  <!-- The Snapshot Card -->
140
+ <div ref="snapshotCard" class="preview-container relative flex items-center justify-center min-w-[300px] lg:min-w-[400px]"
141
  :style="{ background: currentBg.value, padding: padding + 'px' }">
142
 
143
  <!-- Window -->
144
+ <div class="bg-[#1e1e1e] rounded-xl overflow-hidden min-w-[300px] max-w-[90vw] lg:max-w-4xl w-auto"
145
  :class="{'shadow-2xl': showShadow, 'shadow-none': !showShadow}"
146
  :style="{ backgroundColor: themeBgColor }">
147
 
 
154
  <div class="w-3 h-3 rounded-full bg-[#27c93f] border border-[#1aab29]"></div>
155
  </div>
156
  <!-- Title -->
157
+ <div class="w-full text-center text-xs font-medium text-gray-400 font-sans select-none truncate px-12">
158
  {{ windowTitle }}
159
  </div>
160
  </div>
161
 
162
  <!-- Code Content -->
163
+ <div class="p-0 overflow-hidden overflow-x-auto">
164
  <pre class="!m-0 !p-6 !bg-transparent text-sm leading-relaxed outline-none code-font"
165
  :class="{'line-numbers': showLineNumbers}"><code :class="'language-' + language" ref="codeBlock">{{ code }}</code></pre>
166
  </div>
 
170
  </main>
171
  </div>
172
 
173
+ <!-- App Logic -->
174
+ <script src="/static/script.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  </body>
176
+ </html>