@necrolab/dashboard 0.5.14 → 0.5.16

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 (120) hide show
  1. package/backend/api.js +2 -3
  2. package/eslint.config.js +46 -0
  3. package/index.html +2 -1
  4. package/package.json +5 -2
  5. package/src/App.vue +140 -170
  6. package/src/assets/css/base/mixins.scss +72 -0
  7. package/src/assets/css/base/reset.scss +0 -2
  8. package/src/assets/css/base/scroll.scss +43 -36
  9. package/src/assets/css/base/typography.scss +9 -10
  10. package/src/assets/css/base/variables.scss +43 -0
  11. package/src/assets/css/components/accessibility.scss +37 -0
  12. package/src/assets/css/components/buttons.scss +58 -15
  13. package/src/assets/css/components/forms.scss +31 -32
  14. package/src/assets/css/components/headers.scss +119 -0
  15. package/src/assets/css/components/modals.scss +2 -2
  16. package/src/assets/css/components/search-groups.scss +28 -19
  17. package/src/assets/css/components/tables.scss +5 -7
  18. package/src/assets/css/components/toasts.scss +7 -7
  19. package/src/assets/css/components/utilities.scss +220 -0
  20. package/src/assets/css/main.scss +72 -75
  21. package/src/components/Auth/LoginForm.vue +5 -84
  22. package/src/components/Editors/Account/Account.vue +8 -10
  23. package/src/components/Editors/Account/AccountCreator.vue +28 -59
  24. package/src/components/Editors/Account/AccountView.vue +38 -86
  25. package/src/components/Editors/Account/CreateAccount.vue +8 -50
  26. package/src/components/Editors/Profile/CreateProfile.vue +74 -131
  27. package/src/components/Editors/Profile/Profile.vue +15 -17
  28. package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
  29. package/src/components/Editors/Profile/ProfileView.vue +46 -96
  30. package/src/components/Editors/TagLabel.vue +16 -55
  31. package/src/components/Editors/TagToggle.vue +20 -8
  32. package/src/components/Filter/Filter.vue +62 -75
  33. package/src/components/Filter/FilterPreview.vue +161 -135
  34. package/src/components/Filter/PriceSortToggle.vue +36 -43
  35. package/src/components/Table/Header.vue +1 -1
  36. package/src/components/Table/Table.vue +61 -12
  37. package/src/components/Tasks/CheckStock.vue +7 -16
  38. package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
  39. package/src/components/Tasks/Controls/MobileControls.vue +5 -20
  40. package/src/components/Tasks/CreateTaskAXS.vue +20 -118
  41. package/src/components/Tasks/CreateTaskTM.vue +33 -189
  42. package/src/components/Tasks/EventDetailRow.vue +21 -0
  43. package/src/components/Tasks/MassEdit.vue +6 -16
  44. package/src/components/Tasks/QuickSettings.vue +140 -216
  45. package/src/components/Tasks/ScrapeVenue.vue +4 -13
  46. package/src/components/Tasks/Stats.vue +19 -38
  47. package/src/components/Tasks/Task.vue +65 -268
  48. package/src/components/Tasks/TaskLabel.vue +9 -3
  49. package/src/components/Tasks/TaskView.vue +43 -63
  50. package/src/components/Tasks/Utilities.vue +10 -42
  51. package/src/components/Tasks/ViewTask.vue +23 -107
  52. package/src/components/icons/Close.vue +2 -8
  53. package/src/components/icons/Gear.vue +8 -8
  54. package/src/components/icons/Hash.vue +5 -0
  55. package/src/components/icons/Key.vue +2 -8
  56. package/src/components/icons/Pencil.vue +2 -8
  57. package/src/components/icons/Profile.vue +2 -8
  58. package/src/components/icons/Sell.vue +2 -8
  59. package/src/components/icons/Spinner.vue +4 -7
  60. package/src/components/icons/SquareCheck.vue +2 -8
  61. package/src/components/icons/SquareUncheck.vue +2 -8
  62. package/src/components/icons/Wildcard.vue +2 -8
  63. package/src/components/icons/index.js +3 -1
  64. package/src/components/ui/ActionButtonGroup.vue +113 -52
  65. package/src/components/ui/BalanceIndicator.vue +60 -0
  66. package/src/components/ui/EmptyState.vue +24 -0
  67. package/src/components/ui/EnableDisableToggle.vue +23 -0
  68. package/src/components/ui/FormField.vue +48 -48
  69. package/src/components/ui/IconLabel.vue +23 -0
  70. package/src/components/ui/InfoRow.vue +21 -54
  71. package/src/components/ui/Modal.vue +78 -37
  72. package/src/components/ui/Navbar.vue +60 -41
  73. package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
  74. package/src/components/ui/ReconnectIndicator.vue +111 -124
  75. package/src/components/ui/SectionCard.vue +6 -14
  76. package/src/components/ui/Splash.vue +2 -10
  77. package/src/components/ui/StatusBadge.vue +26 -28
  78. package/src/components/ui/TaskToggle.vue +54 -0
  79. package/src/components/ui/controls/CountryChooser.vue +27 -64
  80. package/src/components/ui/controls/EyeToggle.vue +1 -1
  81. package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
  82. package/src/components/ui/controls/atomic/Dropdown.vue +102 -95
  83. package/src/components/ui/controls/atomic/MultiDropdown.vue +72 -94
  84. package/src/components/ui/controls/atomic/Switch.vue +21 -84
  85. package/src/composables/useColorMapping.js +15 -0
  86. package/src/composables/useCopyToClipboard.js +1 -1
  87. package/src/composables/useDateFormatting.js +21 -0
  88. package/src/composables/useDeviceDetection.js +14 -0
  89. package/src/composables/useDropdownPosition.js +5 -6
  90. package/src/composables/useDynamicTableHeight.js +31 -0
  91. package/src/composables/useRowSelection.js +0 -3
  92. package/src/composables/useTicketPricing.js +16 -0
  93. package/src/composables/useWindowDimensions.js +21 -0
  94. package/src/libs/Filter.js +14 -20
  95. package/src/libs/panzoom.js +1 -5
  96. package/src/libs/utils/array.js +60 -0
  97. package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
  98. package/src/libs/utils/eventUrl.js +40 -0
  99. package/src/libs/utils/string.js +28 -0
  100. package/src/libs/utils/time.js +20 -0
  101. package/src/libs/utils/validation.js +88 -0
  102. package/src/main.js +0 -2
  103. package/src/stores/connection.js +1 -4
  104. package/src/stores/logger.js +6 -12
  105. package/src/stores/sampleData.js +1 -2
  106. package/src/stores/ui.js +59 -36
  107. package/src/views/Accounts.vue +17 -31
  108. package/src/views/Console.vue +76 -176
  109. package/src/views/Editor.vue +217 -383
  110. package/src/views/FilterBuilder.vue +190 -373
  111. package/src/views/Login.vue +3 -28
  112. package/src/views/Profiles.vue +12 -22
  113. package/src/views/Tasks.vue +51 -38
  114. package/tailwind.config.js +82 -71
  115. package/workbox-config.cjs +47 -5
  116. package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2416
  117. package/exit +0 -209
  118. package/run +0 -177
  119. package/switch-branch.sh +0 -41
  120. /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
@@ -1,32 +1,32 @@
1
1
  <template>
2
2
  <div>
3
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>
4
+ <div class="page-header" style="padding-bottom: 1.25rem;">
5
+ <div class="page-header-card">
6
+ <img src="@/assets/img/pencil.svg" />
7
+ <h4>Editor</h4>
8
+ </div>
9
+ </div>
8
10
 
9
- <div class="bg-dark-400 border border-dark-650 rounded shadow-sm p-2">
11
+ <div class="bg-dark-400 border-2 rounded-lg shadow-sm p-2" style="border-color: var(--color-border);">
10
12
  <!-- Admin Editor Section - Hidden when proxy editor is open -->
11
13
  <div v-if="ui.profile.admin" class="admin-editor-section" :class="{ 'landscape-hidden': currentProxyList }">
12
14
  <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Admin Editor</h5>
13
15
  <div class="flex flex-wrap items-center gap-3 w-full">
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>
16
+ <div class="unified-search-group flex w-full flex-shrink-0 dropdown-container z-dropdown-high lg:w-auto lg:min-w-64">
17
+ <Dropdown
18
+ class="admin-file-dropdown flex-1"
19
+ default="Select a file"
20
+ :onClick="(f) => loadFile(f)"
21
+ :options="availableFiles"
22
+ :allowDefault="false"
23
+ :includeAdjacentButtons="true"
24
+ rightAmount="right-1" />
25
25
  <button
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"
26
+ class="refresh-button flex items-center justify-center w-9 h-10 flex-shrink-0 text-white bg-dark-400 transition-all duration-150 hover:bg-dark-450"
27
27
  @click="loadAvailableFiles()"
28
28
  title="Refresh file list">
29
- <ReloadIcon class="refresh-icon" />
29
+ <ReloadIcon class="refresh-icon h-4 w-4" />
30
30
  </button>
31
31
  </div>
32
32
  <!-- Admin editor buttons moved here -->
@@ -90,9 +90,9 @@
90
90
  <div v-if="textEditorVisible" class="my-3 relative">
91
91
  <div class="pb-4">
92
92
  <!-- Syntax Highlighting Editor -->
93
- <div class="editor-container" :class="{ 'has-error': errorMessage }">
94
- <div class="editor-wrapper">
95
- <pre ref="codeDisplay" class="language-json code-highlight"></pre>
93
+ <div class="editor-container overflow-hidden rounded-lg shadow-card bg-dark-350" :class="{ 'has-error': errorMessage }">
94
+ <div class="relative w-full h-full">
95
+ <pre ref="codeDisplay" class="code-highlight language-json"></pre>
96
96
  <textarea
97
97
  ref="codeEditor"
98
98
  v-model="currentContent"
@@ -197,11 +197,11 @@
197
197
  </div>
198
198
  </template>
199
199
  <script setup>
200
- import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
201
- import { DownIcon, ReloadIcon, EditIcon } from "@/components/icons";
200
+ import { ref, computed, watch, onMounted, nextTick } from "vue";
201
+ import { ReloadIcon } from "@/components/icons";
202
202
  import { useUIStore } from "@/stores/ui";
203
203
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
204
- import { getProxyLists, getProxyFile } from "@/stores/requests";
204
+ import { getProxyLists, getProxyFile, getQuickConfig, sendQuickConfig } from "@/stores/requests";
205
205
  import { DEBUG } from "@/utils/debug";
206
206
 
207
207
  const loadFromApi = async (path) => {
@@ -217,7 +217,6 @@ const currentContent = ref("");
217
217
  const proxyContent = ref("");
218
218
  const proxyLists = ref(["loading..."]);
219
219
  const currentProxyList = ref("");
220
- const activeModal = computed(() => ui.activeModal);
221
220
  const ui = useUIStore();
222
221
 
223
222
  // References to editor elements
@@ -245,12 +244,15 @@ const loadAvailableFiles = async () => (availableFiles.value = await loadFromApi
245
244
 
246
245
  const isJsonFile = () => currentFile.value.endsWith(".json");
247
246
 
247
+ // Debounce timer for highlighting
248
+ let highlightTimer = null;
249
+
248
250
  // Function to highlight code using Prism
249
251
  const highlightCode = () => {
250
252
  if (!codeDisplay.value || !codeEditor.value) return;
251
253
 
252
254
  // Get the language based on file extension
253
- let language = "javascript"; // Default language
255
+ let language = "javascript";
254
256
 
255
257
  if (currentFile.value) {
256
258
  if (currentFile.value.endsWith(".json")) {
@@ -259,50 +261,65 @@ const highlightCode = () => {
259
261
  language = "javascript";
260
262
  } else if (currentFile.value.endsWith(".txt") || currentFile.value.endsWith(".csv")) {
261
263
  language = "text";
262
- // Plain text doesn't need highlighting
263
- codeDisplay.value.textContent = currentContent.value || "";
264
- codeDisplay.value.className = "language-text code-highlight";
264
+ if (codeDisplay.value) {
265
+ codeDisplay.value.textContent = currentContent.value || "";
266
+ codeDisplay.value.className = "language-text code-highlight";
267
+ }
268
+ syncScroll();
265
269
  return;
266
270
  }
267
271
  }
268
272
 
269
- // Ensure Prism is available
270
- if (typeof Prism === "undefined") {
271
- console.error("Prism is not loaded");
272
- return;
273
- }
273
+ // Debounce to avoid excessive highlighting
274
+ if (highlightTimer) clearTimeout(highlightTimer);
275
+
276
+ highlightTimer = setTimeout(() => {
277
+ requestAnimationFrame(() => {
278
+ try {
279
+ // Check if Prism and language are available
280
+ if (typeof window.Prism === "undefined" || !window.Prism.languages?.[language]) {
281
+ if (codeDisplay.value) {
282
+ codeDisplay.value.textContent = currentContent.value || "";
283
+ codeDisplay.value.className = `language-${language} code-highlight`;
284
+ }
285
+ syncScroll();
286
+ return;
287
+ }
274
288
 
275
- // Use requestAnimationFrame for smoother updates
276
- requestAnimationFrame(() => {
277
- try {
278
- // Update the pre element with highlighted HTML
279
- const highlighted = Prism.highlight(currentContent.value || "", Prism.languages[language], language);
280
- codeDisplay.value.innerHTML = highlighted;
281
- codeDisplay.value.className = `language-${language} code-highlight`;
282
- } catch (e) {
283
- console.error("Highlight error:", e);
284
- // Fallback to plain text if highlighting fails
285
- codeDisplay.value.textContent = currentContent.value || "";
286
- }
289
+ // Highlight the code (Prism.highlight returns sanitized HTML)
290
+ const highlighted = window.Prism.highlight(
291
+ currentContent.value || "",
292
+ window.Prism.languages[language],
293
+ language
294
+ );
287
295
 
288
- // Ensure scroll positions are synced after highlighting
289
- syncScroll();
290
- });
296
+ if (codeDisplay.value) {
297
+ codeDisplay.value.innerHTML = highlighted;
298
+ codeDisplay.value.className = `language-${language} code-highlight`;
299
+ }
300
+ } catch (e) {
301
+ ui.logger.Error("Highlight error:", e);
302
+ if (codeDisplay.value) {
303
+ codeDisplay.value.textContent = currentContent.value || "";
304
+ }
305
+ } finally {
306
+ syncScroll();
307
+ }
308
+ });
309
+ }, 50);
291
310
  };
292
311
 
293
312
  // Function to sync scrolling between textarea and highlighted code
294
313
  const syncScroll = () => {
295
314
  if (!codeDisplay.value || !codeEditor.value) return;
296
315
 
297
- // Synchronize scrolling between the textarea and the highlighted code
298
- requestAnimationFrame(() => {
299
- codeDisplay.value.scrollTop = codeEditor.value.scrollTop;
300
- codeDisplay.value.scrollLeft = codeEditor.value.scrollLeft;
301
- });
316
+ // Direct synchronization without RAF for immediate response
317
+ codeDisplay.value.scrollTop = codeEditor.value.scrollTop;
318
+ codeDisplay.value.scrollLeft = codeEditor.value.scrollLeft;
302
319
  };
303
320
 
304
321
  // Function to handle tab key press in the editor
305
- const handleTab = (e) => {
322
+ const handleTab = () => {
306
323
  const textarea = codeEditor.value;
307
324
  const start = textarea.selectionStart;
308
325
  const end = textarea.selectionEnd;
@@ -403,7 +420,7 @@ const saveDataToAPI = async (data, endpoint, msg, json = true) => {
403
420
  if (json) {
404
421
  try {
405
422
  j = await res.json();
406
- } catch (e) {
423
+ } catch {
407
424
  ui.showError("Error saving proxies");
408
425
  return;
409
426
  }
@@ -411,7 +428,7 @@ const saveDataToAPI = async (data, endpoint, msg, json = true) => {
411
428
  } else {
412
429
  try {
413
430
  j = await res.text();
414
- } catch (e) {
431
+ } catch {
415
432
  ui.showError("Error saving proxies");
416
433
  return;
417
434
  }
@@ -432,6 +449,11 @@ const saveFile = async () => {
432
449
  };
433
450
  };
434
451
 
452
+ const saveQuickConfig = async () => {
453
+ const content = await getQuickConfig();
454
+ await sendQuickConfig(content);
455
+ };
456
+
435
457
  const saveAll = async () => {
436
458
  if (DEBUG) return;
437
459
  if (currentFile.value && currentContent.value) await saveFile();
@@ -472,7 +494,6 @@ const loadProxies = async (list) => {
472
494
  };
473
495
 
474
496
  const saveProxies = async () => {
475
- // if (!DEBUG) return;
476
497
  if (previous.proxyContent.content === proxyContent.value) ui.logger.Info("proxies did not change");
477
498
  else {
478
499
  await saveDataToAPI(
@@ -567,9 +588,8 @@ loadProxyLists();
567
588
  </script>
568
589
  <style lang="scss" scoped>
569
590
  /* ==========================================================================
570
- Z-INDEX MANAGEMENT
591
+ Z-INDEX MANAGEMENT - Required for dropdown layering
571
592
  ========================================================================== */
572
-
573
593
  .z-dropdown-high {
574
594
  position: relative;
575
595
  z-index: 200;
@@ -591,188 +611,139 @@ loadProxyLists();
591
611
  }
592
612
 
593
613
  /* ==========================================================================
594
- ADMIN EDITOR SECTION CONDITIONAL VISIBILITY
595
- ========================================================================== */
596
-
597
- /* Hide admin editor when proxy editor is open - ALL SCREEN SIZES */
598
- .admin-editor-section.landscape-hidden {
599
- display: none !important;
600
- }
601
-
602
- /* ==========================================================================
603
- PROXY EDITOR SECTION CONDITIONAL VISIBILITY
614
+ CONDITIONAL VISIBILITY - Required for editor switching logic
604
615
  ========================================================================== */
605
-
606
- /* Hide proxy editor when admin editor is open - ALL SCREEN SIZES */
616
+ .admin-editor-section.landscape-hidden,
607
617
  .proxy-editor-section.landscape-hidden {
608
618
  display: none !important;
609
619
  }
610
620
 
611
621
  /* ==========================================================================
612
- SECTION HEADERS
622
+ FILE DROPDOWN STYLING
613
623
  ========================================================================== */
614
-
615
- /* ==========================================================================
616
- ADMIN FILE DROPDOWN STYLING
617
- ========================================================================== */
618
-
619
624
  .admin-file-dropdown {
620
625
  height: 2.5rem !important;
621
- border-top-right-radius: 0 !important;
622
- border-bottom-right-radius: 0 !important;
623
- border-top-left-radius: 0.5rem !important;
624
- border-bottom-left-radius: 0.5rem !important;
625
626
  }
626
627
 
627
628
  .proxy-file-dropdown {
629
+ @apply rounded-lg;
628
630
  height: 2.5rem !important;
629
- border-radius: 0.5rem !important;
630
631
  }
631
632
 
632
633
  .refresh-button {
633
634
  height: 2.5rem !important;
634
- transition: all 0.2s ease;
635
635
 
636
636
  &:hover {
637
637
  .refresh-icon {
638
- transform: rotate(180deg);
638
+ @apply rotate-180;
639
639
  }
640
640
  }
641
641
  }
642
642
 
643
643
  .refresh-icon {
644
- transition: transform 0.5s ease;
644
+ @apply transition-transform duration-500;
645
645
  }
646
646
 
647
647
  /* ==========================================================================
648
648
  EDITOR CONTAINERS
649
649
  ========================================================================== */
650
-
651
- .editor-container,
652
- .proxy-editor-container {
650
+ .editor-container {
653
651
  position: relative;
654
- overflow: hidden;
655
652
  height: 400px;
656
653
  max-height: 60vh;
657
- border-radius: 8px;
658
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
659
- background-color: oklch(0.19 0 0);
660
- transition: height 0.3s ease;
661
654
  }
662
655
 
663
656
  .proxy-editor-container {
664
- /* Ensure proxy editor takes full container space */
665
- display: flex;
666
- flex-direction: column;
657
+ @apply relative overflow-hidden rounded-lg shadow-card bg-dark-350 flex flex-col;
658
+ height: 400px;
659
+ max-height: 60vh;
660
+ transition: height 0.3s ease;
667
661
  }
668
662
 
669
663
  .proxy-editor-container .pb-4 {
670
- /* Remove bottom padding to maximize space */
664
+ @apply flex-1 flex flex-col;
671
665
  padding-bottom: 0 !important;
672
- flex: 1;
673
- display: flex;
674
- flex-direction: column;
675
666
  }
676
667
 
677
668
  .proxy-editor-container .table-component {
678
- /* Make proxy editor container take full height */
679
- flex: 1;
680
- display: flex;
681
- flex-direction: column;
682
- }
683
-
684
- .editor-wrapper {
685
- position: relative;
686
- width: 100%;
687
- height: 100%;
688
- overflow: hidden;
689
- border-radius: 8px;
669
+ @apply flex-1 flex flex-col;
690
670
  }
691
671
 
692
672
  /* ==========================================================================
693
- EDITOR TEXTAREAS
673
+ EDITOR TEXTAREAS - CRITICAL: Custom font, scrolling behavior
694
674
  ========================================================================== */
695
-
696
- .code-editor,
697
675
  .proxy-editor {
676
+ @apply w-full h-full overflow-auto resize-none border-none outline-none bg-transparent p-3 m-0 box-border text-light-300 flex-1;
698
677
  font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
699
678
  font-size: 14px;
700
679
  line-height: 1.6;
701
680
  tab-size: 4;
702
- background-color: transparent;
703
- border: none;
704
- outline: none;
705
- resize: none;
681
+ -webkit-overflow-scrolling: touch;
682
+ min-height: 100%;
683
+ }
684
+
685
+ /* EXACT COPY from FilterPreview.vue - Shared base styles for perfect alignment */
686
+ .code-editor,
687
+ .code-highlight {
688
+ position: absolute;
689
+ inset: 0;
706
690
  width: 100%;
707
691
  height: 100%;
708
- overflow: auto;
709
- -webkit-overflow-scrolling: touch;
692
+ min-height: 300px;
693
+ max-height: 500px;
710
694
  padding: 12px;
711
695
  margin: 0;
696
+ overflow: auto;
697
+ white-space: pre;
698
+ font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
699
+ font-size: 14px;
700
+ line-height: 1.6;
701
+ tab-size: 4;
702
+ -moz-tab-size: 4;
703
+ word-wrap: normal;
704
+ word-break: normal;
705
+ border: 0;
706
+ outline: none;
707
+ background: transparent;
712
708
  box-sizing: border-box;
713
709
  }
714
710
 
711
+ /* Code editor layer - interactive */
715
712
  .code-editor {
716
- /* Make textarea text transparent so syntax highlighting shows through */
717
- color: transparent !important;
718
713
  z-index: 2;
719
- position: relative;
720
- caret-color: #ffffff; /* Keep cursor visible */
721
-
722
- /* Selection styling */
723
- &::selection {
724
- background-color: rgba(255, 255, 255, 0.2);
725
- }
714
+ color: transparent !important;
715
+ caret-color: #ffffff;
716
+ resize: none;
726
717
  }
727
718
 
728
- .proxy-editor {
729
- @apply w-full h-full resize-none focus:outline-none;
730
- color: oklch(0.90 0 0); /* Keep normal text color for proxy editor */
731
- /* Ensure proxy editor takes full height */
732
- flex: 1;
733
- min-height: 100%;
719
+ .code-editor::selection {
720
+ background-color: rgba(38, 79, 120, 0.6);
721
+ color: transparent;
734
722
  }
735
723
 
736
- /* ==========================================================================
737
- CODE HIGHLIGHTING
738
- ========================================================================== */
724
+ .code-editor::-moz-selection {
725
+ background-color: rgba(38, 79, 120, 0.6);
726
+ color: transparent;
727
+ }
739
728
 
729
+ /* Highlight layer - visual only */
740
730
  .code-highlight {
741
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
742
- font-size: 14px;
743
- line-height: 1.6;
744
- tab-size: 4;
745
- /* Remove transparent color - let syntax highlighting show */
746
- color: #f8f8f2 !important;
747
- background: transparent;
748
- border: none;
749
- outline: none;
750
- resize: none;
751
- width: 100%;
752
- height: 100%;
753
- overflow: hidden;
754
- white-space: pre;
755
- pointer-events: none;
756
731
  z-index: 1;
757
- position: absolute;
758
- top: 0;
759
- left: 0;
760
- padding: 12px;
761
- margin: 0;
762
- box-sizing: border-box;
732
+ pointer-events: none;
733
+ user-select: none;
734
+ -webkit-user-select: none;
735
+ -moz-user-select: none;
763
736
  }
764
737
 
765
738
  /* ==========================================================================
766
- PRISM.JS SYNTAX HIGHLIGHTING THEME
739
+ PRISM.JS SYNTAX HIGHLIGHTING THEME - CRITICAL: Token colors
767
740
  ========================================================================== */
768
-
769
741
  code[class*="language-"],
770
742
  pre[class*="language-"] {
743
+ @apply bg-transparent text-left;
771
744
  color: #f8f8f2;
772
- background: none;
773
- text-shadow: 0 1px rgba(0, 0, 0, 0.3);
745
+ text-shadow: 0 1px oklch(0 0 0 / 0.3);
774
746
  font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
775
- text-align: left;
776
747
  white-space: pre;
777
748
  word-spacing: normal;
778
749
  word-break: normal;
@@ -782,43 +753,46 @@ pre[class*="language-"] {
782
753
  hyphens: none;
783
754
  }
784
755
 
785
- pre[class*="language-"] {
786
- padding: 12px;
787
- margin: 0;
788
- overflow: auto;
789
- border-radius: 4px;
756
+ pre[class*="language-"]:not(.code-highlight) {
757
+ @apply p-3 m-0 overflow-auto rounded;
790
758
  font-family: "Menlo", "Monaco", "Courier New", monospace;
791
759
  font-size: 14px;
792
760
  line-height: 1.5;
793
761
  tab-size: 4;
794
762
  }
795
763
 
764
+ /* Override for code-highlight to match code-editor */
765
+ .code-highlight.language-json,
766
+ .code-highlight[class*="language-"] {
767
+ line-height: 1.6 !important;
768
+ font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace !important;
769
+ }
770
+
796
771
  :not(pre) > code[class*="language-"],
797
772
  pre[class*="language-"] {
798
- background: oklch(0.19 0 0);
799
- white-space: pre;
773
+ @apply bg-dark-350 whitespace-pre;
800
774
  }
801
775
 
802
776
  :not(pre) > code[class*="language-"] {
777
+ @apply whitespace-normal rounded-sm;
803
778
  padding: 0.1em;
804
- border-radius: 0.3em;
805
- white-space: normal;
806
779
  }
807
780
 
781
+ /* CRITICAL: VSCode Dark+ theme for syntax highlighting */
808
782
  .token.comment,
809
783
  .token.prolog,
810
784
  .token.doctype,
811
785
  .token.cdata {
812
- color: #676f7d;
813
786
  font-style: italic;
787
+ color: #6a9955;
814
788
  }
815
789
 
816
790
  .token.punctuation {
817
- color: #a9b7c6;
791
+ color: #d4d4d4;
818
792
  }
819
793
 
820
794
  .namespace {
821
- opacity: 0.8;
795
+ @apply opacity-80;
822
796
  }
823
797
 
824
798
  .token.property,
@@ -826,12 +800,12 @@ pre[class*="language-"] {
826
800
  .token.constant,
827
801
  .token.symbol,
828
802
  .token.deleted {
829
- color: #e06c75;
803
+ color: #b5cea8;
830
804
  }
831
805
 
832
806
  .token.boolean,
833
807
  .token.number {
834
- color: #c792ea;
808
+ color: #b5cea8;
835
809
  }
836
810
 
837
811
  .token.selector,
@@ -840,32 +814,35 @@ pre[class*="language-"] {
840
814
  .token.char,
841
815
  .token.builtin,
842
816
  .token.inserted {
843
- color: #98c379;
817
+ color: #ce9178;
844
818
  }
845
819
 
846
820
  .token.operator,
847
821
  .token.entity,
848
- .token.url,
822
+ .token.url {
823
+ color: #d4d4d4;
824
+ }
825
+
849
826
  .language-css .token.string,
850
827
  .style .token.string {
851
- color: #89ddff;
828
+ color: #ce9178;
852
829
  }
853
830
 
854
831
  .token.atrule,
855
832
  .token.attr-value,
856
833
  .token.keyword {
857
- color: #61afef;
834
+ color: #c586c0;
858
835
  }
859
836
 
860
837
  .token.function,
861
838
  .token.class-name {
862
- color: #ffcb6b;
839
+ color: #dcdcaa;
863
840
  }
864
841
 
865
842
  .token.regex,
866
843
  .token.important,
867
844
  .token.variable {
868
- color: #c5e478;
845
+ color: #9cdcfe;
869
846
  }
870
847
 
871
848
  .token.important,
@@ -878,25 +855,19 @@ pre[class*="language-"] {
878
855
  }
879
856
 
880
857
  .token.entity {
881
- cursor: help;
858
+ @apply cursor-help;
882
859
  }
883
860
 
884
861
  .editor-container:hover {
885
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
862
+ @apply shadow-xl;
886
863
  transition: box-shadow 0.3s ease;
887
864
  }
888
865
 
889
866
  /* ==========================================================================
890
867
  ERROR MESSAGES
891
868
  ========================================================================== */
892
-
893
869
  .error-container {
894
- @apply flex items-start gap-3 p-4 bg-red-500/10 border border-red-500/20 rounded-lg mt-4 mb-4;
895
-
896
- /* Prevent overflow in landscape mode */
897
- max-width: 100%;
898
- word-wrap: break-word;
899
- overflow-wrap: break-word;
870
+ @apply flex items-start gap-3 p-4 bg-red-500/10 border border-red-500/20 rounded-lg mt-4 mb-4 max-w-full break-words;
900
871
  }
901
872
 
902
873
  .error-icon {
@@ -904,8 +875,7 @@ pre[class*="language-"] {
904
875
  }
905
876
 
906
877
  .error-content {
907
- @apply flex-1;
908
- min-width: 0; /* Allow text to shrink */
878
+ @apply flex-1 min-w-0;
909
879
  }
910
880
 
911
881
  .error-title {
@@ -913,72 +883,39 @@ pre[class*="language-"] {
913
883
  }
914
884
 
915
885
  .error-text {
916
- @apply text-red-300 text-xs;
917
- word-break: break-word;
886
+ @apply text-red-300 text-xs break-words;
918
887
  }
919
888
 
920
889
  /* ==========================================================================
921
- SECTION DIVIDER
890
+ SECTION DIVIDER - CRITICAL: Gradient effect
922
891
  ========================================================================== */
923
-
924
892
  .section-divider {
925
- margin: 24px 0; /* Balanced margin for better centering */
893
+ @apply relative w-full border-none;
894
+ margin: 24px 0;
926
895
  height: 1px;
927
- width: 100%;
928
- position: relative;
929
-
930
- /* Add subtle gradient effect for better visual separation */
931
896
  background: linear-gradient(90deg, transparent 0%, oklch(0.26 0 0) 20%, oklch(0.26 0 0) 80%, transparent 100%);
932
- border: none;
933
-
934
- /* Add a subtle glow effect */
935
- box-shadow: 0 0 8px rgba(61, 62, 68, 0.3);
897
+ box-shadow: 0 0 8px oklch(0.26 0 0 / 0.3);
936
898
  }
937
899
 
938
- /* ==========================================================================
939
- RESPONSIVE LAYOUT UTILITIES
940
- ========================================================================== */
941
-
942
- .flex-responsive-wrap {
943
- @apply flex flex-wrap items-center gap-4;
944
- }
945
-
946
-
947
900
  /* ==========================================================================
948
901
  MOBILE RESPONSIVE STYLES (max-width: 767px)
949
902
  ========================================================================== */
950
-
951
903
  @media (max-width: 767px) {
952
- /* Layout adjustments - almost no gap between dropdown and buttons */
953
- .flex-responsive-wrap {
954
- @apply flex-col items-stretch !important;
955
- gap: 0px !important; /* Reduced from 2px to eliminate space between dropdown and buttons */
956
- }
957
-
958
- /* Button container - even spacing across full width */
959
- .flex-responsive-wrap > div:last-child {
960
- @apply flex justify-center !important;
961
- }
962
-
963
- /* Editor container heights - fill available space on mobile */
964
904
  .editor-container,
965
905
  .proxy-editor-container {
966
- height: calc(100vh - 380px) !important; /* Fill viewport minus header/controls */
967
- max-height: 60vh !important; /* Allow more height */
968
- min-height: 300px !important; /* Ensure minimum usable height */
906
+ height: calc(100vh - 380px) !important;
907
+ max-height: 60vh !important;
908
+ min-height: 300px !important;
969
909
  }
970
910
 
971
- /* Reduce editor height when JSON error is showing in portrait */
972
911
  .editor-container.has-error {
973
- height: calc(100vh - 460px) !important; /* Account for error message */
912
+ height: calc(100vh - 460px) !important;
974
913
  max-height: 50vh !important;
975
914
  min-height: 250px !important;
976
915
  }
977
916
 
978
- /* Error message spacing on mobile */
979
917
  .error-container {
980
- margin: 8px 0 !important;
981
- padding: 8px !important;
918
+ @apply my-2 p-2;
982
919
  font-size: 11px !important;
983
920
  }
984
921
 
@@ -990,36 +927,18 @@ pre[class*="language-"] {
990
927
  font-size: 10px !important;
991
928
  }
992
929
 
993
- /* Compact editor controls spacing - no margins */
994
- .editor-controls-row,
995
- .proxy-controls-row {
996
- margin-bottom: 0px !important; /* Eliminated margin completely */
997
- }
998
-
999
- /* Compact editor containers - reduced margins for more space */
1000
930
  .my-3 {
1001
- margin: 6px 0 !important; /* Reduced margin between editor and controls */
931
+ @apply my-1.5;
1002
932
  }
1003
933
 
1004
934
  .pb-4 {
1005
- padding-bottom: 4px !important; /* Reduced padding */
935
+ @apply pb-1;
1006
936
  }
1007
937
  }
1008
938
 
1009
939
  /* ==========================================================================
1010
- TABLET RESPONSIVE STYLES (min-width: 768px)
940
+ DESKTOP RESPONSIVE STYLES
1011
941
  ========================================================================== */
1012
-
1013
- @media (min-width: 768px) {
1014
- .flex-responsive-wrap {
1015
- @apply flex-row justify-between items-center !important;
1016
- }
1017
- }
1018
-
1019
- /* ==========================================================================
1020
- DESKTOP RESPONSIVE STYLES (min-width: 1024px)
1021
- ========================================================================== */
1022
-
1023
942
  @media (min-width: 1024px) {
1024
943
  .editor-container,
1025
944
  .proxy-editor-container {
@@ -1027,10 +946,6 @@ pre[class*="language-"] {
1027
946
  }
1028
947
  }
1029
948
 
1030
- /* ==========================================================================
1031
- LARGE DESKTOP RESPONSIVE STYLES (min-width: 1280px)
1032
- ========================================================================== */
1033
-
1034
949
  @media (min-width: 1280px) {
1035
950
  .editor-container,
1036
951
  .proxy-editor-container {
@@ -1039,32 +954,17 @@ pre[class*="language-"] {
1039
954
  }
1040
955
 
1041
956
  /* ==========================================================================
1042
- MOBILE LANDSCAPE MODE (max-height: 500px + landscape)
957
+ LANDSCAPE MODE - CRITICAL: Complex responsive behavior
1043
958
  ========================================================================== */
1044
-
1045
959
  @media (max-height: 500px) and (orientation: landscape) and (min-width: 568px) {
1046
- /* Truncate card cleanly when admin editor is open */
1047
960
  .admin-editor-section:not(.landscape-hidden) + .section-divider + .proxy-editor-section.landscape-hidden {
1048
- display: none !important;
1049
- }
1050
-
1051
- /* Ensure proxy editor takes full space when visible */
1052
- .proxy-editor-container {
1053
- height: 130px !important;
1054
- max-height: 130px !important;
1055
- min-height: 100px !important;
1056
- }
1057
-
1058
- .proxy-editor {
1059
- height: 100% !important;
1060
- min-height: 100% !important;
961
+ @apply hidden;
1061
962
  }
1062
963
 
1063
- /* Compact headings */
1064
964
  h4 {
965
+ @apply pt-4;
1065
966
  font-size: 16px !important;
1066
967
  margin-bottom: 6px !important;
1067
- padding-top: 1rem !important;
1068
968
  }
1069
969
 
1070
970
  h5 {
@@ -1072,170 +972,109 @@ pre[class*="language-"] {
1072
972
  margin-bottom: 4px !important;
1073
973
  }
1074
974
 
1075
- /* Section divider */
1076
975
  .section-divider {
1077
- margin: 6px 0 !important;
976
+ @apply my-1.5;
1078
977
  }
1079
978
 
1080
- /* Editor controls layout */
1081
- .editor-controls-row,
1082
- .proxy-controls-row {
1083
- gap: 6px !important;
1084
- }
1085
-
1086
- /* Hide button text to save space in landscape mode */
1087
- .button-default span {
1088
- display: none !important;
1089
- }
1090
-
1091
- /* Compact editor containers - reduced margins for more space */
1092
979
  .my-3 {
1093
- margin: 4px 0 !important; /* Reduced from 6px */
980
+ @apply my-1;
1094
981
  }
1095
982
 
1096
983
  .pb-4 {
1097
- padding-bottom: 2px !important; /* Reduced from 4px */
984
+ @apply pb-0.5;
1098
985
  }
1099
986
 
1100
- /* Editor container heights - increased to use more available space */
1101
987
  .editor-container,
1102
988
  .proxy-editor-container {
1103
- height: 180px !important; /* Increased from 130px */
989
+ height: 180px !important;
1104
990
  max-height: 180px !important;
1105
- min-height: 150px !important; /* Increased from 100px */
991
+ min-height: 150px !important;
1106
992
  }
1107
993
 
1108
- /* Reduce editor height when JSON error is showing */
1109
994
  .editor-container.has-error {
1110
- height: 120px !important; /* Reduced to make room for error message */
995
+ height: 120px !important;
1111
996
  max-height: 120px !important;
1112
997
  min-height: 100px !important;
1113
998
  }
1114
999
 
1115
- /* Ensure proxy editor takes full space when visible - increased height */
1116
- .proxy-editor-container {
1117
- height: 180px !important; /* Increased from 130px */
1118
- max-height: 180px !important;
1119
- min-height: 150px !important;
1000
+ .proxy-editor {
1001
+ @apply h-full;
1002
+ min-height: 100% !important;
1120
1003
  }
1121
1004
 
1122
- /* Editor textarea heights - ensure syntax highlighting works */
1123
- .code-editor,
1124
1005
  .proxy-editor {
1125
- font-size: 11px !important;
1006
+ @apply p-1.5 text-xs;
1126
1007
  line-height: 1.4 !important;
1127
- padding: 6px !important;
1128
1008
  }
1129
1009
 
1130
- /* Syntax highlighting in landscape mode */
1010
+ .code-editor,
1131
1011
  .code-highlight {
1132
- font-size: 11px !important;
1133
- line-height: 1.4 !important;
1134
1012
  padding: 6px !important;
1135
- /* Ensure syntax highlighting is visible in landscape */
1136
- color: #f8f8f2 !important;
1137
- opacity: 1 !important;
1138
- display: block !important;
1013
+ font-size: 12px !important;
1014
+ line-height: 1.4 !important;
1139
1015
  }
1140
1016
 
1141
- /* Compact error messages */
1142
1017
  .error-container {
1143
- margin: 2px 0 !important; /* Reduced from 4px */
1144
- padding: 4px 6px !important; /* More compact padding */
1145
- font-size: 9px !important; /* Reduced from 10px */
1146
- border-radius: 4px !important; /* Smaller border radius */
1147
- gap: 6px !important; /* Reduced gap */
1018
+ @apply rounded gap-1.5 my-0.5 px-1.5 py-1;
1019
+ font-size: 9px !important;
1148
1020
  }
1149
1021
 
1150
1022
  .error-icon {
1151
- width: 12px !important;
1152
- height: 12px !important;
1153
- }
1154
-
1155
- .error-icon svg {
1156
- width: 12px !important;
1157
- height: 12px !important;
1023
+ @apply w-3 h-3;
1158
1024
  }
1159
1025
 
1160
1026
  .error-title {
1161
- font-size: 9px !important; /* Reduced from 10px */
1162
- margin-bottom: 2px !important;
1163
- font-weight: 500 !important;
1027
+ @apply mb-0.5 font-medium;
1028
+ font-size: 9px !important;
1164
1029
  }
1165
1030
 
1166
1031
  .error-text {
1167
- font-size: 8px !important; /* Reduced from 9px */
1032
+ font-size: 8px !important;
1168
1033
  line-height: 1.2 !important;
1169
1034
  }
1170
1035
  }
1171
1036
 
1172
- /* ==========================================================================
1173
- VERY SHORT LANDSCAPE SCREENS (max-height: 400px + landscape)
1174
- ========================================================================== */
1175
-
1037
+ /* Very short landscape screens */
1176
1038
  @media (max-height: 400px) and (orientation: landscape) {
1177
1039
  .editor-container,
1178
1040
  .proxy-editor-container {
1179
- height: 140px !important; /* Increased from 100px */
1041
+ height: 140px !important;
1180
1042
  max-height: 140px !important;
1181
- min-height: 120px !important; /* Increased from 80px */
1043
+ min-height: 120px !important;
1182
1044
  }
1183
1045
 
1184
- /* Reduce editor height when JSON error is showing in very short landscape */
1185
1046
  .editor-container.has-error {
1186
- height: 90px !important; /* Reduced to make room for error message */
1047
+ height: 90px !important;
1187
1048
  max-height: 90px !important;
1188
1049
  min-height: 80px !important;
1189
1050
  }
1190
1051
 
1191
- /* Ensure proxy editor takes full space in very short landscape */
1192
1052
  .proxy-editor {
1193
- height: 100% !important;
1053
+ @apply h-full p-1;
1194
1054
  min-height: 100% !important;
1195
1055
  font-size: 10px !important;
1196
1056
  line-height: 1.3 !important;
1197
- padding: 4px !important;
1198
- }
1199
-
1200
- .code-editor {
1201
- font-size: 10px !important;
1202
- line-height: 1.3 !important;
1203
- padding: 4px !important;
1204
1057
  }
1205
1058
 
1206
- /* Ensure syntax highlighting works in very short landscape */
1059
+ .code-editor,
1207
1060
  .code-highlight {
1061
+ padding: 4px !important;
1208
1062
  font-size: 10px !important;
1209
1063
  line-height: 1.3 !important;
1210
- padding: 4px !important;
1211
- color: #f8f8f2 !important;
1212
- opacity: 1 !important;
1213
- display: block !important;
1214
1064
  }
1215
1065
 
1216
- /* Ultra-compact error messages for very short landscape */
1217
1066
  .error-container {
1218
- margin: 1px 0 !important;
1219
- padding: 2px 4px !important;
1067
+ @apply rounded-sm gap-1 my-px px-1 py-0.5;
1220
1068
  font-size: 8px !important;
1221
- border-radius: 3px !important;
1222
- gap: 4px !important;
1223
1069
  }
1224
1070
 
1225
1071
  .error-icon {
1226
- width: 10px !important;
1227
- height: 10px !important;
1228
- }
1229
-
1230
- .error-icon svg {
1231
- width: 10px !important;
1232
- height: 10px !important;
1072
+ @apply w-2.5 h-2.5;
1233
1073
  }
1234
1074
 
1235
1075
  .error-title {
1076
+ @apply mb-px font-medium;
1236
1077
  font-size: 8px !important;
1237
- margin-bottom: 1px !important;
1238
- font-weight: 500 !important;
1239
1078
  }
1240
1079
 
1241
1080
  .error-text {
@@ -1245,37 +1084,32 @@ pre[class*="language-"] {
1245
1084
  }
1246
1085
 
1247
1086
  /* ==========================================================================
1248
- MOBILE & TABLET - COMPACT EDITOR BUTTONS FOR ONE-ROW LAYOUT
1087
+ COMPACT EDITOR BUTTONS
1249
1088
  ========================================================================== */
1250
-
1251
- /* Icon-sized buttons - compact for all screens */
1252
1089
  .button-default {
1253
- @apply h-7 w-7 p-0; // 28x28px on desktop/tablet
1090
+ @apply h-7 w-7 p-0 rounded-md;
1254
1091
  min-width: 28px !important;
1255
1092
  max-width: 28px !important;
1256
1093
  width: 28px !important;
1257
1094
  height: 28px !important;
1258
1095
  padding: 0 !important;
1259
- border-radius: 6px;
1260
1096
 
1261
1097
  svg {
1262
- @apply w-4 h-4; // 16x16px icons
1098
+ @apply w-4 h-4;
1263
1099
  }
1264
1100
  }
1265
1101
 
1266
- /* Even more compact on mobile */
1267
1102
  @media (max-width: 640px) {
1268
1103
  .button-default {
1269
- @apply h-6 w-6; // 24x24px on mobile
1104
+ @apply h-6 w-6;
1270
1105
  min-width: 24px !important;
1271
1106
  max-width: 24px !important;
1272
1107
  width: 24px !important;
1273
1108
  height: 24px !important;
1274
1109
 
1275
1110
  svg {
1276
- @apply w-3.5 h-3.5; // 14x14px icons
1111
+ @apply w-3.5 h-3.5;
1277
1112
  }
1278
1113
  }
1279
1114
  }
1280
-
1281
1115
  </style>