@necrolab/dashboard 0.4.220 → 0.5.1

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 (140) 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 +78 -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/run +176 -9
  34. package/src/App.vue +498 -85
  35. package/src/assets/css/base/reset.scss +43 -0
  36. package/src/assets/css/base/scroll.scss +114 -0
  37. package/src/assets/css/base/typography.scss +37 -0
  38. package/src/assets/css/components/buttons.scss +216 -0
  39. package/src/assets/css/components/forms.scss +221 -0
  40. package/src/assets/css/components/modals.scss +13 -0
  41. package/src/assets/css/components/tables.scss +27 -0
  42. package/src/assets/css/components/toasts.scss +100 -0
  43. package/src/assets/css/main.scss +201 -122
  44. package/src/assets/img/background.svg +2 -2
  45. package/src/assets/img/background.svg.backup +11 -0
  46. package/src/assets/img/logo_trans.png +0 -0
  47. package/src/components/Auth/LoginForm.vue +62 -11
  48. package/src/components/Editors/Account/Account.vue +116 -40
  49. package/src/components/Editors/Account/AccountCreator.vue +88 -39
  50. package/src/components/Editors/Account/AccountView.vue +102 -34
  51. package/src/components/Editors/Account/CreateAccount.vue +80 -32
  52. package/src/components/Editors/Profile/CreateProfile.vue +269 -83
  53. package/src/components/Editors/Profile/Profile.vue +132 -47
  54. package/src/components/Editors/Profile/ProfileCountryChooser.vue +82 -20
  55. package/src/components/Editors/Profile/ProfileView.vue +89 -32
  56. package/src/components/Editors/TagLabel.vue +67 -6
  57. package/src/components/Editors/TagToggle.vue +7 -2
  58. package/src/components/Filter/Filter.vue +288 -71
  59. package/src/components/Filter/FilterPreview.vue +202 -31
  60. package/src/components/Filter/PriceSortToggle.vue +76 -6
  61. package/src/components/Table/Header.vue +1 -1
  62. package/src/components/Table/Row.vue +1 -1
  63. package/src/components/Table/Table.vue +19 -2
  64. package/src/components/Tasks/CheckStock.vue +6 -8
  65. package/src/components/Tasks/Controls/DesktopControls.vue +27 -17
  66. package/src/components/Tasks/Controls/MobileControls.vue +8 -45
  67. package/src/components/Tasks/CreateTaskAXS.vue +80 -72
  68. package/src/components/Tasks/CreateTaskTM.vue +95 -141
  69. package/src/components/Tasks/MassEdit.vue +4 -6
  70. package/src/components/Tasks/QuickSettings.vue +199 -30
  71. package/src/components/Tasks/ScrapeVenue.vue +5 -6
  72. package/src/components/Tasks/Stats.vue +50 -24
  73. package/src/components/Tasks/Task.vue +384 -179
  74. package/src/components/Tasks/TaskLabel.vue +2 -2
  75. package/src/components/Tasks/TaskView.vue +136 -48
  76. package/src/components/Tasks/Utilities.vue +25 -10
  77. package/src/components/Tasks/ViewTask.vue +321 -0
  78. package/src/components/icons/Bag.vue +1 -1
  79. package/src/components/icons/Check.vue +5 -0
  80. package/src/components/icons/Close.vue +21 -0
  81. package/src/components/icons/CloseX.vue +5 -0
  82. package/src/components/icons/Eye.vue +6 -0
  83. package/src/components/icons/Key.vue +21 -0
  84. package/src/components/icons/Loyalty.vue +1 -1
  85. package/src/components/icons/Mail.vue +2 -2
  86. package/src/components/icons/Pencil.vue +21 -0
  87. package/src/components/icons/Play.vue +2 -2
  88. package/src/components/icons/Profile.vue +18 -0
  89. package/src/components/icons/Reload.vue +4 -5
  90. package/src/components/icons/Sandclock.vue +2 -2
  91. package/src/components/icons/Sell.vue +21 -0
  92. package/src/components/icons/Spinner.vue +42 -0
  93. package/src/components/icons/SquareCheck.vue +18 -0
  94. package/src/components/icons/SquareUncheck.vue +18 -0
  95. package/src/components/icons/Stadium.vue +1 -1
  96. package/src/components/icons/Wildcard.vue +18 -0
  97. package/src/components/icons/index.js +26 -1
  98. package/src/components/ui/Modal.vue +107 -13
  99. package/src/components/ui/Navbar.vue +175 -40
  100. package/src/components/ui/ReconnectIndicator.vue +351 -55
  101. package/src/components/ui/Splash.vue +5 -35
  102. package/src/components/ui/controls/CountryChooser.vue +200 -62
  103. package/src/components/ui/controls/atomic/Checkbox.vue +119 -10
  104. package/src/components/ui/controls/atomic/Dropdown.vue +216 -39
  105. package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
  106. package/src/components/ui/controls/atomic/MultiDropdown.vue +300 -37
  107. package/src/components/ui/controls/atomic/Switch.vue +53 -25
  108. package/src/composables/useClickOutside.js +21 -0
  109. package/src/composables/useDropdownPosition.js +174 -0
  110. package/src/libs/Filter.js +60 -24
  111. package/src/registerServiceWorker.js +1 -1
  112. package/src/stores/connection.js +4 -4
  113. package/src/stores/sampleData.js +172 -199
  114. package/src/stores/ui.js +55 -20
  115. package/src/stores/utils.js +30 -4
  116. package/src/types/index.js +41 -0
  117. package/src/utils/debug.js +1 -0
  118. package/src/views/Accounts.vue +116 -50
  119. package/src/views/Console.vue +394 -79
  120. package/src/views/Editor.vue +1176 -123
  121. package/src/views/FilterBuilder.vue +528 -250
  122. package/src/views/Login.vue +76 -14
  123. package/src/views/Profiles.vue +119 -34
  124. package/src/views/Tasks.vue +266 -98
  125. package/static/offline.html +192 -50
  126. package/switch-branch.sh +41 -0
  127. package/tailwind.config.js +119 -27
  128. package/vite.config.js +73 -16
  129. package/workbox-config.cjs +63 -0
  130. package/ICONS.md +0 -21
  131. package/public/img/background.svg +0 -14
  132. package/public/img/logo.png +0 -0
  133. package/public/img/logo_icon.png +0 -0
  134. package/public/img/logo_icon_2.png +0 -0
  135. package/src/assets/css/_input.scss +0 -143
  136. package/src/assets/img/logo.png +0 -0
  137. package/src/assets/img/logo_icon.png +0 -0
  138. package/src/assets/img/logo_icon_2.png +0 -0
  139. package/vue.config.js +0 -32
  140. package/workbox-config.js +0 -7
package/src/App.vue CHANGED
@@ -9,27 +9,23 @@
9
9
  :style="{
10
10
  'margin-top': maxPull() + 'px',
11
11
  transform: `rotate(${ui.pullChange}deg)`
12
- }"
13
- >
12
+ }">
14
13
  <div
15
14
  class="refresh-icon p-2 rounded-full mx-auto duration-200"
16
15
  :class="{
17
16
  'opacity-100': ui.pullChange > 250,
18
17
  'opacity-0': ui.pullChange < 250
19
- }"
20
- >
18
+ }">
21
19
  <svg
22
20
  xmlns="http://www.w3.org/2000/svg"
23
21
  fill="none"
24
22
  viewBox="0 0 24 24"
25
23
  class="stroke-2 w-4 stroke-dark-400"
26
- :class="{ 'opacity-0': ui.pullChange == 0 }"
27
- >
24
+ :class="{ 'opacity-0': ui.pullChange == 0 }">
28
25
  <path
29
26
  strokeLinecap="round"
30
27
  strokeLinejoin="round"
31
- d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
32
- />
28
+ d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
33
29
  </svg>
34
30
  </div>
35
31
  </div>
@@ -38,8 +34,7 @@
38
34
  :class="{
39
35
  'opacity-100': ui.pullChange > 250,
40
36
  'opacity-0': ui.pullChange < 250
41
- }"
42
- >
37
+ }">
43
38
  Release to refresh
44
39
  </h2>
45
40
  </div>
@@ -49,12 +44,17 @@
49
44
  </div>
50
45
  <div v-else key="main-components" class="flex">
51
46
  <Navbar v-if="layout == 'dashboard'" class="fixed" />
52
- <div :class="`router-wrapper ${ui.pullChange > 1 ? '-mt-[60px]' : ''}`"></div>
53
- <router-view v-slot="{ Component }">
54
- <component
55
- :is="Component"
56
- :class="`component-container pb-2 mt-0 lg:mt-4 ios-wrapper ${landscapeIos ? 'w-full' : ''}`"
57
- />
47
+ <div :class="['router-wrapper', { '-mt-[60px]': ui.pullChange > 1 }]"></div>
48
+ <router-view v-slot="{ Component, route }">
49
+ <transition name="page-transition" mode="out-in">
50
+ <component
51
+ :is="Component"
52
+ :key="route.path"
53
+ :class="[
54
+ 'component-container pb-2 mt-0 lg:mt-4 ios-wrapper',
55
+ { 'w-full': landscapeIos }
56
+ ]" />
57
+ </transition>
58
58
  </router-view>
59
59
  </div>
60
60
  </transition>
@@ -63,7 +63,7 @@
63
63
 
64
64
  <script setup>
65
65
  import { storeToRefs } from "pinia";
66
- import { ref, computed, watch } from "vue";
66
+ import { ref, computed, watch, onMounted } from "vue";
67
67
  import { useRouter } from "vue-router";
68
68
  import Navbar from "@/components/ui/Navbar.vue";
69
69
  import { useUIStore } from "@/stores/ui";
@@ -88,13 +88,50 @@ function isIpadOS() {
88
88
  return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform);
89
89
  }
90
90
 
91
- window.matchMedia("(orientation: portrait)").addEventListener("change", (e) => {
92
- if (!e.matches && isIOS() && !isIpadOS()) landscapeIos.value = true;
93
- else landscapeIos.value = false;
94
- });
91
+ // Handle orientation changes for iOS devices
92
+ const handleOrientationChange = () => {
93
+ if (isIOS()) {
94
+ if (isIpadOS()) {
95
+ // For iPad, we want to allow proper viewport resizing
96
+ landscapeIos.value = window.matchMedia("(orientation: landscape)").matches;
97
+ document.documentElement.style.setProperty("width", "100%", "important");
98
+ document.body.style.setProperty("width", "100%", "important");
99
+ } else {
100
+ // For iPhone, maintain the current behavior
101
+ landscapeIos.value = window.matchMedia("(orientation: landscape)").matches;
102
+ }
103
+ }
104
+ };
105
+
106
+ // Initial orientation check
107
+ handleOrientationChange();
108
+
109
+ // Listen for orientation changes
110
+ window.matchMedia("(orientation: portrait)").addEventListener("change", handleOrientationChange);
111
+ window.matchMedia("(orientation: landscape)").addEventListener("change", handleOrientationChange);
95
112
 
96
113
  if (!window.location.href.includes(":5173")) ui.startSpinner("Loading...");
97
114
 
115
+ // Ensure notch handling runs when Vue app mounts
116
+ onMounted(() => {
117
+ // Multiple aggressive attempts on Vue mount
118
+ handleNotch();
119
+ setTimeout(handleNotch, 10);
120
+ setTimeout(handleNotch, 50);
121
+ setTimeout(handleNotch, 150);
122
+ setTimeout(handleNotch, 300);
123
+
124
+ // Set up a recurring check for the first few seconds
125
+ let attempts = 0;
126
+ const recurringCheck = setInterval(() => {
127
+ if (attempts++ > 10) {
128
+ clearInterval(recurringCheck);
129
+ return;
130
+ }
131
+ handleNotch();
132
+ }, 200);
133
+ });
134
+
98
135
  // close modals on esc
99
136
  document.onkeydown = function (evt) {
100
137
  evt = evt || window.event;
@@ -122,13 +159,64 @@ document.addEventListener("keydown", function (event) {
122
159
  event.preventDefault();
123
160
  }
124
161
  });
125
- // prevent zoom
162
+ // Nuclear iOS keyboard prevention - lock everything down
163
+ let isIOSDevice =
164
+ /iPad|iPhone|iPod/.test(navigator.platform) ||
165
+ (navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform));
166
+
167
+ if (isIOSDevice) {
168
+ const handleViewportResize = () => {
169
+ if (isIpadOS()) {
170
+ // For iPad, allow natural viewport behavior
171
+ return;
172
+ }
173
+
174
+ // For iPhone, maintain viewport stability
175
+ const vh = window.innerHeight * 0.01;
176
+ document.documentElement.style.setProperty("--vh", `${vh}px`);
177
+ };
178
+
179
+ // Initial setup
180
+ handleViewportResize();
181
+
182
+ // Listen for viewport changes
183
+ window.addEventListener("resize", handleViewportResize);
184
+ if (window.visualViewport) {
185
+ window.visualViewport.addEventListener("resize", handleViewportResize);
186
+ }
187
+ }
188
+
189
+ // Precise mouse wheel control - only allow scrolling within specific scrollable elements
126
190
  window.addEventListener(
127
191
  "mousewheel",
128
192
  function (event) {
129
- if (event.ctrlKey == true) {
130
- event.preventDefault();
193
+ // Check if we're on a scrollable textarea
194
+ const isScrollableTextarea =
195
+ event.target.tagName === "TEXTAREA" &&
196
+ (event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
197
+
198
+ // Check if we're in a table but only allow scrolling on actual scrollable content
199
+ const isInTable = event.target.closest(".table-component");
200
+ const isScrollableTableContent =
201
+ isInTable &&
202
+ (event.target.closest(".grid") || // Table rows
203
+ event.target.closest(".table-row") ||
204
+ (event.target.closest(".table-component") && !event.target.closest(".table-header")));
205
+
206
+ // Allow scrolling in navbar and modals
207
+ const isInNavbar = event.target.closest(".navbar") || event.target.closest(".mobile-menu");
208
+ const isInModal = event.target.closest('[role="dialog"]');
209
+
210
+ // Only allow these specific cases
211
+ if (isScrollableTextarea || isScrollableTableContent || isInNavbar || isInModal) {
212
+ // Stop propagation to prevent page scroll
213
+ if (isScrollableTableContent || isScrollableTextarea) {
214
+ event.stopPropagation();
215
+ }
216
+ return;
131
217
  }
218
+
219
+ event.preventDefault();
132
220
  },
133
221
  { passive: false }
134
222
  );
@@ -136,69 +224,349 @@ window.addEventListener(
136
224
  window.addEventListener(
137
225
  "DOMMouseScroll",
138
226
  function (event) {
139
- if (event.ctrlKey == true) {
140
- event.preventDefault();
227
+ // Use same logic as mousewheel
228
+ const isScrollableTextarea =
229
+ event.target.tagName === "TEXTAREA" &&
230
+ (event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
231
+
232
+ const isInTable = event.target.closest(".table-component");
233
+ const isScrollableTableContent =
234
+ isInTable &&
235
+ (event.target.closest(".grid") ||
236
+ event.target.closest(".table-row") ||
237
+ (event.target.closest(".table-component") && !event.target.closest(".table-header")));
238
+
239
+ const isInNavbar = event.target.closest(".navbar") || event.target.closest(".mobile-menu");
240
+ const isInModal = event.target.closest('[role="dialog"]');
241
+
242
+ if (isScrollableTextarea || isScrollableTableContent || isInNavbar || isInModal) {
243
+ if (isScrollableTableContent || isScrollableTextarea) {
244
+ event.stopPropagation();
245
+ }
246
+ return;
247
+ }
248
+
249
+ event.preventDefault();
250
+ },
251
+ { passive: false }
252
+ );
253
+
254
+ window.addEventListener(
255
+ "wheel",
256
+ function (event) {
257
+ // Use same logic as mousewheel
258
+ const isScrollableTextarea =
259
+ event.target.tagName === "TEXTAREA" &&
260
+ (event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
261
+
262
+ const isInTable = event.target.closest(".table-component");
263
+ const isScrollableTableContent =
264
+ isInTable &&
265
+ (event.target.closest(".grid") ||
266
+ event.target.closest(".table-row") ||
267
+ (event.target.closest(".table-component") && !event.target.closest(".table-header")));
268
+
269
+ const isInNavbar = event.target.closest(".navbar") || event.target.closest(".mobile-menu");
270
+ const isInModal = event.target.closest('[role="dialog"]');
271
+
272
+ if (isScrollableTextarea || isScrollableTableContent || isInNavbar || isInModal) {
273
+ if (isScrollableTableContent || isScrollableTextarea) {
274
+ event.stopPropagation();
275
+ }
276
+ return;
277
+ }
278
+
279
+ event.preventDefault();
280
+ },
281
+ { passive: false }
282
+ );
283
+
284
+ window.addEventListener(
285
+ "scroll",
286
+ function (event) {
287
+ event.preventDefault();
288
+ },
289
+ { passive: false }
290
+ );
291
+
292
+ // Precise scroll control - only allow scrolling within specific scrollable elements
293
+ window.addEventListener(
294
+ "touchmove",
295
+ function (event) {
296
+ // Check if we're touching a scrollable element directly
297
+ const isScrollableTextarea =
298
+ event.target.tagName === "TEXTAREA" &&
299
+ (event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
300
+
301
+ // Check if we're in a table but only allow scrolling on actual scrollable content
302
+ const isInTable = event.target.closest(".table-component");
303
+ const isScrollableTableContent =
304
+ isInTable &&
305
+ (event.target.closest(".grid") || // Table rows
306
+ event.target.closest(".table-row") ||
307
+ (event.target.closest(".table-component") && !event.target.closest(".table-header")));
308
+
309
+ // Allow scrolling in navbar and modals (they handle their own boundaries)
310
+ const isInNavbar = event.target.closest(".navbar") || event.target.closest(".mobile-menu");
311
+ const isInModal = event.target.closest('[role="dialog"]');
312
+
313
+ // Only allow these specific cases
314
+ if (isScrollableTextarea || isScrollableTableContent || isInNavbar || isInModal) {
315
+ // For table content, ensure we stop propagation to prevent page scroll
316
+ if (isScrollableTableContent || isScrollableTextarea) {
317
+ event.stopPropagation();
318
+ }
319
+ return;
141
320
  }
321
+
322
+ // Prevent everything else
323
+ event.preventDefault();
142
324
  },
143
325
  { passive: false }
144
326
  );
145
327
 
146
- window.screen.orientation.onchange = () => {
147
- if (document.body.classList.contains("overflow-hidden")) {
148
- document.body.classList.remove("overflow-hidden");
149
- setTimeout(() => {
150
- document.body.classList.add("overflow-hidden");
151
- }, 5);
328
+ // PERFECT notch handling - simple and bulletproof
329
+ let notchTimeout;
330
+ let isNotchBusy = false;
331
+ let lastNotchState = null;
332
+ let animationTimeout;
333
+ let pendingUpdate = false;
334
+
335
+ function isLandscapeMode() {
336
+ // Check orientation first
337
+ let orientationLandscape = false;
338
+
339
+ if (typeof window.orientation !== "undefined") {
340
+ orientationLandscape = Math.abs(window.orientation) === 90;
341
+ } else if (screen.orientation && typeof screen.orientation.angle !== "undefined") {
342
+ orientationLandscape = Math.abs(screen.orientation.angle) === 90;
152
343
  }
153
- handleNotch();
154
- };
155
344
 
156
- document.addEventListener("DOMContentLoaded", handleNotch, false);
157
- window.simulateRotate = handleNotch;
345
+ // Always also check dimensions as backup
346
+ const dimensionLandscape = window.innerWidth > window.innerHeight;
347
+ const hasStrongLandscapeRatio = window.innerWidth / window.innerHeight > 1.5;
158
348
 
159
- function handleNotch() {
160
- const wrappers = [...document.querySelectorAll(".ios-wrapper")];
349
+ // For iPhones, if dimensions clearly indicate landscape, trust that
350
+ if (isIOS() && !isIpadOS() && dimensionLandscape && hasStrongLandscapeRatio) {
351
+ return true;
352
+ }
161
353
 
162
- // Check if the device is an iPhone with a notch
163
- const hasNotch = CSS.supports("padding-left: env(safe-area-inset-left)");
354
+ // Otherwise use orientation if available, fallback to dimensions
355
+ return orientationLandscape || (dimensionLandscape && hasStrongLandscapeRatio);
356
+ }
357
+
358
+ function hasDeviceNotch() {
359
+ // Only check for notch on actual devices that might have one
360
+ if (!isIOS() || isIpadOS()) return false;
361
+
362
+ return (
363
+ CSS.supports("padding-left: env(safe-area-inset-left)") &&
364
+ CSS.supports("padding-right: env(safe-area-inset-right)")
365
+ );
366
+ }
164
367
 
165
- const orientation = window.orientation; //window.screen.orientation.angle;
166
- if (!isIpadOS() && isIOS()) {
167
- if (hasNotch && orientation !== 0) {
168
- // Landscape mode
169
- if (orientation === 90) {
170
- // Notch is on the left
171
- wrappers.forEach((wrapper) => {
368
+ function handleNotch(force = false) {
369
+ // Simple debouncing
370
+ if (isNotchBusy && !force) return;
371
+
372
+ try {
373
+ // Only for iPhone (not iPad)
374
+ if (!isIOS() || isIpadOS()) return;
375
+
376
+ const wrappers = document.querySelectorAll(".ios-wrapper");
377
+ if (wrappers.length === 0) {
378
+ setTimeout(() => handleNotch(force), 100);
379
+ return;
380
+ }
381
+
382
+ const isLandscape = isLandscapeMode();
383
+ const hasNotch = hasDeviceNotch();
384
+
385
+ // Get orientation
386
+ let orientation = window.orientation;
387
+ if (typeof orientation === "undefined" && screen.orientation) {
388
+ orientation = screen.orientation.angle;
389
+ }
390
+
391
+ // Create state signature to prevent redundant updates
392
+ const currentState = `${isLandscape}-${hasNotch}-${orientation}-${window.innerWidth}x${window.innerHeight}`;
393
+ if (lastNotchState === currentState && !force) {
394
+ return;
395
+ }
396
+ lastNotchState = currentState;
397
+
398
+ isNotchBusy = true;
399
+
400
+ // Debug info
401
+ if (window.location.href.includes("localhost") || window.location.href.includes("5173")) {
402
+ console.log("🔥 Notch Debug:", {
403
+ isLandscape,
404
+ hasNotch,
405
+ orientation,
406
+ dimensions: `${window.innerWidth}x${window.innerHeight}`,
407
+ state: currentState
408
+ });
409
+ }
410
+
411
+ if (hasNotch && isLandscape) {
412
+ // Test actual safe area values to determine notch side
413
+ const testDiv = document.createElement("div");
414
+ testDiv.style.position = "fixed";
415
+ testDiv.style.top = "0";
416
+ testDiv.style.left = "0";
417
+ testDiv.style.visibility = "hidden";
418
+ testDiv.style.paddingLeft = "env(safe-area-inset-left)";
419
+ testDiv.style.paddingRight = "env(safe-area-inset-right)";
420
+ document.body.appendChild(testDiv);
421
+
422
+ const computedStyle = getComputedStyle(testDiv);
423
+ const leftInset = parseFloat(computedStyle.paddingLeft) || 0;
424
+ const rightInset = parseFloat(computedStyle.paddingRight) || 0;
425
+
426
+ document.body.removeChild(testDiv);
427
+
428
+ console.log("🔍 Safe area insets:", { leftInset, rightInset });
429
+
430
+ // Apply styles instantly - NO ANIMATION
431
+ wrappers.forEach((wrapper) => {
432
+ if (leftInset > 0) {
433
+ // Notch on left
172
434
  wrapper.style.paddingLeft = "env(safe-area-inset-left)";
173
435
  wrapper.style.paddingRight = "0.5rem";
174
- });
175
- } else if (orientation === -90) {
176
- // Notch is on the right
177
- wrappers.forEach((wrapper) => {
436
+ } else if (rightInset > 0) {
437
+ // Notch on right
438
+ wrapper.style.paddingLeft = "0.5rem";
178
439
  wrapper.style.paddingRight = "env(safe-area-inset-right)";
440
+ } else {
441
+ // Fallback - no detectable notch
179
442
  wrapper.style.paddingLeft = "0.5rem";
180
- });
181
- }
443
+ wrapper.style.paddingRight = "0.5rem";
444
+ }
445
+ });
182
446
  } else {
447
+ // Portrait or no notch
448
+ const padding =
449
+ window.innerWidth > 1280
450
+ ? "2.5rem"
451
+ : window.innerWidth > 1030
452
+ ? "1.5rem"
453
+ : window.innerWidth > 768
454
+ ? "0.5rem"
455
+ : "0.5rem";
456
+
457
+ // Apply styles instantly - NO ANIMATION
183
458
  wrappers.forEach((wrapper) => {
184
- // Portrait mode or no notch
185
- const amount =
186
- window.innerWidth > 1280
187
- ? "2.5rem"
188
- : window.innerWidth > 1030
189
- ? "1.5rem"
190
- : window.innerWidth > 768
191
- ? "0.5rem"
192
- : "0.5rem";
193
- wrapper.style.paddingLeft = amount;
194
- wrapper.style.paddingRight = amount;
459
+ wrapper.style.paddingLeft = padding;
460
+ wrapper.style.paddingRight = padding;
195
461
  });
196
462
  }
463
+
464
+ isNotchBusy = false;
465
+ } catch (error) {
466
+ console.warn("Notch error:", error);
467
+ isNotchBusy = false;
197
468
  }
198
469
  }
199
470
 
200
- // Call the function initially
201
- window.addEventListener("orientationchange", handleNotch);
471
+ function triggerNotch() {
472
+ clearTimeout(notchTimeout);
473
+ notchTimeout = setTimeout(handleNotch, 50);
474
+ }
475
+
476
+ // Event listeners
477
+ window.addEventListener("orientationchange", triggerNotch);
478
+ window.addEventListener("resize", triggerNotch);
479
+ if (screen.orientation) {
480
+ screen.orientation.addEventListener("change", triggerNotch);
481
+ }
482
+
483
+ // Aggressive initial setup to handle all scenarios
484
+ function initNotchHandling() {
485
+ handleNotch(true); // Force first execution
486
+ setTimeout(() => handleNotch(true), 50);
487
+ setTimeout(() => handleNotch(true), 150);
488
+ setTimeout(() => handleNotch(true), 300);
489
+ setTimeout(() => handleNotch(true), 500);
490
+ setTimeout(() => handleNotch(true), 1000);
491
+ setTimeout(() => handleNotch(true), 2000); // Extra long delay for slow loads
492
+ }
493
+
494
+ // Multiple initialization points
495
+ if (document.readyState === "loading") {
496
+ document.addEventListener("DOMContentLoaded", initNotchHandling);
497
+ } else {
498
+ initNotchHandling();
499
+ }
500
+
501
+ window.addEventListener("load", initNotchHandling);
502
+
503
+ // Also run when page becomes visible (handles app switching)
504
+ document.addEventListener("visibilitychange", () => {
505
+ if (!document.hidden) {
506
+ setTimeout(handleNotch, 100);
507
+ }
508
+ });
509
+
510
+ // Run on focus (when returning to app)
511
+ window.addEventListener("focus", () => {
512
+ setTimeout(handleNotch, 100);
513
+ });
514
+
515
+ // Watch for ios-wrapper elements appearing in DOM
516
+ const observer = new MutationObserver((mutations) => {
517
+ let shouldTrigger = false;
518
+
519
+ mutations.forEach((mutation) => {
520
+ if (mutation.type === "childList") {
521
+ mutation.addedNodes.forEach((node) => {
522
+ if (node.nodeType === Node.ELEMENT_NODE) {
523
+ if (node.classList?.contains("ios-wrapper") || node.querySelector?.(".ios-wrapper")) {
524
+ shouldTrigger = true;
525
+ }
526
+ }
527
+ });
528
+ }
529
+ });
530
+
531
+ if (shouldTrigger) {
532
+ setTimeout(() => handleNotch(true), 10);
533
+ setTimeout(() => handleNotch(true), 100);
534
+ }
535
+ });
536
+
537
+ // Start observing
538
+ if (document.body) {
539
+ observer.observe(document.body, {
540
+ childList: true,
541
+ subtree: true
542
+ });
543
+ } else {
544
+ document.addEventListener("DOMContentLoaded", () => {
545
+ observer.observe(document.body, {
546
+ childList: true,
547
+ subtree: true
548
+ });
549
+ });
550
+ }
551
+
552
+ // Navigation handling
553
+ const originalPushState = history.pushState;
554
+ const originalReplaceState = history.replaceState;
555
+
556
+ history.pushState = function (...args) {
557
+ originalPushState.apply(this, args);
558
+ triggerNotch();
559
+ };
560
+
561
+ history.replaceState = function (...args) {
562
+ originalReplaceState.apply(this, args);
563
+ triggerNotch();
564
+ };
565
+
566
+ window.addEventListener("popstate", triggerNotch);
567
+
568
+ // Expose for manual triggering
569
+ window.simulateRotate = handleNotch;
202
570
 
203
571
  const pullStart = (e) => {
204
572
  const { screenY } = e.targetTouches[0];
@@ -252,33 +620,57 @@ function maxPull() {
252
620
  return 250;
253
621
  }
254
622
  }
623
+ // Vue router integration - aggressive triggering on route changes
255
624
  watch(
256
625
  () => router.currentRoute.value.name,
257
626
  () => {
258
- setTimeout(() => handleNotch(), 5);
627
+ // Immediate triggers
628
+ handleNotch();
629
+ triggerNotch();
630
+
631
+ // Staggered attempts
632
+ setTimeout(handleNotch, 10);
633
+ setTimeout(handleNotch, 50);
634
+ setTimeout(handleNotch, 150);
635
+ setTimeout(handleNotch, 300);
636
+ setTimeout(handleNotch, 500);
637
+ }
638
+ );
639
+
640
+ watch(
641
+ () => router.currentRoute.value.path,
642
+ () => {
643
+ // Immediate triggers
644
+ handleNotch();
645
+ triggerNotch();
646
+
647
+ // Staggered attempts
648
+ setTimeout(handleNotch, 25);
649
+ setTimeout(handleNotch, 100);
650
+ setTimeout(handleNotch, 250);
259
651
  }
260
652
  );
261
653
  const layout = computed(() => router.currentRoute.value.meta.layout);
262
654
  </script>
263
655
  <style lang="scss">
264
- .task-buttons {
265
- @apply flex mx-auto gap-x-4;
266
- svg {
267
- width: 15px;
268
- height: 15px;
269
- }
656
+ // Ultra bulletproof scroll prevention
657
+ html,
658
+ body {
659
+ overflow: hidden !important;
660
+ overscroll-behavior: none !important;
661
+ width: 100% !important;
662
+ height: 100% !important;
663
+ touch-action: none !important;
270
664
  }
271
665
 
272
- .hidden-scrollbars::-webkit-scrollbar
273
- /* Hide scrollbar for Chrome, Safari and Opera */ {
274
- display: none;
666
+ #app {
667
+ overflow: hidden !important;
668
+ overscroll-behavior: none !important;
669
+ width: 100% !important;
670
+ height: 100vh !important;
671
+ touch-action: none !important;
275
672
  }
276
673
 
277
- /* Hide scrollbar for IE, Edge and Firefox */
278
- .hidden-scrollbars {
279
- -ms-overflow-style: none; /* IE and Edge */
280
- scrollbar-width: none; /* Firefox */
281
- }
282
674
  .dropdown {
283
675
  position: relative;
284
676
  display: inline-block;
@@ -292,19 +684,19 @@ const layout = computed(() => router.currentRoute.value.meta.layout);
292
684
  z-index: 1;
293
685
  }
294
686
 
295
- .smooth-hover {
296
- @apply hover:opacity-70 active:opacity-50 duration-150;
297
- }
298
-
299
687
  .layout {
300
688
  @apply flex flex-col;
301
689
  min-height: 90vh;
690
+ overflow: hidden !important;
691
+ height: 100vh !important;
302
692
  }
693
+
303
694
  .router-wrapper {
304
695
  @apply pt-5;
305
696
  transition: margin 0.25s;
306
697
  z-index: 0;
307
698
  }
699
+
308
700
  .refresh-container {
309
701
  transition: margin 0.25s;
310
702
  }
@@ -312,4 +704,25 @@ const layout = computed(() => router.currentRoute.value.meta.layout);
312
704
  .component-container {
313
705
  @apply w-full mx-auto px-4 xs:px-4 md:px-2 lg:px-6 xl:px-10;
314
706
  }
707
+
708
+ // Page navigation transitions
709
+ .page-transition-enter-active {
710
+ transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
711
+ }
712
+
713
+ .page-transition-leave-active {
714
+ transition: all 0.15s cubic-bezier(0.55, 0.085, 0.68, 0.53);
715
+ }
716
+
717
+ .page-transition-enter-from {
718
+ opacity: 0;
719
+ transform: translateX(15px) scale(0.98);
720
+ filter: blur(1px);
721
+ }
722
+
723
+ .page-transition-leave-to {
724
+ opacity: 0;
725
+ transform: translateX(-10px) scale(1.02);
726
+ filter: blur(0.5px);
727
+ }
315
728
  </style>