Skip to content

Commit 50c3a6e

Browse files
committed
Small frontend improvements
1 parent 152fca4 commit 50c3a6e

File tree

7 files changed

+125
-41
lines changed

7 files changed

+125
-41
lines changed

frontend/src/app/globals.css

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@
55
:root {
66
--background: #ffffff;
77
--foreground: #171717;
8+
--primary: #3b82f6;
9+
--primary-dark: #1e40af;
10+
--secondary: #10b981;
811
}
912

1013
@media (prefers-color-scheme: dark) {
1114
:root {
1215
--background: #0a0a0a;
1316
--foreground: #ededed;
17+
--card-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.1);
18+
--card-shadow-hover: 0 10px 15px -3px rgba(0, 0, 0, 0.2), 0 4px 6px -2px rgba(0, 0, 0, 0.1);
1419
}
1520
}
1621

1722
body {
1823
color: var(--foreground);
1924
background: var(--background);
20-
font-family: Arial, Helvetica, sans-serif;
25+
font-family: 'Inter', Arial, Helvetica, sans-serif;
2126
}
2227

2328
.perspective {
@@ -32,3 +37,6 @@ body {
3237
transform: rotateY(180deg);
3338
}
3439

40+
.gradient-text {
41+
@apply text-transparent bg-clip-text bg-gradient-to-r from-blue-500 to-teal-400;
42+
}

frontend/src/app/layout.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@ export default function RootLayout({
2424
<body className="bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 text-gray-800 dark:text-gray-200 font-sans min-h-screen flex flex-col">
2525
<ToastProvider>
2626
<Header />
27-
<main className="flex-grow max-w-6xl mx-auto w-full px-4 sm:px-6 lg:px-8 py-12 space-y-8">
27+
<main className="flex-grow max-w-6xl mx-auto w-full px-4 sm:px-6 lg:px-8 py-12 space-y-8 transition-all duration-300">
2828
{children}
2929
</main>
3030
<StatusChecker />
31+
<footer className="text-center p-6 text-gray-500 dark:text-gray-400 text-sm">
32+
<p>© 2025 FluentAI • Made with ❤️ for language learners</p>
33+
</footer>
3134
</ToastProvider>
3235
</body>
3336
</html>

frontend/src/components/CardGenerator.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ export default function CardGenerator({
144144
<button
145145
type="button"
146146
onClick={() => setShowAdvanced(!showAdvanced)}
147-
className="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-100 transition-colors"
147+
className="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-100
148+
transition-colors px-3 py-1.5 rounded-full bg-gray-100 dark:bg-gray-900"
148149
>
149150
{showAdvanced ? <ChevronUp size={20} /> : <ChevronDown size={20} />}
150151
<span>Advanced Options</span>
@@ -170,7 +171,7 @@ export default function CardGenerator({
170171
>
171172
<input
172173
type="text"
173-
className={`w-full border rounded p-2 bg-white text-gray-800 dark:border-gray-600
174+
className={`border rounded-lg p-2 w-full bg-gray-50 dark:bg-gray-700 border-gray-300 dark:border-gray-600 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200
174175
${input.keySentence ? 'opacity-50 cursor-not-allowed' : ''}
175176
${errors.word ? "border-red-500" : "border-gray-300 dark:border-gray-600"}`}
176177
value={input.mnemonicKeyword || ""}

frontend/src/components/Header.tsx

+98-30
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,118 @@ import { useState, useEffect } from "react";
44
import Image from "next/image";
55
import Link from "next/link";
66
import logo from "../../public/logo.png";
7+
import { Sun, Moon, Menu, X } from "lucide-react";
78

89
export default function Header() {
910
const [darkMode, setDarkMode] = useState(false);
11+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
1012

1113
useEffect(() => {
12-
if (darkMode) {
14+
// Check system preference initially
15+
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
16+
setDarkMode(prefersDark);
17+
18+
if (prefersDark) {
1319
document.documentElement.classList.add("dark");
1420
} else {
1521
document.documentElement.classList.remove("dark");
1622
}
17-
}, [darkMode]);
23+
}, []);
1824

19-
const toggleDarkMode = () => setDarkMode(!darkMode);
25+
const toggleDarkMode = () => {
26+
setDarkMode(!darkMode);
27+
if (darkMode) {
28+
document.documentElement.classList.remove("dark");
29+
} else {
30+
document.documentElement.classList.add("dark");
31+
}
32+
};
2033

2134
return (
22-
<header className="w-full flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-sm">
23-
<div className="flex items-center space-x-4">
24-
<Image
25-
src={logo}
26-
alt="FluentAI Logo"
27-
width={50}
28-
height={50}
29-
className="transition-transform duration-300 hover:rotate-12"
30-
/>
31-
<h1 className="text-2xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-500 to-teal-400">
32-
FluentAI
33-
</h1>
35+
<header className="sticky top-0 z-50 w-full bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 shadow-sm transition-all duration-300">
36+
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
37+
<div className="flex items-center justify-between h-16">
38+
<div className="flex items-center space-x-4">
39+
<Link href="/" className="flex items-center space-x-3">
40+
<div className="relative w-10 h-10 transition-transform duration-300 hover:rotate-12">
41+
<Image
42+
src={logo}
43+
alt="FluentAI Logo"
44+
className="object-contain"
45+
fill
46+
sizes="2.5rem"
47+
priority
48+
/>
49+
</div>
50+
<h1 className="text-2xl font-bold gradient-text">
51+
FluentAI
52+
</h1>
53+
</Link>
54+
</div>
55+
56+
{/* Desktop navigation */}
57+
<nav className="hidden md:flex items-center space-x-8">
58+
<Link
59+
href="/"
60+
className="text-gray-600 dark:text-gray-300 hover:text-blue-500 dark:hover:text-blue-400 font-medium transition-colors"
61+
>
62+
Home
63+
</Link>
64+
<Link
65+
href="/library"
66+
className="text-gray-600 dark:text-gray-300 hover:text-blue-500 dark:hover:text-blue-400 font-medium transition-colors"
67+
>
68+
Library
69+
</Link>
70+
<button
71+
onClick={toggleDarkMode}
72+
className="p-2 rounded-full bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300
73+
hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-300"
74+
aria-label="Toggle Dark Mode"
75+
>
76+
{darkMode ? <Sun size={20} /> : <Moon size={20} />}
77+
</button>
78+
</nav>
79+
80+
{/* Mobile menu button */}
81+
<div className="md:hidden flex items-center">
82+
<button
83+
onClick={toggleDarkMode}
84+
className="p-2 rounded-full bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300
85+
hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-300 mr-2"
86+
aria-label="Toggle Dark Mode"
87+
>
88+
{darkMode ? <Sun size={18} /> : <Moon size={18} />}
89+
</button>
90+
<button
91+
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
92+
className="p-2 rounded-md text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
93+
>
94+
{mobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
95+
</button>
96+
</div>
97+
</div>
3498
</div>
35-
<div className="flex items-center space-x-6">
36-
<Link href="/" className="text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-100">
37-
Home
38-
</Link>
39-
<Link href="/library" className="text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-100">
40-
Library
41-
</Link>
99+
100+
{/* Mobile menu */}
101+
<div className={`md:hidden transition-all duration-300 ease-in-out ${mobileMenuOpen ? 'max-h-36 opacity-100' : 'max-h-0 opacity-0 overflow-hidden'}`}>
102+
<div className="px-4 pt-2 pb-4 space-y-2 bg-white dark:bg-gray-900 shadow-lg">
103+
<Link
104+
href="/"
105+
className="block py-2 text-gray-600 dark:text-gray-300 hover:text-blue-500 dark:hover:text-blue-400 font-medium"
106+
onClick={() => setMobileMenuOpen(false)}
107+
>
108+
Home
109+
</Link>
110+
<Link
111+
href="/library"
112+
className="block py-2 text-gray-600 dark:text-gray-300 hover:text-blue-500 dark:hover:text-blue-400 font-medium"
113+
onClick={() => setMobileMenuOpen(false)}
114+
>
115+
Library
116+
</Link>
117+
</div>
42118
</div>
43-
<button
44-
onClick={toggleDarkMode}
45-
className="px-4 py-2 rounded-full bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-300 flex items-center space-x-2"
46-
aria-label="Toggle Dark Mode"
47-
>
48-
{darkMode ? "🌞 Light Mode" : "🌙 Dark Mode"}
49-
</button>
50119
</header>
51120
);
52121
}
53-

frontend/src/components/SaveToAnki.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ export default function SaveToAnki({ card, onError }: SaveToAnkiProps) {
126126
<FormField label="Anki Deck" value={selectedDeck} required>
127127
<div className="flex gap-2">
128128
<select
129-
className="w-full py-2 px-4 border rounded"
129+
className="w-full p-3 border rounded-lg bg-gray-50 dark:bg-gray-700 border-gray-300 dark:border-gray-600
130+
focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200"
130131
value={selectedDeck}
131132
onChange={(e) => handleDeckChange(e.target.value)}
132133
>
@@ -142,15 +143,15 @@ export default function SaveToAnki({ card, onError }: SaveToAnkiProps) {
142143
<button
143144
type="button"
144145
onClick={handleCreateDeck}
145-
className="p-2 border rounded hover:bg-gray-100"
146+
className="p-2 border rounded hover:bg-gray-100 dark:hover:bg-gray-900"
146147
>
147148
<Plus className="h-5 w-5" />
148149
</button>
149150
<button
150151
type="button"
151152
onClick={fetchDecks}
152153
disabled={isRefreshing}
153-
className="p-2 border rounded hover:bg-gray-100 disabled:opacity-50"
154+
className="p-2 border rounded hover:bg-gray-100 dark:hover:bg-gray-900 disabled:opacity-50"
154155
>
155156
<RefreshCw
156157
className={`h-5 w-5 ${isRefreshing ? 'animate-spin' : ''}`}

frontend/src/components/ui/AutoCompleteInput.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,17 @@ export default function AutoCompleteInput({
4141
type="text"
4242
value={inputValue}
4343
onChange={(e) => handleChange(e.target.value)}
44-
className="border rounded p-2 w-full bg-white text-gray-800 dark:text-black-200 dark:border-gray-600"
44+
//className="border rounded p-2 w-full bg-white text-gray-800 dark:text-black-200 dark:border-gray-600"
45+
className="border rounded-lg p-2 w-full bg-gray-50 dark:bg-gray-700 border-gray-300 dark:border-gray-600
46+
focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200"
4547
placeholder={placeholder}
4648
/>
4749
{isFocused && filteredSuggestions.length > 0 && (
48-
<ul className="absolute left-0 w-full border bg-white mt-1 z-10 max-h-60 overflow-y-auto rounded shadow-lg dark:border-gray-700">
50+
<ul className="absolute left-0 w-full border mt-1 z-10 max-h-60 overflow-y-auto rounded shadow-lg bg-gray-50 dark:bg-gray-700 border-gray-300 dark:border-gray-600">
4951
{filteredSuggestions.map((s, idx) => (
5052
<li
5153
key={idx}
52-
className="p-2 hover:bg-gray-200 cursor-pointer text-gray-800"
54+
className="p-2 hover:bg-gray-200 dark:hover:bg-gray-900 cursor-pointer text-gray-800 dark:text-white"
5355
onMouseDown={() => handleSelect(s)}
5456
>
5557
{s}

frontend/src/components/ui/FormField.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ export default function FormField({
3030
children
3131
) : (
3232
<input
33-
className={`border rounded p-2 bg-white text-gray-800 dark:border-gray-600 ${error ? "border-red-500" : "border-gray-300 dark:border-gray-600"
33+
className={`border rounded-lg p-2 w-full bg-gray-50 dark:bg-gray-700 border-gray-300 dark:border-gray-600 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 ${error ? "border-red-500" : "border-gray-300 dark:border-gray-600"
3434
}`}
35+
3536
value={value}
3637
onChange={(e) => onChange?.(e.target.value)}
3738
required={required}

0 commit comments

Comments
 (0)