Skip to content

Commit 36a3bd4

Browse files
authored
Merge pull request #14 from thirdbase1/k/58faf7ae
feat: UI overhaul with enhanced header and design improvements
2 parents e74803d + a11da8c commit 36a3bd4

9 files changed

Lines changed: 1226 additions & 412 deletions

File tree

client/src/components/Layout.tsx

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Link, useLocation } from "wouter";
22
import { HardHat, Menu, X, Download, BookOpen, Home, Printer } from "lucide-react";
3-
import { useState } from "react";
3+
import { useState, useEffect } from "react";
44
import { Button } from "@/components/ui/button";
55
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
66
// @ts-ignore
@@ -9,6 +9,15 @@ import html2pdf from "html2pdf.js";
99
export function Layout({ children }: { children: React.ReactNode }) {
1010
const [location] = useLocation();
1111
const [isMobileOpen, setIsMobileOpen] = useState(false);
12+
const [scrolled, setScrolled] = useState(false);
13+
14+
useEffect(() => {
15+
const handleScroll = () => {
16+
setScrolled(window.scrollY > 20);
17+
};
18+
window.addEventListener("scroll", handleScroll);
19+
return () => window.removeEventListener("scroll", handleScroll);
20+
}, []);
1221

1322
const navItems = [
1423
{ label: "Home", href: "/", icon: Home },
@@ -19,34 +28,54 @@ export function Layout({ children }: { children: React.ReactNode }) {
1928
return (
2029
<div className="min-h-screen flex flex-col bg-background font-roboto">
2130
{/* Header */}
22-
<header className="sticky top-0 z-50 w-full border-b bg-card/95 backdrop-blur supports-[backdrop-filter]:bg-card/60 print:hidden">
31+
<header className={`sticky top-0 z-50 w-full transition-all duration-300 print:hidden ${
32+
scrolled
33+
? "border-b bg-card/95 backdrop-blur-lg shadow-lg"
34+
: "border-b border-white/10 bg-slate-900/95 backdrop-blur-md"
35+
}`}>
2336
<div className="container flex h-16 items-center justify-between px-4 md:px-6">
24-
<Link href="/" className="flex items-center gap-2">
25-
<div className="bg-primary p-1.5 rounded-md">
37+
<Link href="/" className="flex items-center gap-3 group">
38+
<div className="bg-gradient-to-br from-primary to-secondary p-2 rounded-lg shadow-lg shadow-primary/20 group-hover:shadow-primary/40 transition-shadow duration-300">
2639
<HardHat className="h-6 w-6 text-primary-foreground" />
2740
</div>
28-
<span className="text-xl font-bold font-oswald tracking-tight text-foreground">
29-
CRANESIGNAL<span className="text-primary">PRO</span>
30-
</span>
41+
<div className="flex flex-col">
42+
<span className={`text-xl font-bold font-oswald tracking-tight transition-colors duration-300 ${
43+
scrolled ? "text-foreground" : "text-white"
44+
}`}>
45+
CRANESIGNAL<span className="text-primary">PRO</span>
46+
</span>
47+
<span className={`text-xs font-medium transition-colors duration-300 ${
48+
scrolled ? "text-muted-foreground" : "text-white/60"
49+
}`}>
50+
Certified Safety Training
51+
</span>
52+
</div>
3153
</Link>
3254

3355
{/* Desktop Nav */}
3456
<nav className="hidden md:flex items-center gap-6">
3557
{navItems.map((item) => (
3658
<Link key={item.href} href={item.href}>
3759
<span
38-
className={`text-sm font-medium transition-colors hover:text-primary ${
60+
className={`text-sm font-medium transition-all duration-200 hover:text-primary relative ${
3961
location === item.href
40-
? "text-primary border-b-2 border-primary"
41-
: "text-muted-foreground"
62+
? scrolled
63+
? "text-foreground"
64+
: "text-white"
65+
: scrolled
66+
? "text-muted-foreground"
67+
: "text-white/70"
4268
}`}
4369
>
4470
{item.label}
71+
{location === item.href && (
72+
<span className="absolute -bottom-1 left-0 right-0 h-0.5 bg-primary rounded-full" />
73+
)}
4574
</span>
4675
</Link>
4776
))}
4877
<Link href="/curriculum">
49-
<Button size="sm" className="font-oswald uppercase tracking-wide">
78+
<Button size="sm" className="font-oswald uppercase tracking-wide bg-gradient-to-r from-primary to-secondary hover:opacity-90 transition-all duration-200 shadow-md hover:shadow-lg">
5079
Start Course
5180
</Button>
5281
</Link>
@@ -55,7 +84,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
5584
{/* Mobile Nav */}
5685
<Sheet open={isMobileOpen} onOpenChange={setIsMobileOpen}>
5786
<SheetTrigger asChild className="md:hidden">
58-
<Button variant="ghost" size="icon">
87+
<Button variant="ghost" size="icon" className={scrolled ? "" : "text-white"}>
5988
<Menu className="h-6 w-6" />
6089
<span className="sr-only">Toggle menu</span>
6190
</Button>
@@ -65,9 +94,9 @@ export function Layout({ children }: { children: React.ReactNode }) {
6594
{navItems.map((item) => (
6695
<Link key={item.href} href={item.href} onClick={() => setIsMobileOpen(false)}>
6796
<span
68-
className={`flex items-center gap-2 text-lg font-medium p-2 rounded-md transition-colors ${
97+
className={`flex items-center gap-3 text-lg font-medium p-3 rounded-lg transition-all duration-200 ${
6998
location === item.href
70-
? "bg-primary/10 text-primary"
99+
? "bg-primary/10 text-primary border-l-4 border-primary"
71100
: "hover:bg-muted"
72101
}`}
73102
>
@@ -77,7 +106,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
77106
</Link>
78107
))}
79108
<Link href="/curriculum" onClick={() => setIsMobileOpen(false)}>
80-
<Button className="w-full mt-4 font-oswald uppercase">Start Learning</Button>
109+
<Button className="w-full mt-4 font-oswald uppercase bg-gradient-to-r from-primary to-secondary">Start Learning</Button>
81110
</Link>
82111
</nav>
83112
</SheetContent>
@@ -91,16 +120,35 @@ export function Layout({ children }: { children: React.ReactNode }) {
91120
</main>
92121

93122
{/* Footer */}
94-
<footer className="border-t bg-muted/40 py-6 md:py-10 print:hidden">
95-
<div className="container px-4 md:px-6 flex flex-col md:flex-row items-center justify-between gap-4">
96-
<div className="flex items-center gap-2">
97-
<HardHat className="h-5 w-5 text-muted-foreground" />
98-
<span className="text-sm font-semibold text-muted-foreground">
99-
CraneSignal Pro &copy; 2025
100-
</span>
123+
<footer className="border-t bg-gradient-to-b from-slate-900 to-slate-950 py-8 md:py-12 print:hidden">
124+
<div className="container px-4 md:px-6">
125+
<div className="grid md:grid-cols-3 gap-8 items-center">
126+
<div className="flex items-center gap-3">
127+
<div className="bg-primary/20 p-2 rounded-lg">
128+
<HardHat className="h-5 w-5 text-primary" />
129+
</div>
130+
<div>
131+
<span className="text-sm font-bold text-white font-oswald tracking-tight">
132+
CRANESIGNAL<span className="text-primary">PRO</span>
133+
</span>
134+
<p className="text-xs text-white/60">Certified Safety Training &copy; 2025</p>
135+
</div>
136+
</div>
137+
<div className="text-center">
138+
<p className="text-sm text-white/60">Official ANSI standard hand signals for mobile cranes.</p>
139+
<p className="text-xs text-white/40 mt-1">Ensuring jobsite safety through standardized communication.</p>
140+
</div>
141+
<div className="flex md:justify-end gap-4">
142+
<Link href="/curriculum">
143+
<span className="text-sm text-white/60 hover:text-primary transition-colors">Curriculum</span>
144+
</Link>
145+
<Link href="/downloads">
146+
<span className="text-sm text-white/60 hover:text-primary transition-colors">Downloads</span>
147+
</Link>
148+
</div>
101149
</div>
102-
<div className="text-sm text-muted-foreground text-center md:text-right">
103-
<p>Official ANSI standard hand signals for mobile cranes.</p>
150+
<div className="mt-6 pt-6 border-t border-white/10 text-center">
151+
<p className="text-xs text-white/40">Always verify signals with official site safety regulations. This training is for educational purposes.</p>
104152
</div>
105153
</div>
106154
</footer>

client/src/components/LessonComponents.tsx

Lines changed: 90 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,59 @@
11
import { useState, useEffect } from "react";
22
import { Link } from "wouter";
3-
import { CheckCircle, XCircle, ArrowRight, ShieldCheck, Loader2 } from "lucide-react";
3+
import { CheckCircle, XCircle, ArrowRight, ShieldCheck, Loader2, Play } from "lucide-react";
44
import { Signal } from "@/lib/course-data";
55
import { Button } from "@/components/ui/button";
66
import { Card, CardContent } from "@/components/ui/card";
77
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
88
import { Label } from "@/components/ui/label";
99
import { useAleoWallet } from "@/hooks/use-aleo-wallet";
1010
import { useToast } from "@/hooks/use-toast";
11+
import { motion } from "framer-motion";
1112

1213
export function SignalCard({ signal }: { signal: Signal }) {
1314
return (
1415
<Link href={`/lesson/${signal.id}`}>
15-
<div className="group cursor-pointer h-full">
16-
<Card className="h-full overflow-hidden border-2 border-transparent transition-all hover:border-primary hover:shadow-md">
17-
<div className="aspect-square bg-muted p-6 flex items-center justify-center relative overflow-hidden">
16+
<motion.div
17+
className="group cursor-pointer h-full"
18+
whileHover={{ y: -5 }}
19+
transition={{ duration: 0.2 }}
20+
>
21+
<div className="h-full rounded-2xl bg-card border-2 border-transparent hover:border-primary/30 overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300">
22+
<div className="aspect-square bg-gradient-to-br from-muted/50 to-muted p-6 flex items-center justify-center relative overflow-hidden">
1823
<img
1924
src={signal.image}
2025
alt={signal.name}
21-
className="w-full h-full object-contain transition-transform group-hover:scale-105"
26+
className="w-full h-full object-contain transition-transform duration-500 group-hover:scale-110"
2227
/>
23-
<div className="absolute inset-0 bg-primary/10 opacity-0 group-hover:opacity-100 transition-opacity" />
28+
<div className="absolute inset-0 bg-gradient-to-t from-primary/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
29+
<div className="absolute top-3 right-3">
30+
<span className="px-2 py-1 bg-white/90 dark:bg-slate-800/90 rounded-md text-xs font-bold uppercase tracking-wider shadow-sm backdrop-blur-sm">
31+
{signal.category}
32+
</span>
33+
</div>
34+
<div className="absolute bottom-3 left-3 right-3 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
35+
<div className="flex items-center gap-2 text-primary font-medium text-sm bg-white/90 dark:bg-slate-800/90 rounded-lg px-3 py-2 backdrop-blur-sm shadow-sm">
36+
<Play className="h-4 w-4" />
37+
<span>Start Lesson</span>
38+
<ArrowRight className="h-4 w-4 ml-auto group-hover:translate-x-1 transition-transform" />
39+
</div>
40+
</div>
2441
</div>
25-
<CardContent className="p-4">
26-
<div className="flex items-center justify-between mb-2">
27-
<span className="text-xs font-bold uppercase tracking-wider text-primary">{signal.category}</span>
42+
<div className="p-5">
43+
<div className="flex items-center gap-2 mb-2">
44+
<span className="text-xs font-bold uppercase tracking-wider text-primary bg-primary/10 px-2 py-0.5 rounded-full">
45+
{signal.category}
46+
</span>
2847
</div>
29-
<h3 className="text-xl font-bold font-oswald text-foreground mb-2 group-hover:text-primary transition-colors">
48+
<h3 className="text-lg font-bold font-oswald uppercase text-foreground mb-2 group-hover:text-primary transition-colors line-clamp-1">
3049
{signal.name}
3150
</h3>
3251
<p className="text-sm text-muted-foreground line-clamp-2">
3352
{signal.description}
3453
</p>
35-
</CardContent>
36-
</Card>
37-
</div>
54+
</div>
55+
</div>
56+
</motion.div>
3857
</Link>
3958
);
4059
}
@@ -51,7 +70,6 @@ export function QuizComponent({ signal }: { signal: Signal }) {
5170

5271
const isCorrect = selected === signal.quiz.correctAnswer;
5372

54-
// Poll for transaction status if we have a txId
5573
useEffect(() => {
5674
let interval: NodeJS.Timeout;
5775
if (txId && (txStatus === "Pending" || !txStatus)) {
@@ -80,7 +98,6 @@ export function QuizComponent({ signal }: { signal: Signal }) {
8098
try {
8199
const programId = "crane_signals_cert.aleo";
82100
const functionName = "issue_certification";
83-
// Inputs: recipient address, signal_id (u8)
84101
const inputs = [address, `${signal.id}u8`];
85102

86103
const id = await requestTransaction(programId, functionName, inputs, 0.1);
@@ -98,73 +115,100 @@ export function QuizComponent({ signal }: { signal: Signal }) {
98115
};
99116

100117
return (
101-
<Card className="bg-muted/30 border-none">
118+
<Card className="bg-gradient-to-br from-primary/5 to-secondary/5 border-primary/20">
102119
<CardContent className="p-6">
103-
<h3 className="text-lg font-bold font-oswald mb-4 flex items-center gap-2">
104-
<span className="bg-primary text-primary-foreground px-2 py-0.5 rounded text-sm">QUIZ</span>
105-
Test Your Knowledge
106-
</h3>
120+
<div className="flex items-center gap-3 mb-6">
121+
<div className="bg-gradient-to-br from-primary to-secondary p-2 rounded-lg">
122+
<span className="text-xs font-bold text-primary-foreground">Q</span>
123+
</div>
124+
<h3 className="text-lg font-bold font-oswald">Test Your Knowledge</h3>
125+
</div>
107126

108127
{!submitted ? (
109-
<div className="space-y-4">
128+
<motion.div
129+
className="space-y-5"
130+
initial={{ opacity: 0 }}
131+
animate={{ opacity: 1 }}
132+
>
110133
<p className="font-medium text-lg">{signal.quiz.question}</p>
111-
<RadioGroup onValueChange={(v) => setSelected(parseInt(v))}>
134+
<RadioGroup
135+
onValueChange={(v) => setSelected(parseInt(v))}
136+
className="space-y-3"
137+
>
112138
{signal.quiz.options.map((option, idx) => (
113-
<div key={idx} className="flex items-center space-x-2">
139+
<motion.div
140+
key={idx}
141+
className={`flex items-center space-x-3 p-3 rounded-lg border-2 transition-all cursor-pointer ${
142+
selected === idx
143+
? "border-primary bg-primary/5"
144+
: "border-transparent hover:border-muted"
145+
}`}
146+
whileHover={{ scale: 1.01 }}
147+
whileTap={{ scale: 0.99 }}
148+
>
114149
<RadioGroupItem value={idx.toString()} id={`option-${idx}`} />
115-
<Label htmlFor={`option-${idx}`} className="cursor-pointer">{option}</Label>
116-
</div>
150+
<Label htmlFor={`option-${idx}`} className="cursor-pointer flex-1">
151+
{option}
152+
</Label>
153+
</motion.div>
117154
))}
118155
</RadioGroup>
119156
<Button
120157
onClick={() => setSubmitted(true)}
121158
disabled={selected === null}
122-
className="w-full sm:w-auto font-oswald uppercase"
159+
className="w-full sm:w-auto font-oswald uppercase bg-gradient-to-r from-primary to-secondary hover:opacity-90 transition-all duration-200 shadow-sm"
123160
>
124-
Check Answer
161+
Check Answer <ArrowRight className="ml-2 h-4 w-4" />
125162
</Button>
126-
</div>
163+
</motion.div>
127164
) : (
128-
<div className="space-y-4 animate-in fade-in slide-in-from-bottom-2">
129-
<div className={`flex items-center gap-3 text-lg font-bold ${isCorrect ? 'text-green-600' : 'text-red-600'}`}>
165+
<motion.div
166+
className="space-y-5 animate-in fade-in slide-in-from-bottom-2"
167+
initial={{ opacity: 0, y: 10 }}
168+
animate={{ opacity: 1, y: 0 }}
169+
>
170+
<div className={`flex items-center gap-3 text-lg font-bold p-4 rounded-xl ${
171+
isCorrect
172+
? "bg-green-50 text-green-700 border-2 border-green-200"
173+
: "bg-red-50 text-red-700 border-2 border-red-200"
174+
}`}>
130175
{isCorrect ? <CheckCircle className="h-6 w-6" /> : <XCircle className="h-6 w-6" />}
131-
{isCorrect ? "Correct!" : "Incorrect"}
176+
{isCorrect ? "Excellent! You've mastered this signal." : `The correct answer is: ${signal.quiz.options[signal.quiz.correctAnswer]}`}
132177
</div>
133-
<p className="text-muted-foreground">
134-
{isCorrect
135-
? "Great job! You've mastered this signal."
136-
: `The correct answer is: ${signal.quiz.options[signal.quiz.correctAnswer]}`
137-
}
138-
</p>
178+
139179
<div className="flex flex-col sm:flex-row gap-3">
140-
<Button variant="outline" onClick={() => {
141-
setSubmitted(false);
142-
setSelected(null);
143-
setTxId(null);
144-
setTxStatus(null);
145-
}}>
180+
<Button
181+
variant="outline"
182+
onClick={() => {
183+
setSubmitted(false);
184+
setSelected(null);
185+
setTxId(null);
186+
setTxStatus(null);
187+
}}
188+
className="font-oswald uppercase"
189+
>
146190
Try Again
147191
</Button>
148192

149193
{isCorrect && !txId && (
150194
<Button
151195
onClick={handleMintCertificate}
152196
disabled={minting}
153-
className="bg-green-600 hover:bg-green-700 text-white gap-2"
197+
className="bg-gradient-to-r from-green-600 to-emerald-600 hover:opacity-90 text-white gap-2 shadow-sm"
154198
>
155199
{minting ? <Loader2 className="h-4 w-4 animate-spin" /> : <ShieldCheck className="h-4 w-4" />}
156200
{address ? "Mint ZK-Certificate" : "Connect Wallet to Mint"}
157201
</Button>
158202
)}
159203

160204
{txId && (
161-
<div className="flex items-center gap-2 px-4 py-2 bg-background border rounded-md text-sm font-medium">
205+
<div className="flex items-center gap-2 px-4 py-2 bg-background border rounded-xl text-sm font-medium shadow-sm">
162206
<div className={`h-2 w-2 rounded-full ${txStatus === "Completed" ? "bg-green-500" : "bg-amber-500 animate-pulse"}`} />
163207
Status: {txStatus || "Processing..."}
164208
</div>
165209
)}
166210
</div>
167-
</div>
211+
</motion.div>
168212
)}
169213
</CardContent>
170214
</Card>

0 commit comments

Comments
 (0)