pearsonkyle commited on
Commit
2a6a35b
·
verified ·
1 Parent(s): af6cc1b

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +250 -57
index.html CHANGED
@@ -22,6 +22,75 @@
22
  position: relative;
23
  overflow-x: hidden;
24
  scroll-behavior: smooth;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
 
27
  /* Card art background with subtle blur */
@@ -35,10 +104,10 @@
35
  background-size: cover;
36
  background-position: center;
37
  background-repeat: no-repeat;
38
- filter: blur(10px) brightness(0.7); /* Reduced blur and adjusted brightness */
39
  z-index: 0;
40
- opacity: 0.4; /* Reduced opacity for subty */
41
- transform: scale(1.1); /* Reduced scale to prevent edge issues */
42
  }
43
 
44
  /* Static glass panel overlay */
@@ -49,15 +118,13 @@
49
  left: 0;
50
  width: 100%;
51
  height: 100%;
52
- background: rgba(255, 255, 255, 0.1); /* 20% opacity white overlay */
53
- backdrop-filter: blur(10px); /* Subtle blur effect */
54
- -webkit-backdrop-filter: blur(10px); /* Safari support */
55
- z-index: 1; /* Above background but below content */
56
- pointer-events: none; /* Allow interactions to pass through */
57
  }
58
 
59
- /* Remove the additional blur overlay */
60
-
61
  @keyframes gradientShift {
62
  0%, 100% { transform: rotate(0deg) scale(1.2); }
63
  25% { transform: rotate(90deg) scale(1.5); }
@@ -121,7 +188,7 @@
121
  @media (max-width: 768px) {
122
  body::before {
123
  animation-duration: 30s;
124
- filter: blur(80px) brightness(0.6); /* Reduced blur but kept darkening */
125
  }
126
 
127
  .orb {
@@ -147,19 +214,19 @@
147
 
148
  /* Enhanced glass panel effect with better mobile support */
149
  .glass {
150
- background: rgba(0, 0, 0, 0.4); /* Darker background */
151
  backdrop-filter: blur(20px);
152
  -webkit-backdrop-filter: blur(20px);
153
  border: 1px solid rgba(255, 255, 255, 0.08);
154
  box-shadow:
155
- 0 8px 32px 0 rgba(0, 0, 0, 0.6), /* Darker shadow */
156
- inset 0 0 0 1px rgba(255, 255, 255, 0.05), /* Reduced inner glow */
157
- inset 0 -1px 0 0 rgba(255, 255, 255, 0.03); /* Reduced inner highlight */
158
  transition: all 0.3s ease;
159
  }
160
 
161
  .glass-hover:hover {
162
- background: rgba(0, 0, 0, 0.5); /* Darker on hover */
163
  border-color: rgba(255, 255, 255, 0.15);
164
  box-shadow:
165
  0 8px 32px 0 rgba(0, 0, 0, 0.7),
@@ -172,16 +239,16 @@
172
  /* Mobile-first glass card design - Much darker */
173
  .glass-card {
174
  background: linear-gradient(135deg,
175
- rgba(0, 0, 0, 0.8) 0%, /* Much darker gradient */
176
  rgba(0, 0, 0, 0.7) 100%);
177
  backdrop-filter: blur(min(40px, 20px));
178
  -webkit-backdrop-filter: blur(min(40px, 20px));
179
- border: 1px solid rgba(255, 255, 255, 0.08); /* Subtle border */
180
  border-radius: min(24px, 16px);
181
  box-shadow:
182
- 0 20px 40px -15px rgba(0, 0, 0, 0.8), /* Much darker shadow */
183
- inset 0 0 0 1px rgba(255, 255, 255, 0.05), /* Minimal inner glow */
184
- inset 0 0 30px rgba(0, 0, 0, 0.3); /* Dark inner shadow instead of light */
185
  overflow: hidden;
186
  position: relative;
187
  }
@@ -209,7 +276,7 @@
209
 
210
  /* Responsive button styles */
211
  .glass-button {
212
- background: rgba(0, 0, 0, 0.4); /* Darker button background */
213
  backdrop-filter: blur(10px);
214
  border: 1px solid rgba(255, 255, 255, 0.1);
215
  color: rgba(255, 255, 255, 0.9);
@@ -227,7 +294,7 @@
227
  display: inline-flex;
228
  align-items: center;
229
  justify-content: center;
230
- min-height: 44px; /* iOS minimum tap target */
231
  white-space: nowrap;
232
  }
233
 
@@ -272,7 +339,7 @@
272
 
273
  .glass-button:hover,
274
  .glass-button:focus {
275
- background: rgba(0, 0, 0, 0.5); /* Darker on hover */
276
  border-color: rgba(255, 255, 255, 0.2);
277
  transform: translateY(-2px);
278
  box-shadow:
@@ -291,6 +358,7 @@
291
  width: 100%;
292
  max-width: min(420px, 90vw);
293
  margin: 0 auto;
 
294
  }
295
 
296
  .card-3d {
@@ -341,8 +409,8 @@
341
  }
342
 
343
  .stat-box {
344
- background: rgba(0, 0, 0, 0.6); /* Much darker stat boxes */
345
- border: 1px solid rgba(255, 255, 255, 0.08); /* Subtle border */
346
  border-radius: min(12px, 3vw);
347
  padding: min(1rem, 4vw);
348
  transition: all 0.3s;
@@ -350,18 +418,18 @@
350
  display: flex;
351
  flex-direction: column;
352
  justify-content: center;
353
- font-size: 1.2em; /* Increased font size */
354
  }
355
 
356
  .stat-box:hover {
357
- background: rgba(0, 0, 0, 0.7); /* Darker on hover */
358
  border-color: rgba(255, 255, 255, 0.15);
359
  transform: translateY(-2px);
360
  }
361
 
362
  /* Increase font size for info labels and values */
363
  .info-label {
364
- font-size: 0.85rem; /* Increased from 0.75rem */
365
  font-weight: 600;
366
  text-transform: uppercase;
367
  letter-spacing: 0.05em;
@@ -372,7 +440,7 @@
372
  .info-value {
373
  color: rgba(255, 255, 255, 0.9);
374
  font-weight: 500;
375
- font-size: 1.2em; /* Increased font size */
376
  }
377
 
378
  /* Mobile-optimized gallery */
@@ -394,7 +462,7 @@
394
  overflow: hidden;
395
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
396
  cursor: pointer;
397
- background: rgba(0, 0, 0, 0.4); /* Darker gallery items */
398
  border: 1px solid rgba(255, 255, 255, 0.05);
399
  touch-action: manipulation;
400
  -webkit-tap-highlight-color: transparent;
@@ -414,7 +482,7 @@
414
  position: absolute;
415
  inset: 0;
416
  background: linear-gradient(to top,
417
- rgba(0, 0, 0, 0.95) 0%, /* Much darker gradient */
418
  rgba(0, 0, 0, 0.6) 40%,
419
  transparent 100%);
420
  opacity: 0;
@@ -510,12 +578,12 @@
510
  }
511
 
512
  ::-webkit-scrollbar-track {
513
- background: rgba(0, 0, 0, 0.3); /* Darker scrollbar track */
514
  border-radius: min(10px, 2vw);
515
  }
516
 
517
  ::-webkit-scrollbar-thumb {
518
- background: rgba(255, 255, 255, 0.15); /* Slightly brighter thumb */
519
  border-radius: min(10px, 2vw);
520
  border: 1px solid transparent;
521
  background-clip: content-box;
@@ -804,6 +872,9 @@
804
  <div class="orb orb2" aria-hidden="true"></div>
805
  <div class="orb orb3" aria-hidden="true"></div>
806
 
 
 
 
807
  <div class="container mx-auto py-4 md:py-8">
808
  <!-- Header -->
809
  <header class="text-center mb-8 md:mb-16 relative px-4">
@@ -821,7 +892,15 @@
821
  <div class="responsive-grid max-w-7xl mx-auto">
822
  <!-- Card Display Section -->
823
  <div class="flex flex-col items-center order-1 lg:order-none">
824
- <div class="card-3d-container mb-4 md:mb-6">
 
 
 
 
 
 
 
 
825
  <div class="card-3d" id="card-3d">
826
  <!-- Card Front -->
827
  <div class="card-face">
@@ -980,18 +1059,34 @@
980
  const cardFaceNav = document.getElementById('card-face-nav');
981
  const faceCounter = document.getElementById('face-counter');
982
  const card3d = document.getElementById('card-3d');
 
983
  const errorToast = document.getElementById('error-toast');
984
  const successToast = document.getElementById('success-toast');
 
 
 
985
 
986
  let currentCardFace = 0;
987
  let currentCardData = null;
988
  let isLoading = false;
989
 
 
 
 
 
 
 
 
 
 
 
990
  // Event listeners with debouncing
991
  randomBtn.addEventListener('click', debounce(() => {
992
- currentCardFace = 0;
993
- fetchRandomCard();
994
- }, 500));
 
 
995
 
996
  flipBtn.addEventListener('click', handleFlipButton);
997
  prevFaceBtn.addEventListener('click', () => switchFace(-1));
@@ -1000,12 +1095,18 @@
1000
  // Keyboard navigation
1001
  document.addEventListener('keydown', handleKeydown);
1002
 
1003
- // Touch gestures for mobile
1004
- let touchStartX = 0;
1005
- let touchStartY = 0;
 
 
1006
 
1007
- cardImage.addEventListener('touchstart', handleTouchStart, { passive: true });
1008
- cardImage.addEventListener('touchend', handleTouchEnd, { passive: true });
 
 
 
 
1009
 
1010
  // Initialize
1011
  fetchRandomCard();
@@ -1049,7 +1150,7 @@
1049
  case 'Enter':
1050
  if (e.target === flipBtn || e.target === randomBtn) return;
1051
  e.preventDefault();
1052
- fetchRandomCard();
1053
  break;
1054
  case 'f':
1055
  case 'F':
@@ -1072,40 +1173,132 @@
1072
  }
1073
 
1074
  function handleTouchStart(e) {
1075
- touchStartX = e.touches[0].clientX;
1076
- touchStartY = e.touches[0].clientY;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1077
  }
1078
 
1079
  function handleTouchEnd(e) {
1080
- if (!touchStartX || !touchStartY) return;
 
 
 
1081
 
 
 
1082
  const touchEndX = e.changedTouches[0].clientX;
1083
  const touchEndY = e.changedTouches[0].clientY;
1084
 
1085
- const deltaX = touchStartX - touchEndX;
1086
- const deltaY = touchStartY - touchEndY;
 
 
 
 
 
 
1087
 
1088
- // Minimum swipe distance
1089
- const minSwipe = 50;
 
 
 
 
1090
 
1091
- // Horizontal swipe
1092
- if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > minSwipe) {
 
 
1093
  if (currentCardData && currentCardData.card_faces) {
1094
  if (deltaX > 0) {
 
 
 
1095
  switchFace(1); // Swipe left - next face
 
 
 
 
 
 
1096
  } else {
1097
- switchFace(-1); // Swipe right - previous face
1098
  }
1099
  }
1100
  }
1101
-
1102
- // Vertical swipe up for random card
1103
- if (deltaY > minSwipe && Math.abs(deltaY) > Math.abs(deltaX)) {
1104
  fetchRandomCard();
 
1105
  }
1106
 
 
 
 
 
 
 
 
 
 
 
 
1107
  touchStartX = 0;
1108
  touchStartY = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1109
  }
1110
 
1111
  function handleFlipButton() {
 
22
  position: relative;
23
  overflow-x: hidden;
24
  scroll-behavior: smooth;
25
+ /* Prevent pull-to-refresh on mobile */
26
+ overscroll-behavior-y: contain;
27
+ }
28
+
29
+ /* Disable touch gestures globally except on specific elements */
30
+ * {
31
+ touch-action: pan-y;
32
+ }
33
+
34
+ /* Enable horizontal swipe only on card container */
35
+ .card-3d-container {
36
+ touch-action: pan-y pinch-zoom;
37
+ }
38
+
39
+ /* Swipe indicator overlay */
40
+ .swipe-indicator {
41
+ position: absolute;
42
+ top: 50%;
43
+ transform: translateY(-50%);
44
+ background: rgba(255, 255, 255, 0.9);
45
+ padding: 8px 12px;
46
+ border-radius: 20px;
47
+ font-size: 14px;
48
+ font-weight: 600;
49
+ pointer-events: none;
50
+ opacity: 0;
51
+ transition: opacity 0.3s;
52
+ z-index: 100;
53
+ white-space: nowrap;
54
+ }
55
+
56
+ .swipe-indicator.left {
57
+ left: 20px;
58
+ }
59
+
60
+ .swipe-indicator.right {
61
+ right: 20px;
62
+ }
63
+
64
+ .swipe-indicator.active {
65
+ opacity: 1;
66
+ }
67
+
68
+ /* Touch feedback */
69
+ .touch-feedback {
70
+ position: fixed;
71
+ width: 60px;
72
+ height: 60px;
73
+ border-radius: 50%;
74
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, transparent 70%);
75
+ pointer-events: none;
76
+ opacity: 0;
77
+ transform: translate(-50%, -50%) scale(0);
78
+ transition: opacity 0.3s, transform 0.3s;
79
+ z-index: 9999;
80
+ }
81
+
82
+ .touch-feedback.active {
83
+ opacity: 1;
84
+ transform: translate(-50%, -50%) scale(1);
85
+ }
86
+
87
+ /* Disable text selection on interactive elements */
88
+ .no-select {
89
+ -webkit-user-select: none;
90
+ -moz-user-select: none;
91
+ -ms-user-select: none;
92
+ user-select: none;
93
+ -webkit-touch-callout: none;
94
  }
95
 
96
  /* Card art background with subtle blur */
 
104
  background-size: cover;
105
  background-position: center;
106
  background-repeat: no-repeat;
107
+ filter: blur(10px) brightness(0.7);
108
  z-index: 0;
109
+ opacity: 0.4;
110
+ transform: scale(1.1);
111
  }
112
 
113
  /* Static glass panel overlay */
 
118
  left: 0;
119
  width: 100%;
120
  height: 100%;
121
+ background: rgba(255, 255, 255, 0.1);
122
+ backdrop-filter: blur(10px);
123
+ -webkit-backdrop-filter: blur(10px);
124
+ z-index: 1;
125
+ pointer-events: none;
126
  }
127
 
 
 
128
  @keyframes gradientShift {
129
  0%, 100% { transform: rotate(0deg) scale(1.2); }
130
  25% { transform: rotate(90deg) scale(1.5); }
 
188
  @media (max-width: 768px) {
189
  body::before {
190
  animation-duration: 30s;
191
+ filter: blur(80px) brightness(0.6);
192
  }
193
 
194
  .orb {
 
214
 
215
  /* Enhanced glass panel effect with better mobile support */
216
  .glass {
217
+ background: rgba(0, 0, 0, 0.4);
218
  backdrop-filter: blur(20px);
219
  -webkit-backdrop-filter: blur(20px);
220
  border: 1px solid rgba(255, 255, 255, 0.08);
221
  box-shadow:
222
+ 0 8px 32px 0 rgba(0, 0, 0, 0.6),
223
+ inset 0 0 0 1px rgba(255, 255, 255, 0.05),
224
+ inset 0 -1px 0 0 rgba(255, 255, 255, 0.03);
225
  transition: all 0.3s ease;
226
  }
227
 
228
  .glass-hover:hover {
229
+ background: rgba(0, 0, 0, 0.5);
230
  border-color: rgba(255, 255, 255, 0.15);
231
  box-shadow:
232
  0 8px 32px 0 rgba(0, 0, 0, 0.7),
 
239
  /* Mobile-first glass card design - Much darker */
240
  .glass-card {
241
  background: linear-gradient(135deg,
242
+ rgba(0, 0, 0, 0.8) 0%,
243
  rgba(0, 0, 0, 0.7) 100%);
244
  backdrop-filter: blur(min(40px, 20px));
245
  -webkit-backdrop-filter: blur(min(40px, 20px));
246
+ border: 1px solid rgba(255, 255, 255, 0.08);
247
  border-radius: min(24px, 16px);
248
  box-shadow:
249
+ 0 20px 40px -15px rgba(0, 0, 0, 0.8),
250
+ inset 0 0 0 1px rgba(255, 255, 255, 0.05),
251
+ inset 0 0 30px rgba(0, 0, 0, 0.3);
252
  overflow: hidden;
253
  position: relative;
254
  }
 
276
 
277
  /* Responsive button styles */
278
  .glass-button {
279
+ background: rgba(0, 0, 0, 0.4);
280
  backdrop-filter: blur(10px);
281
  border: 1px solid rgba(255, 255, 255, 0.1);
282
  color: rgba(255, 255, 255, 0.9);
 
294
  display: inline-flex;
295
  align-items: center;
296
  justify-content: center;
297
+ min-height: 44px;
298
  white-space: nowrap;
299
  }
300
 
 
339
 
340
  .glass-button:hover,
341
  .glass-button:focus {
342
+ background: rgba(0, 0, 0, 0.5);
343
  border-color: rgba(255, 255, 255, 0.2);
344
  transform: translateY(-2px);
345
  box-shadow:
 
358
  width: 100%;
359
  max-width: min(420px, 90vw);
360
  margin: 0 auto;
361
+ position: relative;
362
  }
363
 
364
  .card-3d {
 
409
  }
410
 
411
  .stat-box {
412
+ background: rgba(0, 0, 0, 0.6);
413
+ border: 1px solid rgba(255, 255, 255, 0.08);
414
  border-radius: min(12px, 3vw);
415
  padding: min(1rem, 4vw);
416
  transition: all 0.3s;
 
418
  display: flex;
419
  flex-direction: column;
420
  justify-content: center;
421
+ font-size: 1.2em;
422
  }
423
 
424
  .stat-box:hover {
425
+ background: rgba(0, 0, 0, 0.7);
426
  border-color: rgba(255, 255, 255, 0.15);
427
  transform: translateY(-2px);
428
  }
429
 
430
  /* Increase font size for info labels and values */
431
  .info-label {
432
+ font-size: 0.85rem;
433
  font-weight: 600;
434
  text-transform: uppercase;
435
  letter-spacing: 0.05em;
 
440
  .info-value {
441
  color: rgba(255, 255, 255, 0.9);
442
  font-weight: 500;
443
+ font-size: 1.2em;
444
  }
445
 
446
  /* Mobile-optimized gallery */
 
462
  overflow: hidden;
463
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
464
  cursor: pointer;
465
+ background: rgba(0, 0, 0, 0.4);
466
  border: 1px solid rgba(255, 255, 255, 0.05);
467
  touch-action: manipulation;
468
  -webkit-tap-highlight-color: transparent;
 
482
  position: absolute;
483
  inset: 0;
484
  background: linear-gradient(to top,
485
+ rgba(0, 0, 0, 0.95) 0%,
486
  rgba(0, 0, 0, 0.6) 40%,
487
  transparent 100%);
488
  opacity: 0;
 
578
  }
579
 
580
  ::-webkit-scrollbar-track {
581
+ background: rgba(0, 0, 0, 0.3);
582
  border-radius: min(10px, 2vw);
583
  }
584
 
585
  ::-webkit-scrollbar-thumb {
586
+ background: rgba(255, 255, 255, 0.15);
587
  border-radius: min(10px, 2vw);
588
  border: 1px solid transparent;
589
  background-clip: content-box;
 
872
  <div class="orb orb2" aria-hidden="true"></div>
873
  <div class="orb orb3" aria-hidden="true"></div>
874
 
875
+ <!-- Touch feedback element -->
876
+ <div class="touch-feedback" id="touch-feedback"></div>
877
+
878
  <div class="container mx-auto py-4 md:py-8">
879
  <!-- Header -->
880
  <header class="text-center mb-8 md:mb-16 relative px-4">
 
892
  <div class="responsive-grid max-w-7xl mx-auto">
893
  <!-- Card Display Section -->
894
  <div class="flex flex-col items-center order-1 lg:order-none">
895
+ <div class="card-3d-container mb-4 md:mb-6 no-select">
896
+ <!-- Swipe indicators -->
897
+ <div class="swipe-indicator left" id="swipe-left">
898
+ <i class="fas fa-chevron-left mr-1"></i> Previous
899
+ </div>
900
+ <div class="swipe-indicator right" id="swipe-right">
901
+ Next <i class="fas fa-chevron-right ml-1"></i>
902
+ </div>
903
+
904
  <div class="card-3d" id="card-3d">
905
  <!-- Card Front -->
906
  <div class="card-face">
 
1059
  const cardFaceNav = document.getElementById('card-face-nav');
1060
  const faceCounter = document.getElementById('face-counter');
1061
  const card3d = document.getElementById('card-3d');
1062
+ const card3dContainer = document.querySelector('.card-3d-container');
1063
  const errorToast = document.getElementById('error-toast');
1064
  const successToast = document.getElementById('success-toast');
1065
+ const touchFeedback = document.getElementById('touch-feedback');
1066
+ const swipeLeftIndicator = document.getElementById('swipe-left');
1067
+ const swipeRightIndicator = document.getElementById('swipe-right');
1068
 
1069
  let currentCardFace = 0;
1070
  let currentCardData = null;
1071
  let isLoading = false;
1072
 
1073
+ // Touch gesture state
1074
+ let touchStartX = 0;
1075
+ let touchStartY = 0;
1076
+ let touchStartTime = 0;
1077
+ let isSwiping = false;
1078
+ let swipeThreshold = 100; // Increased from 50px to prevent accidental swipes
1079
+ let swipeVelocityThreshold = 0.3; // pixels per millisecond
1080
+ let lastSwipeTime = 0;
1081
+ let swipeDebounceTime = 500; // Prevent multiple swipes in quick succession
1082
+
1083
  // Event listeners with debouncing
1084
  randomBtn.addEventListener('click', debounce(() => {
1085
+ if (!isLoading) {
1086
+ currentCardFace = 0;
1087
+ fetchRandomCard();
1088
+ }
1089
+ }, 300));
1090
 
1091
  flipBtn.addEventListener('click', handleFlipButton);
1092
  prevFaceBtn.addEventListener('click', () => switchFace(-1));
 
1095
  // Keyboard navigation
1096
  document.addEventListener('keydown', handleKeydown);
1097
 
1098
+ // Improved touch gesture handling - only on card container
1099
+ card3dContainer.addEventListener('touchstart', handleTouchStart, { passive: false });
1100
+ card3dContainer.addEventListener('touchmove', handleTouchMove, { passive: false });
1101
+ card3dContainer.addEventListener('touchend', handleTouchEnd, { passive: false });
1102
+ card3dContainer.addEventListener('touchcancel', handleTouchCancel, { passive: false });
1103
 
1104
+ // Prevent default touch behavior on buttons
1105
+ [randomBtn, flipBtn, prevFaceBtn, nextFaceBtn].forEach(btn => {
1106
+ btn.addEventListener('touchstart', (e) => {
1107
+ e.stopPropagation();
1108
+ }, { passive: false });
1109
+ });
1110
 
1111
  // Initialize
1112
  fetchRandomCard();
 
1150
  case 'Enter':
1151
  if (e.target === flipBtn || e.target === randomBtn) return;
1152
  e.preventDefault();
1153
+ if (!isLoading) fetchRandomCard();
1154
  break;
1155
  case 'f':
1156
  case 'F':
 
1173
  }
1174
 
1175
  function handleTouchStart(e) {
1176
+ if (isLoading) return;
1177
+
1178
+ // Get the first touch point
1179
+ const touch = e.touches[0];
1180
+ touchStartX = touch.clientX;
1181
+ touchStartY = touch.clientY;
1182
+ touchStartTime = Date.now();
1183
+ isSwiping = false;
1184
+
1185
+ // Show touch feedback
1186
+ showTouchFeedback(touchStartX, touchStartY);
1187
+ }
1188
+
1189
+ function handleTouchMove(e) {
1190
+ if (!touchStartX || !touchStartY || isLoading) return;
1191
+
1192
+ const touch = e.touches[0];
1193
+ const deltaX = touch.clientX - touchStartX;
1194
+ const deltaY = touch.clientY - touchStartY;
1195
+
1196
+ // Determine if this is a horizontal swipe
1197
+ if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 20) {
1198
+ isSwiping = true;
1199
+ e.preventDefault(); // Prevent scrolling only when swiping horizontally
1200
+
1201
+ // Show swipe indicators
1202
+ if (deltaX > 30) {
1203
+ swipeLeftIndicator.classList.add('active');
1204
+ swipeRightIndicator.classList.remove('active');
1205
+ } else if (deltaX < -30) {
1206
+ swipeRightIndicator.classList.add('active');
1207
+ swipeLeftIndicator.classList.remove('active');
1208
+ }
1209
+ }
1210
+
1211
+ // Update touch feedback position
1212
+ updateTouchFeedback(touch.clientX, touch.clientY);
1213
  }
1214
 
1215
  function handleTouchEnd(e) {
1216
+ if (!touchStartX || !touchStartY || isLoading) {
1217
+ resetTouchState();
1218
+ return;
1219
+ }
1220
 
1221
+ const touchEndTime = Date.now();
1222
+ const touchDuration = touchEndTime - touchStartTime;
1223
  const touchEndX = e.changedTouches[0].clientX;
1224
  const touchEndY = e.changedTouches[0].clientY;
1225
 
1226
+ const deltaX = touchEndX - touchStartX;
1227
+ const deltaY = touchEndY - touchStartY;
1228
+ const velocity = Math.abs(deltaX) / touchDuration;
1229
+
1230
+ // Hide swipe indicators
1231
+ swipeLeftIndicator.classList.remove('active');
1232
+ swipeRightIndicator.classList.remove('active');
1233
+ hideTouchFeedback();
1234
 
1235
+ // Check if enough time has passed since last swipe
1236
+ const now = Date.now();
1237
+ if (now - lastSwipeTime < swipeDebounceTime) {
1238
+ resetTouchState();
1239
+ return;
1240
+ }
1241
 
1242
+ // Horizontal swipe detection with velocity check
1243
+ if (isSwiping && Math.abs(deltaX) > swipeThreshold && velocity > swipeVelocityThreshold) {
1244
+ lastSwipeTime = now;
1245
+
1246
  if (currentCardData && currentCardData.card_faces) {
1247
  if (deltaX > 0) {
1248
+ switchFace(-1); // Swipe right - previous face
1249
+ showSuccess('Previous face');
1250
+ } else {
1251
  switchFace(1); // Swipe left - next face
1252
+ showSuccess('Next face');
1253
+ }
1254
+ } else {
1255
+ // No multiple faces, could show a message
1256
+ if (deltaX > 0) {
1257
+ showError('No previous face');
1258
  } else {
1259
+ showError('No next face');
1260
  }
1261
  }
1262
  }
1263
+ // Vertical swipe down for random card (increased threshold)
1264
+ else if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY > 150 && !isSwiping) {
1265
+ lastSwipeTime = now;
1266
  fetchRandomCard();
1267
+ showSuccess('Loading random card...');
1268
  }
1269
 
1270
+ resetTouchState();
1271
+ }
1272
+
1273
+ function handleTouchCancel(e) {
1274
+ resetTouchState();
1275
+ swipeLeftIndicator.classList.remove('active');
1276
+ swipeRightIndicator.classList.remove('active');
1277
+ hideTouchFeedback();
1278
+ }
1279
+
1280
+ function resetTouchState() {
1281
  touchStartX = 0;
1282
  touchStartY = 0;
1283
+ touchStartTime = 0;
1284
+ isSwiping = false;
1285
+ }
1286
+
1287
+ function showTouchFeedback(x, y) {
1288
+ touchFeedback.style.left = x + 'px';
1289
+ touchFeedback.style.top = y + 'px';
1290
+ touchFeedback.classList.add('active');
1291
+ }
1292
+
1293
+ function updateTouchFeedback(x, y) {
1294
+ if (touchFeedback.classList.contains('active')) {
1295
+ touchFeedback.style.left = x + 'px';
1296
+ touchFeedback.style.top = y + 'px';
1297
+ }
1298
+ }
1299
+
1300
+ function hideTouchFeedback() {
1301
+ touchFeedback.classList.remove('active');
1302
  }
1303
 
1304
  function handleFlipButton() {