diff --git a/src/components/BackToTopButton.tsx b/src/components/BackToTopButton.tsx
new file mode 100644
index 0000000..0e97023
--- /dev/null
+++ b/src/components/BackToTopButton.tsx
@@ -0,0 +1,49 @@
+import { useEffect, useState } from "react";
+import { ArrowUp } from "lucide-react";
+
+export default function BackToTopButton() {
+ const [isVisible, setIsVisible] = useState(false);
+
+ useEffect(() => {
+ const toggleVisibility = () => {
+ const main = document.querySelector("main");
+
+ const scrollTop =
+ window.scrollY ||
+ document.documentElement.scrollTop ||
+ document.body.scrollTop ||
+ main?.scrollTop ||
+ 0;
+
+ setIsVisible(scrollTop > 300);
+ };
+
+ window.addEventListener("scroll", toggleVisibility);
+ toggleVisibility();
+
+ return () => window.removeEventListener("scroll", toggleVisibility);
+ }, []);
+
+ const scrollToTop = () => {
+ window.scrollTo({
+ top: 0,
+ behavior: "smooth",
+ });
+ };
+ return (
+
+ );
+}
diff --git a/src/components/shared/ScanSkeleton.tsx b/src/components/shared/ScanSkeleton.tsx
new file mode 100644
index 0000000..60559fd
--- /dev/null
+++ b/src/components/shared/ScanSkeleton.tsx
@@ -0,0 +1,33 @@
+import GlassCard from "../GlassCard";
+export default function ScanSkeleton() {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
index e1d1223..055a6c0 100644
--- a/src/index.css
+++ b/src/index.css
@@ -396,4 +396,25 @@ input::placeholder {
@media print {
nav, .print\:hidden { display: none !important; }
+}
+@keyframes shimmer {
+ 0% {
+ background-position: -200% 0;
+ }
+
+ 100% {
+ background-position: 200% 0;
+ }
+}
+
+.skeleton-shimmer {
+ background: linear-gradient(
+ 90deg,
+ var(--color-surface-mid) 25%,
+ var(--color-surface-highest) 50%,
+ var(--color-surface-mid) 75%
+ );
+
+ background-size: 200% 100%;
+ animation: shimmer 1.5s linear infinite;
}
\ No newline at end of file
diff --git a/src/pages/AnalysisDashboard.tsx b/src/pages/AnalysisDashboard.tsx
index b5c2925..14d4f89 100644
--- a/src/pages/AnalysisDashboard.tsx
+++ b/src/pages/AnalysisDashboard.tsx
@@ -5,6 +5,7 @@ import GlassCard from '../components/GlassCard';
import StatusTerminal from '../components/StatusTerminal';
import { api } from '../lib/api';
import type { ScanResult } from '../lib/types';
+import ScanSkeleton from "../components/shared/ScanSkeleton";
const BIOMARKER_META = {
gill_saturation: { label: 'Gill Saturation', icon: Droplets },
corneal_clarity: { label: 'Corneal Clarity', icon: EyeIcon },
@@ -69,13 +70,9 @@ export default function AnalysisDashboard() {
load();
}, [params]);
-
- if (loading) {
- return (
-
-
-
- );
+ // ── Loading state ────────────────────────────────────────────────────────
+ if (loading) {
+ return ;
}
// ── Error state ──────────────────────────────────────────────────────────
diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx
index 8799eab..f4bdf2b 100644
--- a/src/pages/LandingPage.tsx
+++ b/src/pages/LandingPage.tsx
@@ -3,6 +3,7 @@ import { isAuthenticated } from '../lib/api';
import { Zap, Eye, MapPin, ScanLine, Target, Award, ChevronDown } from 'lucide-react';
import GlassCard from '../components/GlassCard';
import { useState, useEffect } from 'react';
+import BackToTopButton from '../components/BackToTopButton';
const features = [
{
@@ -77,7 +78,7 @@ export default function LandingPage() {
}, []);
return (
-
+
{/* ── HERO ── */}
@@ -279,6 +280,7 @@ export default function LandingPage() {
+
);
}