diff --git a/components/journeys/ColumnJourney.tsx b/components/journeys/ColumnJourney.tsx
index bae0d9c..1646450 100644
--- a/components/journeys/ColumnJourney.tsx
+++ b/components/journeys/ColumnJourney.tsx
@@ -1,6 +1,6 @@
'use client'
-import { Fragment, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'
+import { Fragment, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { TreeStructure } from '@phosphor-icons/react'
import type { PathTransition } from '@/lib/api/journeys'
@@ -53,6 +53,34 @@ function smartLabel(path: string): string {
return `…/${segments[segments.length - 1]}`
}
+// ─── Animated count hook ────────────────────────────────────────────
+
+function useAnimatedCount(target: number, duration = 400): number {
+ const [display, setDisplay] = useState(0)
+ const prevTarget = useRef(target)
+
+ useEffect(() => {
+ const from = prevTarget.current
+ prevTarget.current = target
+ if (from === target) {
+ setDisplay(target)
+ return
+ }
+ const start = performance.now()
+ let raf: number
+ const tick = (now: number) => {
+ const t = Math.min((now - start) / duration, 1)
+ const eased = 1 - Math.pow(1 - t, 3) // ease-out cubic
+ setDisplay(Math.round(from + (target - from) * eased))
+ if (t < 1) raf = requestAnimationFrame(tick)
+ }
+ raf = requestAnimationFrame(tick)
+ return () => cancelAnimationFrame(raf)
+ }, [target, duration])
+
+ return display
+}
+
// ─── Data transformation ────────────────────────────────────────────
function buildColumns(
@@ -112,6 +140,21 @@ function buildColumns(
// ─── Sub-components ─────────────────────────────────────────────────
+function AnimatedDropOff({ percent }: { percent: number }) {
+ const displayed = useAnimatedCount(percent)
+ if (displayed === 0 && percent === 0) return null
+ return (
+
+ {percent > 0 ? '+' : displayed < 0 ? '' : ''}
+ {displayed}%
+
+ )
+}
+
function ColumnHeader({
column,
}: {
@@ -127,14 +170,7 @@ function ColumnHeader({
{column.totalSessions.toLocaleString()} visitors
{column.dropOffPercent !== 0 && (
-
- {column.dropOffPercent > 0 ? '+' : ''}
- {column.dropOffPercent}%
-
+