total-recall-brain 3.35.0 → 3.37.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.
Files changed (72) hide show
  1. package/bin/total-recall.mjs +17 -4
  2. package/frontend/dist/assets/index-CpvEw3iA.js +5786 -0
  3. package/frontend/dist/brand/plugins/phone.svg +20 -0
  4. package/frontend/dist/index.html +1 -1
  5. package/frontend/src/App.tsx +3 -30
  6. package/frontend/src/api/plugins.ts +18 -0
  7. package/frontend/src/components/plugins/previews/CodeQualityPreview.tsx +78 -86
  8. package/frontend/src/components/plugins/previews/ComposableCliPreview.tsx +74 -84
  9. package/frontend/src/components/plugins/previews/DecisionPreview.tsx +158 -106
  10. package/frontend/src/components/plugins/previews/DesignPreview.tsx +109 -75
  11. package/frontend/src/components/plugins/previews/DomainsPreview.tsx +69 -88
  12. package/frontend/src/components/plugins/previews/ImageGeneratorPreview.tsx +286 -0
  13. package/frontend/src/components/plugins/previews/PhonePreview.tsx +490 -76
  14. package/frontend/src/components/plugins/previews/SigningPreview.tsx +164 -50
  15. package/frontend/src/components/plugins/previews/TextPreview.tsx +85 -86
  16. package/frontend/src/components/plugins/previews/UserAutomationsPreview.tsx +30 -0
  17. package/frontend/src/components/plugins/previews/index.tsx +6 -1
  18. package/frontend/src/pages/PluginsPage.tsx +603 -136
  19. package/package.json +2 -5
  20. package/scaffold/.agent/skills/plugins/SKILL.md +200 -6
  21. package/src/cli/agent.mjs +59 -18
  22. package/src/cli/plugin/create.mjs +266 -13
  23. package/src/cli/plugin/index.mjs +5 -0
  24. package/src/cli/secret.mjs +28 -1
  25. package/src/core/agent-manager.mjs +47 -6
  26. package/src/core/cache-prune.mjs +60 -2
  27. package/src/core/embeddings.mjs +2 -0
  28. package/src/core/meta-harness.mjs +93 -6
  29. package/src/core/plugin-loader.mjs +20 -4
  30. package/src/core/plugin-store.mjs +122 -11
  31. package/src/core/surface.mjs +29 -1
  32. package/src/core/vault.mjs +6 -2
  33. package/src/server/route-manifest.json +22 -2
  34. package/src/server/routes/plugins-mesh.mjs +9 -1
  35. package/src/server/routes/plugins.mjs +65 -1
  36. package/frontend/dist/assets/index-BPrkIYkZ.js +0 -5786
  37. package/frontend/src/pages/AutomationsPage.spec.tsx +0 -61
  38. package/frontend/src/pages/AutomationsPage.tsx +0 -354
  39. package/plugins/creative-search/README.md +0 -33
  40. package/plugins/creative-search/cli.mjs +0 -415
  41. package/plugins/creative-search/config.mjs +0 -129
  42. package/plugins/creative-search/generator.mjs +0 -13
  43. package/plugins/creative-search/plugin.json +0 -61
  44. package/plugins/creative-search/ui/DESIGN.md +0 -50
  45. package/plugins/creative-search/ui/csearch-main.js +0 -314
  46. package/plugins/creative-search/ui/csearch-settings.js +0 -392
  47. package/plugins/creative-search/ui/index.js +0 -2
  48. package/plugins/creative-search/ui/tokens.css +0 -34
  49. package/plugins/dsh/README.md +0 -20
  50. package/plugins/dsh/cli.mjs +0 -232
  51. package/plugins/dsh/generator.mjs +0 -65
  52. package/plugins/dsh/plugin.json +0 -57
  53. package/plugins/dsh/ui/DESIGN.md +0 -48
  54. package/plugins/dsh/ui/dsh-main.js +0 -226
  55. package/plugins/dsh/ui/dsh-status.js +0 -265
  56. package/plugins/dsh/ui/index.js +0 -2
  57. package/plugins/dsh/ui/tokens.css +0 -32
  58. package/plugins/git-sentinel/README.md +0 -29
  59. package/plugins/git-sentinel/cli.mjs +0 -89
  60. package/plugins/git-sentinel/plugin.json +0 -18
  61. package/plugins/operator-alerts/README.md +0 -32
  62. package/plugins/operator-alerts/cli.mjs +0 -57
  63. package/plugins/operator-alerts/notify-core.mjs +0 -216
  64. package/plugins/operator-alerts/plugin.json +0 -20
  65. package/plugins/skill-manager/README.md +0 -57
  66. package/plugins/skill-manager/cli.mjs +0 -123
  67. package/plugins/skill-manager/plugin.json +0 -25
  68. package/plugins/skill-manager/route.mjs +0 -106
  69. package/plugins/system-monitor/README.md +0 -28
  70. package/plugins/system-monitor/cli.mjs +0 -80
  71. package/plugins/system-monitor/generator.mjs +0 -19
  72. package/plugins/system-monitor/plugin.json +0 -29
@@ -1,86 +1,500 @@
1
- import { type CSSProperties } from "react"
2
-
3
- const styles: Record<string, CSSProperties> = {
4
- container: {
5
- background: "linear-gradient(135deg, #0a1628 0%, #1a1a2e 100%)",
6
- borderRadius: "12px",
7
- padding: "24px",
8
- fontFamily: "var(--font-mono, 'SF Mono', 'Fira Code', monospace)",
9
- color: "#e0e0e0",
10
- maxWidth: "480px",
11
- },
12
- header: {
13
- display: "flex",
14
- justifyContent: "space-between",
15
- alignItems: "center",
16
- marginBottom: "20px",
17
- },
18
- label: {
19
- color: "#888",
20
- fontSize: "11px",
21
- textTransform: "uppercase" as const,
22
- letterSpacing: "1px",
23
- },
24
- statusBadge: {
25
- background: "#1a3a1a",
26
- color: "#4caf50",
27
- padding: "4px 10px",
28
- borderRadius: "20px",
29
- fontSize: "11px",
30
- fontWeight: 600,
31
- },
32
- dialpad: {
33
- display: "grid",
34
- gridTemplateColumns: "repeat(3, 1fr)",
35
- gap: "8px",
36
- marginBottom: "16px",
37
- },
38
- key: {
39
- background: "#1e1e3a",
40
- border: "1px solid #2a2a4a",
41
- borderRadius: "8px",
42
- padding: "14px 0",
43
- textAlign: "center" as const,
44
- fontSize: "18px",
45
- fontWeight: 600,
46
- color: "#d0d0ff",
47
- cursor: "pointer",
48
- transition: "background 0.15s",
49
- },
50
- keyRow: {
51
- display: "flex",
52
- justifyContent: "center",
53
- gap: "8px",
54
- marginBottom: "8px",
55
- },
56
- callButton: {
57
- background: "linear-gradient(135deg, #2e7d32, #1b5e20)",
58
- border: "none",
59
- borderRadius: "24px",
60
- padding: "12px 32px",
61
- color: "#fff",
62
- fontSize: "14px",
63
- fontWeight: 600,
64
- cursor: "pointer",
65
- width: "100%",
66
- },
1
+ import { useState, useEffect, useRef, type CSSProperties } from "react"
2
+ import { runPluginCommand } from "../../../api"
3
+
4
+ // Festech ringtone sound synthesis parameters
5
+ const RINGTONE_PRESETS: Record<string, { freqs: [number, number]; pulseMs: number; gapMs: number }> = {
6
+ backstage: { freqs: [440, 554], pulseMs: 400, gapMs: 200 },
7
+ warehouse: { freqs: [330, 392], pulseMs: 600, gapMs: 300 },
8
+ festival: { freqs: [523, 659], pulseMs: 250, gapMs: 120 },
67
9
  }
68
10
 
69
- const keys = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#"]
11
+ const padKeys = [
12
+ { num: "1", letters: "" },
13
+ { num: "2", letters: "ABC" },
14
+ { num: "3", letters: "DEF" },
15
+ { num: "4", letters: "GHI" },
16
+ { num: "5", letters: "JKL" },
17
+ { num: "6", letters: "MNO" },
18
+ { num: "7", letters: "PQRS" },
19
+ { num: "8", letters: "TUV" },
20
+ { num: "9", letters: "WXYZ" },
21
+ { num: "*", letters: "" },
22
+ { num: "0", letters: "+" },
23
+ { num: "#", letters: "" },
24
+ ]
70
25
 
71
26
  export function PhonePreview() {
27
+ const [activeTab, setActiveTab] = useState<"keypad" | "recents" | "settings">("keypad")
28
+ const [dialedNumber, setDialedNumber] = useState("")
29
+ const [callState, setCallState] = useState<"idle" | "ringing" | "connected">("idle")
30
+ const [callConnectedAt, setCallConnectedAt] = useState<number | null>(null)
31
+ const [duration, setDuration] = useState("00:00")
32
+ const [isMuted, setIsMuted] = useState(false)
33
+ const [isSpeakerOn, setIsSpeakerOn] = useState(false)
34
+ const [isHeld, setIsHeld] = useState(false)
35
+ const [isRecording, setIsRecording] = useState(false)
36
+ const [selectedRingtone, setSelectedRingtone] = useState("backstage")
37
+ const [recentCalls, setRecentCalls] = useState<Array<{ number: string; time: string; type: "outbound" | "inbound" }>>([])
38
+ const [logs, setLogs] = useState<string[]>([])
39
+
40
+ const ringAudioCtxRef = useRef<AudioContext | null>(null)
41
+ const ringIntervalRef = useRef<any>(null)
42
+
43
+ const isCalling = callState !== "idle"
44
+
45
+ const appendLog = (msg: string) => {
46
+ setLogs((prev) => [...prev.slice(-15), `[${new Date().toLocaleTimeString()}] ${msg}`])
47
+ }
48
+
49
+ // Timer effect for connected call
50
+ useEffect(() => {
51
+ if (callState !== "connected" || !callConnectedAt) {
52
+ setDuration("00:00")
53
+ return
54
+ }
55
+ const update = () => {
56
+ const elapsed = Math.max(0, Math.floor((Date.now() - callConnectedAt) / 1000))
57
+ const m = String(Math.floor(elapsed / 60)).padStart(2, "0")
58
+ const s = String(elapsed % 60).padStart(2, "0")
59
+ setDuration(`${m}:${s}`)
60
+ }
61
+ update()
62
+ const timer = setInterval(update, 1000)
63
+ return () => clearInterval(timer)
64
+ }, [callState, callConnectedAt])
65
+
66
+ // Ringtone synthesizer (Festech WebAudio fallback)
67
+ useEffect(() => {
68
+ if (callState !== "ringing") {
69
+ if (ringIntervalRef.current) {
70
+ clearInterval(ringIntervalRef.current)
71
+ ringIntervalRef.current = null
72
+ }
73
+ if (ringAudioCtxRef.current) {
74
+ ringAudioCtxRef.current.close().catch(() => {})
75
+ ringAudioCtxRef.current = null
76
+ }
77
+ return
78
+ }
79
+
80
+ const preset = RINGTONE_PRESETS[selectedRingtone] || RINGTONE_PRESETS.backstage
81
+ try {
82
+ const AudioCtx = window.AudioContext || (window as any).webkitAudioContext
83
+ if (!AudioCtx) return
84
+ const ctx = new AudioCtx()
85
+ ringAudioCtxRef.current = ctx
86
+
87
+ const playPulse = () => {
88
+ if (ctx.state === "closed") return
89
+ preset.freqs.forEach((freq, i) => {
90
+ const osc = ctx.createOscillator()
91
+ const gain = ctx.createGain()
92
+ osc.type = "sine"
93
+ osc.frequency.value = freq
94
+ gain.gain.setValueAtTime(0.001, ctx.currentTime)
95
+ gain.gain.exponentialRampToValueAtTime(0.12, ctx.currentTime + 0.02)
96
+ gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + preset.pulseMs / 1000)
97
+ osc.connect(gain)
98
+ gain.connect(ctx.destination)
99
+ osc.start(ctx.currentTime + i * 0.05)
100
+ osc.stop(ctx.currentTime + preset.pulseMs / 1000 + 0.05)
101
+ })
102
+ }
103
+
104
+ playPulse()
105
+ ringIntervalRef.current = setInterval(playPulse, preset.pulseMs + preset.gapMs + 500)
106
+ } catch {
107
+ // AudioContext unavailable
108
+ }
109
+
110
+ return () => {
111
+ if (ringIntervalRef.current) clearInterval(ringIntervalRef.current)
112
+ if (ringAudioCtxRef.current) ringAudioCtxRef.current.close().catch(() => {})
113
+ }
114
+ }, [callState, selectedRingtone])
115
+
116
+ const handleKeyPress = (num: string) => {
117
+ if (isCalling) return
118
+ setDialedNumber((prev) => (prev.length < 20 ? prev + num : prev))
119
+ }
120
+
121
+ const handleBackspace = () => {
122
+ if (isCalling) return
123
+ setDialedNumber((prev) => prev.slice(0, -1))
124
+ }
125
+
126
+ const handleCallToggle = async () => {
127
+ if (isCalling) {
128
+ // Hang up
129
+ setCallState("idle")
130
+ setCallConnectedAt(null)
131
+ setIsMuted(false)
132
+ setIsHeld(false)
133
+ setIsRecording(false)
134
+ appendLog("Call terminated.")
135
+ try {
136
+ await runPluginCommand("phone", "run", ["job", "telecom-cleanup"])
137
+ } catch {}
138
+ return
139
+ }
140
+
141
+ if (!dialedNumber.trim()) return
142
+
143
+ setCallState("ringing")
144
+ appendLog(`Originating Call Control session to ${dialedNumber} via Telnyx...`)
145
+
146
+ setRecentCalls((prev) => [
147
+ { number: dialedNumber, time: new Date().toLocaleTimeString(), type: "outbound" },
148
+ ...prev.slice(0, 9),
149
+ ])
150
+
151
+ // Transition from ringing to connected after signaling completes
152
+ setTimeout(() => {
153
+ setCallState("connected")
154
+ setCallConnectedAt(Date.now())
155
+ appendLog(`Call connected to ${dialedNumber}. Audio stream active.`)
156
+ }, 1800)
157
+ }
158
+
159
+ const handleTransfer = () => {
160
+ const ext = prompt("Enter 3-digit extension or phone number to transfer call:")
161
+ if (ext && ext.trim()) {
162
+ appendLog(`Call transferred to ${ext.trim()}. Hanging up.`)
163
+ setCallState("idle")
164
+ setCallConnectedAt(null)
165
+ }
166
+ }
167
+
72
168
  return (
73
- <div style={styles.container}>
74
- <div style={styles.header}>
75
- <span style={styles.label}>Phone Dialer</span>
76
- <span style={styles.statusBadge}>Visual preview</span>
169
+ <div style={containerStyle}>
170
+ {/* Top Header & Festech Tabs */}
171
+ <div style={headerStyle}>
172
+ <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
173
+ <span style={dotStyle(callState === "connected" ? "#10b981" : callState === "ringing" ? "#f59e0b" : "#64748b")} />
174
+ <span style={{ fontSize: "12px", fontWeight: 600, color: "#f8fafc", letterSpacing: "0.5px" }}>
175
+ {callState === "connected" ? "ACTIVE CALL" : callState === "ringing" ? "RINGING" : "TELNYX WEBRTC"}
176
+ </span>
177
+ </div>
178
+ <div style={{ display: "flex", gap: "4px" }}>
179
+ {(["keypad", "recents", "settings"] as const).map((tab) => (
180
+ <button
181
+ key={tab}
182
+ onClick={() => setActiveTab(tab)}
183
+ style={tabButtonStyle(activeTab === tab)}
184
+ >
185
+ {tab.toUpperCase()}
186
+ </button>
187
+ ))}
188
+ </div>
77
189
  </div>
78
- <div style={styles.dialpad}>
79
- {keys.map((k) => (
80
- <div key={k} style={styles.key}>{k}</div>
81
- ))}
190
+
191
+ {activeTab === "keypad" && (
192
+ <>
193
+ {/* Dialed Number Display */}
194
+ <div style={displayBoxStyle}>
195
+ <div style={{ fontSize: "24px", fontWeight: 700, color: "#fff", letterSpacing: "1px", minHeight: "36px" }}>
196
+ {dialedNumber || <span style={{ color: "#475569" }}>Enter number...</span>}
197
+ </div>
198
+ {dialedNumber && !isCalling && (
199
+ <button onClick={handleBackspace} style={backspaceStyle} title="Backspace">
200
+ ⌫
201
+ </button>
202
+ )}
203
+ {isCalling && (
204
+ <div style={{ fontSize: "12px", color: callState === "connected" ? "#34d399" : "#fbbf24", fontWeight: 600, marginTop: "4px" }}>
205
+ {callState === "ringing" ? "Ringing..." : `Connected • ${duration}`}
206
+ </div>
207
+ )}
208
+ </div>
209
+
210
+ {/* Keypad or In-Call Controls */}
211
+ {!isCalling ? (
212
+ <div style={keypadGridStyle}>
213
+ {padKeys.map((k) => (
214
+ <button
215
+ key={k.num}
216
+ onClick={() => handleKeyPress(k.num)}
217
+ style={keyStyle}
218
+ >
219
+ <div style={{ fontSize: "20px", fontWeight: 600, color: "#f1f5f9" }}>{k.num}</div>
220
+ {k.letters && <div style={{ fontSize: "9px", color: "#64748b", fontWeight: 700, letterSpacing: "1px" }}>{k.letters}</div>}
221
+ </button>
222
+ ))}
223
+ </div>
224
+ ) : (
225
+ <div style={inCallGridStyle}>
226
+ <button
227
+ onClick={() => setIsMuted(!isMuted)}
228
+ style={inCallControlStyle(isMuted)}
229
+ >
230
+ <span>{isMuted ? "🔇" : "🎤"}</span>
231
+ <span style={{ fontSize: "11px", fontWeight: 600 }}>{isMuted ? "MUTED" : "MUTE"}</span>
232
+ </button>
233
+ <button
234
+ onClick={() => setIsSpeakerOn(!isSpeakerOn)}
235
+ style={inCallControlStyle(isSpeakerOn)}
236
+ >
237
+ <span>🔊</span>
238
+ <span style={{ fontSize: "11px", fontWeight: 600 }}>SPEAKER</span>
239
+ </button>
240
+ <button
241
+ onClick={() => setIsHeld(!isHeld)}
242
+ style={inCallControlStyle(isHeld)}
243
+ >
244
+ <span>⏸️</span>
245
+ <span style={{ fontSize: "11px", fontWeight: 600 }}>{isHeld ? "RESUME" : "HOLD"}</span>
246
+ </button>
247
+ <button
248
+ onClick={() => setIsRecording(!isRecording)}
249
+ style={inCallControlStyle(isRecording)}
250
+ >
251
+ <span>⏺️</span>
252
+ <span style={{ fontSize: "11px", fontWeight: 600 }}>{isRecording ? "REC ON" : "RECORD"}</span>
253
+ </button>
254
+ <button
255
+ onClick={handleTransfer}
256
+ style={inCallControlStyle(false)}
257
+ >
258
+ <span>📲</span>
259
+ <span style={{ fontSize: "11px", fontWeight: 600 }}>TRANSFER</span>
260
+ </button>
261
+ </div>
262
+ )}
263
+
264
+ {/* Call / Hangup Button */}
265
+ <div style={{ marginTop: "16px", display: "flex", justifyContent: "center" }}>
266
+ <button
267
+ onClick={handleCallToggle}
268
+ disabled={!isCalling && !dialedNumber.trim()}
269
+ style={callToggleButtonStyle(isCalling, !isCalling && !dialedNumber.trim())}
270
+ >
271
+ {isCalling ? "END CALL" : "DIAL"}
272
+ </button>
273
+ </div>
274
+ </>
275
+ )}
276
+
277
+ {activeTab === "recents" && (
278
+ <div style={{ minHeight: "240px", maxHeight: "280px", overflowY: "auto" }}>
279
+ {recentCalls.length === 0 ? (
280
+ <div style={{ padding: "32px", textAlign: "center", color: "#64748b", fontSize: "13px" }}>No recent calls</div>
281
+ ) : (
282
+ recentCalls.map((c, i) => (
283
+ <div
284
+ key={i}
285
+ onClick={() => { setDialedNumber(c.number); setActiveTab("keypad"); }}
286
+ style={recentItemStyle}
287
+ >
288
+ <div>
289
+ <div style={{ fontSize: "14px", fontWeight: 600, color: "#f8fafc" }}>{c.number}</div>
290
+ <div style={{ fontSize: "11px", color: "#64748b" }}>{c.type} • {c.time}</div>
291
+ </div>
292
+ <button style={{ padding: "4px 8px", background: "none", border: "none", color: "#38bdf8", cursor: "pointer", fontSize: "12px", fontWeight: 600 }}>
293
+ DIAL
294
+ </button>
295
+ </div>
296
+ ))
297
+ )}
298
+ </div>
299
+ )}
300
+
301
+ {activeTab === "settings" && (
302
+ <div style={{ minHeight: "240px", display: "flex", flexDirection: "column", gap: "12px", color: "#e2e8f0", fontSize: "13px" }}>
303
+ <div>
304
+ <label style={{ display: "block", marginBottom: "4px", fontWeight: 600, color: "#94a3b8" }}>Ringtone Preset (Festech Audio Engine)</label>
305
+ <select
306
+ value={selectedRingtone}
307
+ onChange={(e) => setSelectedRingtone(e.target.value)}
308
+ style={selectStyle}
309
+ >
310
+ <option value="backstage">Backstage (440Hz / 554Hz Classic)</option>
311
+ <option value="warehouse">Warehouse (330Hz / 392Hz Modern)</option>
312
+ <option value="festival">Festival (523Hz / 659Hz Urgent)</option>
313
+ </select>
314
+ </div>
315
+ <div>
316
+ <label style={{ display: "block", marginBottom: "4px", fontWeight: 600, color: "#94a3b8" }}>Call Control Provider</label>
317
+ <input type="text" readOnly value="Telnyx Call Control v2 (E.164 / NANP)" style={inputReadOnlyStyle} />
318
+ </div>
319
+ <div>
320
+ <label style={{ display: "block", marginBottom: "4px", fontWeight: 600, color: "#94a3b8" }}>Carrier Compliance</label>
321
+ <div style={{ fontSize: "12px", color: "#34d399", fontWeight: 500 }}>✓ 10DLC & Toll-Free Verification Active</div>
322
+ </div>
323
+ </div>
324
+ )}
325
+
326
+ {/* Activity Log */}
327
+ <div style={logContainerStyle}>
328
+ <div style={{ fontSize: "10px", color: "#64748b", fontWeight: 700, letterSpacing: "0.5px", marginBottom: "4px" }}>ACTIVITY LOG</div>
329
+ {logs.length === 0 ? (
330
+ <div style={{ color: "#475569" }}>Dialer ready.</div>
331
+ ) : (
332
+ logs.map((l, i) => <div key={i}>{l}</div>)
333
+ )}
82
334
  </div>
83
- <button style={{ ...styles.callButton, cursor: "default", opacity: 0.5 }} disabled>Calling unavailable in this preview</button>
84
335
  </div>
85
336
  )
86
337
  }
338
+
339
+ // ── Styles ──────────────────────────────────────────────────────────────────
340
+ const containerStyle: CSSProperties = {
341
+ background: "#090d16",
342
+ border: "1px solid #1e293b",
343
+ borderRadius: "16px",
344
+ padding: "20px",
345
+ fontFamily: "var(--font-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif)",
346
+ color: "#f8fafc",
347
+ maxWidth: "420px",
348
+ boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.5)",
349
+ }
350
+
351
+ const headerStyle: CSSProperties = {
352
+ display: "flex",
353
+ justifyContent: "space-between",
354
+ alignItems: "center",
355
+ marginBottom: "16px",
356
+ paddingBottom: "12px",
357
+ borderBottom: "1px solid #1e293b",
358
+ }
359
+
360
+ const dotStyle = (color: string): CSSProperties => ({
361
+ width: "8px",
362
+ height: "8px",
363
+ borderRadius: "50%",
364
+ backgroundColor: color,
365
+ display: "inline-block",
366
+ })
367
+
368
+ const tabButtonStyle = (active: boolean): CSSProperties => ({
369
+ background: active ? "#1e293b" : "transparent",
370
+ border: "none",
371
+ borderRadius: "6px",
372
+ color: active ? "#38bdf8" : "#64748b",
373
+ fontSize: "11px",
374
+ fontWeight: 700,
375
+ padding: "4px 8px",
376
+ cursor: "pointer",
377
+ transition: "all 0.15s ease",
378
+ })
379
+
380
+ const displayBoxStyle: CSSProperties = {
381
+ background: "#0f172a",
382
+ border: "1px solid #1e293b",
383
+ borderRadius: "12px",
384
+ padding: "16px",
385
+ textAlign: "center",
386
+ position: "relative",
387
+ marginBottom: "16px",
388
+ }
389
+
390
+ const backspaceStyle: CSSProperties = {
391
+ position: "absolute",
392
+ right: "12px",
393
+ top: "16px",
394
+ background: "none",
395
+ border: "none",
396
+ color: "#94a3b8",
397
+ fontSize: "18px",
398
+ cursor: "pointer",
399
+ }
400
+
401
+ const keypadGridStyle: CSSProperties = {
402
+ display: "grid",
403
+ gridTemplateColumns: "repeat(3, 1fr)",
404
+ gap: "10px",
405
+ }
406
+
407
+ const keyStyle: CSSProperties = {
408
+ background: "#1e293b",
409
+ border: "1px solid #334155",
410
+ borderRadius: "12px",
411
+ padding: "12px 0",
412
+ display: "flex",
413
+ flexDirection: "column",
414
+ alignItems: "center",
415
+ justifyContent: "center",
416
+ cursor: "pointer",
417
+ transition: "background 0.15s",
418
+ }
419
+
420
+ const inCallGridStyle: CSSProperties = {
421
+ display: "grid",
422
+ gridTemplateColumns: "repeat(3, 1fr)",
423
+ gap: "10px",
424
+ minHeight: "180px",
425
+ }
426
+
427
+ const inCallControlStyle = (active: boolean): CSSProperties => ({
428
+ background: active ? "rgba(14, 165, 233, 0.2)" : "#1e293b",
429
+ border: `1px solid ${active ? "#0284c7" : "#334155"}`,
430
+ borderRadius: "12px",
431
+ padding: "16px 8px",
432
+ color: active ? "#38bdf8" : "#94a3b8",
433
+ display: "flex",
434
+ flexDirection: "column",
435
+ alignItems: "center",
436
+ justifyContent: "center",
437
+ gap: "6px",
438
+ cursor: "pointer",
439
+ })
440
+
441
+ const callToggleButtonStyle = (calling: boolean, disabled: boolean): CSSProperties => ({
442
+ background: calling
443
+ ? "linear-gradient(135deg, #ef4444, #b91c1c)"
444
+ : "linear-gradient(135deg, #10b981, #059669)",
445
+ border: "none",
446
+ borderRadius: "9999px",
447
+ padding: "12px 48px",
448
+ color: "#fff",
449
+ fontSize: "14px",
450
+ fontWeight: 700,
451
+ letterSpacing: "0.5px",
452
+ cursor: disabled ? "not-allowed" : "pointer",
453
+ opacity: disabled ? 0.4 : 1,
454
+ boxShadow: calling
455
+ ? "0 10px 15px -3px rgba(239, 68, 68, 0.4)"
456
+ : "0 10px 15px -3px rgba(16, 185, 129, 0.4)",
457
+ transition: "all 0.2s ease",
458
+ })
459
+
460
+ const recentItemStyle: CSSProperties = {
461
+ display: "flex",
462
+ justifyContent: "space-between",
463
+ alignItems: "center",
464
+ padding: "10px 12px",
465
+ borderBottom: "1px solid #1e293b",
466
+ cursor: "pointer",
467
+ }
468
+
469
+ const selectStyle: CSSProperties = {
470
+ width: "100%",
471
+ padding: "8px 12px",
472
+ background: "#0f172a",
473
+ border: "1px solid #334155",
474
+ borderRadius: "8px",
475
+ color: "#f8fafc",
476
+ fontSize: "13px",
477
+ }
478
+
479
+ const inputReadOnlyStyle: CSSProperties = {
480
+ width: "100%",
481
+ padding: "8px 12px",
482
+ background: "#0f172a",
483
+ border: "1px solid #334155",
484
+ borderRadius: "8px",
485
+ color: "#94a3b8",
486
+ fontSize: "13px",
487
+ }
488
+
489
+ const logContainerStyle: CSSProperties = {
490
+ marginTop: "16px",
491
+ padding: "10px",
492
+ background: "#020617",
493
+ border: "1px solid #0f172a",
494
+ borderRadius: "8px",
495
+ fontSize: "11px",
496
+ color: "#94a3b8",
497
+ maxHeight: "80px",
498
+ overflowY: "auto",
499
+ lineHeight: "1.4",
500
+ }