@wyxos/vibe 1.6.8 → 1.6.10

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": "@wyxos/vibe",
3
- "version": "1.6.8",
3
+ "version": "1.6.10",
4
4
  "main": "lib/index.js",
5
5
  "module": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { ref, onMounted, onUnmounted, watch, computed, withDefaults } from 'vue';
2
+ import { ref, onMounted, onUnmounted, watch, computed, withDefaults, nextTick } from 'vue';
3
3
 
4
4
  const props = withDefaults(defineProps<{
5
5
  item: any;
@@ -20,6 +20,7 @@ const videoError = ref(false);
20
20
  const videoSrc = ref<string | null>(null);
21
21
  const isInView = ref(false);
22
22
  const isLoading = ref(false);
23
+ const showMedia = ref(false); // Controls fade-in animation
23
24
  const containerRef = ref<HTMLElement | null>(null);
24
25
  let intersectionObserver: IntersectionObserver | null = null;
25
26
 
@@ -43,10 +44,14 @@ function preloadImage(src: string): Promise<void> {
43
44
  const remaining = Math.max(0, minLoadTime - elapsed);
44
45
 
45
46
  // Ensure spinner shows for at least minLoadTime
46
- setTimeout(() => {
47
+ setTimeout(async () => {
47
48
  imageLoaded.value = true;
48
49
  imageError.value = false;
49
50
  isLoading.value = false;
51
+ // Wait for Vue to update DOM, then trigger fade-in
52
+ await nextTick();
53
+ await new Promise(resolve => setTimeout(resolve, 100));
54
+ showMedia.value = true;
50
55
  resolve();
51
56
  }, remaining);
52
57
  };
@@ -78,10 +83,14 @@ function preloadVideo(src: string): Promise<void> {
78
83
  const elapsed = Date.now() - startTime;
79
84
  const remaining = Math.max(0, minLoadTime - elapsed);
80
85
 
81
- setTimeout(() => {
86
+ setTimeout(async () => {
82
87
  videoLoaded.value = true;
83
88
  videoError.value = false;
84
89
  isLoading.value = false;
90
+ // Wait for Vue to update DOM, then trigger fade-in
91
+ await nextTick();
92
+ await new Promise(resolve => setTimeout(resolve, 100));
93
+ showMedia.value = true;
85
94
  resolve();
86
95
  }, remaining);
87
96
  };
@@ -117,6 +126,7 @@ async function startPreloading() {
117
126
  if (!src) return;
118
127
 
119
128
  isLoading.value = true;
129
+ showMedia.value = false; // Reset fade-in state
120
130
 
121
131
  if (mediaType.value === 'video') {
122
132
  videoSrc.value = src;
@@ -239,21 +249,29 @@ watch(
239
249
 
240
250
  <!-- Media content (image or video) -->
241
251
  <div v-else class="relative w-full h-full">
242
- <!-- Image (shown immediately when loaded, with lazy loading attribute) -->
252
+ <!-- Image (always rendered when src exists, fades in when loaded) -->
243
253
  <img
244
- v-if="mediaType === 'image' && imageLoaded && imageSrc"
254
+ v-if="mediaType === 'image' && imageSrc"
245
255
  :src="imageSrc"
246
- class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
256
+ :class="[
257
+ 'w-full h-full object-cover transition-opacity duration-700 ease-in-out group-hover:scale-105',
258
+ imageLoaded && showMedia ? 'opacity-100' : 'opacity-0'
259
+ ]"
260
+ style="position: absolute; top: 0; left: 0;"
247
261
  loading="lazy"
248
262
  decoding="async"
249
263
  alt=""
250
264
  />
251
265
 
252
- <!-- Video (shown immediately when loaded) -->
266
+ <!-- Video (always rendered when src exists, fades in when loaded) -->
253
267
  <video
254
- v-if="mediaType === 'video' && videoLoaded && videoSrc"
268
+ v-if="mediaType === 'video' && videoSrc"
255
269
  :src="videoSrc"
256
- class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
270
+ :class="[
271
+ 'w-full h-full object-cover transition-opacity duration-700 ease-in-out group-hover:scale-105',
272
+ videoLoaded && showMedia ? 'opacity-100' : 'opacity-0'
273
+ ]"
274
+ style="position: absolute; top: 0; left: 0;"
257
275
  muted
258
276
  loop
259
277
  playsinline
@@ -262,19 +280,17 @@ watch(
262
280
  @error="videoError = true"
263
281
  />
264
282
 
265
- <!-- Placeholder background while loading or if not loaded yet -->
283
+ <!-- Placeholder background while loading or if not loaded yet (fades out when media appears) -->
266
284
  <div
267
285
  v-if="!imageLoaded && !videoLoaded && !imageError && !videoError"
268
- class="absolute inset-0 bg-slate-100 flex items-center justify-center"
286
+ :class="[
287
+ 'absolute inset-0 bg-slate-100 flex items-center justify-center transition-opacity duration-500',
288
+ showMedia ? 'opacity-0 pointer-events-none' : 'opacity-100'
289
+ ]"
269
290
  >
270
291
  <!-- Media type indicator - shown BEFORE preloading starts -->
271
- <div
272
- class="flex flex-col items-center justify-center gap-2 text-slate-400"
273
- >
274
- <div class="w-12 h-12 rounded-full bg-white/80 backdrop-blur-sm flex items-center justify-center shadow-sm">
275
- <i :class="mediaType === 'video' ? 'fas fa-video text-xl' : 'fas fa-image text-xl'"></i>
276
- </div>
277
- <span class="text-xs font-medium uppercase">{{ mediaType }}</span>
292
+ <div class="w-12 h-12 rounded-full bg-white/80 backdrop-blur-sm flex items-center justify-center shadow-sm">
293
+ <i :class="mediaType === 'video' ? 'fas fa-video text-xl text-slate-400' : 'fas fa-image text-xl text-slate-400'"></i>
278
294
  </div>
279
295
  </div>
280
296