@necrolab/dashboard 0.4.221 → 0.5.2

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