Index: app/(app)/analytics/analytics-client.tsx
===================================================================
--- app/(app)/analytics/analytics-client.tsx	(revision e2fa2e31c4602d840bb4a3d273832bf408b4db19)
+++ app/(app)/analytics/analytics-client.tsx	(revision aa8207eeea5e8109f834cd4729352d126250988e)
@@ -3,5 +3,5 @@
 import { useEffect, useMemo, useRef, useState } from 'react';
 import { usePathname, useRouter, useSearchParams } from 'next/navigation';
-import { CalendarDaysIcon, CheckIcon, XMarkIcon, ArrowsPointingOutIcon } from '@heroicons/react/24/outline';
+import { CalendarDaysIcon, CheckIcon, XMarkIcon } from '@heroicons/react/24/outline';
 import { formatMKD } from '@/app/lib/utils';
 import UrlSearchInput from '@/app/ui/url-search-input';
@@ -11,4 +11,5 @@
 const COLORS = ['#60a5fa', '#34d399', '#fbbf24', '#f472b6', '#a78bfa', '#fb7185', '#22d3ee', '#f97316'];
 const ANALYTICS_KEYS = ['query', 'accountId', 'period', 'startDate', 'endDate', 'focusTags'] as const;
+const NAVIGATION_START_EVENT = 'fein:navigation-start';
 
 export default function AnalyticsClient({
@@ -34,4 +35,9 @@
     const storageKey = `fein:analyticsState:v1:${userId}`;
 
+    function navigateWithIndicator(nextUrl: string) {
+        window.dispatchEvent(new Event(NAVIGATION_START_EVENT));
+        replace(nextUrl);
+    }
+
     useEffect(() => {
         try {
@@ -57,5 +63,5 @@
             const nextQuery = params.toString();
             if (nextQuery) {
-                replace(`${pathname}?${nextQuery}`);
+                navigateWithIndicator(`${pathname}?${nextQuery}`);
             }
         } catch {
@@ -85,5 +91,5 @@
         mutate(params);
         const nextQuery = params.toString();
-        replace(nextQuery ? `${pathname}?${nextQuery}` : pathname);
+        navigateWithIndicator(nextQuery ? `${pathname}?${nextQuery}` : pathname);
     };
 
@@ -131,5 +137,5 @@
 
     const clearAllFilters = () => {
-        replace(pathname);
+        navigateWithIndicator(pathname);
     };
 
@@ -152,5 +158,7 @@
                         />
                     </div>
-                    <AccountFilterIcon accounts={data.accounts} />
+                    <AccountFilterIcon
+                        accounts={data.accounts}
+                    />
                 </div>
 
Index: app/layout.tsx
===================================================================
--- app/layout.tsx	(revision e2fa2e31c4602d840bb4a3d273832bf408b4db19)
+++ app/layout.tsx	(revision aa8207eeea5e8109f834cd4729352d126250988e)
@@ -2,4 +2,5 @@
 import { poppins } from '@/app/ui/fonts';
 import { Metadata } from 'next';
+import GlobalNavigationIndicator from '@/app/ui/global-navigation-indicator';
 
 export const metadata: Metadata = {
@@ -42,4 +43,6 @@
           </div>
         </div>
+
+        <GlobalNavigationIndicator />
       </body>
     </html>
Index: app/ui/account-filter-icon.tsx
===================================================================
--- app/ui/account-filter-icon.tsx	(revision e2fa2e31c4602d840bb4a3d273832bf408b4db19)
+++ app/ui/account-filter-icon.tsx	(revision aa8207eeea5e8109f834cd4729352d126250988e)
@@ -6,12 +6,16 @@
 import type { TransactionAccountLite } from '@/app/lib/queries';
 
+const NAVIGATION_START_EVENT = 'fein:navigation-start';
+
 export default function AccountFilterIcon({
     accounts,
     accountParam = 'accountId',
     resetPageOnChange = false,
+    onNavigateStart,
 }: {
     accounts: TransactionAccountLite[];
     accountParam?: string;
     resetPageOnChange?: boolean;
+    onNavigateStart?: () => void;
 }) {
     const searchParams = useSearchParams();
@@ -50,4 +54,6 @@
 
         const nextQuery = params.toString();
+        window.dispatchEvent(new Event(NAVIGATION_START_EVENT));
+        onNavigateStart?.();
         replace(nextQuery ? `${pathname}?${nextQuery}` : pathname);
         setOpen(false);
Index: app/ui/global-navigation-indicator.tsx
===================================================================
--- app/ui/global-navigation-indicator.tsx	(revision aa8207eeea5e8109f834cd4729352d126250988e)
+++ app/ui/global-navigation-indicator.tsx	(revision aa8207eeea5e8109f834cd4729352d126250988e)
@@ -0,0 +1,59 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+
+const NAVIGATION_START_EVENT = 'fein:navigation-start';
+
+export default function GlobalNavigationIndicator() {
+    const [visible, setVisible] = useState(false);
+
+    useEffect(() => {
+        function onNavigationStart() {
+            setVisible(true);
+        }
+
+        function onDocumentClick(event: MouseEvent) {
+            if (event.defaultPrevented) return;
+            const target = event.target as Element | null;
+            if (!target) return;
+
+            const anchor = target.closest('a[href]') as HTMLAnchorElement | null;
+            if (!anchor) return;
+            if (anchor.target && anchor.target !== '_self') return;
+            if (anchor.hasAttribute('download')) return;
+            if (anchor.getAttribute('href')?.startsWith('#')) return;
+
+            setVisible(true);
+        }
+
+        function onFormSubmit() {
+            setVisible(true);
+        }
+
+        window.addEventListener(NAVIGATION_START_EVENT, onNavigationStart);
+        document.addEventListener('click', onDocumentClick, true);
+        document.addEventListener('submit', onFormSubmit, true);
+
+        return () => {
+            window.removeEventListener(NAVIGATION_START_EVENT, onNavigationStart);
+            document.removeEventListener('click', onDocumentClick, true);
+            document.removeEventListener('submit', onFormSubmit, true);
+        };
+    }, []);
+
+    useEffect(() => {
+        if (!visible) return;
+        const timeoutId = window.setTimeout(() => setVisible(false), 1400);
+        return () => clearTimeout(timeoutId);
+    }, [visible]);
+
+    if (!visible) {
+        return null;
+    }
+
+    return (
+        <div className="pointer-events-none fixed inset-0 z-[95] flex items-center justify-center backdrop-blur-[2px] bg-white/5">
+            <div className="h-10 w-10 animate-spin rounded-full border-2 border-white/30 border-t-white/90 shadow-[0_0_24px_rgba(255,255,255,0.25)]" />
+        </div>
+    );
+}
Index: app/ui/url-search-input.tsx
===================================================================
--- app/ui/url-search-input.tsx	(revision e2fa2e31c4602d840bb4a3d273832bf408b4db19)
+++ app/ui/url-search-input.tsx	(revision aa8207eeea5e8109f834cd4729352d126250988e)
@@ -6,4 +6,6 @@
 import { poppins } from '@/app/ui/fonts';
 
+const NAVIGATION_START_EVENT = 'fein:navigation-start';
+
 export default function UrlSearchInput({
     placeholder,
@@ -11,4 +13,5 @@
     debounceMs = 300,
     resetPageOnChange = false,
+    onNavigateStart,
 }: {
     placeholder: string;
@@ -16,4 +19,5 @@
     debounceMs?: number;
     resetPageOnChange?: boolean;
+    onNavigateStart?: () => void;
 }) {
     const searchParams = useSearchParams();
@@ -35,4 +39,6 @@
 
         const nextQuery = params.toString();
+        window.dispatchEvent(new Event(NAVIGATION_START_EVENT));
+        onNavigateStart?.();
         replace(nextQuery ? `${pathname}?${nextQuery}` : pathname);
     }, debounceMs);
