patchwarden 0.6.1 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PatchWarden-Control-Tray.cmd +11 -0
- package/PatchWarden-Control.cmd +6 -0
- package/PatchWarden-Desktop.cmd +5 -0
- package/PatchWarden.cmd +1 -1
- package/README.en.md +106 -18
- package/README.md +30 -19
- package/Restart-PatchWarden-Control.cmd +6 -0
- package/Stop-PatchWarden.cmd +11 -0
- package/dist/controlCenter.d.ts +14 -0
- package/dist/controlCenter.js +2002 -0
- package/dist/doctor.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/control-center/README.md +33 -0
- package/docs/control-center/control-center-daily-driver.md +211 -0
- package/docs/control-center/control-center-mvp.md +205 -0
- package/docs/control-center/control-center-phase2.md +159 -0
- package/docs/demo.md +3 -0
- package/docs/release-v0.6.4.md +45 -0
- package/examples/openai-tunnel/README.md +5 -5
- package/examples/openai-tunnel/tunnel-client.example.yaml +3 -3
- package/package.json +23 -15
- package/scripts/README.md +47 -0
- package/scripts/{brand-check.js → checks/brand-check.js} +2 -2
- package/scripts/checks/control-center-smoke.js +1098 -0
- package/scripts/{control-smoke.js → checks/control-smoke.js} +17 -3
- package/scripts/{doctor-smoke.js → checks/doctor-smoke.js} +1 -1
- package/scripts/{http-mcp-smoke.js → checks/http-mcp-smoke.js} +2 -2
- package/scripts/{lifecycle-smoke.js → checks/lifecycle-smoke.js} +12 -12
- package/scripts/{mcp-manifest-check.js → checks/mcp-manifest-check.js} +2 -2
- package/scripts/{mcp-smoke.js → checks/mcp-smoke.js} +4 -2
- package/scripts/{package-manifest-check.js → checks/package-manifest-check.js} +2 -1
- package/scripts/{tunnel-supervisor-smoke.js → checks/tunnel-supervisor-smoke.js} +2 -2
- package/scripts/{unit-tests.js → checks/unit-tests.js} +1 -1
- package/scripts/{watcher-supervisor-smoke.js → checks/watcher-supervisor-smoke.js} +11 -5
- package/scripts/control/control-center-tray.ps1 +281 -0
- package/scripts/{get-patchwarden-health.ps1 → control/get-patchwarden-health.ps1} +3 -3
- package/scripts/{manage-patchwarden.ps1 → control/manage-patchwarden.ps1} +30 -4
- package/scripts/control/restart-control-center.ps1 +173 -0
- package/scripts/{restart-patchwarden.ps1 → control/restart-patchwarden.ps1} +1 -1
- package/scripts/control/start-control-center.ps1 +263 -0
- package/scripts/{start-patchwarden-tunnel.ps1 → control/start-patchwarden-tunnel.ps1} +48 -6
- package/scripts/control/stop-patchwarden.ps1 +114 -0
- package/scripts/launchers/Check-PatchWarden-Health.cmd +1 -1
- package/scripts/launchers/Reset-PatchWarden-Tunnel-Key.cmd +1 -1
- package/scripts/launchers/Restart-PatchWarden.cmd +1 -1
- package/scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd +1 -1
- package/scripts/launchers/Start-PatchWarden-Tunnel.cmd +1 -1
- package/scripts/{patchwarden-mcp-direct.cmd → mcp/patchwarden-mcp-direct.cmd} +1 -1
- package/scripts/{patchwarden-mcp-stdio.cmd → mcp/patchwarden-mcp-stdio.cmd} +1 -1
- package/scripts/{pack-clean.js → release/pack-clean.js} +9 -1
- package/src/controlCenter.ts +2152 -0
- package/src/doctor.ts +4 -4
- package/src/version.ts +1 -1
- package/ui/colors_and_type.css +141 -0
- package/ui/pages/audit.html +743 -0
- package/ui/pages/dashboard.html +1154 -0
- package/ui/pages/direct-sessions.html +652 -0
- package/ui/pages/logs.html +502 -0
- package/ui/pages/task-detail.html +1229 -0
- package/ui/pages/tasks.html +702 -0
- package/ui/pages/workspace.html +947 -0
- package/ui/partials/project-shell.html +362 -0
- package/ui/vendor/lucide.js +12 -0
- package/ui/vendor/tailwindcss-browser.js +947 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>PatchWarden</title>
|
|
7
|
+
|
|
8
|
+
<!-- Brand CSS Variables (inline — srcdoc-safe) -->
|
|
9
|
+
<style id="theme-vars">
|
|
10
|
+
:root {
|
|
11
|
+
--pw-bg: #0a0e14;
|
|
12
|
+
--pw-bg-elevated: #111820;
|
|
13
|
+
--pw-bg-surface: #161d27;
|
|
14
|
+
--pw-bg-hover: #1c2533;
|
|
15
|
+
--pw-bg-active: #222d3d;
|
|
16
|
+
--pw-border: #1e2a3a;
|
|
17
|
+
--pw-border-subtle: #172030;
|
|
18
|
+
--pw-border-focus: #2dd4a8;
|
|
19
|
+
--pw-text-primary: #e8edf4;
|
|
20
|
+
--pw-text-secondary: #8b99ad;
|
|
21
|
+
--pw-text-tertiary: #5a6a80;
|
|
22
|
+
--pw-text-inverse: #0a0e14;
|
|
23
|
+
--pw-text-accent: #2dd4a8;
|
|
24
|
+
--pw-accent: #2dd4a8;
|
|
25
|
+
--pw-accent-hover: #34eabc;
|
|
26
|
+
--pw-accent-muted: rgba(45, 212, 168, 0.12);
|
|
27
|
+
--pw-accent-subtle: rgba(45, 212, 168, 0.06);
|
|
28
|
+
--pw-state-success: #22c55e;
|
|
29
|
+
--pw-state-success-bg: rgba(34, 197, 94, 0.1);
|
|
30
|
+
--pw-state-warning: #f59e0b;
|
|
31
|
+
--pw-state-warning-bg: rgba(245, 158, 11, 0.1);
|
|
32
|
+
--pw-state-error: #ef4444;
|
|
33
|
+
--pw-state-error-bg: rgba(239, 68, 68, 0.1);
|
|
34
|
+
--pw-state-info: #3b82f6;
|
|
35
|
+
--pw-state-info-bg: rgba(59, 130, 246, 0.1);
|
|
36
|
+
--pw-radius-sm: 4px;
|
|
37
|
+
--pw-radius-md: 8px;
|
|
38
|
+
--pw-radius-lg: 12px;
|
|
39
|
+
--pw-radius-full: 9999px;
|
|
40
|
+
--pw-shadow-float: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
41
|
+
--pw-shadow-modal: 0 16px 48px rgba(0, 0, 0, 0.5);
|
|
42
|
+
--pw-font-sans: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
43
|
+
--pw-font-mono: 'JetBrains Mono', 'Cascadia Code', 'Consolas', monospace;
|
|
44
|
+
--pw-text-xs: 0.75rem;
|
|
45
|
+
--pw-text-sm: 0.8125rem;
|
|
46
|
+
--pw-text-base: 0.875rem;
|
|
47
|
+
--pw-text-lg: 1rem;
|
|
48
|
+
--pw-text-xl: 1.25rem;
|
|
49
|
+
--pw-text-2xl: 1.5rem;
|
|
50
|
+
--pw-text-3xl: 2rem;
|
|
51
|
+
--pw-leading-tight: 1.25;
|
|
52
|
+
--pw-leading-normal: 1.5;
|
|
53
|
+
--pw-leading-relaxed: 1.625;
|
|
54
|
+
--pw-weight-normal: 400;
|
|
55
|
+
--pw-weight-medium: 500;
|
|
56
|
+
--pw-weight-semibold: 600;
|
|
57
|
+
--pw-weight-bold: 700;
|
|
58
|
+
--pw-space-1: 4px;
|
|
59
|
+
--pw-space-2: 8px;
|
|
60
|
+
--pw-space-3: 12px;
|
|
61
|
+
--pw-space-4: 16px;
|
|
62
|
+
--pw-space-5: 20px;
|
|
63
|
+
--pw-space-6: 24px;
|
|
64
|
+
--pw-space-8: 32px;
|
|
65
|
+
--pw-space-10: 40px;
|
|
66
|
+
--pw-space-12: 48px;
|
|
67
|
+
--pw-transition-fast: 150ms ease;
|
|
68
|
+
--pw-transition-base: 200ms ease;
|
|
69
|
+
--pw-transition-slow: 300ms ease;
|
|
70
|
+
}
|
|
71
|
+
html.dark {
|
|
72
|
+
--pw-bg: #0a0e14;
|
|
73
|
+
--pw-bg-elevated: #111820;
|
|
74
|
+
--pw-bg-surface: #161d27;
|
|
75
|
+
--pw-bg-hover: #1c2533;
|
|
76
|
+
--pw-bg-active: #222d3d;
|
|
77
|
+
--pw-border: #1e2a3a;
|
|
78
|
+
--pw-border-subtle: #172030;
|
|
79
|
+
--pw-text-primary: #e8edf4;
|
|
80
|
+
--pw-text-secondary: #8b99ad;
|
|
81
|
+
--pw-text-tertiary: #5a6a80;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
84
|
+
|
|
85
|
+
<!-- Tailwind v4 CDN -->
|
|
86
|
+
<script src="/vendor/tailwindcss-browser.js"></script>
|
|
87
|
+
|
|
88
|
+
<!-- @theme inline bridge -->
|
|
89
|
+
<style type="text/tailwindcss">
|
|
90
|
+
@theme inline {
|
|
91
|
+
--color-primary: var(--pw-accent);
|
|
92
|
+
--color-foreground: var(--pw-text-primary);
|
|
93
|
+
--color-muted: var(--pw-text-secondary);
|
|
94
|
+
--color-card: var(--pw-bg-surface);
|
|
95
|
+
--color-border: var(--pw-border);
|
|
96
|
+
}
|
|
97
|
+
</style>
|
|
98
|
+
|
|
99
|
+
<!-- Lucide Icons CDN -->
|
|
100
|
+
<script src="/vendor/lucide.js"></script>
|
|
101
|
+
|
|
102
|
+
<!-- Base layer -->
|
|
103
|
+
<style>
|
|
104
|
+
@layer base {
|
|
105
|
+
body {
|
|
106
|
+
margin: 0;
|
|
107
|
+
padding: 0;
|
|
108
|
+
background-color: var(--pw-bg);
|
|
109
|
+
color: var(--pw-text-primary);
|
|
110
|
+
font-family: var(--pw-font-sans);
|
|
111
|
+
font-size: var(--pw-text-base);
|
|
112
|
+
line-height: var(--pw-leading-normal);
|
|
113
|
+
-webkit-font-smoothing: antialiased;
|
|
114
|
+
-moz-osx-font-smoothing: grayscale;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
118
|
+
</head>
|
|
119
|
+
|
|
120
|
+
<body class="flex h-screen overflow-hidden">
|
|
121
|
+
|
|
122
|
+
<!-- ========== SIDEBAR ========== -->
|
|
123
|
+
<aside class="flex flex-col w-60 shrink-0 h-screen" style="background-color: var(--pw-bg); border-right: 1px solid var(--pw-border);">
|
|
124
|
+
|
|
125
|
+
<!-- Logo Area -->
|
|
126
|
+
<div class="flex items-center gap-3 px-5 py-5" style="border-bottom: 1px solid var(--pw-border-subtle);">
|
|
127
|
+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="var(--pw-accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
128
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
|
129
|
+
<path d="M9 12l2 2 4-4"/>
|
|
130
|
+
</svg>
|
|
131
|
+
<span class="truncate text-lg font-semibold tracking-tight" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">PatchWarden</span>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<!-- Navigation -->
|
|
135
|
+
<nav class="flex-1 flex flex-col gap-1 px-3 pt-4 overflow-y-auto">
|
|
136
|
+
|
|
137
|
+
<!-- SLOT: activeNavItem — set data-active="true" on the matching nav item -->
|
|
138
|
+
|
|
139
|
+
<a href="/pages/dashboard.html" data-nav-key="dashboard" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
140
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
|
|
141
|
+
<rect x="3" y="3" width="7" height="7" rx="1"/>
|
|
142
|
+
<rect x="14" y="3" width="7" height="7" rx="1"/>
|
|
143
|
+
<rect x="3" y="14" width="7" height="7" rx="1"/>
|
|
144
|
+
<rect x="14" y="14" width="7" height="7" rx="1"/>
|
|
145
|
+
</svg>
|
|
146
|
+
<span class="truncate">控制台</span>
|
|
147
|
+
</a>
|
|
148
|
+
|
|
149
|
+
<a href="/pages/tasks.html" data-nav-key="tasks" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
150
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
|
|
151
|
+
<path d="M9 11l3 3L22 4"/>
|
|
152
|
+
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
|
153
|
+
</svg>
|
|
154
|
+
<span class="truncate">任务面板</span>
|
|
155
|
+
</a>
|
|
156
|
+
|
|
157
|
+
<a href="/pages/workspace.html" data-nav-key="workspace" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
158
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
|
|
159
|
+
<path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2z"/>
|
|
160
|
+
</svg>
|
|
161
|
+
<span class="truncate">工作区</span>
|
|
162
|
+
</a>
|
|
163
|
+
|
|
164
|
+
<a href="/pages/audit.html" data-nav-key="audit" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
165
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
|
|
166
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
|
167
|
+
<polyline points="14 2 14 8 20 8"/>
|
|
168
|
+
<line x1="16" y1="13" x2="8" y2="13"/>
|
|
169
|
+
<line x1="16" y1="17" x2="8" y2="17"/>
|
|
170
|
+
<polyline points="10 9 9 9 8 9"/>
|
|
171
|
+
</svg>
|
|
172
|
+
<span class="truncate">审计日志</span>
|
|
173
|
+
</a>
|
|
174
|
+
|
|
175
|
+
</nav>
|
|
176
|
+
|
|
177
|
+
<!-- Bottom Section -->
|
|
178
|
+
<div class="px-5 py-4" style="border-top: 1px solid var(--pw-border-subtle);">
|
|
179
|
+
<!-- Version Badge -->
|
|
180
|
+
<div class="flex items-center gap-2 mb-3">
|
|
181
|
+
<span class="text-xs font-medium px-2 py-0.5 rounded-md whitespace-nowrap" style="color: var(--pw-text-tertiary); background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-mono);">Core v0.6.0</span>
|
|
182
|
+
</div>
|
|
183
|
+
<!-- Connection Status -->
|
|
184
|
+
<div class="flex items-center gap-2">
|
|
185
|
+
<span class="shrink-0 w-2 h-2 rounded-full" style="background-color: var(--pw-state-success);"></span>
|
|
186
|
+
<span class="text-xs truncate" style="color: var(--pw-text-tertiary);">Connected</span>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
</aside>
|
|
191
|
+
|
|
192
|
+
<!-- ========== MAIN AREA ========== -->
|
|
193
|
+
<div class="flex flex-col flex-1 min-w-0 h-screen overflow-hidden">
|
|
194
|
+
|
|
195
|
+
<!-- Top Bar -->
|
|
196
|
+
<header class="flex items-center justify-between shrink-0 px-6 py-3" style="border-bottom: 1px solid var(--pw-border); background-color: var(--pw-bg);">
|
|
197
|
+
<!-- Page Title Slot -->
|
|
198
|
+
<h1 class="text-lg font-semibold truncate min-w-0" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans); text-wrap: balance; word-break: keep-all; overflow-wrap: break-word;">
|
|
199
|
+
<!-- SLOT: pageTitle -->
|
|
200
|
+
控制台
|
|
201
|
+
</h1>
|
|
202
|
+
|
|
203
|
+
<!-- Right Actions -->
|
|
204
|
+
<div class="flex items-center gap-4 shrink-0">
|
|
205
|
+
<!-- Search Input -->
|
|
206
|
+
<div class="relative">
|
|
207
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none">
|
|
208
|
+
<circle cx="11" cy="11" r="8"/>
|
|
209
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
|
210
|
+
</svg>
|
|
211
|
+
<input
|
|
212
|
+
type="text"
|
|
213
|
+
placeholder="搜索任务、文件..."
|
|
214
|
+
class="pl-9 pr-4 py-2 rounded-lg text-sm outline-none w-64 transition-colors"
|
|
215
|
+
style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans);"
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<!-- Theme Toggle -->
|
|
220
|
+
<button id="theme-toggle" class="pw-toggle-btn" title="切换主题" onclick="toggleTheme()">
|
|
221
|
+
<!-- Sun icon (shown in dark mode — click to switch to light) -->
|
|
222
|
+
<svg class="theme-icon-sun" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
223
|
+
<circle cx="12" cy="12" r="5"/>
|
|
224
|
+
<line x1="12" y1="1" x2="12" y2="3"/>
|
|
225
|
+
<line x1="12" y1="21" x2="12" y2="23"/>
|
|
226
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
|
|
227
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
|
228
|
+
<line x1="1" y1="12" x2="3" y2="12"/>
|
|
229
|
+
<line x1="21" y1="12" x2="23" y2="12"/>
|
|
230
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
|
|
231
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
|
232
|
+
</svg>
|
|
233
|
+
<!-- Moon icon (shown in light mode — click to switch to dark) -->
|
|
234
|
+
<svg class="theme-icon-moon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none;">
|
|
235
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
|
236
|
+
</svg>
|
|
237
|
+
</button>
|
|
238
|
+
|
|
239
|
+
<!-- Language Toggle -->
|
|
240
|
+
<button id="lang-toggle" class="pw-toggle-btn pw-lang-btn" title="切换语言" onclick="toggleLang()">
|
|
241
|
+
EN
|
|
242
|
+
</button>
|
|
243
|
+
|
|
244
|
+
<!-- User Avatar -->
|
|
245
|
+
<div class="shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-xs font-semibold" style="background-color: var(--pw-accent-muted); color: var(--pw-accent); font-family: var(--pw-font-mono);">
|
|
246
|
+
PW
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</header>
|
|
250
|
+
|
|
251
|
+
<!-- Page Content Area -->
|
|
252
|
+
<main class="flex-1 overflow-y-auto p-6" style="background-color: var(--pw-bg-elevated);">
|
|
253
|
+
<!-- SLOT: pageContent -->
|
|
254
|
+
</main>
|
|
255
|
+
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<!-- Active nav item styling + sidebar hover states -->
|
|
259
|
+
<style>
|
|
260
|
+
/* Nav item base hover */
|
|
261
|
+
[data-nav-key] {
|
|
262
|
+
transition: background-color var(--pw-transition-fast), color var(--pw-transition-fast);
|
|
263
|
+
}
|
|
264
|
+
[data-nav-key]:hover {
|
|
265
|
+
background-color: var(--pw-bg-hover);
|
|
266
|
+
color: var(--pw-text-primary);
|
|
267
|
+
}
|
|
268
|
+
/* Active nav item */
|
|
269
|
+
[data-nav-key][data-active="true"] {
|
|
270
|
+
background-color: var(--pw-accent-subtle);
|
|
271
|
+
color: var(--pw-accent);
|
|
272
|
+
border-left: 3px solid var(--pw-accent);
|
|
273
|
+
padding-left: 9px;
|
|
274
|
+
}
|
|
275
|
+
/* Search input focus */
|
|
276
|
+
input[type="text"]:focus {
|
|
277
|
+
border-color: var(--pw-border-focus);
|
|
278
|
+
background-color: var(--pw-bg-hover);
|
|
279
|
+
}
|
|
280
|
+
input[type="text"]::placeholder {
|
|
281
|
+
color: var(--pw-text-tertiary);
|
|
282
|
+
}
|
|
283
|
+
/* Toggle button base */
|
|
284
|
+
.pw-toggle-btn {
|
|
285
|
+
display: inline-flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
justify-content: center;
|
|
288
|
+
width: 32px;
|
|
289
|
+
height: 32px;
|
|
290
|
+
border-radius: var(--pw-radius-md);
|
|
291
|
+
border: 1px solid var(--pw-border);
|
|
292
|
+
background: transparent;
|
|
293
|
+
color: var(--pw-text-secondary);
|
|
294
|
+
cursor: pointer;
|
|
295
|
+
transition: all var(--pw-transition-fast);
|
|
296
|
+
}
|
|
297
|
+
.pw-toggle-btn:hover {
|
|
298
|
+
background-color: var(--pw-bg-hover);
|
|
299
|
+
color: var(--pw-text-primary);
|
|
300
|
+
border-color: var(--pw-text-tertiary);
|
|
301
|
+
}
|
|
302
|
+
/* Language toggle variant */
|
|
303
|
+
.pw-lang-btn {
|
|
304
|
+
width: auto;
|
|
305
|
+
padding: 0 10px;
|
|
306
|
+
font-size: var(--pw-text-xs);
|
|
307
|
+
font-weight: var(--pw-weight-medium);
|
|
308
|
+
letter-spacing: 0.5px;
|
|
309
|
+
}
|
|
310
|
+
</style>
|
|
311
|
+
|
|
312
|
+
<!-- Lucide init -->
|
|
313
|
+
<script>lucide.createIcons();</script>
|
|
314
|
+
|
|
315
|
+
<!-- Shared JS: Theme & Language toggles -->
|
|
316
|
+
<script>
|
|
317
|
+
(function () {
|
|
318
|
+
/* ---- Theme Toggle ---- */
|
|
319
|
+
function toggleTheme() {
|
|
320
|
+
var html = document.documentElement;
|
|
321
|
+
var isDark = html.classList.contains('dark');
|
|
322
|
+
html.classList.toggle('dark');
|
|
323
|
+
html.classList.toggle('light');
|
|
324
|
+
updateThemeIcon(!isDark);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function updateThemeIcon(isDark) {
|
|
328
|
+
var sunIcon = document.querySelector('.theme-icon-sun');
|
|
329
|
+
var moonIcon = document.querySelector('.theme-icon-moon');
|
|
330
|
+
if (sunIcon && moonIcon) {
|
|
331
|
+
sunIcon.style.display = isDark ? '' : 'none';
|
|
332
|
+
moonIcon.style.display = isDark ? 'none' : '';
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/* ---- Language Toggle ---- */
|
|
337
|
+
function toggleLang() {
|
|
338
|
+
var btn = document.getElementById('lang-toggle');
|
|
339
|
+
var isZh = btn.textContent.trim() === 'EN';
|
|
340
|
+
btn.textContent = isZh ? '\u4e2d\u6587' : 'EN';
|
|
341
|
+
document.querySelectorAll('[data-zh]').forEach(function (el) {
|
|
342
|
+
el.textContent = isZh ? el.getAttribute('data-en') : el.getAttribute('data-zh');
|
|
343
|
+
});
|
|
344
|
+
document.querySelectorAll('[data-zh-placeholder]').forEach(function (el) {
|
|
345
|
+
el.placeholder = isZh ? el.getAttribute('data-en-placeholder') : el.getAttribute('data-zh-placeholder');
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* ---- Init ---- */
|
|
350
|
+
(function init() {
|
|
351
|
+
var html = document.documentElement;
|
|
352
|
+
var isDark = html.classList.contains('dark');
|
|
353
|
+
updateThemeIcon(isDark);
|
|
354
|
+
})();
|
|
355
|
+
|
|
356
|
+
/* Expose to inline onclick handlers */
|
|
357
|
+
window.toggleTheme = toggleTheme;
|
|
358
|
+
window.toggleLang = toggleLang;
|
|
359
|
+
})();
|
|
360
|
+
</script>
|
|
361
|
+
</body>
|
|
362
|
+
</html>
|