omni-notify-mcp 1.0.0
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/README.md +109 -0
- package/config.example.json +26 -0
- package/dist/channels/desktop.js +13 -0
- package/dist/channels/email.js +29 -0
- package/dist/channels/sms.js +7 -0
- package/dist/channels/telegram.js +11 -0
- package/dist/channels/whatsapp.js +11 -0
- package/dist/config.js +16 -0
- package/dist/index.js +146 -0
- package/dist/ui/server.js +708 -0
- package/package.json +62 -0
- package/ui/public/app.js +467 -0
- package/ui/public/index.html +192 -0
- package/ui/public/style.css +437 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Claude Notify</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="toast" class="toast hidden"></div>
|
|
11
|
+
|
|
12
|
+
<header>
|
|
13
|
+
<div class="logo">
|
|
14
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
|
15
|
+
Claude Notify
|
|
16
|
+
</div>
|
|
17
|
+
<span class="subtitle">Configure notification channels</span>
|
|
18
|
+
</header>
|
|
19
|
+
|
|
20
|
+
<main>
|
|
21
|
+
<div class="card-list" id="card-list">
|
|
22
|
+
|
|
23
|
+
<!-- ── Desktop ───────────────────────────────────────────────── -->
|
|
24
|
+
<div class="card" id="card-desktop">
|
|
25
|
+
<div class="card-hd">
|
|
26
|
+
<div class="ch-meta">
|
|
27
|
+
<div class="channel-icon desktop-icon">
|
|
28
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
|
|
29
|
+
</div>
|
|
30
|
+
<span class="ch-name">Desktop</span>
|
|
31
|
+
</div>
|
|
32
|
+
<span class="badge badge-idle" id="badge-desktop">–</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="card-body">
|
|
35
|
+
<div class="actions">
|
|
36
|
+
<label class="toggle-wrap">
|
|
37
|
+
<input type="checkbox" id="desktop-enabled" onchange="saveDesktop()">
|
|
38
|
+
<span class="toggle"></span>
|
|
39
|
+
</label>
|
|
40
|
+
<span class="toggle-lbl">Enable</span>
|
|
41
|
+
<button class="btn btn-sm btn-ghost" onclick="testChannel('desktop')">Test</button>
|
|
42
|
+
</div>
|
|
43
|
+
<span id="os-hint" class="os-tag hidden"></span>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<!-- ── Email ─────────────────────────────────────────────────── -->
|
|
48
|
+
<div class="card" id="card-email">
|
|
49
|
+
<div class="card-hd">
|
|
50
|
+
<div class="ch-meta">
|
|
51
|
+
<div class="channel-icon gmail-icon">
|
|
52
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
|
|
53
|
+
</div>
|
|
54
|
+
<span class="ch-name">Email</span><span class="tag">Gmail</span>
|
|
55
|
+
</div>
|
|
56
|
+
<span class="badge badge-idle" id="badge-email">Not configured</span>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="card-body">
|
|
59
|
+
<!-- Connected -->
|
|
60
|
+
<div id="gmail-connected-state" class="hidden">
|
|
61
|
+
<div class="actions">
|
|
62
|
+
<label class="toggle-wrap">
|
|
63
|
+
<input type="checkbox" id="email-enabled" oninput="markDirty('email')">
|
|
64
|
+
<span class="toggle"></span>
|
|
65
|
+
</label>
|
|
66
|
+
<span class="toggle-lbl">Enable</span>
|
|
67
|
+
<button class="btn btn-sm btn-primary" id="save-email-btn" onclick="saveEmail()">Save</button>
|
|
68
|
+
<button class="btn btn-sm btn-ghost" onclick="testChannel('email')">Test</button>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="ok-banner">
|
|
71
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
|
|
72
|
+
<strong id="gmail-connected-email"></strong>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="fg"><label>Send to</label><input type="email" id="gmail-to-connected" placeholder="you@gmail.com" oninput="markDirty('email')"></div>
|
|
75
|
+
</div>
|
|
76
|
+
<!-- Setup -->
|
|
77
|
+
<div id="gmail-setup-state">
|
|
78
|
+
<div class="actions">
|
|
79
|
+
<button class="btn btn-sm btn-ghost" onclick="openAppPasswords()">Open Google Account</button>
|
|
80
|
+
<button class="btn btn-sm btn-primary" onclick="saveAppPassword()">Connect</button>
|
|
81
|
+
</div>
|
|
82
|
+
<details class="guide" id="gmail-guide">
|
|
83
|
+
<summary>How to get an App Password</summary>
|
|
84
|
+
<ol>
|
|
85
|
+
<li>Click <b>Open Google Account</b> above (needs 2FA enabled)</li>
|
|
86
|
+
<li>Select app: <b>Mail</b>, device: <b>Mac</b> → Generate</li>
|
|
87
|
+
<li>Copy the 16-char password and paste below</li>
|
|
88
|
+
</ol>
|
|
89
|
+
</details>
|
|
90
|
+
<div class="fg"><label>Gmail address</label><input type="email" id="gmail-address" placeholder="you@gmail.com"></div>
|
|
91
|
+
<div class="fg"><label>App Password (16 chars)</label><input type="password" id="gmail-app-password" placeholder="xxxx xxxx xxxx xxxx"></div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<!-- ── Telegram ──────────────────────────────────────────────── -->
|
|
97
|
+
<div class="card" id="card-telegram">
|
|
98
|
+
<div class="card-hd">
|
|
99
|
+
<div class="ch-meta">
|
|
100
|
+
<div class="channel-icon telegram-icon">
|
|
101
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
|
|
102
|
+
</div>
|
|
103
|
+
<span class="ch-name">Telegram</span><span class="tag">Bot API</span>
|
|
104
|
+
</div>
|
|
105
|
+
<span class="badge badge-idle" id="badge-telegram">Not configured</span>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="card-body">
|
|
108
|
+
<div class="actions">
|
|
109
|
+
<label class="toggle-wrap">
|
|
110
|
+
<input type="checkbox" id="telegram-enabled" oninput="markDirty('telegram')">
|
|
111
|
+
<span class="toggle"></span>
|
|
112
|
+
</label>
|
|
113
|
+
<span class="toggle-lbl">Enable</span>
|
|
114
|
+
<button class="btn btn-sm btn-primary" id="save-telegram-btn" onclick="saveTelegram()">Save</button>
|
|
115
|
+
<button class="btn btn-sm btn-ghost" onclick="testChannel('telegram')">Test</button>
|
|
116
|
+
</div>
|
|
117
|
+
<details class="guide">
|
|
118
|
+
<summary>Create a bot (2 min)</summary>
|
|
119
|
+
<ol>
|
|
120
|
+
<li>Open Telegram → search <b>@BotFather</b></li>
|
|
121
|
+
<li>Send <code class="cp" onclick="copyText(this)">/newbot</code> → follow prompts → copy token</li>
|
|
122
|
+
<li>Start a chat with your new bot (send any message)</li>
|
|
123
|
+
<li>Paste token below → click <b>Detect</b></li>
|
|
124
|
+
</ol>
|
|
125
|
+
</details>
|
|
126
|
+
<div class="fg"><label>Bot Token</label><input type="password" id="telegram-token" placeholder="123456:ABC-DEF…" oninput="markDirty('telegram')"></div>
|
|
127
|
+
<div class="fg">
|
|
128
|
+
<label>Chat ID</label>
|
|
129
|
+
<div style="display:flex;gap:6px">
|
|
130
|
+
<input type="text" id="telegram-chatid" placeholder="auto-detected" oninput="markDirty('telegram')" style="flex:1">
|
|
131
|
+
<button class="btn btn-sm btn-ghost" onclick="detectChatId()">Detect</button>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<!-- ── SMS ───────────────────────────────────────────────────── -->
|
|
138
|
+
<div class="card" id="card-sms">
|
|
139
|
+
<div class="card-hd">
|
|
140
|
+
<div class="ch-meta">
|
|
141
|
+
<div class="channel-icon sms-icon">
|
|
142
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
|
|
143
|
+
</div>
|
|
144
|
+
<span class="ch-name">SMS</span><span class="tag">Twilio</span>
|
|
145
|
+
</div>
|
|
146
|
+
<span class="badge badge-idle" id="badge-sms">Not configured</span>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="card-body">
|
|
149
|
+
<div class="actions">
|
|
150
|
+
<label class="toggle-wrap">
|
|
151
|
+
<input type="checkbox" id="sms-enabled" oninput="markDirty('sms')">
|
|
152
|
+
<span class="toggle"></span>
|
|
153
|
+
</label>
|
|
154
|
+
<span class="toggle-lbl">Enable</span>
|
|
155
|
+
<button class="btn btn-sm btn-primary" id="save-sms-btn" onclick="saveSms()">Save</button>
|
|
156
|
+
<button class="btn btn-sm btn-ghost" onclick="testChannel('sms')">Test</button>
|
|
157
|
+
</div>
|
|
158
|
+
<details class="guide">
|
|
159
|
+
<summary>Get Twilio credentials</summary>
|
|
160
|
+
<ol>
|
|
161
|
+
<li>Sign up at <a href="https://twilio.com" target="_blank">twilio.com</a> (free ~$15 trial)</li>
|
|
162
|
+
<li>Copy <b>Account SID</b> + <b>Auth Token</b> from dashboard</li>
|
|
163
|
+
<li>Buy a number under Phone Numbers</li>
|
|
164
|
+
</ol>
|
|
165
|
+
</details>
|
|
166
|
+
<div class="row2">
|
|
167
|
+
<div class="fg"><label>Account SID</label><input type="text" id="sms-sid" placeholder="ACxxxxxxxxxxxxxxxx" oninput="markDirty('sms')"></div>
|
|
168
|
+
<div class="fg"><label>Auth Token</label><input type="password" id="sms-token" placeholder="••••••••••••••••" oninput="markDirty('sms')"></div>
|
|
169
|
+
<div class="fg"><label>From (Twilio)</label><input type="tel" id="sms-from" placeholder="+1 555 000 0000" oninput="markDirty('sms')"></div>
|
|
170
|
+
<div class="fg"><label>To (your number)</label><input type="tel" id="sms-to" placeholder="+1 234 567 8900" oninput="markDirty('sms')"></div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div class="log-section">
|
|
178
|
+
<div class="log-header">
|
|
179
|
+
<span class="log-title">
|
|
180
|
+
<span class="dot dot-ok" id="log-dot" style="display:inline-block"></span>
|
|
181
|
+
Activity Log
|
|
182
|
+
</span>
|
|
183
|
+
<button class="btn btn-sm btn-ghost" onclick="clearLog()">Clear</button>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="log-panel" id="log-panel"></div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
</main>
|
|
189
|
+
|
|
190
|
+
<script src="app.js"></script>
|
|
191
|
+
</body>
|
|
192
|
+
</html>
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--bg: #0a0a0b;
|
|
5
|
+
--surface: #111113;
|
|
6
|
+
--border: #222226;
|
|
7
|
+
--text: #f0f0f0;
|
|
8
|
+
--text-2: #a0a0b0;
|
|
9
|
+
--text-3: #555565;
|
|
10
|
+
--accent: #7c6dfa;
|
|
11
|
+
--accent-h:#9080ff;
|
|
12
|
+
--success: #10b981;
|
|
13
|
+
--danger: #ef4444;
|
|
14
|
+
--warn: #f59e0b;
|
|
15
|
+
--r: 9px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
html, body { height: 100%; }
|
|
19
|
+
|
|
20
|
+
body {
|
|
21
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
22
|
+
background: var(--bg);
|
|
23
|
+
color: var(--text);
|
|
24
|
+
font-size: 13px;
|
|
25
|
+
line-height: 1.5;
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* ── Header ──────────────────────────────────────────────────────────────── */
|
|
31
|
+
|
|
32
|
+
header {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: 14px;
|
|
36
|
+
padding: 0 20px;
|
|
37
|
+
height: 48px;
|
|
38
|
+
flex-shrink: 0;
|
|
39
|
+
background: var(--surface);
|
|
40
|
+
border-bottom: 1px solid var(--border);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.logo {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: 7px;
|
|
47
|
+
font-size: 14px;
|
|
48
|
+
font-weight: 700;
|
|
49
|
+
color: var(--accent);
|
|
50
|
+
white-space: nowrap;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.subtitle {
|
|
54
|
+
color: var(--text-3);
|
|
55
|
+
font-size: 12px;
|
|
56
|
+
white-space: nowrap;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.os-tag {
|
|
60
|
+
font-size: 11px;
|
|
61
|
+
color: var(--warn);
|
|
62
|
+
background: #1c1500;
|
|
63
|
+
border: 1px solid #3a2e00;
|
|
64
|
+
border-radius: 5px;
|
|
65
|
+
padding: 1px 7px;
|
|
66
|
+
white-space: nowrap;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ── Main ────────────────────────────────────────────────────────────────── */
|
|
70
|
+
|
|
71
|
+
main {
|
|
72
|
+
flex: 1;
|
|
73
|
+
padding: 14px 18px;
|
|
74
|
+
min-height: 0;
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: row;
|
|
78
|
+
gap: 14px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* ── Card list (left column) ─────────────────────────────────────────────── */
|
|
82
|
+
|
|
83
|
+
.card-list {
|
|
84
|
+
flex-shrink: 0;
|
|
85
|
+
height: 100%;
|
|
86
|
+
overflow-x: auto;
|
|
87
|
+
display: flex;
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
flex-wrap: wrap;
|
|
90
|
+
align-content: flex-start;
|
|
91
|
+
gap: 14px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ── Log section (right, fills remaining width) ──────────────────────────── */
|
|
95
|
+
|
|
96
|
+
.log-section {
|
|
97
|
+
flex: 1;
|
|
98
|
+
min-width: 0;
|
|
99
|
+
display: flex;
|
|
100
|
+
flex-direction: column;
|
|
101
|
+
background: var(--surface);
|
|
102
|
+
border: 1px solid var(--border);
|
|
103
|
+
border-radius: var(--r);
|
|
104
|
+
overflow: hidden;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.log-header {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
justify-content: space-between;
|
|
111
|
+
padding: 6px 12px;
|
|
112
|
+
border-bottom: 1px solid var(--border);
|
|
113
|
+
flex-shrink: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.log-title {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 7px;
|
|
120
|
+
font-size: 11px;
|
|
121
|
+
font-weight: 700;
|
|
122
|
+
color: var(--text-3);
|
|
123
|
+
text-transform: uppercase;
|
|
124
|
+
letter-spacing: .5px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ── Cards ───────────────────────────────────────────────────────────────── */
|
|
128
|
+
|
|
129
|
+
.card {
|
|
130
|
+
width: 300px;
|
|
131
|
+
background: var(--surface);
|
|
132
|
+
border: 1px solid var(--border);
|
|
133
|
+
border-radius: var(--r);
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-direction: column;
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.card-hd {
|
|
140
|
+
display: flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
justify-content: space-between;
|
|
143
|
+
padding: 10px 14px;
|
|
144
|
+
border-bottom: 1px solid var(--border);
|
|
145
|
+
gap: 8px;
|
|
146
|
+
flex-shrink: 0;
|
|
147
|
+
}
|
|
148
|
+
.ch-meta {
|
|
149
|
+
display: flex;
|
|
150
|
+
align-items: center;
|
|
151
|
+
gap: 8px;
|
|
152
|
+
}
|
|
153
|
+
.ch-name { font-size: 14px; font-weight: 600; }
|
|
154
|
+
|
|
155
|
+
.card-body {
|
|
156
|
+
padding: 12px 14px;
|
|
157
|
+
display: flex;
|
|
158
|
+
flex-direction: column;
|
|
159
|
+
gap: 10px;
|
|
160
|
+
flex: 1;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ── Channel icons ───────────────────────────────────────────────────────── */
|
|
164
|
+
|
|
165
|
+
.channel-icon {
|
|
166
|
+
width: 26px; height: 26px;
|
|
167
|
+
border-radius: 6px;
|
|
168
|
+
display: flex; align-items: center; justify-content: center;
|
|
169
|
+
flex-shrink: 0;
|
|
170
|
+
}
|
|
171
|
+
.desktop-icon { background: #0d1a2e; color: #60a5fa; }
|
|
172
|
+
.gmail-icon { background: #2e0d0d; color: #f87171; }
|
|
173
|
+
.telegram-icon { background: #0d1e2e; color: #38bdf8; }
|
|
174
|
+
.whatsapp-icon { background: #0d2e1a; color: #4ade80; }
|
|
175
|
+
.sms-icon { background: #1e0d2e; color: #c084fc; }
|
|
176
|
+
|
|
177
|
+
/* ── Tags & badges ───────────────────────────────────────────────────────── */
|
|
178
|
+
|
|
179
|
+
.tag {
|
|
180
|
+
font-size: 10px;
|
|
181
|
+
font-weight: 500;
|
|
182
|
+
background: #1a1a24;
|
|
183
|
+
border: 1px solid var(--border);
|
|
184
|
+
color: var(--text-3);
|
|
185
|
+
padding: 1px 6px;
|
|
186
|
+
border-radius: 20px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.badge {
|
|
190
|
+
font-size: 10px;
|
|
191
|
+
font-weight: 600;
|
|
192
|
+
padding: 2px 8px;
|
|
193
|
+
border-radius: 20px;
|
|
194
|
+
white-space: nowrap;
|
|
195
|
+
flex-shrink: 0;
|
|
196
|
+
}
|
|
197
|
+
.badge-idle { background: #1e1e24; color: var(--text-3); }
|
|
198
|
+
.badge-ok { background: #0d2e22; color: #34d399; }
|
|
199
|
+
.badge-error { background: #2e0d0d; color: #f87171; }
|
|
200
|
+
.badge-warn { background: #2e200d; color: #fbbf24; }
|
|
201
|
+
|
|
202
|
+
/* ── Setup guides ────────────────────────────────────────────────────────── */
|
|
203
|
+
|
|
204
|
+
.guide {
|
|
205
|
+
background: #0d0d18;
|
|
206
|
+
border: 1px solid #252538;
|
|
207
|
+
border-radius: 7px;
|
|
208
|
+
overflow: hidden;
|
|
209
|
+
flex-shrink: 0;
|
|
210
|
+
}
|
|
211
|
+
.guide summary {
|
|
212
|
+
padding: 7px 11px;
|
|
213
|
+
cursor: pointer;
|
|
214
|
+
font-weight: 600;
|
|
215
|
+
font-size: 11px;
|
|
216
|
+
color: var(--accent);
|
|
217
|
+
list-style: none;
|
|
218
|
+
user-select: none;
|
|
219
|
+
display: flex;
|
|
220
|
+
align-items: center;
|
|
221
|
+
gap: 5px;
|
|
222
|
+
}
|
|
223
|
+
.guide summary::-webkit-details-marker { display: none; }
|
|
224
|
+
.guide summary::before { content: "▶"; font-size: 8px; transition: transform .15s; }
|
|
225
|
+
.guide[open] summary::before { transform: rotate(90deg); }
|
|
226
|
+
.guide ol {
|
|
227
|
+
padding: 0 11px 10px 26px;
|
|
228
|
+
font-size: 11px;
|
|
229
|
+
color: var(--text-2);
|
|
230
|
+
display: flex;
|
|
231
|
+
flex-direction: column;
|
|
232
|
+
gap: 4px;
|
|
233
|
+
line-height: 1.5;
|
|
234
|
+
}
|
|
235
|
+
.guide a { color: var(--accent); }
|
|
236
|
+
|
|
237
|
+
code.cp {
|
|
238
|
+
display: inline-block;
|
|
239
|
+
background: #1a1a2e;
|
|
240
|
+
border: 1px solid var(--border);
|
|
241
|
+
border-radius: 4px;
|
|
242
|
+
padding: 1px 6px;
|
|
243
|
+
font-family: monospace;
|
|
244
|
+
font-size: 10px;
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
}
|
|
247
|
+
code.cp:hover { background: #22223a; }
|
|
248
|
+
code.cp::after { content: " 📋"; font-size: 9px; }
|
|
249
|
+
|
|
250
|
+
/* ── Form fields ─────────────────────────────────────────────────────────── */
|
|
251
|
+
|
|
252
|
+
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
253
|
+
.fg { display: flex; flex-direction: column; gap: 4px; }
|
|
254
|
+
|
|
255
|
+
.fg label, label.lbl {
|
|
256
|
+
font-size: 11px;
|
|
257
|
+
font-weight: 600;
|
|
258
|
+
color: var(--text-3);
|
|
259
|
+
text-transform: uppercase;
|
|
260
|
+
letter-spacing: .4px;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
input[type="text"],
|
|
264
|
+
input[type="email"],
|
|
265
|
+
input[type="tel"],
|
|
266
|
+
input[type="password"] {
|
|
267
|
+
width: 100%;
|
|
268
|
+
padding: 7px 10px;
|
|
269
|
+
border: 1px solid var(--border);
|
|
270
|
+
border-radius: 7px;
|
|
271
|
+
font-size: 13px;
|
|
272
|
+
color: var(--text);
|
|
273
|
+
background: #0d0d10;
|
|
274
|
+
outline: none;
|
|
275
|
+
transition: border-color .15s, box-shadow .15s;
|
|
276
|
+
}
|
|
277
|
+
input:focus {
|
|
278
|
+
border-color: var(--accent);
|
|
279
|
+
box-shadow: 0 0 0 2px rgba(124,109,250,.15);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* ── Toggle ──────────────────────────────────────────────────────────────── */
|
|
283
|
+
|
|
284
|
+
.toggle-wrap {
|
|
285
|
+
display: inline-flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
cursor: pointer;
|
|
288
|
+
user-select: none;
|
|
289
|
+
}
|
|
290
|
+
.toggle-wrap input { display: none; }
|
|
291
|
+
.toggle {
|
|
292
|
+
width: 34px; height: 19px;
|
|
293
|
+
background: var(--border);
|
|
294
|
+
border-radius: 20px;
|
|
295
|
+
position: relative;
|
|
296
|
+
transition: background .15s;
|
|
297
|
+
flex-shrink: 0;
|
|
298
|
+
}
|
|
299
|
+
.toggle::after {
|
|
300
|
+
content: "";
|
|
301
|
+
position: absolute;
|
|
302
|
+
width: 13px; height: 13px;
|
|
303
|
+
background: #fff;
|
|
304
|
+
border-radius: 50%;
|
|
305
|
+
top: 3px; left: 3px;
|
|
306
|
+
transition: transform .15s;
|
|
307
|
+
box-shadow: 0 1px 2px rgba(0,0,0,.3);
|
|
308
|
+
}
|
|
309
|
+
.toggle-wrap input:checked + .toggle { background: var(--accent); }
|
|
310
|
+
.toggle-wrap input:checked + .toggle::after { transform: translateX(15px); }
|
|
311
|
+
.toggle-lbl { font-size: 12px; color: var(--text-2); margin-left: 6px; }
|
|
312
|
+
|
|
313
|
+
/* ── Buttons ─────────────────────────────────────────────────────────────── */
|
|
314
|
+
|
|
315
|
+
.actions {
|
|
316
|
+
display: flex;
|
|
317
|
+
align-items: center;
|
|
318
|
+
gap: 7px;
|
|
319
|
+
flex-wrap: wrap;
|
|
320
|
+
padding-bottom: 4px;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.btn {
|
|
324
|
+
padding: 7px 14px;
|
|
325
|
+
border-radius: 7px;
|
|
326
|
+
font-size: 13px;
|
|
327
|
+
font-weight: 600;
|
|
328
|
+
border: none;
|
|
329
|
+
cursor: pointer;
|
|
330
|
+
display: inline-flex;
|
|
331
|
+
align-items: center;
|
|
332
|
+
gap: 5px;
|
|
333
|
+
white-space: nowrap;
|
|
334
|
+
transition: all .15s;
|
|
335
|
+
}
|
|
336
|
+
.btn-sm { padding: 6px 12px; font-size: 12px; }
|
|
337
|
+
.btn:disabled { opacity: .4; cursor: not-allowed; }
|
|
338
|
+
|
|
339
|
+
.btn-primary { background: var(--accent); color: #fff; }
|
|
340
|
+
.btn-primary:hover:not(:disabled) { background: var(--accent-h); }
|
|
341
|
+
|
|
342
|
+
.btn-ghost { background: #1a1a22; color: var(--text-2); border: 1px solid var(--border); }
|
|
343
|
+
.btn-ghost:hover:not(:disabled) { background: #222230; }
|
|
344
|
+
|
|
345
|
+
.btn-danger { background: transparent; color: #f87171; border: 1px solid #3d1515; }
|
|
346
|
+
.btn-danger:hover:not(:disabled) { background: #2e0d0d; }
|
|
347
|
+
|
|
348
|
+
.btn-google { background: #1a1a22; color: var(--text); border: 1px solid var(--border); }
|
|
349
|
+
.btn-google:hover:not(:disabled) { background: #222230; }
|
|
350
|
+
|
|
351
|
+
/* ── Connected banner ────────────────────────────────────────────────────── */
|
|
352
|
+
|
|
353
|
+
.ok-banner {
|
|
354
|
+
display: flex;
|
|
355
|
+
align-items: center;
|
|
356
|
+
gap: 7px;
|
|
357
|
+
background: #0d2e22;
|
|
358
|
+
border: 1px solid #134e35;
|
|
359
|
+
border-radius: 7px;
|
|
360
|
+
padding: 8px 12px;
|
|
361
|
+
font-size: 13px;
|
|
362
|
+
color: #34d399;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* ── gcloud panel ────────────────────────────────────────────────────────── */
|
|
366
|
+
|
|
367
|
+
.gcloud-row {
|
|
368
|
+
display: flex;
|
|
369
|
+
align-items: center;
|
|
370
|
+
gap: 8px;
|
|
371
|
+
padding: 8px 11px;
|
|
372
|
+
border-radius: 7px;
|
|
373
|
+
font-size: 12px;
|
|
374
|
+
border: 1px solid var(--border);
|
|
375
|
+
background: #111118;
|
|
376
|
+
}
|
|
377
|
+
.gcloud-row .status-text { flex: 1; color: var(--text-2); }
|
|
378
|
+
.gcloud-row .status-account { font-weight: 600; }
|
|
379
|
+
|
|
380
|
+
.dot {
|
|
381
|
+
width: 7px; height: 7px;
|
|
382
|
+
border-radius: 50%;
|
|
383
|
+
flex-shrink: 0;
|
|
384
|
+
}
|
|
385
|
+
.dot-ok { background: var(--success); box-shadow: 0 0 5px var(--success); }
|
|
386
|
+
.dot-warn { background: var(--warn); box-shadow: 0 0 5px var(--warn); }
|
|
387
|
+
.dot-idle { background: var(--text-3); }
|
|
388
|
+
.dot-spin {
|
|
389
|
+
background: transparent;
|
|
390
|
+
border: 2px solid var(--accent);
|
|
391
|
+
border-top-color: transparent;
|
|
392
|
+
animation: spin .7s linear infinite;
|
|
393
|
+
}
|
|
394
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
395
|
+
|
|
396
|
+
.log-panel {
|
|
397
|
+
flex: 1;
|
|
398
|
+
overflow-y: auto;
|
|
399
|
+
padding: 6px 12px;
|
|
400
|
+
font-family: monospace;
|
|
401
|
+
font-size: 11px;
|
|
402
|
+
line-height: 1.7;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.log-entry { display: flex; gap: 8px; align-items: baseline; }
|
|
406
|
+
.log-ts { color: var(--text-3); white-space: nowrap; flex-shrink: 0; }
|
|
407
|
+
.log-client { color: #7c6dfa; font-weight: 700; white-space: nowrap; flex-shrink: 0; }
|
|
408
|
+
.log-dir-out { color: #38bdf8; flex-shrink: 0; }
|
|
409
|
+
.log-dir-in { color: #4ade80; flex-shrink: 0; }
|
|
410
|
+
.log-dir-info { color: var(--text-3); flex-shrink: 0; }
|
|
411
|
+
.log-channel { color: #fbbf24; white-space: nowrap; flex-shrink: 0; }
|
|
412
|
+
.log-msg { color: var(--text-2); word-break: break-word; }
|
|
413
|
+
|
|
414
|
+
/* ── Toast ───────────────────────────────────────────────────────────────── */
|
|
415
|
+
|
|
416
|
+
.toast {
|
|
417
|
+
position: fixed;
|
|
418
|
+
bottom: 20px;
|
|
419
|
+
left: 50%;
|
|
420
|
+
transform: translateX(-50%);
|
|
421
|
+
background: #1a202c;
|
|
422
|
+
color: #fff;
|
|
423
|
+
padding: 9px 18px;
|
|
424
|
+
border-radius: 8px;
|
|
425
|
+
font-size: 12px;
|
|
426
|
+
font-weight: 500;
|
|
427
|
+
box-shadow: 0 4px 20px rgba(0,0,0,.5);
|
|
428
|
+
z-index: 1000;
|
|
429
|
+
transition: opacity .2s, transform .2s;
|
|
430
|
+
}
|
|
431
|
+
.toast.toast-ok { background: #065f46; }
|
|
432
|
+
.toast.toast-error { background: #7f1d1d; }
|
|
433
|
+
.toast.hidden { opacity: 0; pointer-events: none; transform: translateX(-50%) translateY(8px); }
|
|
434
|
+
|
|
435
|
+
/* ── Dirty marker ────────────────────────────────────────────────────────── */
|
|
436
|
+
.btn-primary.dirty::after { content: " •"; }
|
|
437
|
+
.hidden { display: none !important; }
|