@necrolab/dashboard 0.4.35 → 0.4.36

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@necrolab/dashboard",
3
- "version": "0.4.35",
3
+ "version": "0.4.36",
4
4
  "scripts": {
5
5
  "build": "rm -rf dist && vite build && npx workbox-cli generateSW workbox-config.js",
6
6
  "dev": "vite",
package/src/App.vue CHANGED
@@ -50,14 +50,17 @@
50
50
  <div v-else key="main-components" class="flex">
51
51
  <Navbar v-if="layout == 'dashboard'" class="fixed" />
52
52
  <div :class="['router-wrapper', { '-mt-[60px]': ui.pullChange > 1 }]"></div>
53
- <router-view v-slot="{ Component }">
54
- <component
55
- :is="Component"
56
- :class="[
57
- 'component-container pb-2 mt-0 lg:mt-4 ios-wrapper',
58
- { 'w-full': landscapeIos }
59
- ]"
60
- />
53
+ <router-view v-slot="{ Component, route }">
54
+ <transition name="page-transition" mode="out-in">
55
+ <component
56
+ :is="Component"
57
+ :key="route.path"
58
+ :class="[
59
+ 'component-container pb-2 mt-0 lg:mt-4 ios-wrapper',
60
+ { 'w-full': landscapeIos }
61
+ ]"
62
+ />
63
+ </transition>
61
64
  </router-view>
62
65
  </div>
63
66
  </transition>
@@ -296,4 +299,25 @@ const layout = computed(() => router.currentRoute.value.meta.layout);
296
299
  .component-container {
297
300
  @apply w-full mx-auto px-4 xs:px-4 md:px-2 lg:px-6 xl:px-10;
298
301
  }
302
+
303
+ // Page navigation transitions
304
+ .page-transition-enter-active {
305
+ transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
306
+ }
307
+
308
+ .page-transition-leave-active {
309
+ transition: all 0.15s cubic-bezier(0.55, 0.085, 0.68, 0.53);
310
+ }
311
+
312
+ .page-transition-enter-from {
313
+ opacity: 0;
314
+ transform: translateX(15px) scale(0.98);
315
+ filter: blur(1px);
316
+ }
317
+
318
+ .page-transition-leave-to {
319
+ opacity: 0;
320
+ transform: translateX(-10px) scale(1.02);
321
+ filter: blur(0.5px);
322
+ }
299
323
  </style>
@@ -35,7 +35,7 @@
35
35
  :items="toRender"
36
36
  :item-size="64"
37
37
  key-field="_id"
38
- class="scroller vue-recycle-scroller ready direction-vertical flex flex-col divide-y-2 divide-border max-h-big overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
38
+ class="scroller vue-recycle-scroller ready direction-vertical flex flex-col divide-y divide-dark-650 max-h-big overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
39
39
  >
40
40
  <template #default="props">
41
41
  <div class="task" :key="i[props.item._id]">
@@ -48,7 +48,7 @@
48
48
  </template>
49
49
  </RecycleScroller>
50
50
  </div>
51
- <div v-else class="flex justify-center text-light-400 py-2 bg-dark-500 border-b-2 border-border">
51
+ <div v-else class="flex justify-center py-8 bg-dark-400 empty-state">
52
52
  No accounts found
53
53
  </div>
54
54
  </Table>
@@ -64,6 +64,12 @@ h4 {
64
64
  .stop-pan {
65
65
  touch-action: pan-y pan-up pan-down;
66
66
  }
67
+
68
+ .empty-state {
69
+ color: #969696;
70
+ font-size: 14px;
71
+ font-weight: 500;
72
+ }
67
73
  </style>
68
74
  <script setup>
69
75
  import { Table, Header } from "@/components/Table";
@@ -39,7 +39,7 @@
39
39
  :items="toRender"
40
40
  :item-size="64"
41
41
  key-field="index"
42
- class="scroller max-h-big vue-recycle-scroller ready direction-vertical flex flex-col divide-y-2 divide-border overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
42
+ class="scroller max-h-big vue-recycle-scroller ready direction-vertical flex flex-col divide-y divide-dark-650 overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
43
43
  >
44
44
  <template #default="props">
45
45
  <div class="task" :key="i[props.item.index]">
@@ -52,7 +52,7 @@
52
52
  </template>
53
53
  </RecycleScroller>
54
54
  </div>
55
- <div v-else class="flex justify-center text-light-400 py-2 bg-dark-500 border-b-2 border-border">
55
+ <div v-else class="flex justify-center py-8 bg-dark-400 empty-state">
56
56
  No profiles found
57
57
  </div>
58
58
  </Table>
@@ -73,6 +73,12 @@ h4 {
73
73
  max-height: calc(100vh - 20rem);
74
74
  overflow: hidden;
75
75
  }
76
+
77
+ .empty-state {
78
+ color: #969696;
79
+ font-size: 14px;
80
+ font-weight: 500;
81
+ }
76
82
  </style>
77
83
  <script setup>
78
84
  import { Table, Header } from "@/components/Table";
@@ -1,16 +1,78 @@
1
1
  <template>
2
- <div class="flex rounded-2xl w-fit shadow-3xl items-center justify-center bg-dark-600">
3
- <span class="font-bold p-2 truncate small">{{ props.text }}</span>
2
+ <div class="tag-pill">
3
+ <span class="tag-text">{{ formatText(props.text) }}</span>
4
4
  </div>
5
5
  </template>
6
6
 
7
- <style scoped>
8
- .small {
9
- font-size: 0.6rem;
10
- line-height: 0.8rem;
7
+ <style lang="scss" scoped>
8
+ .tag-pill {
9
+ @apply inline-flex items-center justify-center rounded-md transition-all duration-200;
10
+ background: linear-gradient(145deg, #3d3e44, #35363c);
11
+ border: 1px solid #4b4c53;
12
+ padding: 0.1875rem 0.5rem;
13
+ min-width: 2rem;
14
+ max-width: 4.5rem;
15
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05);
16
+
17
+ &:hover {
18
+ background: linear-gradient(145deg, #44454b, #3d3e44);
19
+ border-color: #52535a;
20
+ transform: translateY(-0.5px);
21
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.08);
22
+ }
23
+ }
24
+
25
+ .tag-text {
26
+ @apply text-white font-semibold truncate;
27
+ font-size: 0.6875rem;
28
+ line-height: 1.1;
29
+ letter-spacing: 0.025em;
30
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
31
+ }
32
+
33
+ // Ultra responsive design
34
+ @media (max-width: 1024px) {
35
+ .tag-pill {
36
+ padding: 0.1rem 0.3rem;
37
+ max-width: 3.5rem;
38
+ }
39
+
40
+ .tag-text {
41
+ font-size: 0.575rem;
42
+ }
43
+ }
44
+
45
+ @media (max-width: 768px) {
46
+ .tag-pill {
47
+ padding: 0.075rem 0.25rem;
48
+ max-width: 3rem;
49
+ min-width: 1.25rem;
50
+ }
51
+
52
+ .tag-text {
53
+ font-size: 0.55rem;
54
+ }
55
+ }
56
+
57
+ @media (max-width: 480px) {
58
+ .tag-pill {
59
+ padding: 0.05rem 0.2rem;
60
+ max-width: 2.5rem;
61
+ min-width: 1rem;
62
+ }
63
+
64
+ .tag-text {
65
+ font-size: 0.5rem;
66
+ }
11
67
  }
12
68
  </style>
13
69
 
14
70
  <script setup>
15
71
  const props = defineProps({ text: { type: String } });
72
+
73
+ const formatText = (text) => {
74
+ if (!text) return '';
75
+ // Capitalize first letter and keep rest as-is
76
+ return text.charAt(0).toUpperCase() + text.slice(1);
77
+ };
16
78
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="border-b-2 border-border w-full px-2 md:px-4 text-white text-xs py-4 grid items-center">
2
+ <div class="border-b border-dark-600 w-full px-2 md:px-4 text-white text-xs py-4 grid items-center">
3
3
  <slot />
4
4
  </div>
5
5
  </template>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="px-2 md:px-4 text-xs grid text-white py-4 items-center h-full">
2
+ <div class="px-2 md:px-4 text-xs grid text-white py-4 items-center h-full bg-dark-400 even:bg-dark-350 hover:bg-dark-500 transition-colors">
3
3
  <slot />
4
4
  </div>
5
5
  </template>
@@ -5,12 +5,12 @@
5
5
  </template>
6
6
  <style lang="scss">
7
7
  .table-component {
8
- @apply flex-col bg-clip-padding rounded relative box-border border border-b-0 border-dark-650 bg-dark-400;
8
+ @apply flex-col bg-clip-padding rounded relative box-border border border-dark-600 bg-dark-500;
9
9
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
10
10
  }
11
11
 
12
12
  .table-component > .grid {
13
- @apply bg-dark-500;
13
+ @apply bg-dark-400;
14
14
  border-bottom: 1px solid #3d3e44;
15
15
  }
16
16
  </style>
@@ -24,10 +24,7 @@
24
24
  <TicketIcon class="mr-0 ipadlg:mr-3" />
25
25
  <h4 class="hidden ipadlg:flex">Tickets</h4>
26
26
  </div>
27
- <div
28
- class="col-span-6 md:col-span-4 lg:col-span-3 flex-center"
29
- @click="ui.toggleSort('status')"
30
- >
27
+ <div class="col-span-6 md:col-span-4 lg:col-span-3 flex-center" @click="ui.toggleSort('status')">
31
28
  <StatusIcon class="mr-0 ipadlg:mr-3" />
32
29
  <h4 class="hidden ipadlg:flex">Status</h4>
33
30
  <DownIcon v-if="ui.sortData.sortBy === 'status' && !ui.sortData.reversed" class="ml-1" />
@@ -44,22 +41,19 @@
44
41
  </div>
45
42
  </Header>
46
43
  <div
47
- class="flex flex-col divide-y divide-border overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
44
+ class="flex flex-col divide-y divide-dark-650 overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
48
45
  :style="{ maxHeight: dynamicTableHeight }"
49
46
  >
50
- <div
51
- v-for="(task, i) in getTasksInOrder()"
52
- :key="task.taskId"
47
+ <div
48
+ v-for="(task, i) in getTasksInOrder()"
49
+ :key="task.taskId"
53
50
  :class="[i % 2 == 1 ? 'task-row-odd' : 'task-row-even']"
54
51
  class="task-row-container"
55
52
  >
56
53
  <Task :task="task" />
57
54
  </div>
58
- <div
59
- v-if="getTasksInOrder().length === 0"
60
- class="flex justify-center py-8 empty-state"
61
- >
62
- <span v-if="ui.queueStats.total === 0"> No tasks yet.</span>
55
+ <div v-if="getTasksInOrder().length === 0" class="flex justify-center py-8 empty-state">
56
+ <span v-if="ui.queueStats.total === 0"> No tasks yet</span>
63
57
  <span v-else>{{ ui.queueStats.total }} hidden task{{ ui.queueStats.total === 1 ? "" : "s" }}</span>
64
58
  </div>
65
59
  </div>
@@ -78,11 +72,11 @@ h4 {
78
72
  min-height: 69px;
79
73
  flex-shrink: 0;
80
74
  transition: background-color 0.15s ease;
81
-
75
+
82
76
  &:hover {
83
77
  @apply bg-dark-550 !important;
84
78
  }
85
-
79
+
86
80
  @media (max-width: 768px) {
87
81
  min-height: 58px;
88
82
  }
@@ -159,11 +153,11 @@ const updateDimensions = () => {
159
153
  };
160
154
 
161
155
  onMounted(() => {
162
- window.addEventListener('resize', updateDimensions);
156
+ window.addEventListener("resize", updateDimensions);
163
157
  });
164
158
 
165
159
  onUnmounted(() => {
166
- window.removeEventListener('resize', updateDimensions);
160
+ window.removeEventListener("resize", updateDimensions);
167
161
  });
168
162
 
169
163
  const dynamicTableHeight = computed(() => {
@@ -174,20 +168,22 @@ const dynamicTableHeight = computed(() => {
174
168
  const controlsHeight = windowWidth.value >= 650 ? 60 : 0; // Desktop controls
175
169
  const filtersHeight = 50; // Event dropdown and filter toggle
176
170
  const utilitiesHeight = windowWidth.value <= 480 && windowHeight.value > windowWidth.value ? 150 : 200; // Reduce utilities height in mobile portrait
177
- const margins = windowWidth.value >= 1024 ? 40 : (windowWidth.value <= 480 && windowHeight.value > windowWidth.value ? 15 : 20); // Reduce margins in mobile portrait
178
-
179
- const totalUsedSpace = headerHeight + titleHeight + statsHeight + controlsHeight + filtersHeight + utilitiesHeight + margins;
171
+ const margins =
172
+ windowWidth.value >= 1024 ? 40 : windowWidth.value <= 480 && windowHeight.value > windowWidth.value ? 15 : 20; // Reduce margins in mobile portrait
173
+
174
+ const totalUsedSpace =
175
+ headerHeight + titleHeight + statsHeight + controlsHeight + filtersHeight + utilitiesHeight + margins;
180
176
  const availableHeight = windowHeight.value - totalUsedSpace;
181
-
177
+
182
178
  // Calculate row height based on screen size
183
179
  const rowHeight = windowWidth.value <= 768 ? 58 : 69; // Mobile vs desktop row height
184
180
  const minRowsToShow = 2; // Always show at least 2 rows
185
181
  const minHeight = minRowsToShow * rowHeight;
186
-
182
+
187
183
  // Calculate how many complete rows can fit
188
184
  const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight);
189
185
  const exactHeight = maxCompleteRows * rowHeight;
190
-
191
- return exactHeight + 'px';
186
+
187
+ return exactHeight + "px";
192
188
  });
193
189
  </script>
@@ -127,16 +127,55 @@
127
127
  @apply gap-x-4;
128
128
 
129
129
  li a {
130
- @apply flex text-white text-sm border-none h-10 items-center px-4 rounded-lg;
130
+ @apply flex text-white text-sm border-none h-10 items-center rounded-lg;
131
131
 
132
132
  svg {
133
133
  @apply mr-2;
134
+ width: 20px;
135
+ height: 20px;
134
136
  }
135
137
 
136
138
  &.router-link-exact-active {
137
139
  @apply border-solid border border-light-300;
138
140
  }
139
141
  }
142
+
143
+ // Desktop mode (lg to xl): adjust padding and border for icon-only view
144
+ @media (min-width: 1024px) and (max-width: 1279px) {
145
+ li a {
146
+ @apply px-3;
147
+
148
+ &.router-link-exact-active {
149
+ @apply border-none bg-transparent;
150
+
151
+ svg {
152
+ @apply border border-light-300 rounded-lg;
153
+ padding: 6px;
154
+ width: 32px;
155
+ height: 32px;
156
+ }
157
+ }
158
+
159
+ svg {
160
+ @apply mr-0;
161
+ width: 20px;
162
+ height: 20px;
163
+ }
164
+ }
165
+ }
166
+
167
+ // XL and above: full width with text
168
+ @media (min-width: 1280px) {
169
+ li a {
170
+ @apply px-4;
171
+
172
+ svg {
173
+ @apply mr-2;
174
+ width: 20px;
175
+ height: 20px;
176
+ }
177
+ }
178
+ }
140
179
  }
141
180
  }
142
181
 
@@ -1,23 +1,98 @@
1
1
  <template>
2
- <div class="my-auto relative flex justify-center flex-col items-center" style="height: 80vh">
3
- <div class="spinner-container">
4
- <div class="spinner"></div>
5
- <img :src="logoIcon" alt="Beaker Logo" class="logo" />
2
+ <div class="reconnect-overlay">
3
+ <transition name="background-fill" appear>
4
+ <div class="background-layer"></div>
5
+ </transition>
6
+ <transition name="reconnect-content" appear>
7
+ <div class="reconnect-container">
8
+ <div class="loading-system">
9
+ <!-- Geometric loading rings -->
10
+ <div class="ring-system">
11
+ <div class="ring ring-outer"></div>
12
+ <div class="ring ring-middle"></div>
13
+ <div class="ring ring-inner"></div>
14
+ <!-- Central logo -->
15
+ <img :src="logoIcon" alt="Logo" class="logo" />
16
+ </div>
17
+
18
+ <!-- Progress indicators -->
19
+ <div class="progress-dots">
20
+ <div class="dot" :class="{ active: dotIndex >= 0 }"></div>
21
+ <div class="dot" :class="{ active: dotIndex >= 1 }"></div>
22
+ <div class="dot" :class="{ active: dotIndex >= 2 }"></div>
23
+ <div class="dot" :class="{ active: dotIndex >= 3 }"></div>
24
+ </div>
6
25
  </div>
7
- <span class="text-white text-3xl font-light mt-3">{{ props.message }}{{ dots }}</span>
26
+
27
+ <div class="message-container">
28
+ <h2 class="message-text">{{ props.message }}</h2>
29
+ <div class="status-bar">
30
+ <div class="status-fill"></div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </transition>
8
35
  </div>
9
36
  </template>
10
37
 
11
38
  <script setup>
12
- import { ref } from "vue";
39
+ import { ref, onMounted, onUnmounted } from "vue";
13
40
  import logoIcon from "@/assets/img/logo_icon.png";
14
41
 
15
- const dots = ref(".");
42
+ const dotIndex = ref(0);
43
+ const progressWidth = ref(0);
44
+ let dotInterval;
45
+ let progressInterval;
46
+
47
+ // Animate progress dots
48
+ const animateDots = () => {
49
+ dotIndex.value = (dotIndex.value + 1) % 5; // 0-4, with 4 being all off
50
+ };
51
+
52
+ // Animate progress bar
53
+ onMounted(() => {
54
+ dotInterval = setInterval(animateDots, 600);
55
+ });
56
+
57
+ // Add page fade-in effect when modal closes
58
+ const addPageFadeIn = () => {
59
+ // Add global style for underlying page fade-in
60
+ const style = document.createElement('style');
61
+ style.textContent = `
62
+ .page-fade-in {
63
+ animation: pageReveal 0.6s ease-out forwards;
64
+ }
65
+ @keyframes pageReveal {
66
+ from {
67
+ opacity: 0.7;
68
+ transform: scale(0.98);
69
+ filter: blur(1px);
70
+ }
71
+ to {
72
+ opacity: 1;
73
+ transform: scale(1);
74
+ filter: blur(0);
75
+ }
76
+ }
77
+ `;
78
+ document.head.appendChild(style);
79
+
80
+ // Apply the class to the body
81
+ document.body.classList.add('page-fade-in');
82
+
83
+ // Remove the class and style after animation
84
+ setTimeout(() => {
85
+ document.body.classList.remove('page-fade-in');
86
+ if (document.head.contains(style)) {
87
+ document.head.removeChild(style);
88
+ }
89
+ }, 600);
90
+ };
16
91
 
17
- setInterval(() => {
18
- dots.value += ".";
19
- if (dots.value.length > 3) dots.value = ".";
20
- }, 1000);
92
+ onUnmounted(() => {
93
+ if (dotInterval) clearInterval(dotInterval);
94
+ addPageFadeIn();
95
+ });
21
96
 
22
97
  const props = defineProps({
23
98
  message: {
@@ -27,64 +102,285 @@ const props = defineProps({
27
102
  });
28
103
  </script>
29
104
 
30
- <style scoped>
31
- .spinner-container {
105
+ <style lang="scss" scoped>
106
+ .reconnect-overlay {
107
+ position: fixed;
108
+ top: -100vh;
109
+ left: -100vw;
110
+ right: -100vw;
111
+ bottom: -100vh;
112
+ z-index: 9999;
113
+ --tw-ring-color: transparent !important;
114
+ }
115
+
116
+ .background-layer {
117
+ position: absolute;
118
+ top: 0;
119
+ left: 0;
120
+ right: 0;
121
+ bottom: 0;
122
+ background: rgba(26, 27, 30, 0.98);
123
+ backdrop-filter: blur(12px);
124
+ -webkit-backdrop-filter: blur(12px);
125
+ }
126
+
127
+ .reconnect-container {
128
+ position: absolute;
129
+ top: 100vh;
130
+ left: 100vw;
131
+ right: 100vw;
132
+ bottom: 100vh;
133
+ display: flex;
134
+ flex-direction: column;
135
+ align-items: center;
136
+ justify-content: center;
137
+ gap: 3rem;
138
+ --tw-ring-color: transparent !important;
139
+ }
140
+
141
+ .loading-system {
32
142
  position: relative;
33
- width: 200px;
34
- height: 200px;
143
+ display: flex;
144
+ align-items: center;
145
+ justify-content: center;
35
146
  }
36
- .logo {
147
+
148
+ .ring-system {
149
+ position: relative;
150
+ width: 160px;
151
+ height: 160px;
152
+ display: flex;
153
+ align-items: center;
154
+ justify-content: center;
155
+ }
156
+
157
+ .ring {
37
158
  position: absolute;
38
- width: 180px;
39
- height: 180px;
40
- top: 50%;
41
- left: 50%;
42
- transform: translate(-50%, -50%);
159
+ border-radius: 50%;
160
+ background: transparent !important;
161
+ --tw-ring-color: transparent !important;
162
+ border: 4px solid transparent;
163
+ }
164
+
165
+ .ring-outer {
166
+ width: 160px;
167
+ height: 160px;
168
+ border-top: 4px solid #9dd3a8;
169
+ border-right: 4px solid rgba(157, 211, 168, 0.3);
170
+ border-bottom: 4px solid transparent;
171
+ border-left: 4px solid transparent;
172
+ animation: breathe-slow 3s ease-in-out infinite;
173
+ top: 0;
174
+ left: 0;
175
+ }
176
+
177
+ .ring-middle {
178
+ width: 120px;
179
+ height: 120px;
180
+ border-top: 4px solid #88c999;
181
+ border-right: 4px solid rgba(136, 201, 153, 0.4);
182
+ border-bottom: 4px solid transparent;
183
+ border-left: 4px solid transparent;
184
+ animation: breathe-medium 2.5s ease-in-out infinite reverse;
185
+ top: 20px;
186
+ left: 20px;
187
+ }
188
+
189
+ .ring-inner {
190
+ width: 80px;
191
+ height: 80px;
192
+ border-top: 4px solid #7bc187;
193
+ border-right: 4px solid rgba(123, 193, 135, 0.5);
194
+ border-bottom: 4px solid transparent;
195
+ border-left: 4px solid transparent;
196
+ animation: breathe-fast 2s ease-in-out infinite;
197
+ top: 40px;
198
+ left: 40px;
199
+ }
200
+
201
+ .logo {
202
+ width: 72px;
203
+ height: 72px;
43
204
  border-radius: 50%;
44
205
  object-fit: cover;
45
- object-position: center;
46
- z-index: 2;
206
+ z-index: 10;
207
+ border: 2px solid rgba(136, 201, 153, 0.3);
208
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
47
209
  }
48
- .spinner {
210
+
211
+ .progress-dots {
49
212
  position: absolute;
50
- width: 185px;
51
- height: 185px;
52
- top: 50%;
213
+ bottom: -40px;
53
214
  left: 50%;
54
- transform: translate(-50%, -50%);
215
+ transform: translateX(-50%);
216
+ display: flex;
217
+ gap: 8px;
218
+ }
219
+
220
+ .dot {
221
+ width: 8px;
222
+ height: 8px;
55
223
  border-radius: 50%;
56
- border: 2px solid rgba(255, 255, 255, 0.1);
57
- border-top: 2px solid #ffffff;
58
- animation: spin 1s linear infinite;
224
+ background: #44454b;
225
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
226
+
227
+ &.active {
228
+ background: #88c999;
229
+ transform: scale(1.2);
230
+ }
231
+ }
232
+
233
+ .message-container {
234
+ text-align: center;
235
+ max-width: 400px;
236
+ }
237
+
238
+ .message-text {
239
+ color: #e2e2e5;
240
+ font-size: 1.5rem;
241
+ font-weight: 500;
242
+ margin-bottom: 1rem;
243
+ letter-spacing: 0.025em;
244
+ }
245
+
246
+ .status-bar {
247
+ width: 300px;
248
+ height: 8px;
249
+ background: #35363c;
250
+ border-radius: 4px;
251
+ overflow: hidden;
252
+ position: relative;
253
+ }
254
+
255
+ .status-fill {
256
+ height: 100%;
257
+ background: linear-gradient(90deg, #88c999 0%, #9dd3a8 50%, #88c999 100%);
258
+ border-radius: 4px;
259
+ width: 30%;
260
+ animation: infinite-slide 2s ease-in-out infinite;
261
+ position: relative;
262
+ overflow: hidden;
59
263
  }
60
- .glow {
264
+
265
+ .status-fill::after {
266
+ content: '';
61
267
  position: absolute;
62
- width: 220px;
63
- height: 220px;
64
- top: 50%;
65
- left: 50%;
66
- transform: translate(-50%, -50%);
67
- border-radius: 50%;
68
- background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 70%);
69
- animation: pulse 2s ease-in-out infinite;
268
+ top: 0;
269
+ left: -100%;
270
+ width: 100%;
271
+ height: 100%;
272
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
273
+ animation: shimmer 2s ease-in-out infinite;
274
+ }
275
+
276
+ // Smooth beautiful animations
277
+ @keyframes breathe-slow {
278
+ 0% { transform: rotate(0deg) scale(1); opacity: 0.8; }
279
+ 50% { transform: rotate(180deg) scale(1.05); opacity: 1; }
280
+ 100% { transform: rotate(360deg) scale(1); opacity: 0.8; }
281
+ }
282
+
283
+ @keyframes breathe-medium {
284
+ 0% { transform: rotate(0deg) scale(1); opacity: 0.9; }
285
+ 50% { transform: rotate(-180deg) scale(0.95); opacity: 1; }
286
+ 100% { transform: rotate(-360deg) scale(1); opacity: 0.9; }
287
+ }
288
+
289
+ @keyframes breathe-fast {
290
+ 0% { transform: rotate(0deg) scale(1); opacity: 1; }
291
+ 50% { transform: rotate(180deg) scale(1.08); opacity: 0.7; }
292
+ 100% { transform: rotate(360deg) scale(1); opacity: 1; }
293
+ }
294
+
295
+ @keyframes infinite-slide {
296
+ 0% { transform: translateX(-100%); }
297
+ 50% { transform: translateX(250%); }
298
+ 100% { transform: translateX(-100%); }
299
+ }
300
+
301
+ @keyframes shimmer {
302
+ 0% { transform: translateX(-100%); }
303
+ 50% { transform: translateX(100%); }
304
+ 100% { transform: translateX(300%); }
305
+ }
306
+
307
+ // Staged beautiful transitions
308
+ .background-fill-enter-active {
309
+ transition: all 0.15s ease-out;
70
310
  }
71
- @keyframes spin {
72
- 0% {
73
- transform: translate(-50%, -50%) rotate(0deg);
311
+
312
+ .background-fill-leave-active {
313
+ transition: all 0.3s ease-in;
314
+ }
315
+
316
+ .background-fill-enter-from {
317
+ opacity: 0;
318
+ transform: scale(0.8);
319
+ }
320
+
321
+ .background-fill-leave-to {
322
+ opacity: 0;
323
+ transform: scale(1.2);
324
+ }
325
+
326
+ .reconnect-content-enter-active {
327
+ transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
328
+ transition-delay: 0.1s;
329
+ }
330
+
331
+ .reconnect-content-leave-active {
332
+ transition: all 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53);
333
+ }
334
+
335
+ .reconnect-content-enter-from {
336
+ opacity: 0;
337
+ transform: scale(0.9) translateY(20px);
338
+ filter: blur(4px);
339
+ }
340
+
341
+ .reconnect-content-leave-to {
342
+ opacity: 0;
343
+ transform: scale(1.1) translateY(-10px);
344
+ filter: blur(2px);
345
+ }
346
+
347
+ // Responsive design
348
+ @media (max-width: 768px) {
349
+ .ring-system {
350
+ width: 120px;
351
+ height: 120px;
74
352
  }
75
- 100% {
76
- transform: translate(-50%, -50%) rotate(360deg);
353
+
354
+ .ring-outer {
355
+ width: 120px;
356
+ height: 120px;
77
357
  }
78
- }
79
- @keyframes pulse {
80
- 0%,
81
- 100% {
82
- transform: translate(-50%, -50%) scale(1);
83
- opacity: 0.5;
358
+
359
+ .ring-middle {
360
+ width: 90px;
361
+ height: 90px;
362
+ top: 15px;
363
+ left: 15px;
364
+ }
365
+
366
+ .ring-inner {
367
+ width: 60px;
368
+ height: 60px;
369
+ top: 30px;
370
+ left: 30px;
371
+ }
372
+
373
+ .logo {
374
+ width: 54px;
375
+ height: 54px;
376
+ }
377
+
378
+ .message-text {
379
+ font-size: 1.25rem;
84
380
  }
85
- 50% {
86
- transform: translate(-50%, -50%) scale(1.05);
87
- opacity: 0.7;
381
+
382
+ .status-bar {
383
+ width: 250px;
88
384
  }
89
385
  }
90
386
  </style>
@@ -68,10 +68,10 @@ const open = ref(false);
68
68
  .dropdown-content {
69
69
  left: -1.25rem;
70
70
  background-clip: border-box !important;
71
- @apply border border-light-300;
71
+ @apply border border-dark-650;
72
72
  border-width: 2px;
73
73
  --tw-border-opacity: 1;
74
- border-color: rgb(41 42 67 / var(--tw-border-opacity));
74
+ border-color: rgb(61 62 68 / var(--tw-border-opacity));
75
75
  max-height: 13rem;
76
76
  }
77
77
 
@@ -47,12 +47,12 @@
47
47
  }
48
48
 
49
49
  &.checked {
50
- background: #5d7cc0;
51
- border-color: #5d7cc0;
50
+ background: #88c999;
51
+ border-color: #88c999;
52
52
 
53
53
  &:hover {
54
- background: #6985c8;
55
- border-color: #6985c8;
54
+ background: #9dd3a8;
55
+ border-color: #9dd3a8;
56
56
  }
57
57
  }
58
58
 
@@ -62,23 +62,23 @@
62
62
  border-color: #52535a;
63
63
 
64
64
  &:hover {
65
- border-color: #5d7cc0;
66
- background: rgba(93, 124, 192, 0.1);
65
+ border-color: #88c999;
66
+ background: rgba(136, 201, 153, 0.1);
67
67
  }
68
68
 
69
69
  &.checked {
70
- background: #5d7cc0;
71
- border-color: #5d7cc0;
70
+ background: #88c999;
71
+ border-color: #88c999;
72
72
 
73
73
  &:hover {
74
- background: #6985c8;
75
- border-color: #6985c8;
74
+ background: #9dd3a8;
75
+ border-color: #9dd3a8;
76
76
  }
77
77
  }
78
78
  }
79
79
 
80
80
  &:focus-visible {
81
- outline: 2px solid #5d7cc0;
81
+ outline: 2px solid #88c999;
82
82
  outline-offset: 2px;
83
83
  }
84
84
  }
@@ -92,7 +92,19 @@
92
92
  </div>
93
93
  </div>
94
94
 
95
- <p class="text-red-400 text-bold">{{ errorMessage }}</p>
95
+ <div v-if="errorMessage" class="error-container">
96
+ <div class="error-icon">
97
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
98
+ <circle cx="12" cy="12" r="10"/>
99
+ <line x1="15" y1="9" x2="9" y2="15"/>
100
+ <line x1="9" y1="9" x2="15" y2="15"/>
101
+ </svg>
102
+ </div>
103
+ <div class="error-content">
104
+ <div class="error-title">JSON Syntax Error</div>
105
+ <div class="error-text">{{ errorMessage }}</div>
106
+ </div>
107
+ </div>
96
108
  </div>
97
109
  </transition>
98
110
  <div class="border border-dark-650 my-8" />
@@ -782,4 +794,52 @@ pre[class*="language-"] {
782
794
  .token.entity {
783
795
  cursor: help;
784
796
  }
797
+
798
+ /* Modern error message styling */
799
+ .error-container {
800
+ display: flex;
801
+ align-items: flex-start;
802
+ gap: 12px;
803
+ padding: 16px;
804
+ margin-top: 12px;
805
+ border-radius: 8px;
806
+ border: 1px solid #dc2626;
807
+ background: linear-gradient(135deg, rgba(220, 38, 38, 0.1), rgba(220, 38, 38, 0.05));
808
+ box-shadow: 0 2px 8px rgba(220, 38, 38, 0.15);
809
+ }
810
+
811
+ .error-icon {
812
+ display: flex;
813
+ align-items: center;
814
+ justify-content: center;
815
+ width: 28px;
816
+ height: 28px;
817
+ border-radius: 50%;
818
+ background: rgba(220, 38, 38, 0.2);
819
+ border: 1px solid rgba(220, 38, 38, 0.4);
820
+ color: #ef4444;
821
+ flex-shrink: 0;
822
+ }
823
+
824
+ .error-content {
825
+ flex: 1;
826
+ min-width: 0;
827
+ }
828
+
829
+ .error-title {
830
+ color: #fca5a5;
831
+ font-size: 14px;
832
+ font-weight: 600;
833
+ margin-bottom: 4px;
834
+ letter-spacing: 0.015em;
835
+ }
836
+
837
+ .error-text {
838
+ color: #fecaca;
839
+ font-size: 12px;
840
+ line-height: 1.5;
841
+ font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
842
+ word-break: break-word;
843
+ opacity: 0.9;
844
+ }
785
845
  </style>
@@ -101,7 +101,7 @@
101
101
  </div>
102
102
  </div>
103
103
  </Header>
104
- <div class="overflow-auto hidden-scrollbars flex-1">
104
+ <div class="bg-dark-400 overflow-auto hidden-scrollbars flex-1">
105
105
  <div v-if="filterBuilder.filters.length" class="filters-container">
106
106
  <draggable
107
107
  :list="filterBuilder.filters"
@@ -136,8 +136,8 @@
136
136
  class="empty-state flex flex-col items-center justify-center py-8 text-center"
137
137
  >
138
138
  <FilterIcon class="w-12 h-12 text-dark-400 mb-3 opacity-50" />
139
- <p class="text-dark-400 text-sm">No filters yet</p>
140
- <p class="text-dark-500 text-xs mt-1">Click on the map to create filters</p>
139
+ <p class="text-light-400 text-sm">No filters yet</p>
140
+ <p class="text-light-500 text-xs mt-1">Click on the map to create filters</p>
141
141
  </div>
142
142
  </div>
143
143
  </Table>
@@ -4,14 +4,14 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
6
6
  <title>Site Offline</title>
7
- <meta name="theme-color" content="rgba(28, 28, 49, 1)" />
7
+ <meta name="theme-color" content="#1a1b1e" />
8
8
  <style>
9
9
  body,
10
10
  html {
11
11
  margin: 0;
12
12
  padding: 0;
13
13
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
14
- background-color: rgba(28, 28, 49, 1);
14
+ background-color: #1a1b1e;
15
15
  color: #fff;
16
16
  height: 100%;
17
17
  }
@@ -46,7 +46,7 @@
46
46
  min-height: 100vh;
47
47
  }
48
48
  .error-card {
49
- background-color: rgba(39, 39, 67, 0.4);
49
+ background-color: #2e2f34;
50
50
  border-radius: 0.5rem;
51
51
  padding: 2rem;
52
52
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
@@ -56,7 +56,7 @@
56
56
  margin: auto;
57
57
  }
58
58
  .icon-wrapper {
59
- background-color: #4a4a61;
59
+ background-color: #3d3e44;
60
60
  width: 4rem;
61
61
  height: 4rem;
62
62
  border-radius: 50%;
@@ -65,18 +65,18 @@
65
65
  align-items: center;
66
66
  margin: 0 auto 1rem;
67
67
  font-size: 2rem;
68
- color: #ee8282;
68
+ color: #f87171;
69
69
  }
70
70
  h1 {
71
71
  font-size: 1.5rem;
72
72
  margin-bottom: 0.5rem;
73
73
  }
74
74
  p {
75
- color: rgba(255, 255, 255, 0.7);
75
+ color: #d0d0d3;
76
76
  margin-bottom: 1.5rem;
77
77
  }
78
78
  .refresh-button {
79
- background-color: #4a4a61;
79
+ background-color: #3d3e44;
80
80
  color: white;
81
81
  border: none;
82
82
  padding: 0.5rem 1rem;