diff --git a/src/components/WaitingTime.vue b/src/components/WaitingTime.vue index ddf8df8..dc02c92 100644 --- a/src/components/WaitingTime.vue +++ b/src/components/WaitingTime.vue @@ -9,15 +9,10 @@ const props = defineProps<{ }>() const waitingMinutes = ref(props.at.diff(dayjs(), "minutes")) -const minutes = ref() +const displayedMinutes = ref(waitingMinutes.value) +const isUpdating = ref(false) -const keyframes = [{ transform: "scale(1.4)" }, { transform: "scale(1)" }] -const { play } = useAnimate(minutes, keyframes, { - duration: 2000, - easing: "cubic-bezier(.21,.92,.49,.97)", -}) - -const showDots = computed(() => waitingMinutes.value < 0) +const showDots = computed(() => displayedMinutes.value < 0) const blinking = ref(false) useIntervalFn(() => { @@ -26,8 +21,25 @@ useIntervalFn(() => { }, 5000) watch( - () => waitingMinutes.value, - () => play() + waitingMinutes, + () => { + if (isUpdating.value) { + return + } + + isUpdating.value = true + + // Update the value at the peak of the shrink (1.5s mark when scale is at minimum) + setTimeout(() => { + displayedMinutes.value = waitingMinutes.value + }, 1500) + + // End the animation after 3 seconds + setTimeout(() => { + isUpdating.value = false + }, 3000) + }, + { immediate: false } ) @@ -38,7 +50,7 @@ watch(
- {{ waitingMinutes }} + {{ displayedMinutes }} @@ -56,6 +68,24 @@ time { font-weight: bold; } +.updating { + animation: waiting-time-update 3s cubic-bezier(0.4, 0, 0.2, 1); +} + +@keyframes waiting-time-update { + 0% { + transform: scale(1); + } + + 50% { + transform: scale(0.8); + } + + 100% { + transform: scale(1); + } +} + .blinking { animation: blink-number 1s infinite; } diff --git a/src/components/disruptions/Slider.vue b/src/components/disruptions/Slider.vue index 7921913..44494bd 100644 --- a/src/components/disruptions/Slider.vue +++ b/src/components/disruptions/Slider.vue @@ -8,10 +8,15 @@ const props = defineProps<{ }>() const rotatingDisruptions = ref([]) -const isAnimating = ref(true) +const currentDisruption = ref(null) +const nextDisruption = ref(null) +const isTransitioning = ref(false) watchEffect(() => { rotatingDisruptions.value = [...filterDisruptions()] + if (rotatingDisruptions.value.length > 0 && !currentDisruption.value) { + currentDisruption.value = rotatingDisruptions.value[0] + } }) function filterDisruptions() { @@ -31,21 +36,29 @@ function enhanceDisruptionText(text: string): string { }) } -function triggerAnimation() { - isAnimating.value = false - requestAnimationFrame(() => { - requestAnimationFrame(() => { - isAnimating.value = true - }) - }) +function triggerTransition() { + if (rotatingDisruptions.value.length <= 1) return + + // Get the next disruption + const currentIndex = rotatingDisruptions.value.findIndex(d => d === currentDisruption.value) + const nextIndex = (currentIndex + 1) % rotatingDisruptions.value.length + nextDisruption.value = rotatingDisruptions.value[nextIndex] + + // Start transition + isTransitioning.value = true + + // After transition completes, update current and reset state + setTimeout(() => { + currentDisruption.value = nextDisruption.value + nextDisruption.value = null + isTransitioning.value = false + }, 1200) // Match the CSS animation duration } onMounted(() => { const interval = setInterval(() => { - if (rotatingDisruptions.value.length > 0) { - const firstElement = rotatingDisruptions.value.shift() - rotatingDisruptions.value.push(firstElement!) - triggerAnimation() + if (rotatingDisruptions.value.length > 1) { + triggerTransition() } }, 10_000) @@ -56,18 +69,29 @@ onMounted(() => { @@ -103,6 +138,7 @@ header { padding-bottom: 0; background-color: white; overflow: hidden; + position: relative; } .content { @@ -114,6 +150,7 @@ header { padding: 0.8vw 2vw; padding-right: calc(env(safe-area-inset-right) + 2vw); font-size: var(--font-size); + position: relative; } .content p { @@ -123,6 +160,10 @@ header { -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden; + position: absolute; + top: 0.8vw; + left: 2vw; + right: calc(env(safe-area-inset-right) + 2vw); } .content p:deep(time) { @@ -141,28 +182,37 @@ header { overflow: hidden; } -[appear="true"] .current .indicator, -[appear="true"] [role="row"] .indicator { - animation: slide-to-left 1.6s cubic-bezier(0.21, 0.07, 0.49, 1); +/* Enhanced slide animations */ +.slide-out { + animation: slide-out-left 1.2s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.slide-in { + animation: slide-in-right 1.2s cubic-bezier(0.4, 0, 0.2, 1) forwards; } -[appear="true"] .content { - animation: slide-to-left-light 1s cubic-bezier(0.21, 0.07, 0.49, 1); +.current .slide-in { + position: absolute; + top: 1vw; + left: 2vw; + right: 3vw; } -@keyframes slide-to-left { +@keyframes slide-out-left { 0% { - transform: translateX(100%); + transform: translateX(0); + opacity: 1; } 100% { - transform: translateX(0); + transform: translateX(-100%); + opacity: 0; } } -@keyframes slide-to-left-light { +@keyframes slide-in-right { 0% { - transform: translateX(4vw); - opacity: 0.5; + transform: translateX(100%); + opacity: 0; } 100% { transform: translateX(0);