mongotokyo 1.0.8

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.
@@ -0,0 +1,147 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ :root {
8
+ --mono: 'JetBrains Mono', monospace;
9
+ /* Darker text for readability but still faint */
10
+ --text-color: rgba(0, 0, 0, 0.5);
11
+ --accent: rgba(0, 0, 0, 0.2);
12
+ }
13
+
14
+ html, body {
15
+ width: 100vw;
16
+ height: 100vh;
17
+ background: transparent;
18
+ overflow: hidden;
19
+ font-family: var(--mono);
20
+ pointer-events: none;
21
+ user-select: none;
22
+ }
23
+
24
+ /* ─── Loading Dot ─── */
25
+ #loadingDot {
26
+ position: fixed;
27
+ bottom: 8px;
28
+ right: 8px;
29
+ width: 3px;
30
+ height: 3px;
31
+ border-radius: 50%;
32
+ background: var(--accent);
33
+ opacity: 0;
34
+ }
35
+ #loadingDot.visible {
36
+ opacity: 1;
37
+ animation: pulse 2s infinite;
38
+ }
39
+ @keyframes pulse {
40
+ 0%, 100% { opacity: 0.05; }
41
+ 50% { opacity: 0.25; }
42
+ }
43
+
44
+ /* ─── Error Dot ─── */
45
+ #errorDot {
46
+ position: fixed;
47
+ bottom: 8px;
48
+ right: 8px;
49
+ width: 4px;
50
+ height: 4px;
51
+ border-radius: 50%;
52
+ background: rgba(255, 0, 0, 0.4); /* Faint red */
53
+ opacity: 0;
54
+ pointer-events: none;
55
+ }
56
+ #errorDot.visible {
57
+ opacity: 1;
58
+ }
59
+
60
+ /* ─── Answer Panel ─── */
61
+ #answerPanel {
62
+ position: fixed;
63
+ bottom: 8px;
64
+ right: 8px;
65
+ display: flex;
66
+ flex-direction: column;
67
+ align-items: flex-end;
68
+ opacity: 0;
69
+ transition: opacity 0.4s ease;
70
+ pointer-events: auto; /* Enable scrolling */
71
+ }
72
+ #answerPanel.visible { opacity: 1; }
73
+
74
+ #answerLines {
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: 1px;
78
+ align-items: flex-end;
79
+ }
80
+
81
+ /* ─── Answer Lines (MCQ) ─── */
82
+ .answer-line {
83
+ font-size: 8px;
84
+ color: var(--text-color);
85
+ background: transparent;
86
+ text-align: right;
87
+ }
88
+
89
+ /* ─── DSA Code Block (Tiny & Scrollable) ─── */
90
+ .code-block {
91
+ width: 300px; /* Slightly wider */
92
+ max-height: 45px; /* Exactly ~3 lines of 10px text */
93
+ background: rgba(255, 255, 255, 0.02); /* Very subtle background */
94
+ overflow-y: auto; /* Vertical scroll only */
95
+ overflow-x: hidden;
96
+ scrollbar-width: none;
97
+ position: relative;
98
+ border-radius: 4px;
99
+ }
100
+ .code-block::-webkit-scrollbar {
101
+ display: none; /* Hide scrollbar Chrome/Safari */
102
+ }
103
+
104
+ .copy-btn {
105
+ position: absolute;
106
+ top: 1px;
107
+ right: 1px;
108
+ font-family: var(--mono);
109
+ font-size: 8px;
110
+ color: var(--text-color);
111
+ background: rgba(255, 255, 255, 0.1);
112
+ border: 1px solid var(--accent);
113
+ border-radius: 3px;
114
+ padding: 1px 5px;
115
+ cursor: pointer;
116
+ opacity: 0.4;
117
+ transition: opacity 0.2s;
118
+ z-index: 100;
119
+ }
120
+ .copy-btn:hover {
121
+ opacity: 0.8;
122
+ background: rgba(255, 255, 255, 0.1);
123
+ }
124
+
125
+ .code-pre {
126
+ font-family: var(--mono);
127
+ font-size: 10px; /* Readable proper font */
128
+ line-height: 1.4;
129
+ color: var(--text-color);
130
+ white-space: pre-wrap;
131
+ word-break: break-word;
132
+ text-align: left;
133
+ padding: 4px;
134
+ margin-top: 0;
135
+ background: transparent;
136
+ }
137
+
138
+ /* ─── Saved Toast ─── */
139
+ #savedToast {
140
+ position: fixed;
141
+ bottom: 8px;
142
+ right: 8px;
143
+ font-size: 7px;
144
+ color: var(--text-color);
145
+ opacity: 0;
146
+ }
147
+ #savedToast.visible { opacity: 1; }
@@ -0,0 +1,180 @@
1
+ const api = window.electronAPI;
2
+
3
+ // ─── Elements ──────────────────────────────────────────────────────────────────
4
+ const loadingDot = document.getElementById('loadingDot');
5
+ const answerPanel = document.getElementById('answerPanel');
6
+ const answerLines = document.getElementById('answerLines');
7
+ const savedToast = document.getElementById('savedToast');
8
+ const errorDot = document.getElementById('errorDot');
9
+
10
+ // ─── State ─────────────────────────────────────────────────────────────────────
11
+ let isHidden = false;
12
+ let savedToastTimer = null;
13
+
14
+ // ─── Helpers ───────────────────────────────────────────────────────────────────
15
+ function showLoading() {
16
+ loadingDot.classList.add('visible');
17
+ errorDot.classList.remove('visible');
18
+ }
19
+
20
+ function showError() {
21
+ loadingDot.classList.remove('visible');
22
+ errorDot.classList.add('visible');
23
+ setTimeout(() => errorDot.classList.remove('visible'), 4000);
24
+ }
25
+
26
+ function hideAll() {
27
+ loadingDot.classList.remove('visible');
28
+ errorDot.classList.remove('visible');
29
+ answerPanel.classList.remove('visible');
30
+ savedToast.classList.remove('visible');
31
+ }
32
+
33
+ function flashSavedToast(folderName) {
34
+ if (savedToastTimer) clearTimeout(savedToastTimer);
35
+ savedToast.textContent = `✓ Saved → ${folderName}`;
36
+ savedToast.classList.add('visible');
37
+ savedToastTimer = setTimeout(() => {
38
+ savedToast.classList.remove('visible');
39
+ }, 4000);
40
+ }
41
+
42
+ function showAnswerPanel() {
43
+ if (!isHidden) {
44
+ answerPanel.classList.add('visible');
45
+ }
46
+ }
47
+
48
+ // ─── Render Answer ──────────────────────────────────────────────────────────────
49
+ function renderAnswer(data) {
50
+ loadingDot.classList.remove('visible');
51
+ answerLines.innerHTML = '';
52
+
53
+ const type = data.type || '';
54
+
55
+ if (type === 'web') {
56
+ // MERN/Web: just show a saved toast, files are on Desktop
57
+ const name = data.questionName || 'WebProject';
58
+ flashSavedToast(name);
59
+
60
+ } else if (type === 'dsa') {
61
+ // DSA: render code on screen in faint style
62
+ renderDSACode(data);
63
+
64
+ } else if (type === 'mcq') {
65
+ // MCQ: render answers list
66
+ renderMCQAnswers(data.answers || []);
67
+
68
+ } else if (type === 'mixed') {
69
+ // Mixed: could have both MCQ answers and code
70
+ if (data.files && data.files.length > 0) {
71
+ renderDSACode(data);
72
+ }
73
+ if (data.answers && data.answers.length > 0) {
74
+ renderMCQAnswers(data.answers);
75
+ }
76
+ }
77
+ }
78
+
79
+ // ─── DSA Code Renderer ─────────────────────────────────────────────────────────
80
+ function renderDSACode(data) {
81
+ answerLines.innerHTML = '';
82
+
83
+ // Render each file (usually just one for DSA)
84
+ (data.files || []).forEach((file) => {
85
+ if (!file.content) return;
86
+
87
+ const block = document.createElement('div');
88
+ block.className = 'code-block';
89
+
90
+ const copyBtn = document.createElement('button');
91
+ copyBtn.className = 'copy-btn';
92
+ copyBtn.textContent = 'Copy';
93
+ copyBtn.onclick = () => {
94
+ navigator.clipboard.writeText(file.content);
95
+ copyBtn.textContent = 'Copied!';
96
+ setTimeout(() => { copyBtn.textContent = 'Copy'; }, 1000);
97
+ };
98
+ block.appendChild(copyBtn);
99
+
100
+ const pre = document.createElement('pre');
101
+ pre.className = 'code-pre';
102
+
103
+ // Ensure content is a string joined by newlines, not commas
104
+ let content = file.content;
105
+ if (Array.isArray(content)) {
106
+ content = content.join('\n');
107
+ }
108
+ pre.textContent = content;
109
+ block.appendChild(pre);
110
+
111
+ answerLines.appendChild(block);
112
+ });
113
+
114
+ showAnswerPanel();
115
+ }
116
+
117
+ // ─── MCQ Renderer ──────────────────────────────────────────────────────────────
118
+ function renderMCQAnswers(answers) {
119
+ if (answers.length === 0) return;
120
+
121
+ answers.forEach((item, i) => {
122
+ const line = document.createElement('div');
123
+ line.className = 'answer-line';
124
+ // Only show the answer itself (e.g., "A" or "Heap")
125
+ line.textContent = item.answer || '?';
126
+ answerLines.appendChild(line);
127
+ });
128
+
129
+ showAnswerPanel();
130
+ }
131
+
132
+ // ─── IPC Listeners ─────────────────────────────────────────────────────────────
133
+ api.onStatus(({ type }) => {
134
+ if (type === 'loading') {
135
+ showLoading();
136
+ } else if (type === 'idle') {
137
+ hideAll();
138
+ } else if (type === 'error') {
139
+ showError();
140
+ } else if (type === 'info') {
141
+ // Optional: show a tiny brief indicator for mouse mode
142
+ }
143
+ });
144
+
145
+ api.onAnswer((data) => renderAnswer(data));
146
+
147
+ api.onClear(() => {
148
+ hideAll();
149
+ answerLines.innerHTML = '';
150
+ });
151
+
152
+ // ─── Mouse Interaction logic handled by Ctrl+Shift+M ───────────────────────────
153
+
154
+
155
+ // ─── Panic (` key) ─────────────────────────────────────────────────────────────
156
+ api.onPanic((state) => {
157
+ if (state === 'hide') {
158
+ isHidden = true;
159
+ answerPanel.classList.remove('visible');
160
+ savedToast.classList.remove('visible');
161
+ } else if (state === 'show') {
162
+ isHidden = false;
163
+ if (answerLines.children.length > 0) {
164
+ answerPanel.classList.add('visible');
165
+ }
166
+ }
167
+ });
168
+
169
+ // ─── Toggle Text (Ctrl+Shift+H) ────────────────────────────────────────────────
170
+ api.onToggleText(() => {
171
+ if (answerPanel.classList.contains('visible')) {
172
+ answerPanel.classList.remove('visible');
173
+ isHidden = true;
174
+ } else {
175
+ isHidden = false;
176
+ if (answerLines.children.length > 0) {
177
+ answerPanel.classList.add('visible');
178
+ }
179
+ }
180
+ });
@@ -0,0 +1,54 @@
1
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2
+
3
+ html, body {
4
+ width: 100vw;
5
+ height: 100vh;
6
+ background: rgba(0, 0, 0, 0.35);
7
+ cursor: crosshair;
8
+ overflow: hidden;
9
+ user-select: none;
10
+ }
11
+
12
+ .crosshair-h {
13
+ position: fixed;
14
+ left: 0; right: 0;
15
+ height: 1px;
16
+ background: rgba(124, 106, 247, 0.5);
17
+ pointer-events: none;
18
+ top: -999px;
19
+ transition: top 0s;
20
+ }
21
+
22
+ .crosshair-v {
23
+ position: fixed;
24
+ top: 0; bottom: 0;
25
+ width: 1px;
26
+ background: rgba(124, 106, 247, 0.5);
27
+ pointer-events: none;
28
+ left: -999px;
29
+ }
30
+
31
+ .selection-box {
32
+ position: fixed;
33
+ border: 2px solid #7c6af7;
34
+ background: rgba(124, 106, 247, 0.08);
35
+ box-shadow: 0 0 0 1px rgba(124,106,247,0.3), inset 0 0 20px rgba(124,106,247,0.05);
36
+ display: none;
37
+ pointer-events: none;
38
+ }
39
+
40
+ .coords-tip {
41
+ position: fixed;
42
+ top: 16px;
43
+ left: 50%;
44
+ transform: translateX(-50%);
45
+ background: rgba(10,10,18,0.9);
46
+ color: #94a3b8;
47
+ font-family: 'Inter', 'Segoe UI', sans-serif;
48
+ font-size: 12px;
49
+ padding: 6px 16px;
50
+ border-radius: 20px;
51
+ border: 1px solid rgba(255,255,255,0.08);
52
+ white-space: nowrap;
53
+ pointer-events: none;
54
+ }
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Select Region</title>
6
+ <link rel="stylesheet" href="selector.css" />
7
+ </head>
8
+ <body>
9
+ <div class="crosshair-h"></div>
10
+ <div class="crosshair-v"></div>
11
+ <div class="selection-box" id="selectionBox"></div>
12
+ <div class="coords-tip" id="coordsTip">Click and drag to select region · ESC to cancel</div>
13
+ <script src="selector.js"></script>
14
+ </body>
15
+ </html>
@@ -0,0 +1,74 @@
1
+ const api = window.electronAPI;
2
+
3
+ const selBox = document.getElementById('selectionBox');
4
+ const tip = document.getElementById('coordsTip');
5
+ const crossH = document.querySelector('.crosshair-h');
6
+ const crossV = document.querySelector('.crosshair-v');
7
+
8
+ let startX = 0, startY = 0;
9
+ let isDragging = false;
10
+
11
+ // Move crosshairs with mouse
12
+ document.addEventListener('mousemove', (e) => {
13
+ crossH.style.top = e.clientY + 'px';
14
+ crossV.style.left = e.clientX + 'px';
15
+
16
+ if (isDragging) {
17
+ const x = Math.min(e.clientX, startX);
18
+ const y = Math.min(e.clientY, startY);
19
+ const w = Math.abs(e.clientX - startX);
20
+ const h = Math.abs(e.clientY - startY);
21
+
22
+ selBox.style.left = x + 'px';
23
+ selBox.style.top = y + 'px';
24
+ selBox.style.width = w + 'px';
25
+ selBox.style.height = h + 'px';
26
+
27
+ tip.textContent = `${Math.round(w)} × ${Math.round(h)} px · Release to capture`;
28
+ }
29
+ });
30
+
31
+ document.addEventListener('mousedown', (e) => {
32
+ if (e.button !== 0) return;
33
+ isDragging = true;
34
+ startX = e.clientX;
35
+ startY = e.clientY;
36
+
37
+ selBox.style.left = startX + 'px';
38
+ selBox.style.top = startY + 'px';
39
+ selBox.style.width = '0px';
40
+ selBox.style.height = '0px';
41
+ selBox.style.display = 'block';
42
+ });
43
+
44
+ document.addEventListener('mouseup', (e) => {
45
+ if (!isDragging) return;
46
+ isDragging = false;
47
+
48
+ const x = Math.min(e.clientX, startX);
49
+ const y = Math.min(e.clientY, startY);
50
+ const w = Math.abs(e.clientX - startX);
51
+ const h = Math.abs(e.clientY - startY);
52
+
53
+ if (w < 20 || h < 20) {
54
+ // Too small, cancel
55
+ api.cancelSelection();
56
+ return;
57
+ }
58
+
59
+ // Use devicePixelRatio for HiDPI screens
60
+ const dpr = window.devicePixelRatio || 1;
61
+ api.sendRegion({
62
+ x: x * dpr,
63
+ y: y * dpr,
64
+ width: w * dpr,
65
+ height: h * dpr
66
+ });
67
+ });
68
+
69
+ // Cancel on Escape
70
+ document.addEventListener('keydown', (e) => {
71
+ if (e.key === 'Escape') {
72
+ api.cancelSelection();
73
+ }
74
+ });
@@ -0,0 +1,236 @@
1
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2
+
3
+ :root {
4
+ --bg: rgba(8, 8, 16, 0.96);
5
+ --surface: rgba(255,255,255,0.04);
6
+ --surface-hover: rgba(255,255,255,0.07);
7
+ --border: rgba(255,255,255,0.08);
8
+ --accent: #7c6af7;
9
+ --green: #34d399;
10
+ --yellow: #fbbf24;
11
+ --text: #e2e8f0;
12
+ --text-dim: #94a3b8;
13
+ --text-muted: #64748b;
14
+ --radius: 16px;
15
+ }
16
+
17
+ html, body {
18
+ width: 100%; height: 100%;
19
+ background: transparent;
20
+ font-family: 'Inter', 'Segoe UI', sans-serif;
21
+ color: var(--text);
22
+ overflow: hidden;
23
+ }
24
+
25
+ .window {
26
+ width: 100%; height: 100%;
27
+ background: var(--bg);
28
+ border: 1px solid var(--border);
29
+ border-radius: var(--radius);
30
+ backdrop-filter: blur(32px);
31
+ -webkit-backdrop-filter: blur(32px);
32
+ box-shadow: 0 40px 100px rgba(0,0,0,0.7), 0 0 0 1px rgba(124,106,247,0.12);
33
+ display: flex;
34
+ flex-direction: column;
35
+ overflow: hidden;
36
+ }
37
+
38
+ /* Header */
39
+ .header {
40
+ padding: 28px 28px 20px;
41
+ text-align: center;
42
+ border-bottom: 1px solid var(--border);
43
+ -webkit-app-region: drag;
44
+ background: linear-gradient(180deg, rgba(124,106,247,0.07) 0%, transparent 100%);
45
+ }
46
+
47
+ .header-icon { font-size: 36px; margin-bottom: 8px; }
48
+
49
+ .header-title {
50
+ font-size: 22px;
51
+ font-weight: 700;
52
+ letter-spacing: -0.02em;
53
+ background: linear-gradient(135deg, #fff 0%, #7c6af7 100%);
54
+ -webkit-background-clip: text;
55
+ -webkit-text-fill-color: transparent;
56
+ background-clip: text;
57
+ margin-bottom: 6px;
58
+ }
59
+
60
+ .header-sub {
61
+ font-size: 12.5px;
62
+ color: var(--text-muted);
63
+ line-height: 1.5;
64
+ }
65
+
66
+ /* Body */
67
+ .setup-body {
68
+ flex: 1;
69
+ overflow-y: auto;
70
+ padding: 20px 24px;
71
+ scrollbar-width: thin;
72
+ scrollbar-color: var(--border) transparent;
73
+ -webkit-app-region: no-drag;
74
+ }
75
+
76
+ .setup-body::-webkit-scrollbar { width: 4px; }
77
+ .setup-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
78
+
79
+ /* Info banner */
80
+ .info-banner {
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 10px;
84
+ background: rgba(124,106,247,0.07);
85
+ border: 1px solid rgba(124,106,247,0.2);
86
+ border-radius: 10px;
87
+ padding: 10px 14px;
88
+ font-size: 11.5px;
89
+ color: var(--text-dim);
90
+ line-height: 1.5;
91
+ margin-bottom: 18px;
92
+ }
93
+
94
+ .info-icon { font-size: 16px; flex-shrink: 0; }
95
+
96
+ /* Fields */
97
+ .fields { display: flex; flex-direction: column; gap: 14px; margin-bottom: 16px; }
98
+
99
+ .field-group { display: flex; flex-direction: column; gap: 6px; }
100
+
101
+ .field-label {
102
+ display: flex;
103
+ align-items: center;
104
+ gap: 8px;
105
+ font-size: 12px;
106
+ font-weight: 500;
107
+ color: var(--text-dim);
108
+ }
109
+
110
+ .provider-badge {
111
+ width: 20px; height: 20px;
112
+ border-radius: 50%;
113
+ display: flex; align-items: center; justify-content: center;
114
+ font-size: 10px; font-weight: 700;
115
+ flex-shrink: 0;
116
+ }
117
+
118
+ .provider-badge.gemini { background: rgba(52,211,153,0.2); color: var(--green); }
119
+ .provider-badge.groq { background: rgba(124,106,247,0.2); color: var(--accent); }
120
+ .provider-badge.openrouter { background: rgba(251,191,36,0.2); color: var(--yellow); }
121
+ .provider-badge.openai { background: rgba(255,255,255,0.1); color: #fff; }
122
+
123
+ .badge {
124
+ font-size: 9px;
125
+ padding: 2px 6px;
126
+ border-radius: 20px;
127
+ font-weight: 600;
128
+ text-transform: uppercase;
129
+ letter-spacing: 0.06em;
130
+ margin-left: auto;
131
+ }
132
+
133
+ .badge.free { background: rgba(52,211,153,0.12); color: var(--green); border: 1px solid rgba(52,211,153,0.25); }
134
+ .badge.paid { background: rgba(251,191,36,0.12); color: var(--yellow); border: 1px solid rgba(251,191,36,0.25); }
135
+
136
+ .input-row {
137
+ display: flex;
138
+ gap: 6px;
139
+ align-items: center;
140
+ }
141
+
142
+ .key-input {
143
+ flex: 1;
144
+ background: var(--surface);
145
+ border: 1px solid var(--border);
146
+ border-radius: 8px;
147
+ padding: 9px 12px;
148
+ color: var(--text);
149
+ font-size: 12.5px;
150
+ font-family: 'JetBrains Mono', 'Courier New', monospace;
151
+ outline: none;
152
+ transition: border-color 0.15s;
153
+ }
154
+
155
+ .key-input:focus { border-color: var(--accent); }
156
+ .key-input::placeholder { color: var(--text-muted); }
157
+
158
+ .eye-btn {
159
+ width: 34px; height: 34px;
160
+ background: var(--surface);
161
+ border: 1px solid var(--border);
162
+ border-radius: 8px;
163
+ cursor: pointer;
164
+ font-size: 14px;
165
+ display: flex; align-items: center; justify-content: center;
166
+ flex-shrink: 0;
167
+ transition: background 0.15s;
168
+ }
169
+
170
+ .eye-btn:hover { background: var(--surface-hover); }
171
+
172
+ .get-key-link {
173
+ font-size: 10.5px;
174
+ color: var(--accent);
175
+ text-decoration: none;
176
+ margin-left: 28px;
177
+ opacity: 0.8;
178
+ transition: opacity 0.15s;
179
+ }
180
+
181
+ .get-key-link:hover { opacity: 1; }
182
+
183
+ /* Notes */
184
+ .storage-note {
185
+ font-size: 10.5px;
186
+ color: var(--text-muted);
187
+ text-align: center;
188
+ padding: 10px 0 6px;
189
+ line-height: 1.6;
190
+ }
191
+
192
+ .storage-note code {
193
+ font-family: 'JetBrains Mono', monospace;
194
+ color: var(--text-dim);
195
+ background: var(--surface);
196
+ padding: 1px 5px;
197
+ border-radius: 4px;
198
+ }
199
+
200
+ .error-msg {
201
+ font-size: 11.5px;
202
+ color: #f87171;
203
+ text-align: center;
204
+ padding: 8px;
205
+ background: rgba(248,113,113,0.08);
206
+ border: 1px solid rgba(248,113,113,0.2);
207
+ border-radius: 8px;
208
+ margin-bottom: 10px;
209
+ }
210
+
211
+ .hidden { display: none !important; }
212
+
213
+ /* Save button */
214
+ .save-btn {
215
+ width: 100%;
216
+ padding: 12px;
217
+ background: linear-gradient(135deg, #7c6af7, #5b47e0);
218
+ color: white;
219
+ border: none;
220
+ border-radius: 10px;
221
+ font-size: 13.5px;
222
+ font-weight: 600;
223
+ font-family: 'Inter', sans-serif;
224
+ cursor: pointer;
225
+ display: flex;
226
+ align-items: center;
227
+ justify-content: center;
228
+ gap: 8px;
229
+ transition: opacity 0.15s, transform 0.1s;
230
+ box-shadow: 0 4px 20px rgba(124,106,247,0.4);
231
+ margin-top: 8px;
232
+ }
233
+
234
+ .save-btn:hover { opacity: 0.9; transform: translateY(-1px); }
235
+ .save-btn:active { transform: translateY(0); }
236
+ .btn-arrow { font-size: 16px; }