bloby-bot 0.21.13 → 0.22.1
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/bin/cli.js +2 -2
- package/cli/commands/start.ts +1 -1
- package/cli/commands/tunnel.ts +1 -1
- package/cli/core/config.ts +1 -1
- package/cli/core/server.ts +1 -1
- package/package.json +1 -1
- package/shared/config.ts +1 -1
- package/supervisor/agents/prompts/coder.txt +1 -1
- package/supervisor/chat/src/components/Chat/MessageBubble.tsx +2 -2
- package/supervisor/chat/src/lib/ws-client.ts +1 -1
- package/supervisor/index.ts +24 -0
- package/vite.config.ts +2 -2
- package/worker/prompts/bloby-system-prompt.txt +7 -7
- package/workspace/backend/index.ts +1 -1
- package/workspace/skills/chrome-extension/.claude-plugin/plugin.json +6 -0
- package/workspace/skills/chrome-extension/SKILL.md +133 -0
- package/workspace/skills/chrome-extension/background.js +239 -0
- package/workspace/skills/chrome-extension/content-script.js +218 -0
- package/workspace/skills/chrome-extension/content-style.css +86 -0
- package/workspace/skills/chrome-extension/icons/icon-128.png +0 -0
- package/workspace/skills/chrome-extension/icons/icon-16.png +0 -0
- package/workspace/skills/chrome-extension/icons/icon-48.png +0 -0
- package/workspace/skills/chrome-extension/manifest.json +46 -0
- package/workspace/skills/chrome-extension/panel/panel.html +74 -0
- package/workspace/skills/chrome-extension/popup/popup.css +124 -0
- package/workspace/skills/chrome-extension/popup/popup.html +47 -0
- package/workspace/skills/chrome-extension/popup/popup.js +115 -0
- package/workspace/skills/chrome-extension/skill.json +15 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bloby Chrome Extension — Content Script
|
|
3
|
+
*
|
|
4
|
+
* Injected into every page. Responsibilities:
|
|
5
|
+
* - Renders the Bloby bubble (bottom-right corner)
|
|
6
|
+
* - Opens/closes the chat panel (iframe to extension's panel.html)
|
|
7
|
+
* - Extracts page context (URL, title, selection) for the agent
|
|
8
|
+
* - Bridges messages between the panel iframe and the background worker
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
(function () {
|
|
12
|
+
// Guard: don't inject on extension pages or Bloby's own domain
|
|
13
|
+
if (document.getElementById('bloby-ext-bubble')) return;
|
|
14
|
+
if (location.hostname.endsWith('bloby.bot')) return;
|
|
15
|
+
|
|
16
|
+
const BUBBLE_SIZE = 60;
|
|
17
|
+
const BUBBLE_MARGIN = 24;
|
|
18
|
+
const PANEL_WIDTH = 420;
|
|
19
|
+
|
|
20
|
+
let panelOpen = false;
|
|
21
|
+
let paired = false;
|
|
22
|
+
|
|
23
|
+
// ── Check if paired ────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
chrome.runtime.sendMessage({ type: 'bloby:get-state' }, (state) => {
|
|
26
|
+
if (chrome.runtime.lastError) return;
|
|
27
|
+
if (state?.paired) {
|
|
28
|
+
paired = true;
|
|
29
|
+
createBubble();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Listen for pairing changes
|
|
34
|
+
chrome.storage.onChanged.addListener((changes) => {
|
|
35
|
+
if (changes.serverUrl) {
|
|
36
|
+
if (changes.serverUrl.newValue) {
|
|
37
|
+
paired = true;
|
|
38
|
+
if (!document.getElementById('bloby-ext-bubble')) createBubble();
|
|
39
|
+
} else {
|
|
40
|
+
paired = false;
|
|
41
|
+
removeBubble();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ── Bubble ─────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
function createBubble() {
|
|
49
|
+
const bubble = document.createElement('div');
|
|
50
|
+
bubble.id = 'bloby-ext-bubble';
|
|
51
|
+
bubble.title = 'Chat with Bloby';
|
|
52
|
+
bubble.addEventListener('click', togglePanel);
|
|
53
|
+
document.body.appendChild(bubble);
|
|
54
|
+
|
|
55
|
+
// Inner dot (gradient)
|
|
56
|
+
const dot = document.createElement('div');
|
|
57
|
+
dot.className = 'bloby-ext-bubble-dot';
|
|
58
|
+
bubble.appendChild(dot);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function removeBubble() {
|
|
62
|
+
const bubble = document.getElementById('bloby-ext-bubble');
|
|
63
|
+
if (bubble) bubble.remove();
|
|
64
|
+
const panel = document.getElementById('bloby-ext-panel');
|
|
65
|
+
if (panel) panel.remove();
|
|
66
|
+
const backdrop = document.getElementById('bloby-ext-backdrop');
|
|
67
|
+
if (backdrop) backdrop.remove();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Panel ──────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
function togglePanel() {
|
|
73
|
+
if (panelOpen) {
|
|
74
|
+
closePanel();
|
|
75
|
+
} else {
|
|
76
|
+
openPanel();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function openPanel() {
|
|
81
|
+
if (document.getElementById('bloby-ext-panel')) {
|
|
82
|
+
const panel = document.getElementById('bloby-ext-panel');
|
|
83
|
+
panel.classList.add('bloby-ext-panel-open');
|
|
84
|
+
panelOpen = true;
|
|
85
|
+
showBackdrop();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Create backdrop
|
|
90
|
+
showBackdrop();
|
|
91
|
+
|
|
92
|
+
// Create panel
|
|
93
|
+
const panel = document.createElement('div');
|
|
94
|
+
panel.id = 'bloby-ext-panel';
|
|
95
|
+
|
|
96
|
+
// Create iframe — loads the extension's own panel page
|
|
97
|
+
const iframe = document.createElement('iframe');
|
|
98
|
+
iframe.id = 'bloby-ext-iframe';
|
|
99
|
+
iframe.src = chrome.runtime.getURL('panel/panel.html');
|
|
100
|
+
iframe.allow = 'microphone';
|
|
101
|
+
panel.appendChild(iframe);
|
|
102
|
+
|
|
103
|
+
document.body.appendChild(panel);
|
|
104
|
+
|
|
105
|
+
// Trigger slide-in animation on next frame
|
|
106
|
+
requestAnimationFrame(() => {
|
|
107
|
+
panel.classList.add('bloby-ext-panel-open');
|
|
108
|
+
panelOpen = true;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Listen for messages from the panel iframe
|
|
112
|
+
window.addEventListener('message', handlePanelMessage);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function closePanel() {
|
|
116
|
+
const panel = document.getElementById('bloby-ext-panel');
|
|
117
|
+
if (panel) {
|
|
118
|
+
panel.classList.remove('bloby-ext-panel-open');
|
|
119
|
+
panelOpen = false;
|
|
120
|
+
hideBackdrop();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function showBackdrop() {
|
|
125
|
+
let backdrop = document.getElementById('bloby-ext-backdrop');
|
|
126
|
+
if (!backdrop) {
|
|
127
|
+
backdrop = document.createElement('div');
|
|
128
|
+
backdrop.id = 'bloby-ext-backdrop';
|
|
129
|
+
backdrop.addEventListener('click', closePanel);
|
|
130
|
+
document.body.appendChild(backdrop);
|
|
131
|
+
}
|
|
132
|
+
requestAnimationFrame(() => backdrop.classList.add('bloby-ext-backdrop-visible'));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function hideBackdrop() {
|
|
136
|
+
const backdrop = document.getElementById('bloby-ext-backdrop');
|
|
137
|
+
if (backdrop) {
|
|
138
|
+
backdrop.classList.remove('bloby-ext-backdrop-visible');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Panel ↔ Background Message Bridge ──────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
function handlePanelMessage(event) {
|
|
145
|
+
if (event.source !== document.getElementById('bloby-ext-iframe')?.contentWindow) return;
|
|
146
|
+
|
|
147
|
+
const msg = event.data;
|
|
148
|
+
if (!msg?.type) return;
|
|
149
|
+
|
|
150
|
+
// Close panel request
|
|
151
|
+
if (msg.type === 'bloby:close') {
|
|
152
|
+
closePanel();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Forward chat messages to background worker → server
|
|
157
|
+
if (msg.type === 'bloby:send') {
|
|
158
|
+
chrome.runtime.sendMessage({ type: 'bloby:send', payload: msg.payload });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Panel requests page context
|
|
163
|
+
if (msg.type === 'bloby:get-page-context') {
|
|
164
|
+
const context = getPageContext();
|
|
165
|
+
const iframe = document.getElementById('bloby-ext-iframe');
|
|
166
|
+
if (iframe?.contentWindow) {
|
|
167
|
+
iframe.contentWindow.postMessage({ type: 'bloby:page-context', context }, '*');
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Forward server messages from background to panel iframe
|
|
174
|
+
chrome.runtime.onMessage.addListener((msg) => {
|
|
175
|
+
const iframe = document.getElementById('bloby-ext-iframe');
|
|
176
|
+
if (iframe?.contentWindow && panelOpen) {
|
|
177
|
+
iframe.contentWindow.postMessage(msg, '*');
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ── Page Context Extraction ────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
function getPageContext() {
|
|
184
|
+
const context = {
|
|
185
|
+
url: location.href,
|
|
186
|
+
title: document.title,
|
|
187
|
+
selection: window.getSelection()?.toString()?.slice(0, 2000) || '',
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Try to extract structured data (JSON-LD)
|
|
191
|
+
const jsonLd = document.querySelector('script[type="application/ld+json"]');
|
|
192
|
+
if (jsonLd) {
|
|
193
|
+
try {
|
|
194
|
+
const data = JSON.parse(jsonLd.textContent);
|
|
195
|
+
if (data.name) context.productName = data.name;
|
|
196
|
+
if (data.offers?.price) context.price = data.offers.price;
|
|
197
|
+
if (data.offers?.priceCurrency) context.currency = data.offers.priceCurrency;
|
|
198
|
+
if (data.description) context.description = data.description?.slice(0, 500);
|
|
199
|
+
} catch { /* ignore malformed JSON-LD */ }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Meta description fallback
|
|
203
|
+
const metaDesc = document.querySelector('meta[name="description"]');
|
|
204
|
+
if (metaDesc && !context.description) {
|
|
205
|
+
context.description = metaDesc.content?.slice(0, 500);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return context;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── Keyboard shortcut ──────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
document.addEventListener('keydown', (e) => {
|
|
214
|
+
if (e.key === 'Escape' && panelOpen) {
|
|
215
|
+
closePanel();
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
})();
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* Bloby Chrome Extension — Bubble & Panel Styles */
|
|
2
|
+
|
|
3
|
+
#bloby-ext-bubble {
|
|
4
|
+
position: fixed;
|
|
5
|
+
bottom: 24px;
|
|
6
|
+
right: 24px;
|
|
7
|
+
width: 60px;
|
|
8
|
+
height: 60px;
|
|
9
|
+
border-radius: 50%;
|
|
10
|
+
background: linear-gradient(135deg, #04D1FE, #AF27E3, #FB4072);
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
z-index: 2147483646;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
17
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#bloby-ext-bubble:hover {
|
|
21
|
+
transform: scale(1.08);
|
|
22
|
+
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#bloby-ext-bubble:active {
|
|
26
|
+
transform: scale(0.95);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.bloby-ext-bubble-dot {
|
|
30
|
+
width: 24px;
|
|
31
|
+
height: 24px;
|
|
32
|
+
border-radius: 50%;
|
|
33
|
+
background: rgba(255, 255, 255, 0.9);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Panel */
|
|
37
|
+
#bloby-ext-panel {
|
|
38
|
+
position: fixed;
|
|
39
|
+
top: 0;
|
|
40
|
+
right: 0;
|
|
41
|
+
bottom: 0;
|
|
42
|
+
width: 420px;
|
|
43
|
+
z-index: 2147483647;
|
|
44
|
+
transform: translateX(100%);
|
|
45
|
+
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
46
|
+
box-shadow: -4px 0 24px rgba(0, 0, 0, 0.3);
|
|
47
|
+
border-left: 1px solid #3a3a3a;
|
|
48
|
+
background: #212121;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#bloby-ext-panel.bloby-ext-panel-open {
|
|
52
|
+
transform: translateX(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Mobile: full width */
|
|
56
|
+
@media (max-width: 480px) {
|
|
57
|
+
#bloby-ext-panel {
|
|
58
|
+
width: 100vw;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#bloby-ext-iframe {
|
|
63
|
+
width: 100%;
|
|
64
|
+
height: 100%;
|
|
65
|
+
border: none;
|
|
66
|
+
background: #212121;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Backdrop */
|
|
70
|
+
#bloby-ext-backdrop {
|
|
71
|
+
position: fixed;
|
|
72
|
+
top: 0;
|
|
73
|
+
left: 0;
|
|
74
|
+
right: 0;
|
|
75
|
+
bottom: 0;
|
|
76
|
+
background: rgba(0, 0, 0, 0.4);
|
|
77
|
+
z-index: 2147483645;
|
|
78
|
+
opacity: 0;
|
|
79
|
+
pointer-events: none;
|
|
80
|
+
transition: opacity 0.25s ease;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#bloby-ext-backdrop.bloby-ext-backdrop-visible {
|
|
84
|
+
opacity: 1;
|
|
85
|
+
pointer-events: auto;
|
|
86
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "Bloby",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Your AI agent, on every page. Chat with Bloby from anywhere.",
|
|
6
|
+
"permissions": [
|
|
7
|
+
"storage",
|
|
8
|
+
"activeTab"
|
|
9
|
+
],
|
|
10
|
+
"host_permissions": [
|
|
11
|
+
"https://*.bloby.bot/*",
|
|
12
|
+
"https://*.my.bloby.bot/*",
|
|
13
|
+
"https://*.trycloudflare.com/*"
|
|
14
|
+
],
|
|
15
|
+
"action": {
|
|
16
|
+
"default_popup": "popup/popup.html",
|
|
17
|
+
"default_icon": {
|
|
18
|
+
"16": "icons/icon-16.png",
|
|
19
|
+
"48": "icons/icon-48.png",
|
|
20
|
+
"128": "icons/icon-128.png"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"background": {
|
|
24
|
+
"service_worker": "background.js",
|
|
25
|
+
"type": "module"
|
|
26
|
+
},
|
|
27
|
+
"content_scripts": [
|
|
28
|
+
{
|
|
29
|
+
"matches": ["<all_urls>"],
|
|
30
|
+
"js": ["content-script.js"],
|
|
31
|
+
"css": ["content-style.css"],
|
|
32
|
+
"run_at": "document_idle"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"web_accessible_resources": [
|
|
36
|
+
{
|
|
37
|
+
"resources": ["panel/panel.html", "assets/*", "icons/*"],
|
|
38
|
+
"matches": ["<all_urls>"]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"icons": {
|
|
42
|
+
"16": "icons/icon-16.png",
|
|
43
|
+
"48": "icons/icon-48.png",
|
|
44
|
+
"128": "icons/icon-128.png"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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, maximum-scale=1.0, user-scalable=no">
|
|
6
|
+
<title>Bloby</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
body { background: #212121; width: 100%; height: 100vh; overflow: hidden; }
|
|
10
|
+
iframe { width: 100%; height: 100%; border: none; }
|
|
11
|
+
.loading {
|
|
12
|
+
display: flex; align-items: center; justify-content: center;
|
|
13
|
+
height: 100vh; color: #a1a1aa; font-family: system-ui, sans-serif;
|
|
14
|
+
font-size: 14px;
|
|
15
|
+
}
|
|
16
|
+
.loading .dots span {
|
|
17
|
+
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
|
|
18
|
+
background: #a1a1aa; margin: 0 3px; animation: bounce 1s infinite;
|
|
19
|
+
}
|
|
20
|
+
.loading .dots span:nth-child(2) { animation-delay: 0.15s; }
|
|
21
|
+
.loading .dots span:nth-child(3) { animation-delay: 0.3s; }
|
|
22
|
+
@keyframes bounce {
|
|
23
|
+
0%, 80%, 100% { transform: scale(0); }
|
|
24
|
+
40% { transform: scale(1); }
|
|
25
|
+
}
|
|
26
|
+
</style>
|
|
27
|
+
</head>
|
|
28
|
+
<body>
|
|
29
|
+
<div class="loading" id="loading">
|
|
30
|
+
<div class="dots"><span></span><span></span><span></span></div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<script>
|
|
34
|
+
/**
|
|
35
|
+
* Panel page — loads the user's Bloby chat in an iframe.
|
|
36
|
+
* Reads serverUrl from chrome.storage.local.
|
|
37
|
+
*/
|
|
38
|
+
(async () => {
|
|
39
|
+
const data = await chrome.storage.local.get(['serverUrl', 'authToken']);
|
|
40
|
+
|
|
41
|
+
if (!data.serverUrl) {
|
|
42
|
+
document.getElementById('loading').innerHTML =
|
|
43
|
+
'<span style="color:#ef4444">Not paired. Click the Bloby icon in the toolbar to set up.</span>';
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Build the chat URL — same as the dashboard chat iframe
|
|
48
|
+
let chatUrl = `${data.serverUrl}/bloby/`;
|
|
49
|
+
if (data.authToken) {
|
|
50
|
+
chatUrl += `?token=${data.authToken}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Create iframe to the user's Bloby server
|
|
54
|
+
const iframe = document.createElement('iframe');
|
|
55
|
+
iframe.src = chatUrl;
|
|
56
|
+
iframe.allow = 'microphone';
|
|
57
|
+
iframe.style.cssText = 'width:100%;height:100%;border:none;';
|
|
58
|
+
|
|
59
|
+
iframe.onload = () => {
|
|
60
|
+
document.getElementById('loading').style.display = 'none';
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
document.body.appendChild(iframe);
|
|
64
|
+
|
|
65
|
+
// Forward close events from chat iframe to content script (parent of this panel)
|
|
66
|
+
window.addEventListener('message', (e) => {
|
|
67
|
+
if (e.data?.type === 'bloby:close') {
|
|
68
|
+
window.parent.postMessage({ type: 'bloby:close' }, '*');
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
})();
|
|
72
|
+
</script>
|
|
73
|
+
</body>
|
|
74
|
+
</html>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
* {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
body {
|
|
8
|
+
width: 320px;
|
|
9
|
+
min-height: 280px;
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
11
|
+
background: #0a0a0b;
|
|
12
|
+
color: #e4e4e7;
|
|
13
|
+
padding: 32px 24px;
|
|
14
|
+
text-align: center;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.logo {
|
|
18
|
+
display: flex;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
margin-bottom: 20px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.logo-dot {
|
|
24
|
+
width: 48px;
|
|
25
|
+
height: 48px;
|
|
26
|
+
border-radius: 50%;
|
|
27
|
+
background: linear-gradient(135deg, #04D1FE, #AF27E3, #FB4072);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.logo-dot.connected {
|
|
31
|
+
box-shadow: 0 0 20px rgba(4, 209, 254, 0.4);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
h1 {
|
|
35
|
+
font-size: 18px;
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
margin-bottom: 6px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.subtitle {
|
|
41
|
+
font-size: 13px;
|
|
42
|
+
color: #71717a;
|
|
43
|
+
margin-bottom: 24px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Code Input */
|
|
47
|
+
.code-input {
|
|
48
|
+
display: flex;
|
|
49
|
+
gap: 8px;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
margin-bottom: 16px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.code-input input {
|
|
55
|
+
width: 40px;
|
|
56
|
+
height: 48px;
|
|
57
|
+
border: 2px solid #27272a;
|
|
58
|
+
border-radius: 10px;
|
|
59
|
+
background: #18181b;
|
|
60
|
+
color: #e4e4e7;
|
|
61
|
+
font-size: 22px;
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
text-align: center;
|
|
64
|
+
outline: none;
|
|
65
|
+
transition: border-color 0.15s;
|
|
66
|
+
caret-color: transparent;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.code-input input:focus {
|
|
70
|
+
border-color: #AF27E3;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.code-input input.filled {
|
|
74
|
+
border-color: #04D1FE;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.error {
|
|
78
|
+
color: #ef4444;
|
|
79
|
+
font-size: 13px;
|
|
80
|
+
min-height: 18px;
|
|
81
|
+
margin-bottom: 8px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.pairing {
|
|
85
|
+
color: #a1a1aa;
|
|
86
|
+
font-size: 13px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Connected Screen */
|
|
90
|
+
.status-badge {
|
|
91
|
+
display: inline-flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 6px;
|
|
94
|
+
background: #18181b;
|
|
95
|
+
border: 1px solid #27272a;
|
|
96
|
+
border-radius: 999px;
|
|
97
|
+
padding: 6px 14px;
|
|
98
|
+
font-size: 13px;
|
|
99
|
+
color: #a1a1aa;
|
|
100
|
+
margin-bottom: 20px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.status-dot {
|
|
104
|
+
width: 8px;
|
|
105
|
+
height: 8px;
|
|
106
|
+
border-radius: 50%;
|
|
107
|
+
background: #22c55e;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.btn-disconnect {
|
|
111
|
+
background: none;
|
|
112
|
+
border: 1px solid #27272a;
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
color: #71717a;
|
|
115
|
+
padding: 8px 16px;
|
|
116
|
+
font-size: 13px;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
transition: all 0.15s;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.btn-disconnect:hover {
|
|
122
|
+
border-color: #ef4444;
|
|
123
|
+
color: #ef4444;
|
|
124
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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>Bloby</title>
|
|
7
|
+
<link rel="stylesheet" href="popup.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<!-- Pairing Screen -->
|
|
11
|
+
<div id="pair-screen">
|
|
12
|
+
<div class="logo">
|
|
13
|
+
<div class="logo-dot"></div>
|
|
14
|
+
</div>
|
|
15
|
+
<h1>Connect to Bloby</h1>
|
|
16
|
+
<p class="subtitle">Ask your Bloby for a pairing code</p>
|
|
17
|
+
|
|
18
|
+
<div class="code-input" id="code-input">
|
|
19
|
+
<input type="text" maxlength="1" pattern="[0-9]" inputmode="numeric" autofocus>
|
|
20
|
+
<input type="text" maxlength="1" pattern="[0-9]" inputmode="numeric">
|
|
21
|
+
<input type="text" maxlength="1" pattern="[0-9]" inputmode="numeric">
|
|
22
|
+
<input type="text" maxlength="1" pattern="[0-9]" inputmode="numeric">
|
|
23
|
+
<input type="text" maxlength="1" pattern="[0-9]" inputmode="numeric">
|
|
24
|
+
<input type="text" maxlength="1" pattern="[0-9]" inputmode="numeric">
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div id="error" class="error"></div>
|
|
28
|
+
<div id="pairing" class="pairing" style="display:none">Connecting...</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<!-- Connected Screen -->
|
|
32
|
+
<div id="connected-screen" style="display:none">
|
|
33
|
+
<div class="logo">
|
|
34
|
+
<div class="logo-dot connected"></div>
|
|
35
|
+
</div>
|
|
36
|
+
<h1 id="connected-name">Connected</h1>
|
|
37
|
+
<p class="subtitle" id="connected-url"></p>
|
|
38
|
+
<div class="status-badge">
|
|
39
|
+
<span class="status-dot"></span>
|
|
40
|
+
<span id="status-text">Connected</span>
|
|
41
|
+
</div>
|
|
42
|
+
<button id="disconnect-btn" class="btn-disconnect">Disconnect</button>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<script src="popup.js"></script>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|