Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/components/BackToTopButton.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<button
type="button"
onClick={scrollToTop}
aria-label="Back to top"
tabIndex={isVisible ? 0 : -1}
aria-hidden={!isVisible}
className={`fixed bottom-20 right-6 z-50 flex h-12 w-12 items-center justify-center rounded-full border border-neon/40 bg-surface-mid text-neon shadow-lg shadow-neon/20 backdrop-blur transition-all duration-300 hover:-translate-y-1 hover:bg-neon hover:text-on-primary hover:shadow-neon/40 focus:outline-none focus:ring-2 focus:ring-neon focus:ring-offset-2 ${
isVisible
? "translate-y-0 opacity-100"
: "pointer-events-none translate-y-4 opacity-0"
}`}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
>
<ArrowUp className="h-5 w-5" />
</button>
);
}
33 changes: 33 additions & 0 deletions src/components/shared/ScanSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import GlassCard from "../GlassCard";
export default function ScanSkeleton() {
return (
<div className="p-6 space-y-6 animate-pulse">
<div className="flex flex-col md:flex-row gap-6">
<GlassCard className="flex-1 p-8">
<div className="h-4 w-32 skeleton-shimmer rounded mb-4"></div>
<div className="h-16 w-40 skeleton-shimmer rounded mb-4"></div>
<div className="h-2 w-full skeleton-shimmer rounded"></div>
</GlassCard>

<GlassCard className="md:w-72 p-6">
<div className="h-4 w-24 skeleton-shimmer rounded mb-4"></div>
<div className="h-8 w-full skeleton-shimmer rounded mb-3"></div>
<div className="h-8 w-full skeleton-shimmer rounded"></div>
</GlassCard>
</div>

<GlassCard className="p-6">
<div className="h-5 w-40 skeleton-shimmer rounded mb-4"></div>
<div className="space-y-3">
<div className="h-16 skeleton-shimmer rounded"></div>
<div className="h-16 skeleton-shimmer rounded"></div>
<div className="h-16 skeleton-shimmer rounded"></div>
</div>
</GlassCard>

<GlassCard className="p-4">
<div className="h-12 skeleton-shimmer rounded"></div>
</GlassCard>
</div>
);
}
21 changes: 21 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
11 changes: 4 additions & 7 deletions src/pages/AnalysisDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -69,13 +70,9 @@ export default function AnalysisDashboard() {
load();
}, [params]);


if (loading) {
return (
<div className="min-h-[calc(100vh-4rem)] flex items-center justify-center">
<StatusTerminal messages={['LOADING_ANALYSIS...', 'FETCHING_RESULT']} />
</div>
);
// ── Loading state ────────────────────────────────────────────────────────
if (loading) {
return <ScanSkeleton />;
}

// ── Error state ──────────────────────────────────────────────────────────
Expand Down
4 changes: 3 additions & 1 deletion src/pages/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand Down Expand Up @@ -77,7 +78,7 @@ export default function LandingPage() {
}, []);

return (
<div className="relative">
<div id="landing-top" className="relative">
{/* ── HERO ── */}
<section className="relative min-h-[90vh] flex flex-col items-center justify-center px-6 md:px-16 lg:px-24 py-20 overflow-hidden text-center">
<div className="relative z-10 max-w-5xl mx-auto">
Expand Down Expand Up @@ -279,6 +280,7 @@ export default function LandingPage() {
</Link>
</div>
</section>
<BackToTopButton />
</div>
);
}
Loading