diff --git a/apps/web/src/features/trade/components/positions/PositionsList.tsx b/apps/web/src/features/trade/components/positions/PositionsList.tsx
index 61b2178f..3bab645d 100644
--- a/apps/web/src/features/trade/components/positions/PositionsList.tsx
+++ b/apps/web/src/features/trade/components/positions/PositionsList.tsx
@@ -129,7 +129,7 @@ export function PositionsList({ onSelectPosition }: Props) {
{
id: "mark",
header: "Mark",
- accessor: (p) => ,
+ accessor: (p) => ,
},
{
id: "liq",
@@ -176,9 +176,9 @@ export function PositionsList({ onSelectPosition }: Props) {
const role = p.pnlAfterFees >= 0 ? "positive" : "negative"
return (
<>
-
+
{" "}
-
+
>
)
},
diff --git a/apps/web/src/features/trade/components/trade-panel/ConfirmationDialog.tsx b/apps/web/src/features/trade/components/trade-panel/ConfirmationDialog.tsx
index 422cb3b4..f0ae7921 100644
--- a/apps/web/src/features/trade/components/trade-panel/ConfirmationDialog.tsx
+++ b/apps/web/src/features/trade/components/trade-panel/ConfirmationDialog.tsx
@@ -7,6 +7,7 @@ import {
DialogTitle,
} from "@workspace/ui/components/dialog"
import { Button } from "@workspace/ui/components/button"
+import { cn } from "@workspace/ui/lib/utils"
import { useQuery } from "@tanstack/react-query"
import { createSwapOrder, sendBatchOrderTxn } from "../../lib/stellar"
import { formatUsd } from "../../lib/trade-math"
@@ -447,9 +448,30 @@ export function ConfirmationDialog({
: ""
}
>
- {isSubmitting
- ? "Submitting..."
- : `Confirm ${typeLabel}`}
+
+
+ {`Confirm ${typeLabel}`}
+
+
+
+ Submitting...
+
+
diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx
index 365c5eac..2a9c04db 100644
--- a/apps/web/src/routes/__root.tsx
+++ b/apps/web/src/routes/__root.tsx
@@ -11,6 +11,7 @@ import { DensityProvider } from "@workspace/ui/components/density-provider"
import appCss from "@workspace/ui/globals.css?url"
import { AppProviders } from "../app/providers"
import { RouteAnnouncer } from "../shared/components/RouteAnnouncer"
+import { RouteProgressBar } from "../shared/components/RouteProgressBar"
// Update this to your production domain before going live.
const SITE_URL = "https://levee.market"
@@ -194,6 +195,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
+
{children}
diff --git a/apps/web/src/shared/components/RouteProgressBar.tsx b/apps/web/src/shared/components/RouteProgressBar.tsx
new file mode 100644
index 00000000..567fe961
--- /dev/null
+++ b/apps/web/src/shared/components/RouteProgressBar.tsx
@@ -0,0 +1,20 @@
+import { useRouterState } from "@tanstack/react-router"
+import { cn } from "@workspace/ui/lib/utils"
+
+export function RouteProgressBar() {
+ const isLoading = useRouterState({ select: (state) => state.isLoading })
+
+ return (
+
+ )
+}
diff --git a/packages/ui/src/components/numeric.tsx b/packages/ui/src/components/numeric.tsx
index 49f71e2b..2e69f5af 100644
--- a/packages/ui/src/components/numeric.tsx
+++ b/packages/ui/src/components/numeric.tsx
@@ -1,6 +1,7 @@
import { cva } from 'class-variance-authority'
import { cn } from '@workspace/ui/lib/utils'
import { useNumericChangeFeedback } from '../hooks/use-numeric-change-feedback'
+import { RollingNumber } from './rolling-number'
import type { VariantProps } from 'class-variance-authority'
const numericVariants = cva('font-mono tabular-nums', {
@@ -164,6 +165,7 @@ interface NumericProps {
threshold?: number
highlightOnChange?: boolean
coalesceMs?: number
+ rolling?: boolean
className?: string
}
@@ -179,6 +181,7 @@ function Numeric({
threshold,
highlightOnChange = false,
coalesceMs = 500,
+ rolling = false,
className,
}: NumericProps) {
const { className: emphasisClassName } = useNumericChangeFeedback({
@@ -210,7 +213,7 @@ function Numeric({
data-slot="numeric"
className={cn(numericVariants({ role }), highlightOnChange && emphasisClassName, className)}
>
- {display}
+ {rolling ? : display}
)
}
diff --git a/packages/ui/src/components/rolling-number.tsx b/packages/ui/src/components/rolling-number.tsx
new file mode 100644
index 00000000..db976180
--- /dev/null
+++ b/packages/ui/src/components/rolling-number.tsx
@@ -0,0 +1,53 @@
+import { cn } from '@workspace/ui/lib/utils'
+
+const DIGITS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
+
+interface RollingNumberProps {
+ value: string
+ className?: string
+}
+
+/**
+ * Renders a formatted number with each digit on a vertical odometer strip.
+ * Only transform moves, so a change slides digits without shifting layout.
+ * Non-digit characters (sign, currency, separators) render as-is.
+ */
+function RollingNumber({ value, className }: RollingNumberProps) {
+ const chars = Array.from(value)
+
+ return (
+
+ {value}
+ {chars.map((char, index) => {
+ // Key by position from the right so digits keep their column identity.
+ const key = chars.length - index
+ return /\d/.test(char) ? (
+
+ ) : (
+
+ {char}
+
+ )
+ })}
+
+ )
+}
+
+function RollingDigit({ digit }: { digit: number }) {
+ return (
+
+
+ {DIGITS.map((d) => (
+
+ {d}
+
+ ))}
+
+
+ )
+}
+
+export { RollingNumber }
diff --git a/packages/ui/src/components/skeleton.tsx b/packages/ui/src/components/skeleton.tsx
index df09e3b3..d3c84b40 100644
--- a/packages/ui/src/components/skeleton.tsx
+++ b/packages/ui/src/components/skeleton.tsx
@@ -4,10 +4,8 @@ import { cn } from "@workspace/ui/lib/utils"
// ---------------------------------------------------------------------------
// Base primitive
//
-// Uses `motion-safe:animate-pulse` so the animation only runs when the user
-// has NOT requested reduced motion. A CSS-level safety net in globals.css
-// provides belt-and-suspenders coverage for older browsers / non-Tailwind
-// class consumers.
+// The loading sheen is a transform-only sweep drawn on ::before, so it never
+// triggers layout. globals.css disables it under reduced motion.
// ---------------------------------------------------------------------------
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
@@ -15,7 +13,10 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
)
diff --git a/packages/ui/src/styles/globals.css b/packages/ui/src/styles/globals.css
index cb90d21f..35007e25 100644
--- a/packages/ui/src/styles/globals.css
+++ b/packages/ui/src/styles/globals.css
@@ -287,6 +287,13 @@
--radius-12: 0.75rem; /* 12px — sponsor logo cards */
--radius-20: 1.25rem; /* 20px — feature/pool/program cards */
+ /* Skeleton loading sheen (transform-only) */
+ --animate-skeleton-sweep: skeleton-sweep 1.5s ease-in-out infinite;
+ @keyframes skeleton-sweep {
+ from { transform: translateX(-100%); }
+ to { transform: translateX(100%); }
+ }
+
/* Hero title word-rotation (AnimatedTitle) */
--animate-title-in: title-in 0.25s ease-in-out forwards;
--animate-title-out: title-out 0.25s ease-in-out forwards;
@@ -992,3 +999,7 @@
:root[data-reduced-motion="true"] .so4-confirm-check,
:root[data-reduced-motion="true"] .so4-confirm-moment { animation: none; }
:root[data-reduced-motion="true"] [data-slot="tooltip-content"] { transition: none; }
+@media (prefers-reduced-motion: reduce) {
+ [data-slot="skeleton"]::before { animation: none; display: none; }
+}
+:root[data-reduced-motion="true"] [data-slot="skeleton"]::before { animation: none; display: none; }