@necrolab/dashboard 0.4.47 → 0.4.49

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 (39) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/exit +209 -0
  3. package/index.html +1 -1
  4. package/package.json +1 -1
  5. package/postinstall.js +9 -0
  6. package/public/manifest.json +8 -3
  7. package/src/assets/css/_input.scss +104 -111
  8. package/src/assets/css/_utilities.scss +441 -0
  9. package/src/assets/css/main.scss +228 -154
  10. package/src/components/Auth/LoginForm.vue +8 -8
  11. package/src/components/Editors/Account/Account.vue +156 -146
  12. package/src/components/Editors/Account/AccountCreator.vue +1 -1
  13. package/src/components/Editors/Account/AccountView.vue +13 -13
  14. package/src/components/Editors/Account/CreateAccount.vue +25 -16
  15. package/src/components/Editors/Profile/CreateProfile.vue +1 -1
  16. package/src/components/Editors/Profile/Profile.vue +1 -1
  17. package/src/components/Editors/Profile/ProfileCountryChooser.vue +83 -19
  18. package/src/components/Editors/Profile/ProfileView.vue +11 -11
  19. package/src/components/Tasks/CreateTaskAXS.vue +3 -3
  20. package/src/components/Tasks/CreateTaskTM.vue +7 -35
  21. package/src/components/Tasks/QuickSettings.vue +112 -9
  22. package/src/components/Tasks/Stats.vue +29 -25
  23. package/src/components/Tasks/Task.vue +489 -365
  24. package/src/components/Tasks/TaskView.vue +21 -23
  25. package/src/components/icons/Sandclock.vue +2 -2
  26. package/src/components/icons/Stadium.vue +1 -1
  27. package/src/components/ui/Modal.vue +37 -35
  28. package/src/components/ui/controls/CountryChooser.vue +200 -62
  29. package/src/components/ui/controls/atomic/Dropdown.vue +177 -91
  30. package/src/components/ui/controls/atomic/MultiDropdown.vue +247 -168
  31. package/src/composables/useClickOutside.js +21 -0
  32. package/src/composables/useDropdownPosition.js +174 -0
  33. package/src/stores/ui.js +5 -4
  34. package/src/views/Accounts.vue +2 -2
  35. package/src/views/Console.vue +25 -45
  36. package/src/views/Editor.vue +1194 -730
  37. package/src/views/Profiles.vue +2 -2
  38. package/src/views/Tasks.vue +170 -137
  39. package/tailwind.config.js +47 -21
@@ -1,202 +1,257 @@
1
1
  <template>
2
- <div>
3
- <!-- Heading -->
4
- <h4 class="text-white text-xl font-bold mb-5 pt-5 flex gap-2 items-center">
5
- Editor <img src="@/assets/img/pencil.svg" />
6
- </h4>
7
-
8
- <div class="card-dark p-2">
9
- <div v-if="ui.profile.admin">
10
- <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Admin Editor</h5>
11
- <div class="flex flex-col lg:flex-row lg:justify-between lg:items-center gap-4 editor-controls-row">
12
- <div class="w-full lg:w-64 flex flex-shrink-0 dropdown-container">
13
- <div class="relative flex-grow">
14
- <Dropdown
15
- class="bg-dark-500 border-2 border-r-0 border-dark-550 admin-file-dropdown w-full"
16
- default="Select a file"
17
- :onClick="(f) => loadFile(f)"
18
- :options="availableFiles"
19
- :allowDefault="false"
20
- rightAmount="right-1"
21
- />
22
- </div>
23
- <button
24
- class="refresh-button flex items-center justify-center w-10 h-10 text-white bg-dark-400 rounded-r-lg border-2 border-dark-550 border-l-0"
25
- @click="loadAvailableFiles()"
26
- title="Refresh file list"
27
- >
28
- <ReloadIcon class="refresh-icon" />
29
- </button>
30
- </div>
31
- <!-- Admin editor buttons moved here -->
32
- <div v-if="textEditorVisible" class="flex flex-wrap gap-2 justify-center lg:justify-end">
33
- <button class="btn-action flex-shrink-0" @click="format()" v-if="isJsonFile()">
34
- <svg
35
- xmlns="http://www.w3.org/2000/svg"
36
- width="16"
37
- height="16"
38
- viewBox="0 0 24 24"
39
- fill="none"
40
- stroke="currentColor"
41
- stroke-width="2"
42
- stroke-linecap="round"
43
- stroke-linejoin="round"
44
- >
45
- <path d="M21 10H7" />
46
- <path d="M21 6H3" />
47
- <path d="M21 14H3" />
48
- <path d="M21 18H7" />
49
- </svg>
50
- <span class="hidden sm:inline">Format</span>
51
- </button>
52
- <button class="btn-action flex-shrink-0" @click="saveAll">
53
- <svg
54
- xmlns="http://www.w3.org/2000/svg"
55
- width="16"
56
- height="16"
57
- viewBox="0 0 24 24"
58
- fill="none"
59
- stroke="currentColor"
60
- stroke-width="2"
61
- stroke-linecap="round"
62
- stroke-linejoin="round"
63
- >
64
- <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
65
- <polyline points="17 21 17 13 7 13 7 21" />
66
- <polyline points="7 3 7 8 15 8" />
67
- </svg>
68
- <span class="hidden sm:inline">Save File</span>
69
- </button>
70
- <button class="btn-action flex-shrink-0" @click="closeFile">
71
- <svg
72
- xmlns="http://www.w3.org/2000/svg"
73
- width="16"
74
- height="16"
75
- viewBox="0 0 24 24"
76
- fill="none"
77
- stroke="currentColor"
78
- stroke-width="2"
79
- stroke-linecap="round"
80
- stroke-linejoin="round"
81
- >
82
- <line x1="18" y1="6" x2="6" y2="18"></line>
83
- <line x1="6" y1="6" x2="18" y2="18"></line>
84
- </svg>
85
- <span class="hidden sm:inline">Close File</span>
86
- </button>
87
- </div>
88
- </div>
2
+ <div>
3
+ <!-- Heading -->
4
+ <h4 class="text-white text-xl font-bold mb-5 pt-5 flex gap-2 items-center">
5
+ Editor
6
+ <img src="@/assets/img/pencil.svg" />
7
+ </h4>
8
+
9
+ <div class="card-dark p-2">
10
+ <!-- Admin Editor Section - Hidden when proxy editor is open -->
11
+ <div
12
+ v-if="ui.profile.admin"
13
+ class="admin-editor-section"
14
+ :class="{ 'landscape-hidden': currentProxyList }"
15
+ >
16
+ <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Admin Editor</h5>
17
+ <div class="flex-responsive-wrap editor-controls-row">
18
+ <div
19
+ class="w-full lg:w-64 flex flex-shrink-0 dropdown-container z-dropdown-high"
20
+ >
21
+ <div class="relative flex-grow">
22
+ <Dropdown
23
+ class="bg-dark-500 border-2 border-r-0 border-dark-550 admin-file-dropdown w-full"
24
+ default="Select a file"
25
+ :onClick="(f) => loadFile(f)"
26
+ :options="availableFiles"
27
+ :allowDefault="false"
28
+ :includeAdjacentButtons="true"
29
+ rightAmount="right-1"
30
+ />
89
31
  </div>
32
+ <button
33
+ class="refresh-button flex items-center justify-center w-10 h-10 text-white bg-dark-400 rounded-r-lg border-2 border-dark-550 border-l-0"
34
+ @click="loadAvailableFiles()"
35
+ title="Refresh file list"
36
+ >
37
+ <ReloadIcon class="refresh-icon" />
38
+ </button>
39
+ </div>
40
+ <!-- Admin editor buttons moved here -->
41
+ <div
42
+ v-if="textEditorVisible"
43
+ class="flex flex-wrap gap-responsive justify-center lg:justify-end editor-buttons-responsive"
44
+ >
45
+ <button
46
+ class="btn-action flex-shrink-0"
47
+ @click="format()"
48
+ v-if="isJsonFile()"
49
+ title="Format JSON"
50
+ >
51
+ <svg
52
+ xmlns="http://www.w3.org/2000/svg"
53
+ width="16"
54
+ height="16"
55
+ viewBox="0 0 24 24"
56
+ fill="none"
57
+ stroke="currentColor"
58
+ stroke-width="2"
59
+ stroke-linecap="round"
60
+ stroke-linejoin="round"
61
+ >
62
+ <path d="M21 10H7" />
63
+ <path d="M21 6H3" />
64
+ <path d="M21 14H3" />
65
+ <path d="M21 18H7" />
66
+ </svg>
67
+ <span class="hidden sm:inline">Format</span>
68
+ </button>
69
+ <button class="btn-action flex-shrink-0" @click="saveAll" title="Save File">
70
+ <svg
71
+ xmlns="http://www.w3.org/2000/svg"
72
+ width="16"
73
+ height="16"
74
+ viewBox="0 0 24 24"
75
+ fill="none"
76
+ stroke="currentColor"
77
+ stroke-width="2"
78
+ stroke-linecap="round"
79
+ stroke-linejoin="round"
80
+ >
81
+ <path
82
+ d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"
83
+ />
84
+ <polyline points="17 21 17 13 7 13 7 21" />
85
+ <polyline points="7 3 7 8 15 8" />
86
+ </svg>
87
+ <span class="hidden sm:inline">Save File</span>
88
+ </button>
89
+ <button
90
+ class="btn-action flex-shrink-0"
91
+ @click="closeFile"
92
+ title="Close File"
93
+ >
94
+ <svg
95
+ xmlns="http://www.w3.org/2000/svg"
96
+ width="16"
97
+ height="16"
98
+ viewBox="0 0 24 24"
99
+ fill="none"
100
+ stroke="currentColor"
101
+ stroke-width="2"
102
+ stroke-linecap="round"
103
+ stroke-linejoin="round"
104
+ >
105
+ <line x1="18" y1="6" x2="6" y2="18"></line>
106
+ <line x1="6" y1="6" x2="18" y2="18"></line>
107
+ </svg>
108
+ <span class="hidden sm:inline">Close File</span>
109
+ </button>
110
+ </div>
111
+ </div>
90
112
 
91
- <transition name="fade">
92
- <div v-if="textEditorVisible" class="my-3 relative">
93
- <div class="pb-4">
94
- <!-- Syntax Highlighting Editor -->
95
- <div class="editor-container table-component">
96
- <div class="editor-wrapper">
97
- <pre ref="codeDisplay" class="language-json code-highlight"></pre>
98
- <textarea
99
- ref="codeEditor"
100
- v-model="currentContent"
101
- class="code-editor"
102
- spellcheck="false"
103
- @scroll="syncScroll"
104
- @input="highlightCode"
105
- @keydown.tab.prevent="handleTab"
106
- ></textarea>
107
- </div>
108
- </div>
109
- </div>
110
-
111
- <div v-if="errorMessage" class="error-container">
112
- <div class="error-icon">
113
- <svg
114
- width="16"
115
- height="16"
116
- viewBox="0 0 24 24"
117
- fill="none"
118
- stroke="currentColor"
119
- stroke-width="2"
120
- >
121
- <circle cx="12" cy="12" r="10" />
122
- <line x1="15" y1="9" x2="9" y2="15" />
123
- <line x1="9" y1="9" x2="15" y2="15" />
124
- </svg>
125
- </div>
126
- <div class="error-content">
127
- <div class="error-title">JSON Syntax Error</div>
128
- <div class="error-text">{{ errorMessage }}</div>
129
- </div>
130
- </div>
131
- </div>
132
- </transition>
133
- <div class="border border-dark-650 my-8" />
134
-
135
- <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Proxy Editor</h5>
136
- <div class="flex flex-col lg:flex-row lg:justify-between lg:items-center gap-4 proxy-controls-row mb-4">
137
- <div class="w-full lg:w-64 flex flex-shrink-0 dropdown-container">
138
- <div class="relative flex-grow">
139
- <Dropdown
140
- class="bg-dark-500 border-2 border-dark-550 rounded-lg w-full h-10"
141
- :default="ui.profile.proxyList?.checkout || proxyLists[0]"
142
- :onClick="loadProxies"
143
- :options="proxyLists"
144
- :allowDefault="false"
145
- rightAmount="right-1"
146
- />
147
- </div>
148
- </div>
149
- <!-- Proxy save button moved here -->
150
- <div v-if="currentProxyList" class="flex flex-wrap gap-2 justify-center lg:justify-end">
151
- <button class="btn-action flex-shrink-0" @click="saveProxies">
152
- <svg
153
- xmlns="http://www.w3.org/2000/svg"
154
- width="16"
155
- height="16"
156
- viewBox="0 0 24 24"
157
- fill="none"
158
- stroke="currentColor"
159
- stroke-width="2"
160
- stroke-linecap="round"
161
- stroke-linejoin="round"
162
- >
163
- <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
164
- <polyline points="17 21 17 13 7 13 7 21" />
165
- <polyline points="7 3 7 8 15 8" />
166
- </svg>
167
- <span class="hidden sm:inline">Save Proxies</span>
168
- </button>
169
- <button class="btn-action flex-shrink-0" @click="closeProxyFile">
170
- <svg
171
- xmlns="http://www.w3.org/2000/svg"
172
- width="16"
173
- height="16"
174
- viewBox="0 0 24 24"
175
- fill="none"
176
- stroke="currentColor"
177
- stroke-width="2"
178
- stroke-linecap="round"
179
- stroke-linejoin="round"
180
- >
181
- <line x1="18" y1="6" x2="6" y2="18"></line>
182
- <line x1="6" y1="6" x2="18" y2="18"></line>
183
- </svg>
184
- <span class="hidden sm:inline">Close File</span>
185
- </button>
113
+ <transition name="fade">
114
+ <div v-if="textEditorVisible" class="my-3 relative">
115
+ <div class="pb-4">
116
+ <!-- Syntax Highlighting Editor -->
117
+ <div
118
+ class="editor-container table-component"
119
+ :class="{ 'has-error': errorMessage }"
120
+ >
121
+ <div class="editor-wrapper">
122
+ <pre ref="codeDisplay" class="language-json code-highlight"></pre>
123
+ <textarea
124
+ ref="codeEditor"
125
+ v-model="currentContent"
126
+ class="code-editor"
127
+ spellcheck="false"
128
+ @scroll="syncScroll"
129
+ @input="highlightCode"
130
+ @keydown.tab.prevent="handleTab"
131
+ ></textarea>
186
132
  </div>
133
+ </div>
187
134
  </div>
188
- <!-- Textarea -->
189
- <transition name="fade">
190
- <div v-if="currentProxyList" class="relative my-3">
191
- <div class="pb-4">
192
- <div class="proxy-editor-container table-component">
193
- <textarea v-model="proxyContent" class="proxy-editor" spellcheck="false"></textarea>
194
- </div>
195
- </div>
196
- </div>
197
- </transition>
135
+
136
+ <div v-if="errorMessage" class="error-container">
137
+ <div class="error-icon">
138
+ <svg
139
+ width="16"
140
+ height="16"
141
+ viewBox="0 0 24 24"
142
+ fill="none"
143
+ stroke="currentColor"
144
+ stroke-width="2"
145
+ >
146
+ <circle cx="12" cy="12" r="10" />
147
+ <line x1="15" y1="9" x2="9" y2="15" />
148
+ <line x1="9" y1="9" x2="15" y2="15" />
149
+ </svg>
150
+ </div>
151
+ <div class="error-content">
152
+ <div class="error-title">JSON Syntax Error</div>
153
+ <div class="error-text">{{ errorMessage }}</div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ </transition>
158
+ </div>
159
+
160
+ <!-- Section Divider - Show when both editors are closed -->
161
+ <div
162
+ v-if="ui.profile.admin && !textEditorVisible && !currentProxyList"
163
+ class="section-divider"
164
+ />
165
+
166
+ <!-- Proxy Editor Section - Hidden when admin editor is open -->
167
+ <div
168
+ class="proxy-editor-section"
169
+ :class="{ 'landscape-hidden': textEditorVisible }"
170
+ >
171
+ <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Proxy Editor</h5>
172
+ <div class="flex-responsive-wrap proxy-controls-row mb-1">
173
+ <div class="w-full lg:w-64 flex flex-shrink-0 dropdown-container z-dropdown">
174
+ <div class="relative flex-grow">
175
+ <Dropdown
176
+ class="bg-dark-500 border-2 border-dark-550 proxy-file-dropdown w-full"
177
+ :default="ui.profile.proxyList?.checkout || proxyLists[0]"
178
+ :onClick="loadProxies"
179
+ :options="proxyLists"
180
+ :allowDefault="false"
181
+ :includeAdjacentButtons="true"
182
+ rightAmount="right-1"
183
+ />
184
+ </div>
185
+ </div>
186
+ <!-- Proxy save button moved here -->
187
+ <div
188
+ v-if="currentProxyList"
189
+ class="flex flex-wrap gap-responsive justify-center lg:justify-end editor-buttons-responsive"
190
+ >
191
+ <button
192
+ class="btn-action flex-shrink-0"
193
+ @click="saveProxies"
194
+ title="Save Proxies"
195
+ >
196
+ <svg
197
+ xmlns="http://www.w3.org/2000/svg"
198
+ width="16"
199
+ height="16"
200
+ viewBox="0 0 24 24"
201
+ fill="none"
202
+ stroke="currentColor"
203
+ stroke-width="2"
204
+ stroke-linecap="round"
205
+ stroke-linejoin="round"
206
+ >
207
+ <path
208
+ d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"
209
+ />
210
+ <polyline points="17 21 17 13 7 13 7 21" />
211
+ <polyline points="7 3 7 8 15 8" />
212
+ </svg>
213
+ <span class="hidden sm:inline">Save Proxies</span>
214
+ </button>
215
+ <button
216
+ class="btn-action flex-shrink-0"
217
+ @click="closeProxyFile"
218
+ title="Close File"
219
+ >
220
+ <svg
221
+ xmlns="http://www.w3.org/2000/svg"
222
+ width="16"
223
+ height="16"
224
+ viewBox="0 0 24 24"
225
+ fill="none"
226
+ stroke="currentColor"
227
+ stroke-width="2"
228
+ stroke-linecap="round"
229
+ stroke-linejoin="round"
230
+ >
231
+ <line x1="18" y1="6" x2="6" y2="18"></line>
232
+ <line x1="6" y1="6" x2="18" y2="18"></line>
233
+ </svg>
234
+ <span class="hidden sm:inline">Close File</span>
235
+ </button>
236
+ </div>
198
237
  </div>
238
+ <!-- Textarea -->
239
+ <transition name="fade">
240
+ <div v-if="currentProxyList" class="relative my-3">
241
+ <div class="pb-4">
242
+ <div class="proxy-editor-container table-component">
243
+ <textarea
244
+ v-model="proxyContent"
245
+ class="proxy-editor"
246
+ spellcheck="false"
247
+ ></textarea>
248
+ </div>
249
+ </div>
250
+ </div>
251
+ </transition>
252
+ </div>
199
253
  </div>
254
+ </div>
200
255
  </template>
201
256
  <script setup>
202
257
  import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
@@ -207,12 +262,18 @@ import { getProxyLists, getProxyFile } from "@/stores/requests";
207
262
  import { DEBUG } from "@/utils/debug";
208
263
 
209
264
  const loadFromApi = async (path) => {
210
- const res = await fetch(path);
211
- return res.json();
265
+ const res = await fetch(path);
266
+ return res.json();
212
267
  };
213
268
 
214
269
  const availableFiles = ref([]);
215
- if (DEBUG) availableFiles.value.push("used-codes.json", "proxies.txt", "toMonitor.txt", "test.txt");
270
+ if (DEBUG)
271
+ availableFiles.value.push(
272
+ "used-codes.json",
273
+ "proxies.txt",
274
+ "toMonitor.txt",
275
+ "test.txt"
276
+ );
216
277
  const currentFile = ref("");
217
278
  const textEditorVisible = ref(false);
218
279
  const currentContent = ref("");
@@ -227,336 +288,362 @@ const codeEditor = ref(null);
227
288
  const codeDisplay = ref(null);
228
289
 
229
290
  let previous = {
230
- proxyContent: { list: ui.profile.proxyList, content: "" },
231
- currentContent: { file: "", content: "" }
291
+ proxyContent: { list: ui.profile.proxyList, content: "" },
292
+ currentContent: { file: "", content: "" },
232
293
  };
233
294
 
234
295
  const errorMessage = computed(() => {
235
- if (!currentFile.value.endsWith(".json")) return;
236
-
237
- let err;
238
- try {
239
- JSON.parse(currentContent.value);
240
- } catch (e) {
241
- err = e;
242
- }
243
- return err?.message;
296
+ if (!currentFile.value.endsWith(".json")) return;
297
+
298
+ let err;
299
+ try {
300
+ JSON.parse(currentContent.value);
301
+ } catch (e) {
302
+ err = e;
303
+ }
304
+ return err?.message;
244
305
  });
245
306
 
246
- const loadAvailableFiles = async () => (availableFiles.value = await loadFromApi("/api/json-files"));
307
+ const loadAvailableFiles = async () =>
308
+ (availableFiles.value = await loadFromApi("/api/json-files"));
247
309
 
248
310
  const isJsonFile = () => currentFile.value.endsWith(".json");
249
311
 
250
312
  // Function to highlight code using Prism
251
313
  const highlightCode = () => {
252
- if (!codeDisplay.value || !codeEditor.value) return;
253
-
254
- // Get the language based on file extension
255
- let language = "javascript"; // Default language
256
-
257
- if (currentFile.value) {
258
- if (currentFile.value.endsWith(".json")) {
259
- language = "json";
260
- } else if (currentFile.value.endsWith(".js")) {
261
- language = "javascript";
262
- } else if (currentFile.value.endsWith(".txt") || currentFile.value.endsWith(".csv")) {
263
- language = "text";
264
- // Plain text doesn't need highlighting
265
- codeDisplay.value.textContent = currentContent.value || "";
266
- codeDisplay.value.className = "language-text code-highlight";
267
- return;
268
- }
314
+ if (!codeDisplay.value || !codeEditor.value) return;
315
+
316
+ // Get the language based on file extension
317
+ let language = "javascript"; // Default language
318
+
319
+ if (currentFile.value) {
320
+ if (currentFile.value.endsWith(".json")) {
321
+ language = "json";
322
+ } else if (currentFile.value.endsWith(".js")) {
323
+ language = "javascript";
324
+ } else if (currentFile.value.endsWith(".txt") || currentFile.value.endsWith(".csv")) {
325
+ language = "text";
326
+ // Plain text doesn't need highlighting
327
+ codeDisplay.value.textContent = currentContent.value || "";
328
+ codeDisplay.value.className = "language-text code-highlight";
329
+ return;
269
330
  }
331
+ }
270
332
 
271
- // Ensure Prism is available
272
- if (typeof Prism === "undefined") {
273
- console.error("Prism is not loaded");
274
- return;
275
- }
333
+ // Ensure Prism is available
334
+ if (typeof Prism === "undefined") {
335
+ console.error("Prism is not loaded");
336
+ return;
337
+ }
276
338
 
277
- // Use requestAnimationFrame for smoother updates
278
- requestAnimationFrame(() => {
279
- try {
280
- // Update the pre element with highlighted HTML
281
- const highlighted = Prism.highlight(currentContent.value || "", Prism.languages[language], language);
282
- codeDisplay.value.innerHTML = highlighted;
283
- codeDisplay.value.className = `language-${language} code-highlight`;
284
- } catch (e) {
285
- console.error("Highlight error:", e);
286
- // Fallback to plain text if highlighting fails
287
- codeDisplay.value.textContent = currentContent.value || "";
288
- }
339
+ // Use requestAnimationFrame for smoother updates
340
+ requestAnimationFrame(() => {
341
+ try {
342
+ // Update the pre element with highlighted HTML
343
+ const highlighted = Prism.highlight(
344
+ currentContent.value || "",
345
+ Prism.languages[language],
346
+ language
347
+ );
348
+ codeDisplay.value.innerHTML = highlighted;
349
+ codeDisplay.value.className = `language-${language} code-highlight`;
350
+ } catch (e) {
351
+ console.error("Highlight error:", e);
352
+ // Fallback to plain text if highlighting fails
353
+ codeDisplay.value.textContent = currentContent.value || "";
354
+ }
289
355
 
290
- // Ensure scroll positions are synced after highlighting
291
- syncScroll();
292
- });
356
+ // Ensure scroll positions are synced after highlighting
357
+ syncScroll();
358
+ });
293
359
  };
294
360
 
295
361
  // Function to sync scrolling between textarea and highlighted code
296
362
  const syncScroll = () => {
297
- if (!codeDisplay.value || !codeEditor.value) return;
363
+ if (!codeDisplay.value || !codeEditor.value) return;
298
364
 
299
- // Synchronize scrolling between the textarea and the highlighted code
300
- requestAnimationFrame(() => {
301
- codeDisplay.value.scrollTop = codeEditor.value.scrollTop;
302
- codeDisplay.value.scrollLeft = codeEditor.value.scrollLeft;
303
- });
365
+ // Synchronize scrolling between the textarea and the highlighted code
366
+ requestAnimationFrame(() => {
367
+ codeDisplay.value.scrollTop = codeEditor.value.scrollTop;
368
+ codeDisplay.value.scrollLeft = codeEditor.value.scrollLeft;
369
+ });
304
370
  };
305
371
 
306
372
  // Function to handle tab key press in the editor
307
373
  const handleTab = (e) => {
308
- const textarea = codeEditor.value;
309
- const start = textarea.selectionStart;
310
- const end = textarea.selectionEnd;
311
-
312
- // Insert 4 spaces at cursor position
313
- const spaces = " ";
314
- currentContent.value = currentContent.value.substring(0, start) + spaces + currentContent.value.substring(end);
315
-
316
- // Move cursor position after the inserted tab
317
- nextTick(() => {
318
- textarea.selectionStart = textarea.selectionEnd = start + spaces.length;
319
- highlightCode();
320
- });
374
+ const textarea = codeEditor.value;
375
+ const start = textarea.selectionStart;
376
+ const end = textarea.selectionEnd;
377
+
378
+ // Insert 4 spaces at cursor position
379
+ const spaces = " ";
380
+ currentContent.value =
381
+ currentContent.value.substring(0, start) +
382
+ spaces +
383
+ currentContent.value.substring(end);
384
+
385
+ // Move cursor position after the inserted tab
386
+ nextTick(() => {
387
+ textarea.selectionStart = textarea.selectionEnd = start + spaces.length;
388
+ highlightCode();
389
+ });
321
390
  };
322
391
 
323
392
  const format = () => {
324
- try {
325
- if (!isJsonFile()) return;
393
+ try {
394
+ if (!isJsonFile()) return;
326
395
 
327
- const formatted = JSON.stringify(JSON.parse(currentContent.value), null, 4);
328
- currentContent.value = formatted;
396
+ const formatted = JSON.stringify(JSON.parse(currentContent.value), null, 4);
397
+ currentContent.value = formatted;
329
398
 
330
- // Apply syntax highlighting after formatting
331
- nextTick(() => {
332
- highlightCode();
333
- });
334
- } catch (e) {
335
- ui.logger.Error("Could not format JSON", e);
336
- }
399
+ // Apply syntax highlighting after formatting
400
+ nextTick(() => {
401
+ highlightCode();
402
+ });
403
+ } catch (e) {
404
+ ui.logger.Error("Could not format JSON", e);
405
+ }
337
406
  };
338
407
 
339
408
  // Watch for content changes to apply syntax highlighting
340
409
  watch(currentContent, () => {
341
- nextTick(() => {
342
- highlightCode();
343
- });
410
+ nextTick(() => {
411
+ highlightCode();
412
+ });
344
413
  });
345
414
 
346
415
  const loadFile = async (f) => {
347
- currentFile.value = f;
348
- if (DEBUG) {
349
- textEditorVisible.value = true;
350
- currentProxyList.value = "";
351
- currentContent.value = new Array(100).fill(0, 0, 100).join("\n");
352
-
353
- // Update pre element class based on file type
354
- if (codeDisplay.value) {
355
- if (f.endsWith(".json")) {
356
- codeDisplay.value.className = "language-json";
357
- } else if (f.endsWith(".js")) {
358
- codeDisplay.value.className = "language-javascript";
359
- } else {
360
- codeDisplay.value.className = "language-text";
361
- }
362
- }
363
-
364
- // Apply syntax highlighting after content is set
365
- nextTick(() => {
366
- highlightCode();
367
- });
368
- return;
369
- }
370
-
371
- const res = await loadFromApi("/api/json-file?file=" + currentFile.value);
372
- const text = atob(res.content);
416
+ currentFile.value = f;
417
+ if (DEBUG) {
373
418
  textEditorVisible.value = true;
374
419
  currentProxyList.value = "";
375
- currentContent.value = text;
376
- previous.currentContent = { file: currentFile.value, content: text };
420
+ currentContent.value = new Array(100).fill(0, 0, 100).join("\n");
377
421
 
378
422
  // Update pre element class based on file type
379
423
  if (codeDisplay.value) {
380
- if (f.endsWith(".json")) {
381
- codeDisplay.value.className = "language-json";
382
- } else if (f.endsWith(".js")) {
383
- codeDisplay.value.className = "language-javascript";
384
- } else {
385
- codeDisplay.value.className = "language-text";
386
- }
424
+ if (f.endsWith(".json")) {
425
+ codeDisplay.value.className = "language-json";
426
+ } else if (f.endsWith(".js")) {
427
+ codeDisplay.value.className = "language-javascript";
428
+ } else {
429
+ codeDisplay.value.className = "language-text";
430
+ }
387
431
  }
388
432
 
389
- // Apply syntax highlighting after content is loaded
433
+ // Apply syntax highlighting after content is set
390
434
  nextTick(() => {
391
- highlightCode();
435
+ highlightCode();
392
436
  });
437
+ return;
438
+ }
439
+
440
+ const res = await loadFromApi("/api/json-file?file=" + currentFile.value);
441
+ const text = atob(res.content);
442
+ textEditorVisible.value = true;
443
+ currentProxyList.value = "";
444
+ currentContent.value = text;
445
+ previous.currentContent = { file: currentFile.value, content: text };
446
+
447
+ // Update pre element class based on file type
448
+ if (codeDisplay.value) {
449
+ if (f.endsWith(".json")) {
450
+ codeDisplay.value.className = "language-json";
451
+ } else if (f.endsWith(".js")) {
452
+ codeDisplay.value.className = "language-javascript";
453
+ } else {
454
+ codeDisplay.value.className = "language-text";
455
+ }
456
+ }
457
+
458
+ // Apply syntax highlighting after content is loaded
459
+ nextTick(() => {
460
+ highlightCode();
461
+ });
393
462
  };
394
463
 
395
464
  const saveDataToAPI = async (data, endpoint, msg, json = true) => {
396
- if (DEBUG) return;
397
- const options = {
398
- method: "POST",
399
- headers: { "Content-Type": "application/json" },
400
- body: JSON.stringify({ content: data })
401
- };
402
-
403
- let j;
404
- const res = await fetch(endpoint, options);
405
- if (json) {
406
- try {
407
- j = await res.json();
408
- } catch (e) {
409
- ui.showError("Error saving proxies");
410
- return;
411
- }
412
- if (j?.error) return ui.showError(j.error);
413
- } else {
414
- try {
415
- j = await res.text();
416
- } catch (e) {
417
- ui.showError("Error saving proxies");
418
- return;
419
- }
465
+ if (DEBUG) return;
466
+ const options = {
467
+ method: "POST",
468
+ headers: { "Content-Type": "application/json" },
469
+ body: JSON.stringify({ content: data }),
470
+ };
471
+
472
+ let j;
473
+ const res = await fetch(endpoint, options);
474
+ if (json) {
475
+ try {
476
+ j = await res.json();
477
+ } catch (e) {
478
+ ui.showError("Error saving proxies");
479
+ return;
480
+ }
481
+ if (j?.error) return ui.showError(j.error);
482
+ } else {
483
+ try {
484
+ j = await res.text();
485
+ } catch (e) {
486
+ ui.showError("Error saving proxies");
487
+ return;
420
488
  }
421
- ui.showSuccess(msg);
489
+ }
490
+ ui.showSuccess(msg);
422
491
  };
423
492
 
424
493
  const saveFile = async () => {
425
- if (
426
- btoa(currentContent.value) === previous.currentContent.content &&
427
- currentFile.value === previous.currentContent.file
428
- )
429
- return ui.logger.Info("Content did not change");
430
- saveDataToAPI(btoa(currentContent.value), "/api/json-file?file=" + currentFile.value, "Successfully saved file");
431
- previous.currentContent = { file: currentFile.value, content: btoa(currentContent.value) };
494
+ if (
495
+ btoa(currentContent.value) === previous.currentContent.content &&
496
+ currentFile.value === previous.currentContent.file
497
+ )
498
+ return ui.logger.Info("Content did not change");
499
+ saveDataToAPI(
500
+ btoa(currentContent.value),
501
+ "/api/json-file?file=" + currentFile.value,
502
+ "Successfully saved file"
503
+ );
504
+ previous.currentContent = {
505
+ file: currentFile.value,
506
+ content: btoa(currentContent.value),
507
+ };
432
508
  };
433
509
 
434
510
  const saveAll = async () => {
435
- if (DEBUG) return;
436
- if (currentFile.value && currentContent.value) await saveFile();
437
- await saveQuickConfig();
511
+ if (DEBUG) return;
512
+ if (currentFile.value && currentContent.value) await saveFile();
513
+ await saveQuickConfig();
438
514
  };
439
515
 
440
516
  const closeFile = () => {
441
- currentFile.value = "";
442
- currentContent.value = "";
443
- textEditorVisible.value = false;
444
- ui.logger.Info("File closed");
517
+ currentFile.value = "";
518
+ currentContent.value = "";
519
+ textEditorVisible.value = false;
520
+ ui.logger.Info("File closed");
445
521
  };
446
522
 
447
523
  const closeProxyFile = () => {
448
- currentProxyList.value = "";
449
- proxyContent.value = "";
450
- ui.logger.Info("Proxy file closed");
524
+ currentProxyList.value = "";
525
+ proxyContent.value = "";
526
+ ui.logger.Info("Proxy file closed");
451
527
  };
452
528
 
453
529
  const loadProxies = async (list) => {
454
- try {
455
- if (DEBUG) {
456
- currentProxyList.value = list;
457
- textEditorVisible.value = false;
458
- proxyContent.value = new Array(100).fill(0, 0, 100).join("\n");
459
- return;
460
- }
461
-
462
- ui.logger.Info("Loading proxies: " + list);
463
- currentProxyList.value = list;
464
- const file = await getProxyFile(list);
465
- proxyContent.value = file;
466
- previous.proxyContent = { content: proxyContent.value };
467
- textEditorVisible.value = false;
468
- } catch (ex) {
469
- ui.logger.Error("Could not load proxies", ex);
530
+ try {
531
+ if (DEBUG) {
532
+ currentProxyList.value = list;
533
+ textEditorVisible.value = false;
534
+ proxyContent.value = new Array(100).fill(0, 0, 100).join("\n");
535
+ return;
470
536
  }
537
+
538
+ ui.logger.Info("Loading proxies: " + list);
539
+ currentProxyList.value = list;
540
+ const file = await getProxyFile(list);
541
+ proxyContent.value = file;
542
+ previous.proxyContent = { content: proxyContent.value };
543
+ textEditorVisible.value = false;
544
+ } catch (ex) {
545
+ ui.logger.Error("Could not load proxies", ex);
546
+ }
471
547
  };
472
548
 
473
549
  const saveProxies = async () => {
474
- // if (!DEBUG) return;
475
- if (previous.proxyContent.content === proxyContent.value) ui.logger.Info("proxies did not change");
476
- else {
477
- await saveDataToAPI(
478
- btoa(proxyContent.value),
479
- "/api/proxies?file=" + currentProxyList.value,
480
- "Successfully saved proxies",
481
- false
482
- );
483
- previous.proxyContent.content = btoa(proxyContent.value);
484
- }
550
+ // if (!DEBUG) return;
551
+ if (previous.proxyContent.content === proxyContent.value)
552
+ ui.logger.Info("proxies did not change");
553
+ else {
554
+ await saveDataToAPI(
555
+ btoa(proxyContent.value),
556
+ "/api/proxies?file=" + currentProxyList.value,
557
+ "Successfully saved proxies",
558
+ false
559
+ );
560
+ previous.proxyContent.content = btoa(proxyContent.value);
561
+ }
485
562
  };
486
563
 
487
564
  const loadProxyLists = async () => {
488
- if (DEBUG) return (proxyLists.value = ["test-proxies", "recaptcha-proxies", "checkout-proxies", "queue-proxies"]);
489
- const res = await getProxyLists();
490
- if (Array.isArray(res)) proxyLists.value = res;
491
- else ui.showError("Could not load proxylists");
565
+ if (DEBUG)
566
+ return (proxyLists.value = [
567
+ "test-proxies",
568
+ "recaptcha-proxies",
569
+ "checkout-proxies",
570
+ "queue-proxies",
571
+ ]);
572
+ const res = await getProxyLists();
573
+ if (Array.isArray(res)) proxyLists.value = res;
574
+ else ui.showError("Could not load proxylists");
492
575
  };
493
576
 
494
577
  // Initialize syntax highlighting
495
578
  onMounted(() => {
496
- // Apply highlighting when the component is mounted
497
- nextTick(() => {
498
- highlightCode();
579
+ // Apply highlighting when the component is mounted
580
+ nextTick(() => {
581
+ highlightCode();
499
582
 
500
- // Ensure scroll synchronization on initial load
501
- if (codeEditor.value && codeDisplay.value) {
502
- syncScroll();
503
- }
583
+ // Ensure scroll synchronization on initial load
584
+ if (codeEditor.value && codeDisplay.value) {
585
+ syncScroll();
586
+ }
587
+ });
588
+
589
+ // iOS-specific editor fixes with cursor tracking
590
+ if (
591
+ /iPad|iPhone|iPod/.test(navigator.platform) ||
592
+ (navigator.maxTouchPoints &&
593
+ navigator.maxTouchPoints > 2 &&
594
+ /MacIntel/.test(navigator.platform))
595
+ ) {
596
+ const trackCursor = (editor) => {
597
+ const scrollToCursor = () => {
598
+ setTimeout(() => {
599
+ const container =
600
+ editor.closest(".editor-container") ||
601
+ editor.closest(".proxy-editor-container");
602
+ if (container) {
603
+ // Get cursor position and scroll to it
604
+ const lines = editor.value.substring(0, editor.selectionStart).split("\n");
605
+ const lineHeight = 24; // Approximate line height
606
+ const cursorY = lines.length * lineHeight;
607
+ const containerHeight = container.clientHeight;
608
+
609
+ // Scroll to keep cursor in view
610
+ if (cursorY > container.scrollTop + containerHeight - 60) {
611
+ container.scrollTop = cursorY - containerHeight + 60;
612
+ }
613
+ }
614
+ }, 100);
615
+ };
616
+
617
+ editor.addEventListener("focus", scrollToCursor);
618
+ editor.addEventListener("input", scrollToCursor);
619
+ editor.addEventListener("click", scrollToCursor);
620
+ editor.addEventListener("keyup", scrollToCursor);
621
+ };
622
+
623
+ nextTick(() => {
624
+ if (codeEditor.value) {
625
+ trackCursor(codeEditor.value);
626
+ }
627
+ const proxyEditor = document.querySelector(".proxy-editor");
628
+ if (proxyEditor) {
629
+ trackCursor(proxyEditor);
630
+ }
504
631
  });
632
+ }
505
633
 
506
- // iOS-specific editor fixes with cursor tracking
507
- if (
508
- /iPad|iPhone|iPod/.test(navigator.platform) ||
509
- (navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform))
510
- ) {
511
- const trackCursor = (editor) => {
512
- const scrollToCursor = () => {
513
- setTimeout(() => {
514
- const container = editor.closest(".editor-container") || editor.closest(".proxy-editor-container");
515
- if (container) {
516
- // Get cursor position and scroll to it
517
- const lines = editor.value.substring(0, editor.selectionStart).split("\n");
518
- const lineHeight = 24; // Approximate line height
519
- const cursorY = lines.length * lineHeight;
520
- const containerHeight = container.clientHeight;
521
-
522
- // Scroll to keep cursor in view
523
- if (cursorY > container.scrollTop + containerHeight - 60) {
524
- container.scrollTop = cursorY - containerHeight + 60;
525
- }
526
- }
527
- }, 100);
528
- };
529
-
530
- editor.addEventListener("focus", scrollToCursor);
531
- editor.addEventListener("input", scrollToCursor);
532
- editor.addEventListener("click", scrollToCursor);
533
- editor.addEventListener("keyup", scrollToCursor);
534
- };
535
-
536
- nextTick(() => {
537
- if (codeEditor.value) {
538
- trackCursor(codeEditor.value);
539
- }
540
- const proxyEditor = document.querySelector(".proxy-editor");
541
- if (proxyEditor) {
542
- trackCursor(proxyEditor);
543
- }
544
- });
545
- }
634
+ // Watch for editor visibility changes
635
+ watch(textEditorVisible, (newValue) => {
636
+ if (newValue) {
637
+ nextTick(() => {
638
+ highlightCode();
546
639
 
547
- // Watch for editor visibility changes
548
- watch(textEditorVisible, (newValue) => {
549
- if (newValue) {
550
- nextTick(() => {
551
- highlightCode();
552
-
553
- // Focus the editor when it becomes visible
554
- if (codeEditor.value) {
555
- codeEditor.value.focus();
556
- }
557
- });
640
+ // Focus the editor when it becomes visible
641
+ if (codeEditor.value) {
642
+ codeEditor.value.focus();
558
643
  }
559
- });
644
+ });
645
+ }
646
+ });
560
647
  });
561
648
 
562
649
  // No cleanup needed - global iOS handling takes care of everything
@@ -565,285 +652,293 @@ if (ui.profile.admin && !DEBUG) loadAvailableFiles();
565
652
  loadProxyLists();
566
653
  </script>
567
654
  <style lang="scss" scoped>
568
- .console {
569
- @apply bg-dark-400 p-7 rounded relative border-dark-550 border-2;
655
+ /* ==========================================================================
656
+ Z-INDEX MANAGEMENT
657
+ ========================================================================== */
570
658
 
571
- textarea {
572
- background: transparent;
573
- resize: none;
574
- height: calc(100vh - 450px);
575
- @apply w-full focus:outline-none text-xs text-white;
576
- }
577
-
578
- $border: 2px;
659
+ .z-dropdown-high {
660
+ position: relative;
661
+ z-index: 200;
662
+ }
579
663
 
580
- // &:before {
581
- // content: "";
582
- // @apply absolute top-0 left-0 right-0 bottom-0 opacity-60;
583
- // z-index: -2;
584
- // margin: -$border;
585
- // border-radius: inherit;
586
- // background: radial-gradient(rgba(96, 66, 255, 0.6), rgba(255, 255, 255, 0));
587
- // }
664
+ .z-dropdown-high .dropdown-menu,
665
+ .z-dropdown-high .option-list {
666
+ z-index: 250;
588
667
  }
589
668
 
590
- .z-inf {
591
- z-index: 1000;
669
+ .z-dropdown {
670
+ position: relative;
671
+ z-index: 100;
592
672
  }
593
673
 
594
- .z-inf2 {
595
- z-index: 2000;
674
+ .z-dropdown .dropdown-menu,
675
+ .z-dropdown .option-list {
676
+ z-index: 150;
596
677
  }
597
678
 
598
- .z-inf3 {
599
- z-index: 3000;
679
+ /* ==========================================================================
680
+ CARD CONTAINER RESPONSIVENESS
681
+ ========================================================================== */
682
+
683
+ .card-dark {
684
+ max-height: calc(100vh - 100px) !important; /* Increased from 120px */
685
+ overflow-y: auto !important;
686
+ -webkit-overflow-scrolling: touch !important;
687
+ margin-bottom: 0.5rem !important; /* Reduced from 1rem */
688
+ /* Reduced bottom padding to minimize empty space */
689
+ padding-bottom: 0.5rem !important; /* Reduced from 1.5rem */
600
690
  }
601
691
 
602
- .special-dropdown {
603
- margin-left: 0 !important;
692
+ /* ==========================================================================
693
+ ADMIN EDITOR SECTION CONDITIONAL VISIBILITY
694
+ ========================================================================== */
695
+
696
+ .admin-editor-section {
697
+ /* Default: always visible */
604
698
  }
605
699
 
606
- .button-default-smaller -smaller {
607
- @apply h-10;
608
- border-radius: 0.5rem;
609
- font-weight: 500;
610
- --tw-text-opacity: 1;
611
- color: rgb(255 255 255 / 1);
612
- transition-duration: 150ms;
700
+ /* Hide admin editor when proxy editor is open - ALL SCREEN SIZES */
701
+ .admin-editor-section.landscape-hidden {
702
+ display: none !important;
613
703
  }
614
704
 
615
- /* Modern dropdown with refresh button styles */
616
- /* Removed custom dropdown styling to ensure default behavior */
705
+ /* ==========================================================================
706
+ PROXY EDITOR SECTION CONDITIONAL VISIBILITY
707
+ ========================================================================== */
617
708
 
618
- .refresh-button {
619
- &:hover {
620
- .refresh-icon {
621
- transform: rotate(180deg);
622
- }
623
- }
709
+ .proxy-editor-section {
710
+ /* Default: always visible */
624
711
  }
625
712
 
626
- .refresh-icon {
627
- transition: transform 0.5s ease;
713
+ /* Hide proxy editor when admin editor is open - ALL SCREEN SIZES */
714
+ .proxy-editor-section.landscape-hidden {
715
+ display: none !important;
628
716
  }
629
717
 
630
- /* Admin file dropdown should match refresh button height and have no right corners */
631
- .admin-file-dropdown {
632
- height: 2.5rem !important; /* Always 40px to match refresh button */
633
- border-top-right-radius: 0 !important;
634
- border-bottom-right-radius: 0 !important;
635
- border-top-left-radius: 0.5rem !important; /* lg border radius */
636
- border-bottom-left-radius: 0.5rem !important; /* lg border radius */
718
+ /* Ensure card has proper bottom margin when sections are hidden */
719
+ .proxy-editor-section.landscape-hidden ~ .card-dark,
720
+ .admin-editor-section.landscape-hidden ~ .card-dark {
721
+ margin-bottom: 1rem !important;
637
722
  }
638
723
 
639
- /* Prism.js syntax highlighting styles */
640
- .editor-container {
641
- position: relative;
642
- min-height: 300px;
643
- max-height: 600px;
644
- border-radius: 8px;
645
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
646
- overflow: visible !important;
647
- background-color: #2e2f34;
648
- touch-action: auto !important;
724
+ /* ==========================================================================
725
+ SECTION HEADERS
726
+ ========================================================================== */
727
+
728
+ .admin-editor-section h5,
729
+ .proxy-editor-section h5 {
730
+ /* Remove the gradient underline styling */
649
731
  }
650
732
 
651
- .editor-wrapper {
652
- position: relative;
653
- width: 100%;
654
- height: 100%;
655
- min-height: 300px;
656
- max-height: 600px;
657
- overflow: visible !important; /* ALLOW SCROLLING */
733
+ /* ==========================================================================
734
+ ADMIN FILE DROPDOWN STYLING
735
+ ========================================================================== */
736
+
737
+ .admin-file-dropdown {
738
+ height: 2.5rem !important;
739
+ border-top-right-radius: 0 !important;
740
+ border-bottom-right-radius: 0 !important;
741
+ border-top-left-radius: 0.5rem !important;
742
+ border-bottom-left-radius: 0.5rem !important;
658
743
  }
659
744
 
660
- .code-editor {
661
- width: 100%;
662
- height: 100%;
663
- min-height: 300px;
664
- max-height: 600px;
665
- background-color: transparent;
666
- /* Make text completely transparent */
667
- color: rgba(0, 0, 0, 0);
668
- caret-color: #e2e8f0;
669
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
670
- padding: 12px;
671
- border: none;
672
- resize: none;
673
- font-size: 14px;
674
- line-height: 1.6;
675
- tab-size: 4;
676
- outline: none;
677
- border: 1px solid #2d2d3b;
678
- border-radius: 8px;
679
- z-index: 10;
680
- position: absolute;
681
- top: 0;
682
- left: 0;
683
- right: 0;
684
- bottom: 0;
685
- white-space: pre;
686
- overflow: auto;
687
- }
688
-
689
-
690
- /* iOS keyboard focus fix and scrolling */
691
- .code-editor:focus,
692
- .proxy-editor:focus {
693
- transform: translateZ(0);
694
- -webkit-transform: translateZ(0);
695
- }
696
-
697
- /* Force textarea scrolling to work */
698
- .code-editor,
699
- .proxy-editor {
700
- overflow: auto !important;
701
- -webkit-overflow-scrolling: touch !important;
702
- user-select: text !important;
703
- -webkit-user-select: text !important;
704
- touch-action: auto !important;
705
- overscroll-behavior: auto !important;
745
+ .proxy-file-dropdown {
746
+ height: 2.5rem !important;
747
+ border-radius: 0.5rem !important;
706
748
  }
707
749
 
708
- /* Additional override for any global styles */
709
- textarea.code-editor,
710
- textarea.proxy-editor {
711
- overflow: auto !important;
712
- -webkit-overflow-scrolling: touch !important;
713
- touch-action: auto !important;
714
- overscroll-behavior: auto !important;
750
+ .refresh-button {
751
+ height: 2.5rem !important;
752
+ transition: all 0.2s ease;
753
+
754
+ &:hover {
755
+ .refresh-icon {
756
+ transform: rotate(180deg);
757
+ }
758
+ }
715
759
  }
716
760
 
717
- .code-highlight {
718
- width: 100%;
719
- height: 100%;
720
- min-height: 300px;
721
- max-height: 600px;
722
- overflow: auto;
723
- white-space: pre;
724
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
725
- font-size: 14px;
726
- line-height: 1.6;
727
- background-color: transparent !important;
728
- pointer-events: none;
729
- z-index: 5;
730
- position: absolute;
731
- top: 0;
732
- left: 0;
733
- right: 0;
734
- bottom: 0;
735
- padding: 12px;
736
- margin: 0;
761
+ .refresh-icon {
762
+ transition: transform 0.5s ease;
737
763
  }
738
764
 
739
- .code-editor,
740
- .prism-editor {
741
- font-family: "Menlo", "Monaco", "Courier New", monospace !important;
742
- font-size: 14px !important;
743
- line-height: 1.5 !important;
765
+ /* ==========================================================================
766
+ EDITOR CONTAINERS
767
+ ========================================================================== */
768
+
769
+ .editor-container,
770
+ .proxy-editor-container {
771
+ position: relative;
772
+ overflow: hidden;
773
+ height: 400px;
774
+ max-height: 60vh;
775
+ border-radius: 8px;
776
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
777
+ background-color: #2e2f34;
778
+ transition: height 0.3s ease;
744
779
  }
745
780
 
746
- /* Proxy editor container */
747
781
  .proxy-editor-container {
748
- @apply w-full rounded-lg border border-dark-600;
749
- overflow: visible !important; /* ALLOW SCROLLING */
750
- height: 400px;
751
- max-height: 60vh;
752
- background-color: #2e2f34;
753
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
782
+ /* Ensure proxy editor takes full container space */
783
+ display: flex;
784
+ flex-direction: column;
754
785
  }
755
786
 
756
- @media (min-width: 1024px) {
757
- .proxy-editor-container {
758
- max-height: 60vh;
759
- }
787
+ .proxy-editor-container .pb-4 {
788
+ /* Remove bottom padding to maximize space */
789
+ padding-bottom: 0 !important;
790
+ flex: 1;
791
+ display: flex;
792
+ flex-direction: column;
760
793
  }
761
794
 
762
- @media (min-width: 1280px) {
763
- .proxy-editor-container {
764
- max-height: 70vh;
765
- }
795
+ .proxy-editor-container .table-component {
796
+ /* Make proxy editor container take full height */
797
+ flex: 1;
798
+ display: flex;
799
+ flex-direction: column;
800
+ }
801
+
802
+ .editor-wrapper {
803
+ position: relative;
804
+ width: 100%;
805
+ height: 100%;
806
+ overflow: hidden;
807
+ border-radius: 8px;
766
808
  }
767
809
 
768
- /* Proxy editor styles */
810
+ /* ==========================================================================
811
+ EDITOR TEXTAREAS
812
+ ========================================================================== */
813
+
814
+ .code-editor,
769
815
  .proxy-editor {
770
- @apply w-full h-full resize-none focus:outline-none;
771
- background-color: transparent;
772
- color: #e2e2e5;
773
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
774
- padding: 12px;
775
- border: none;
776
- font-size: 14px;
777
- line-height: 1.6;
778
- tab-size: 4;
779
- overflow: auto;
816
+ font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
817
+ font-size: 14px;
818
+ line-height: 1.6;
819
+ tab-size: 4;
820
+ background-color: transparent;
821
+ border: none;
822
+ outline: none;
823
+ resize: none;
824
+ width: 100%;
825
+ height: 100%;
826
+ overflow: auto;
827
+ -webkit-overflow-scrolling: touch;
828
+ padding: 12px;
829
+ margin: 0;
830
+ box-sizing: border-box;
780
831
  }
781
832
 
782
- .proxy-editor:focus {
783
- outline: none;
833
+ .code-editor {
834
+ /* Make textarea text transparent so syntax highlighting shows through */
835
+ color: transparent !important;
836
+ z-index: 2;
837
+ position: relative;
838
+ background-color: transparent;
839
+ caret-color: #ffffff; /* Keep cursor visible */
840
+
841
+ /* Selection styling */
842
+ &::selection {
843
+ background-color: rgba(255, 255, 255, 0.2);
844
+ }
784
845
  }
785
846
 
786
- .proxy-editor-container:focus-within {
787
- border-color: #44454b;
788
- box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2);
847
+ .proxy-editor {
848
+ @apply w-full h-full resize-none focus:outline-none;
849
+ background-color: transparent;
850
+ color: #e2e2e5; /* Keep normal text color for proxy editor */
851
+ /* Ensure proxy editor takes full height */
852
+ flex: 1;
853
+ min-height: 100%;
789
854
  }
790
855
 
791
- /* Prism.js dark theme */
856
+ /* ==========================================================================
857
+ CODE HIGHLIGHTING
858
+ ========================================================================== */
859
+
860
+ .code-highlight {
861
+ font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
862
+ font-size: 14px;
863
+ line-height: 1.6;
864
+ tab-size: 4;
865
+ /* Remove transparent color - let syntax highlighting show */
866
+ color: #f8f8f2 !important;
867
+ background: transparent;
868
+ border: none;
869
+ outline: none;
870
+ resize: none;
871
+ width: 100%;
872
+ height: 100%;
873
+ overflow: hidden;
874
+ white-space: pre;
875
+ pointer-events: none;
876
+ z-index: 1;
877
+ position: absolute;
878
+ top: 0;
879
+ left: 0;
880
+ padding: 12px;
881
+ margin: 0;
882
+ box-sizing: border-box;
883
+ }
884
+
885
+ /* ==========================================================================
886
+ PRISM.JS SYNTAX HIGHLIGHTING THEME
887
+ ========================================================================== */
888
+
792
889
  code[class*="language-"],
793
890
  pre[class*="language-"] {
794
- color: #f8f8f2;
795
- background: none;
796
- text-shadow: 0 1px rgba(0, 0, 0, 0.3);
797
- font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
798
- text-align: left;
799
- white-space: pre;
800
- word-spacing: normal;
801
- word-break: normal;
802
- word-wrap: normal;
803
- line-height: 1.5;
804
- tab-size: 4;
805
- hyphens: none;
806
- }
807
-
808
- /* Code blocks */
891
+ color: #f8f8f2;
892
+ background: none;
893
+ text-shadow: 0 1px rgba(0, 0, 0, 0.3);
894
+ font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
895
+ text-align: left;
896
+ white-space: pre;
897
+ word-spacing: normal;
898
+ word-break: normal;
899
+ word-wrap: normal;
900
+ line-height: 1.5;
901
+ tab-size: 4;
902
+ hyphens: none;
903
+ }
904
+
809
905
  pre[class*="language-"] {
810
- padding: 12px;
811
- margin: 0;
812
- overflow: auto;
813
- border-radius: 4px;
814
- font-family: "Menlo", "Monaco", "Courier New", monospace;
815
- font-size: 14px;
816
- line-height: 1.5;
817
- tab-size: 4;
906
+ padding: 12px;
907
+ margin: 0;
908
+ overflow: auto;
909
+ border-radius: 4px;
910
+ font-family: "Menlo", "Monaco", "Courier New", monospace;
911
+ font-size: 14px;
912
+ line-height: 1.5;
913
+ tab-size: 4;
818
914
  }
819
915
 
820
916
  :not(pre) > code[class*="language-"],
821
917
  pre[class*="language-"] {
822
- background: #1e1e2e;
823
- white-space: pre;
918
+ background: #2e2f34;
919
+ white-space: pre;
824
920
  }
825
921
 
826
- /* Inline code */
827
922
  :not(pre) > code[class*="language-"] {
828
- padding: 0.1em;
829
- border-radius: 0.3em;
830
- white-space: normal;
923
+ padding: 0.1em;
924
+ border-radius: 0.3em;
925
+ white-space: normal;
831
926
  }
832
927
 
833
928
  .token.comment,
834
929
  .token.prolog,
835
930
  .token.doctype,
836
931
  .token.cdata {
837
- color: #676f7d;
838
- font-style: italic;
932
+ color: #676f7d;
933
+ font-style: italic;
839
934
  }
840
935
 
841
936
  .token.punctuation {
842
- color: #a9b7c6;
937
+ color: #a9b7c6;
843
938
  }
844
939
 
845
940
  .namespace {
846
- opacity: 0.8;
941
+ opacity: 0.8;
847
942
  }
848
943
 
849
944
  .token.property,
@@ -851,12 +946,12 @@ pre[class*="language-"] {
851
946
  .token.constant,
852
947
  .token.symbol,
853
948
  .token.deleted {
854
- color: #e06c75;
949
+ color: #e06c75;
855
950
  }
856
951
 
857
952
  .token.boolean,
858
953
  .token.number {
859
- color: #c792ea;
954
+ color: #c792ea;
860
955
  }
861
956
 
862
957
  .token.selector,
@@ -865,7 +960,7 @@ pre[class*="language-"] {
865
960
  .token.char,
866
961
  .token.builtin,
867
962
  .token.inserted {
868
- color: #98c379;
963
+ color: #98c379;
869
964
  }
870
965
 
871
966
  .token.operator,
@@ -873,120 +968,489 @@ pre[class*="language-"] {
873
968
  .token.url,
874
969
  .language-css .token.string,
875
970
  .style .token.string {
876
- color: #89ddff;
971
+ color: #89ddff;
877
972
  }
878
973
 
879
974
  .token.atrule,
880
975
  .token.attr-value,
881
976
  .token.keyword {
882
- color: #61afef;
977
+ color: #61afef;
883
978
  }
884
979
 
885
980
  .token.function,
886
981
  .token.class-name {
887
- color: #ffcb6b;
982
+ color: #ffcb6b;
888
983
  }
889
984
 
890
985
  .token.regex,
891
986
  .token.important,
892
987
  .token.variable {
893
- color: #c5e478;
894
- }
895
-
896
- /* Add a slight hover effect to the editor for better UX */
897
- .editor-container:hover {
898
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
899
- transition: box-shadow 0.3s ease;
988
+ color: #c5e478;
900
989
  }
901
990
 
902
991
  .token.important,
903
992
  .token.bold {
904
- font-weight: bold;
993
+ font-weight: bold;
905
994
  }
906
995
 
907
996
  .token.italic {
908
- font-style: italic;
997
+ font-style: italic;
909
998
  }
910
999
 
911
1000
  .token.entity {
912
- cursor: help;
1001
+ cursor: help;
1002
+ }
1003
+
1004
+ .editor-container:hover {
1005
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
1006
+ transition: box-shadow 0.3s ease;
913
1007
  }
914
1008
 
915
- /* Modern error message styling */
1009
+ /* ==========================================================================
1010
+ ERROR MESSAGES
1011
+ ========================================================================== */
1012
+
916
1013
  .error-container {
917
- display: flex;
918
- align-items: flex-start;
919
- gap: 12px;
920
- padding: 16px;
921
- margin-top: 12px;
922
- border-radius: 8px;
923
- border: 1px solid #dc2626;
924
- background: linear-gradient(135deg, rgba(220, 38, 38, 0.1), rgba(220, 38, 38, 0.05));
925
- box-shadow: 0 2px 8px rgba(220, 38, 38, 0.15);
1014
+ @apply flex items-start gap-3 p-4 bg-red-500/10 border border-red-500/20 rounded-lg mt-4 mb-4;
1015
+
1016
+ /* Prevent overflow in landscape mode */
1017
+ max-width: 100%;
1018
+ word-wrap: break-word;
1019
+ overflow-wrap: break-word;
926
1020
  }
927
1021
 
928
1022
  .error-icon {
929
- display: flex;
930
- align-items: center;
931
- justify-content: center;
932
- width: 28px;
933
- height: 28px;
934
- border-radius: 50%;
935
- background: rgba(220, 38, 38, 0.2);
936
- border: 1px solid rgba(220, 38, 38, 0.4);
937
- color: #ef4444;
938
- flex-shrink: 0;
1023
+ @apply flex-shrink-0 text-red-400;
939
1024
  }
940
1025
 
941
1026
  .error-content {
942
- flex: 1;
943
- min-width: 0;
1027
+ @apply flex-1;
1028
+ min-width: 0; /* Allow text to shrink */
944
1029
  }
945
1030
 
946
1031
  .error-title {
947
- color: #fca5a5;
948
- font-size: 14px;
949
- font-weight: 600;
950
- margin-bottom: 4px;
951
- letter-spacing: 0.015em;
1032
+ @apply text-red-400 font-medium text-sm mb-1;
952
1033
  }
953
1034
 
954
1035
  .error-text {
955
- color: #fecaca;
956
- font-size: 12px;
957
- line-height: 1.5;
958
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
959
- word-break: break-word;
960
- opacity: 0.9;
961
- }
962
-
963
- /* iPhone portrait mode spacing fixes */
964
- @media (max-width: 480px) and (orientation: portrait) {
965
- .editor-controls-row {
966
- gap: 0.75rem !important;
967
- flex-wrap: wrap;
968
- }
1036
+ @apply text-red-300 text-xs;
1037
+ word-break: break-word;
1038
+ }
969
1039
 
970
- .dropdown-container {
971
- width: 100% !important;
972
- max-width: 16rem;
973
- margin-bottom: 0.5rem;
974
- }
1040
+ /* ==========================================================================
1041
+ CONSOLE STYLES
1042
+ ========================================================================== */
975
1043
 
976
- .editor-controls-row > div:last-child {
977
- margin-right: auto;
978
- margin-top: 0.5rem;
979
- }
1044
+ .console {
1045
+ @apply bg-dark-400 p-7 rounded relative border-dark-550 border-2;
980
1046
 
981
- /* Proxy editor responsive layout */
982
- .proxy-controls-row {
983
- gap: 0.75rem !important;
984
- flex-wrap: wrap;
985
- }
1047
+ textarea {
1048
+ background: transparent;
1049
+ resize: none;
1050
+ height: calc(100vh - 450px);
1051
+ @apply w-full focus:outline-none text-xs text-white;
1052
+ }
1053
+ }
1054
+
1055
+ /* ==========================================================================
1056
+ SECTION DIVIDER
1057
+ ========================================================================== */
1058
+
1059
+ .section-divider {
1060
+ margin: 24px 0; /* Balanced margin for better centering */
1061
+ height: 1px;
1062
+ width: 100%;
1063
+ position: relative;
1064
+
1065
+ /* Add subtle gradient effect for better visual separation */
1066
+ background: linear-gradient(
1067
+ 90deg,
1068
+ transparent 0%,
1069
+ #3d3e44 20%,
1070
+ #3d3e44 80%,
1071
+ transparent 100%
1072
+ );
1073
+ border: none;
1074
+
1075
+ /* Add a subtle glow effect */
1076
+ box-shadow: 0 0 8px rgba(61, 62, 68, 0.3);
1077
+ }
1078
+
1079
+ /* ==========================================================================
1080
+ RESPONSIVE LAYOUT UTILITIES
1081
+ ========================================================================== */
1082
+
1083
+ .flex-responsive-wrap {
1084
+ @apply flex flex-wrap items-center gap-4;
1085
+ }
986
1086
 
987
- .proxy-controls-row > div:last-child {
988
- margin-right: auto;
989
- margin-top: 0.5rem;
1087
+ /* ==========================================================================
1088
+ MOBILE RESPONSIVE STYLES (max-width: 767px)
1089
+ ========================================================================== */
1090
+
1091
+ @media (max-width: 767px) {
1092
+ /* Card container - increased height to use more available space */
1093
+ .card-dark {
1094
+ max-height: calc(100vh - 100px) !important; /* Increased from 120px */
1095
+ overflow-y: auto !important;
1096
+ -webkit-overflow-scrolling: touch !important;
1097
+ margin-bottom: 0.5rem !important; /* Reduced from 1rem */
1098
+ /* Reduced bottom padding to minimize empty space */
1099
+ padding-bottom: 0.5rem !important; /* Reduced from 1.5rem */
1100
+ }
1101
+
1102
+ /* Layout adjustments - almost no gap between dropdown and buttons */
1103
+ .flex-responsive-wrap {
1104
+ @apply flex-col items-stretch !important;
1105
+ gap: 0px !important; /* Reduced from 2px to eliminate space between dropdown and buttons */
1106
+ }
1107
+
1108
+ /* Button container - even spacing across full width */
1109
+ .flex-responsive-wrap > div:last-child {
1110
+ @apply flex justify-center !important;
1111
+ }
1112
+
1113
+ /* Smaller buttons on mobile */
1114
+ .btn-action {
1115
+ height: 32px !important;
1116
+ min-height: 32px !important;
1117
+ padding: 0 8px !important;
1118
+ font-size: 12px !important;
1119
+
1120
+ svg {
1121
+ width: 14px !important;
1122
+ height: 14px !important;
990
1123
  }
1124
+ }
1125
+
1126
+ /* Editor container heights - reduced for better mobile experience */
1127
+ .editor-container,
1128
+ .proxy-editor-container {
1129
+ height: 200px !important; /* Reduced from 280px for better mobile experience */
1130
+ max-height: 30vh !important; /* Reduced from 35vh */
1131
+ min-height: 180px !important; /* Reduced from 220px */
1132
+ }
1133
+
1134
+ /* Reduce editor height when JSON error is showing in portrait */
1135
+ .editor-container.has-error {
1136
+ height: 160px !important; /* Reduced to make room for error message */
1137
+ max-height: 25vh !important;
1138
+ min-height: 140px !important;
1139
+ }
1140
+
1141
+ /* Error message spacing on mobile */
1142
+ .error-container {
1143
+ margin: 8px 0 !important;
1144
+ padding: 8px !important;
1145
+ font-size: 11px !important;
1146
+ }
1147
+
1148
+ .error-title {
1149
+ font-size: 11px !important;
1150
+ }
1151
+
1152
+ .error-text {
1153
+ font-size: 10px !important;
1154
+ }
1155
+
1156
+ /* Compact editor controls spacing - no margins */
1157
+ .editor-controls-row,
1158
+ .proxy-controls-row {
1159
+ margin-bottom: 0px !important; /* Eliminated margin completely */
1160
+ }
1161
+
1162
+ /* Compact editor containers - reduced margins for more space */
1163
+ .my-3 {
1164
+ margin: 6px 0 !important; /* Reduced margin between editor and controls */
1165
+ }
1166
+
1167
+ .pb-4 {
1168
+ padding-bottom: 4px !important; /* Reduced padding */
1169
+ }
1170
+ }
1171
+
1172
+ /* ==========================================================================
1173
+ TABLET RESPONSIVE STYLES (min-width: 768px)
1174
+ ========================================================================== */
1175
+
1176
+ @media (min-width: 768px) {
1177
+ .flex-responsive-wrap {
1178
+ @apply flex-row justify-between items-center !important;
1179
+ }
1180
+ }
1181
+
1182
+ /* ==========================================================================
1183
+ DESKTOP RESPONSIVE STYLES (min-width: 1024px)
1184
+ ========================================================================== */
1185
+
1186
+ @media (min-width: 1024px) {
1187
+ .editor-container,
1188
+ .proxy-editor-container {
1189
+ max-height: 60vh;
1190
+ }
1191
+ }
1192
+
1193
+ /* ==========================================================================
1194
+ LARGE DESKTOP RESPONSIVE STYLES (min-width: 1280px)
1195
+ ========================================================================== */
1196
+
1197
+ @media (min-width: 1280px) {
1198
+ .editor-container,
1199
+ .proxy-editor-container {
1200
+ max-height: 70vh;
1201
+ }
1202
+ }
1203
+
1204
+ /* ==========================================================================
1205
+ MOBILE LANDSCAPE MODE (max-height: 500px + landscape)
1206
+ ========================================================================== */
1207
+
1208
+ @media (max-height: 500px) and (orientation: landscape) and (min-width: 568px) {
1209
+ /* Make card fit in landscape mode with reduced bottom spacing */
1210
+ .card-dark {
1211
+ max-height: calc(100vh - 80px) !important; /* Increased from 60px to 80px */
1212
+ overflow-y: auto !important;
1213
+ -webkit-overflow-scrolling: touch !important;
1214
+ margin-bottom: 0.5rem !important; /* Reduced from 1rem */
1215
+ padding: 8px !important;
1216
+ /* Reduced bottom padding to minimize empty space */
1217
+ padding-bottom: 0.5rem !important; /* Reduced from 1.5rem */
1218
+ }
1219
+
1220
+ /* Truncate card cleanly when admin editor is open */
1221
+ .admin-editor-section:not(.landscape-hidden)
1222
+ + .section-divider
1223
+ + .proxy-editor-section.landscape-hidden {
1224
+ display: none !important;
1225
+ }
1226
+
1227
+ /* Ensure proxy editor takes full space when visible */
1228
+ .proxy-editor-container {
1229
+ height: 130px !important;
1230
+ max-height: 130px !important;
1231
+ min-height: 100px !important;
1232
+ }
1233
+
1234
+ .proxy-editor {
1235
+ height: 100% !important;
1236
+ min-height: 100% !important;
1237
+ }
1238
+
1239
+ /* Compact headings */
1240
+ h4 {
1241
+ font-size: 16px !important;
1242
+ margin-bottom: 6px !important;
1243
+ padding-top: 6px !important;
1244
+ }
1245
+
1246
+ h5 {
1247
+ font-size: 14px !important;
1248
+ margin-bottom: 4px !important;
1249
+ }
1250
+
1251
+ /* Section divider */
1252
+ .section-divider {
1253
+ margin: 6px 0 !important;
1254
+ }
1255
+
1256
+ /* Editor controls layout */
1257
+ .editor-controls-row,
1258
+ .proxy-controls-row {
1259
+ gap: 6px !important;
1260
+ }
1261
+
1262
+ /* Compact button styling */
1263
+ .btn-action {
1264
+ min-height: 28px !important;
1265
+ height: 28px !important;
1266
+ padding: 0 6px !important;
1267
+ font-size: 11px !important;
1268
+ }
1269
+
1270
+ /* Hide button text to save space */
1271
+ .btn-action span {
1272
+ display: none !important;
1273
+ }
1274
+
1275
+ /* Compact editor containers - reduced margins for more space */
1276
+ .my-3 {
1277
+ margin: 4px 0 !important; /* Reduced from 6px */
1278
+ }
1279
+
1280
+ .pb-4 {
1281
+ padding-bottom: 2px !important; /* Reduced from 4px */
1282
+ }
1283
+
1284
+ /* Editor container heights - increased to use more available space */
1285
+ .editor-container,
1286
+ .proxy-editor-container {
1287
+ height: 180px !important; /* Increased from 130px */
1288
+ max-height: 180px !important;
1289
+ min-height: 150px !important; /* Increased from 100px */
1290
+ }
1291
+
1292
+ /* Reduce editor height when JSON error is showing */
1293
+ .editor-container.has-error {
1294
+ height: 120px !important; /* Reduced to make room for error message */
1295
+ max-height: 120px !important;
1296
+ min-height: 100px !important;
1297
+ }
1298
+
1299
+ /* Ensure proxy editor takes full space when visible - increased height */
1300
+ .proxy-editor-container {
1301
+ height: 180px !important; /* Increased from 130px */
1302
+ max-height: 180px !important;
1303
+ min-height: 150px !important;
1304
+ }
1305
+
1306
+ /* Editor textarea heights - ensure syntax highlighting works */
1307
+ .code-editor,
1308
+ .proxy-editor {
1309
+ font-size: 11px !important;
1310
+ line-height: 1.4 !important;
1311
+ padding: 6px !important;
1312
+ }
1313
+
1314
+ /* Syntax highlighting in landscape mode */
1315
+ .code-highlight {
1316
+ font-size: 11px !important;
1317
+ line-height: 1.4 !important;
1318
+ padding: 6px !important;
1319
+ /* Ensure syntax highlighting is visible in landscape */
1320
+ color: #f8f8f2 !important;
1321
+ opacity: 1 !important;
1322
+ display: block !important;
1323
+ }
1324
+
1325
+ /* Compact error messages */
1326
+ .error-container {
1327
+ margin: 2px 0 !important; /* Reduced from 4px */
1328
+ padding: 4px 6px !important; /* More compact padding */
1329
+ font-size: 9px !important; /* Reduced from 10px */
1330
+ border-radius: 4px !important; /* Smaller border radius */
1331
+ gap: 6px !important; /* Reduced gap */
1332
+ }
1333
+
1334
+ .error-icon {
1335
+ width: 12px !important;
1336
+ height: 12px !important;
1337
+ }
1338
+
1339
+ .error-icon svg {
1340
+ width: 12px !important;
1341
+ height: 12px !important;
1342
+ }
1343
+
1344
+ .error-title {
1345
+ font-size: 9px !important; /* Reduced from 10px */
1346
+ margin-bottom: 2px !important;
1347
+ font-weight: 500 !important;
1348
+ }
1349
+
1350
+ .error-text {
1351
+ font-size: 8px !important; /* Reduced from 9px */
1352
+ line-height: 1.2 !important;
1353
+ }
1354
+ }
1355
+
1356
+ /* ==========================================================================
1357
+ VERY SHORT LANDSCAPE SCREENS (max-height: 400px + landscape)
1358
+ ========================================================================== */
1359
+
1360
+ @media (max-height: 400px) and (orientation: landscape) {
1361
+ .card-dark {
1362
+ max-height: calc(100vh - 70px) !important; /* Increased from 60px */
1363
+ padding: 6px !important;
1364
+ margin-bottom: 0.5rem !important; /* Reduced from 1rem */
1365
+ /* Reduced bottom padding to minimize empty space */
1366
+ padding-bottom: 0.5rem !important; /* Reduced from 1.5rem */
1367
+ }
1368
+
1369
+ .editor-container,
1370
+ .proxy-editor-container {
1371
+ height: 140px !important; /* Increased from 100px */
1372
+ max-height: 140px !important;
1373
+ min-height: 120px !important; /* Increased from 80px */
1374
+ }
1375
+
1376
+ /* Reduce editor height when JSON error is showing in very short landscape */
1377
+ .editor-container.has-error {
1378
+ height: 90px !important; /* Reduced to make room for error message */
1379
+ max-height: 90px !important;
1380
+ min-height: 80px !important;
1381
+ }
1382
+
1383
+ /* Ensure proxy editor takes full space in very short landscape */
1384
+ .proxy-editor {
1385
+ height: 100% !important;
1386
+ min-height: 100% !important;
1387
+ font-size: 10px !important;
1388
+ line-height: 1.3 !important;
1389
+ padding: 4px !important;
1390
+ }
1391
+
1392
+ .code-editor {
1393
+ font-size: 10px !important;
1394
+ line-height: 1.3 !important;
1395
+ padding: 4px !important;
1396
+ }
1397
+
1398
+ /* Ensure syntax highlighting works in very short landscape */
1399
+ .code-highlight {
1400
+ font-size: 10px !important;
1401
+ line-height: 1.3 !important;
1402
+ padding: 4px !important;
1403
+ color: #f8f8f2 !important;
1404
+ opacity: 1 !important;
1405
+ display: block !important;
1406
+ }
1407
+
1408
+ /* Ultra-compact error messages for very short landscape */
1409
+ .error-container {
1410
+ margin: 1px 0 !important;
1411
+ padding: 2px 4px !important;
1412
+ font-size: 8px !important;
1413
+ border-radius: 3px !important;
1414
+ gap: 4px !important;
1415
+ }
1416
+
1417
+ .error-icon {
1418
+ width: 10px !important;
1419
+ height: 10px !important;
1420
+ }
1421
+
1422
+ .error-icon svg {
1423
+ width: 10px !important;
1424
+ height: 10px !important;
1425
+ }
1426
+
1427
+ .error-title {
1428
+ font-size: 8px !important;
1429
+ margin-bottom: 1px !important;
1430
+ font-weight: 500 !important;
1431
+ }
1432
+
1433
+ .error-text {
1434
+ font-size: 7px !important;
1435
+ line-height: 1.1 !important;
1436
+ }
1437
+ }
1438
+
1439
+ /* Section divider */
1440
+ .section-divider {
1441
+ margin: 12px 0 !important;
1442
+ /* Reduce glow effect in landscape */
1443
+ box-shadow: 0 0 4px rgba(61, 62, 68, 0.2) !important;
1444
+ }
1445
+
1446
+ /* Compact section headers in landscape */
1447
+ .admin-editor-section h5,
1448
+ .proxy-editor-section h5 {
1449
+ padding-bottom: 4px !important;
1450
+
1451
+ &::after {
1452
+ width: 30px !important;
1453
+ height: 1px !important;
1454
+ }
991
1455
  }
992
1456
  </style>