diff --git a/src/client/src/components/hooks/useSolvedProblems.js b/src/client/src/components/hooks/useSolvedProblems.js new file mode 100644 index 0000000..689d5eb --- /dev/null +++ b/src/client/src/components/hooks/useSolvedProblems.js @@ -0,0 +1,74 @@ +import { useState, useEffect } from 'react'; + +const useSolvedProblems = () => { + // Initialize state directly from localStorage using lazy initial state + const [solvedProblems, setSolvedProblems] = useState(() => { + try { + const stored = localStorage.getItem('leetcode-solved-problems'); + if (stored) { + const parsed = JSON.parse(stored); + return new Set(parsed); + } + } catch (error) { + console.error('Error loading solved problems from localStorage:', error); + } + return new Set(); + }); + + // Single useEffect for saving to localStorage + useEffect(() => { + try { + const array = Array.from(solvedProblems); + localStorage.setItem('leetcode-solved-problems', JSON.stringify(array)); + } catch (error) { + console.error('Error saving solved problems to localStorage:', error); + } + }, [solvedProblems]); + + const toggleSolved = (problemId) => { + setSolvedProblems(prev => { + const newSet = new Set(prev); + if (newSet.has(problemId)) { + newSet.delete(problemId); + } else { + newSet.add(problemId); + } + return newSet; + }); + }; + + const isSolved = (problemId) => { + return solvedProblems.has(problemId); + }; + + const markSolved = (problemId) => { + setSolvedProblems(prev => { + const newSet = new Set(prev); + newSet.add(problemId); + return newSet; + }); + }; + + const markUnsolved = (problemId) => { + setSolvedProblems(prev => { + const newSet = new Set(prev); + newSet.delete(problemId); + return newSet; + }); + }; + + const clearAllSolved = () => { + setSolvedProblems(new Set()); + }; + + return { + solvedProblems, + toggleSolved, + isSolved, + markSolved, + markUnsolved, + clearAllSolved + }; +}; + +export default useSolvedProblems; \ No newline at end of file diff --git a/src/client/src/components/layout/Body.js b/src/client/src/components/layout/Body.js index b8db1a2..acf92f0 100644 --- a/src/client/src/components/layout/Body.js +++ b/src/client/src/components/layout/Body.js @@ -13,7 +13,8 @@ const Body = ({ onPageChange, hasActiveFilters, currentView, - onViewChange + onViewChange, + solvedProblems, }) => { if (loading) { return ( @@ -41,11 +42,12 @@ const Body = ({ return (
- { @@ -15,7 +16,9 @@ const Main = () => { }); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); - const [currentView, setCurrentView] = useState('list'); // s states 'list' or 'grid' + const [currentView, setCurrentView] = useState('list'); + // Use the solved problems hook + const solvedProblems = useSolvedProblems(); const PROBLEMS_PER_PAGE = 50; @@ -48,7 +51,7 @@ const Main = () => { }; loadProblems(); - }, [currentPage, filters]); // Re-fetch when page OR filters change + }, [currentPage, filters]); // Extract unique company names from ALL problems (initial load for dropdown) const companies = useMemo(() => { @@ -91,6 +94,7 @@ const Main = () => { hasActiveFilters={hasActiveFilters} currentView={currentView} onViewChange={setCurrentView} + solvedProblems={solvedProblems} />
); diff --git a/src/client/src/components/layout/ViewToggle.js b/src/client/src/components/layout/ViewToggle.js index 723edf7..eb5faab 100644 --- a/src/client/src/components/layout/ViewToggle.js +++ b/src/client/src/components/layout/ViewToggle.js @@ -1,25 +1,49 @@ import React from "react"; import "../../styles/layout/ViewToggle.css"; -const ViewToggle = ({ currentView, onViewChange }) => { +const ViewToggle = ({ currentView, onViewChange, solvedProblems }) => { + const handleClearAll = () => { + if ( + window.confirm( + "Are you sure you want to clear all solved problems? This action cannot be undone." + ) + ) { + solvedProblems.clearAllSolved(); + } + }; + + const hasSolvedProblems = solvedProblems.solvedProblems.size > 0; + return ( -
- - +
+
+ + +
+ + {hasSolvedProblems && ( + + )}
); }; -export default ViewToggle; \ No newline at end of file +export default ViewToggle; diff --git a/src/client/src/components/problems/ProblemCard.js b/src/client/src/components/problems/ProblemCard.js index d715b53..369c5bd 100644 --- a/src/client/src/components/problems/ProblemCard.js +++ b/src/client/src/components/problems/ProblemCard.js @@ -14,9 +14,14 @@ const TIME_PERIOD_LABELS = { all: "All Time", }; -const ProblemCard = ({ problem }) => { +const ProblemCard = ({ problem, solvedProblems }) => { const difficultyText = getDifficultyText(problem.difficulty); const difficultyClass = getDifficultyClass(problem.difficulty); + const isSolved = solvedProblems.isSolved(problem.id); + + const handleCheckboxChange = () => { + solvedProblems.toggleSolved(problem.id); + }; const getTopCompanies = (companies) => { if (!companies) return []; @@ -41,7 +46,17 @@ const ProblemCard = ({ problem }) => { const topCompanies = getTopCompanies(problem.companies); return ( -
+
+ {/* Checkbox in top-right corner */} +
+ +
+

{problem.title} @@ -91,4 +106,4 @@ const ProblemCard = ({ problem }) => { ); }; -export default ProblemCard; +export default ProblemCard; \ No newline at end of file diff --git a/src/client/src/components/problems/ProblemGrid.js b/src/client/src/components/problems/ProblemGrid.js index c6a8e9d..824cbd6 100644 --- a/src/client/src/components/problems/ProblemGrid.js +++ b/src/client/src/components/problems/ProblemGrid.js @@ -4,7 +4,13 @@ import ProblemList from "./ProblemList"; import ViewToggle from "../layout/ViewToggle"; import "../../styles/components/ProblemGrid.css"; -const ProblemGrid = ({ problems, hasActiveFilters, currentView, onViewChange }) => { +const ProblemGrid = ({ + problems, + hasActiveFilters, + currentView, + onViewChange, + solvedProblems +}) => { if (problems.length === 0) { return (
@@ -18,17 +24,28 @@ const ProblemGrid = ({ problems, hasActiveFilters, currentView, onViewChange }) return (
- + {currentView === 'grid' ? (
{problems.map((problem) => ( - + ))}
) : (
- +
)}
diff --git a/src/client/src/components/problems/ProblemList.js b/src/client/src/components/problems/ProblemList.js index a08eeee..e1c467c 100644 --- a/src/client/src/components/problems/ProblemList.js +++ b/src/client/src/components/problems/ProblemList.js @@ -6,7 +6,7 @@ import { } from "../../constants/difficulty"; import "../../styles/components/ProblemList.css"; -const ProblemList = ({ problems }) => { +const ProblemList = ({ problems, solvedProblems }) => { if (problems.length === 0) { return (
@@ -18,6 +18,7 @@ const ProblemList = ({ problems }) => { return (
+ Solved ID Problem Name Difficulty @@ -25,19 +26,37 @@ const ProblemList = ({ problems }) => {
{problems.map((problem, index) => ( - + ))}
); }; -const ProblemListItem = ({ problem, index }) => { +const ProblemListItem = ({ problem, index, solvedProblems }) => { const difficultyText = getDifficultyText(problem.difficulty); const difficultyClass = getDifficultyClass(problem.difficulty); + const isSolved = solvedProblems.isSolved(problem.id); + + const handleCheckboxChange = () => { + solvedProblems.toggleSolved(problem.id); + }; return ( -
+
+
+ +
{problem.id}
{problem.title} diff --git a/src/client/src/styles/components/FilterOptions.css b/src/client/src/styles/components/FilterOptions.css index a5ff42d..0037ce7 100644 --- a/src/client/src/styles/components/FilterOptions.css +++ b/src/client/src/styles/components/FilterOptions.css @@ -1,9 +1,10 @@ +/* styles/components/FilterOptions.css */ .filter-options { display: flex; - gap: 2rem; + gap: 1rem; /* Reduced from 2rem */ align-items: flex-end; flex-wrap: wrap; - padding: 1.5rem 0; + padding: 0.75rem 0; /* Reduced from 1.5rem */ position: relative; z-index: 10; } @@ -11,101 +12,98 @@ .filter-group { display: flex; flex-direction: column; - gap: 0.75rem; - min-width: 220px; + gap: 0.4rem; /* Reduced from 0.75rem */ + min-width: 180px; /* Reduced from 220px */ position: relative; - z-index: 1000; /* Ensure high z-index for dropdown */ + z-index: 1000; } .filter-group label { - font-size: 0.85rem; + font-size: 0.75rem; /* Reduced from 0.85rem */ color: #4a5568; font-weight: 600; text-transform: uppercase; - letter-spacing: 0.5px; + letter-spacing: 0.3px; /* Reduced from 0.5px */ + margin-bottom: 0; } .filter-group select { - padding: 0.75rem 1rem; - border: 2px solid #e2e8f0; - border-radius: 10px; + padding: 0.5rem 0.75rem; /* Reduced from 0.75rem 1rem */ + border: 1px solid #e2e8f0; /* Reduced from 2px */ + border-radius: 6px; /* Reduced from 10px */ background-color: white; - font-size: 0.95rem; + font-size: 0.85rem; /* Reduced from 0.95rem */ color: #2d3748; cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transition: all 0.2s ease; /* Simplified transition */ appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%234a5568'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E"); background-repeat: no-repeat; - background-position: right 1rem center; - background-size: 1rem; - padding-right: 2.5rem; + background-position: right 0.75rem center; /* Adjusted */ + background-size: 0.875rem; /* Reduced from 1rem */ + padding-right: 2rem; /* Reduced from 2.5rem */ font-weight: 500; position: relative; - z-index: 1001; /* Higher z-index for the select element */ + z-index: 1001; + height: 36px; /* Fixed height for consistency */ } -/* Ensure dropdown options are visible */ .filter-group select option { background: white; color: #2d3748; - padding: 12px 16px; - font-size: 0.95rem; + padding: 8px 12px; /* Reduced from 12px 16px */ + font-size: 0.85rem; /* Reduced from 0.95rem */ border-bottom: 1px solid #f1f5f9; position: relative; - z-index: 10000; /* Very high z-index for options */ -} - -.filter-group select option:hover { - background-color: #f0f7ff; - color: #0066cc; -} - -.filter-group select option:checked { - background-color: #0066cc; - color: white; + z-index: 10000; } .filter-group select:focus { outline: none; border-color: #0066cc; - box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1); + box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.1); /* Reduced from 3px */ background-color: white; - z-index: 1002; /* Even higher when focused */ + z-index: 1002; } -/* Style the dropdown container */ -.filter-group select { - z-index: 1001; -} - -/* Ensure the dropdown appears above other elements */ -.filter-group { - position: relative; -} - -/* Fix for dropdown visibility */ -select { +/* Clear filters button - more compact */ +.clear-filters { + padding: 0.5rem 1rem; /* Reduced from 0.75rem 1.5rem */ + background: linear-gradient(135deg, #dc3545, #c82333); + color: white; + border: none; + border-radius: 6px; /* Reduced from 10px */ + cursor: pointer; + font-size: 0.8rem; /* Reduced from 0.9rem */ + font-weight: 600; + height: 36px; /* Match select height */ + transition: all 0.2s ease; + text-transform: uppercase; + letter-spacing: 0.3px; /* Reduced from 0.5px */ + box-shadow: 0 1px 4px rgba(220, 53, 69, 0.3); /* Reduced shadow */ position: relative; + z-index: 100; + white-space: nowrap; } -/* Additional styles to ensure dropdown visibility */ -.filter-options * { - box-sizing: border-box; +.clear-filters:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 2px 6px rgba(220, 53, 69, 0.4); } -/* Company dropdown specific fixes */ -#company { - min-height: 48px; - max-height: 200px; /* Limit height if needed */ +.clear-filters:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + box-shadow: none; } -/* Ensure the nav has proper z-index */ +/* Ensure the nav has proper z-index but minimal padding */ .nav { background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); border-bottom: 1px solid #e2e8f0; padding: 0; - box-shadow: 0 2px 20px rgba(0, 0, 0, 0.06); + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.04); /* Reduced shadow */ position: relative; z-index: 100; } @@ -118,31 +116,17 @@ select { z-index: 101; } -/* Clear filters button */ -.clear-filters { - padding: 0.75rem 1.5rem; - background: linear-gradient(135deg, #dc3545, #c82333); - color: white; - border: none; - border-radius: 10px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 600; - height: fit-content; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - text-transform: uppercase; - letter-spacing: 0.5px; - box-shadow: 0 2px 8px rgba(220, 53, 69, 0.3); - position: relative; - z-index: 100; +/* Compact layout for filter groups */ +.filter-group select { + min-height: 36px; /* Reduced from 48px */ } -/* Responsive adjustments */ +/* Responsive adjustments - more compact */ @media (max-width: 768px) { .filter-options { flex-direction: column; - gap: 1.25rem; - padding: 1.25rem 0; + gap: 0.75rem; /* Reduced from 1.25rem */ + padding: 0.5rem 0; /* Reduced from 1.25rem */ z-index: 1000; } @@ -150,6 +134,7 @@ select { width: 100%; min-width: unset; z-index: 1001; + gap: 0.3rem; /* Reduced gap */ } .filter-group select { @@ -159,22 +144,21 @@ select { .clear-filters { width: 100%; - margin-top: 0.5rem; + margin-top: 0.25rem; /* Reduced from 0.5rem */ z-index: 100; } + + .filter-group label { + font-size: 0.7rem; /* Even smaller on mobile */ + } } /* Emergency fix - if dropdown still doesn't show */ .filter-group select { - transform: translateZ(0); /* Force hardware acceleration */ -} - -/* Alternative: Increase the size of the dropdown */ -.filter-group select { - min-height: 48px; + transform: translateZ(0); } -/* Make sure the dropdown has enough space */ +/* Make sure the dropdown has enough space but compact */ .filter-group { margin-bottom: 0; } \ No newline at end of file diff --git a/src/client/src/styles/components/ProblemCard.css b/src/client/src/styles/components/ProblemCard.css index 1b18551..9615365 100644 --- a/src/client/src/styles/components/ProblemCard.css +++ b/src/client/src/styles/components/ProblemCard.css @@ -1,14 +1,32 @@ +/* styles/components/ProblemCard.css */ .problem-card { background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); border-radius: 12px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); - padding: 1.25rem; /* Reduced from 1.75rem */ + padding: 1.25rem; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); border: 1px solid rgba(255, 255, 255, 0.8); position: relative; overflow: hidden; } +/* Solved state for grid cards */ +.problem-card.solved { + background: linear-gradient(135deg, #f0f9f0 0%, #e8f5e8 100%); + border-left: 4px solid #10b981; +} + +.problem-card.solved::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, #10b981, #34d399); + transform: scaleX(1); +} + .problem-card::before { content: ''; position: absolute; @@ -22,28 +40,79 @@ } .problem-card:hover { - transform: translateY(-2px); /* Reduced from -4px */ - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); /* Reduced shadow */ + transform: translateY(-2px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); } .problem-card:hover::before { transform: scaleX(1); } +/* Checkbox positioning in grid cards */ +.problem-card-checkbox { + position: absolute; + top: 12px; + right: 12px; + z-index: 10; +} + +/* Custom Checkbox Styling for Grid */ +.problem-card .solved-checkbox { + width: 20px; + height: 20px; + cursor: pointer; + border: 2px solid #d1d5db; + border-radius: 4px; + background: white; + position: relative; + transition: all 0.2s ease; + appearance: none; + -webkit-appearance: none; + margin: 0; +} + +.problem-card .solved-checkbox:checked { + background: #10b981; + border-color: #10b981; +} + +.problem-card .solved-checkbox:checked::before { + content: '✓'; + position: absolute; + color: white; + font-size: 14px; + font-weight: bold; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.problem-card .solved-checkbox:hover { + border-color: #10b981; + box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1); +} + +.problem-card .solved-checkbox:focus { + outline: none; + border-color: #10b981; + box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.2); +} + .problem-header { display: flex; justify-content: space-between; align-items: flex-start; - margin-bottom: 1rem; /* Reduced from 1.25rem */ + margin-bottom: 1rem; gap: 1rem; + padding-right: 30px; /* Make space for checkbox */ } .problem-header h3 { margin: 0; - font-size: 1.1rem; /* Slightly smaller */ + font-size: 1.1rem; flex: 1; font-weight: 600; - line-height: 1.3; /* Tighter line height */ + line-height: 1.3; } .problem-header a { @@ -79,9 +148,9 @@ } .difficulty { - padding: 0.3rem 0.8rem; /* Slightly smaller */ + padding: 0.3rem 0.8rem; border-radius: 20px; - font-size: 0.75rem; /* Smaller font */ + font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; @@ -110,9 +179,9 @@ .problem-stats { display: flex; - gap: 1rem; /* Reduced from 1.5rem */ - margin-bottom: 1rem; /* Reduced from 1.25rem */ - font-size: 0.85rem; /* Slightly smaller */ + gap: 1rem; + margin-bottom: 1rem; + font-size: 0.85rem; } .problem-stats span { @@ -148,16 +217,16 @@ .problem-companies { border-top: 1px solid #e2e8f0; - padding-top: 1rem; /* Reduced from 1.25rem */ - margin-top: 0.75rem; /* Reduced from 1rem */ + padding-top: 1rem; + margin-top: 0.75rem; } .problem-companies small { display: block; - margin-bottom: 0.5rem; /* Reduced from 0.75rem */ + margin-bottom: 0.5rem; color: #718096; font-weight: 600; - font-size: 0.75rem; /* Smaller */ + font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px; } @@ -165,17 +234,17 @@ .company-tags { display: flex; flex-wrap: wrap; - gap: 0.5rem; /* Reduced from 0.6rem */ + gap: 0.5rem; } .company-tag { background: linear-gradient(135deg, #ffffff, #f7fafc); - padding: 0.4rem 0.8rem; /* Reduced padding */ + padding: 0.4rem 0.8rem; border-radius: 10px; - font-size: 0.75rem; /* Smaller */ + font-size: 0.75rem; display: flex; align-items: center; - gap: 0.3rem; /* Reduced gap */ + gap: 0.3rem; border: 1px solid #e2e8f0; transition: all 0.2s ease; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); @@ -185,7 +254,7 @@ .company-tag:hover { transform: translateY(-1px); - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); /* Reduced shadow */ + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); border-color: #ffa116; background: linear-gradient(135deg, #fffaf0, #fffbeb); } @@ -196,7 +265,7 @@ } .recent-indicator { - font-size: 0.65rem; /* Smaller */ + font-size: 0.65rem; animation: pulse 2s infinite; } @@ -214,18 +283,19 @@ /* Responsive Design */ @media (max-width: 768px) { .problem-card { - padding: 1rem; /* Reduced from 1.25rem */ - margin: 0.25rem; /* Reduced from 0.5rem */ + padding: 1rem; + margin: 0.25rem; } .problem-header { flex-direction: column; align-items: flex-start; - gap: 0.5rem; /* Reduced from 0.75rem */ + gap: 0.5rem; + padding-right: 0; } .problem-header h3 { - font-size: 1rem; /* Smaller on mobile */ + font-size: 1rem; padding-right: 0; } @@ -235,7 +305,7 @@ .problem-stats { flex-direction: column; - gap: 0.25rem; /* Reduced from 0.5rem */ + gap: 0.25rem; } .problem-stats span::before { @@ -243,12 +313,26 @@ } .company-tags { - gap: 0.3rem; /* Reduced from 0.4rem */ + gap: 0.3rem; } .company-tag { - padding: 0.3rem 0.6rem; /* Smaller on mobile */ - font-size: 0.7rem; /* Smaller on mobile */ + padding: 0.3rem 0.6rem; + font-size: 0.7rem; + } + + .problem-card-checkbox { + top: 8px; + right: 8px; + } + + .problem-card .solved-checkbox { + width: 18px; + height: 18px; + } + + .problem-card .solved-checkbox:checked::before { + font-size: 12px; } } @@ -256,7 +340,7 @@ @keyframes cardEntrance { from { opacity: 0; - transform: translateY(10px); /* Reduced from 20px */ + transform: translateY(10px); } to { opacity: 1; diff --git a/src/client/src/styles/components/ProblemGrid.css b/src/client/src/styles/components/ProblemGrid.css index 7eeadfb..4680458 100644 --- a/src/client/src/styles/components/ProblemGrid.css +++ b/src/client/src/styles/components/ProblemGrid.css @@ -1,26 +1,28 @@ .problem-container { - margin-bottom: 1rem; + margin-bottom: 0.5rem; padding-bottom: 0; } .problem-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 1rem; - padding: 0.5rem 0; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 0.75rem; + padding: 0.25rem 0; } .no-problems { text-align: center; - padding: 1.5rem; + padding: 1rem; background: white; - border-radius: 8px; + border-radius: 6px; color: #666; - margin: 1rem 0; + margin: 0.75rem 0; + font-size: 0.9rem; } -/* Ensure list view takes only needed space */ +/* Remove space between toggle and list */ .problem-list-container { + margin-top: 0; /* Ensure no top margin */ margin-bottom: 0; padding-bottom: 0; } @@ -29,6 +31,6 @@ @media (max-width: 640px) { .problem-grid { grid-template-columns: 1fr; - gap: 0.75rem; + gap: 0.5rem; } } \ No newline at end of file diff --git a/src/client/src/styles/components/ProblemList.css b/src/client/src/styles/components/ProblemList.css index 59e4c2c..aef5aa4 100644 --- a/src/client/src/styles/components/ProblemList.css +++ b/src/client/src/styles/components/ProblemList.css @@ -1,26 +1,27 @@ /* styles/components/ProblemList.css */ .problem-list { background: white; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-radius: 6px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); overflow: hidden; - margin: 0.5rem 0; + margin: 0.25rem 0; } .list-header { display: grid; - grid-template-columns: 80px 1fr 120px 100px; - gap: 1rem; - padding: 0.75rem 1.5rem; + grid-template-columns: 50px 60px 1fr 90px 80px; /* Adjusted column widths */ + gap: 0.75rem; + padding: 0.5rem 1rem; background: #f8fafc; - border-bottom: 2px solid #e2e8f0; + border-bottom: 1px solid #e2e8f0; font-weight: 600; color: #4a5568; - font-size: 0.9rem; + font-size: 0.8rem; align-items: center; } -/* Left align all headers explicitly */ +/* Header alignment */ +.header-solved, .header-id, .header-title, .header-difficulty, @@ -29,24 +30,35 @@ justify-self: start; } +.header-solved { + text-align: center; + justify-self: center; +} + .list-content { min-height: auto; } .problem-list-item { display: grid; - grid-template-columns: 80px 1fr 120px 100px; - gap: 1rem; - padding: 0.75rem 1.5rem; + grid-template-columns: 50px 60px 1fr 90px 80px; /* Adjusted column widths */ + gap: 0.75rem; + padding: 0.5rem 1rem; border-bottom: 1px solid #f1f5f9; - transition: background-color 0.2s ease; + transition: all 0.2s ease; align-items: center; - min-height: 60px; + min-height: 44px; +} + +/* Solved row styling */ +.problem-list-item.solved { + background-color: #f0f9f0 !important; + border-left: 2px solid #10b981; } /* Alternate background colors for rows */ .problem-list-item:nth-child(even) { - background-color: #fafbfc; + background-color: #fcfcfc; } .problem-list-item:nth-child(odd) { @@ -54,20 +66,69 @@ } .problem-list-item:hover { - background: #f0f7ff !important; /* Override alternate colors on hover */ - border-left: 3px solid #0066cc; - margin-left: -3px; + background: #f0f7ff !important; + border-left: 2px solid #0066cc; + margin-left: -2px; } .problem-list-item:last-child { border-bottom: none; } +/* Custom Checkbox Styling */ +.problem-solved { + display: flex; + justify-content: center; + align-items: center; + justify-self: center; +} + +.solved-checkbox { + width: 20px; + height: 20px; + cursor: pointer; + border: 2px solid #d1d5db; + border-radius: 4px; + background: white; + position: relative; + transition: all 0.2s ease; + appearance: none; + -webkit-appearance: none; + margin: 0; +} + +.solved-checkbox:checked { + background: #10b981; + border-color: #10b981; +} + +.solved-checkbox:checked::before { + content: '✓'; + position: absolute; + color: white; + font-size: 14px; + font-weight: bold; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.solved-checkbox:hover { + border-color: #10b981; + box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1); +} + +.solved-checkbox:focus { + outline: none; + border-color: #10b981; + box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.2); +} + /* Left align all content cells */ .problem-id { color: #666; font-weight: 500; - font-size: 0.9rem; + font-size: 0.8rem; text-align: left; justify-self: start; } @@ -84,6 +145,8 @@ transition: color 0.2s ease; display: block; width: 100%; + font-size: 0.85rem; + line-height: 1.3; } .problem-title a:hover { @@ -91,21 +154,23 @@ } .problem-difficulty { - padding: 0.25rem 0.75rem; - border-radius: 12px; - font-size: 0.8rem; - font-weight: 500; + padding: 0.2rem 0.5rem; + border-radius: 8px; + font-size: 0.7rem; + font-weight: 600; text-align: center; text-transform: uppercase; - justify-self: start; /* Changed to left align */ + justify-self: start; width: fit-content; + min-width: 60px; } .problem-acceptance { color: #666; - font-size: 0.9rem; - text-align: left; /* Changed to left align */ + font-size: 0.8rem; + text-align: left; justify-self: start; + font-weight: 500; } .problem-difficulty.easy { @@ -131,16 +196,30 @@ display: none; } -/* Responsive Design */ +/* Compact hover effects */ +.problem-list-item { + transition: all 0.2s ease; +} + +.problem-list-item:hover { + transform: translateX(2px); + box-shadow: 1px 0 4px rgba(0, 102, 204, 0.1); +} + +/* Responsive Design - More compact */ @media (max-width: 768px) { .list-header { - grid-template-columns: 60px 1fr 100px; - padding: 0.75rem 1rem; + grid-template-columns: 40px 50px 1fr 70px; /* Hide acceptance on mobile */ + padding: 0.4rem 0.75rem; + font-size: 0.75rem; + gap: 0.5rem; } .problem-list-item { - grid-template-columns: 60px 1fr 100px; - padding: 0.75rem 1rem; + grid-template-columns: 40px 50px 1fr 70px; + padding: 0.4rem 0.75rem; + gap: 0.5rem; + min-height: 40px; } .header-acceptance, @@ -149,17 +228,44 @@ } .problem-difficulty { + font-size: 0.65rem; + padding: 0.15rem 0.4rem; + min-width: 55px; + } + + .problem-title a { + font-size: 0.8rem; + } + + .problem-id { font-size: 0.75rem; - padding: 0.2rem 0.5rem; + } + + .solved-checkbox { + width: 18px; + height: 18px; + } + + .solved-checkbox:checked::before { + font-size: 12px; } } -/* Enhanced hover effects */ -.problem-list-item { - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} - -.problem-list-item:hover { - transform: translateX(4px); - box-shadow: 2px 0 8px rgba(0, 102, 204, 0.1); +/* Ultra-compact for very small screens */ +@media (max-width: 480px) { + .list-header { + padding: 0.3rem 0.5rem; + gap: 0.4rem; + } + + .problem-list-item { + padding: 0.3rem 0.5rem; + gap: 0.4rem; + min-height: 36px; + } + + .problem-difficulty { + min-width: 50px; + font-size: 0.6rem; + } } \ No newline at end of file diff --git a/src/client/src/styles/layout/Body.css b/src/client/src/styles/layout/Body.css index a1c8ffa..a184219 100644 --- a/src/client/src/styles/layout/Body.css +++ b/src/client/src/styles/layout/Body.css @@ -1,7 +1,7 @@ /* styles/layout/Body.css */ .body { flex: 1; - padding: 0.5rem 0; + padding: 0.125rem 0; /* Further reduced */ background-color: #f5f5f5; min-height: auto; } @@ -9,9 +9,8 @@ .body-content { max-width: 1200px; margin: 0 auto; - padding: 0 1rem; + padding: 0 0.75rem; /* Reduced from 1rem */ min-height: auto; - /* Ensure no extra padding at bottom */ padding-bottom: 0; } @@ -19,34 +18,36 @@ display: flex; justify-content: center; align-items: center; - min-height: 200px; + min-height: 150px; /* Reduced */ } .loading-spinner { color: #666; - font-size: 1.2rem; + font-size: 1rem; /* Smaller */ } .error { display: flex; justify-content: center; align-items: center; - min-height: 200px; + min-height: 150px; /* Reduced */ } .error-message { color: #dc3545; text-align: center; + font-size: 0.9rem; /* Smaller */ } .error-message button { - margin-top: 1rem; - padding: 0.5rem 1rem; + margin-top: 0.75rem; + padding: 0.4rem 0.8rem; background-color: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer; + font-size: 0.8rem; } .error-message button:hover { diff --git a/src/client/src/styles/layout/Header.css b/src/client/src/styles/layout/Header.css index 7796726..fec5051 100644 --- a/src/client/src/styles/layout/Header.css +++ b/src/client/src/styles/layout/Header.css @@ -1,9 +1,10 @@ +/* styles/layout/Header.css */ .header { width: 100%; - background: linear-gradient(135deg, #1a1a1a 0%, #2d1b69 50%, #1a1a1a 100%); + background: linear-gradient(135deg, #1a1a1a 0%, #2d1b69 100%); color: white; - padding: 1.25rem 0; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + padding: 0.75rem 0; /* Reduced from 1.25rem */ + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); /* Reduced shadow */ position: relative; overflow: hidden; border-bottom: 1px solid rgba(255, 255, 255, 0.1); @@ -17,9 +18,8 @@ right: 0; bottom: 0; background: - radial-gradient(circle at 20% 80%, rgba(255, 161, 22, 0.1) 0%, transparent 50%), - radial-gradient(circle at 80% 20%, rgba(0, 102, 204, 0.1) 0%, transparent 50%), - radial-gradient(circle at 40% 40%, rgba(67, 160, 71, 0.05) 0%, transparent 50%); + radial-gradient(circle at 20% 80%, rgba(255, 161, 22, 0.08) 0%, transparent 50%), + radial-gradient(circle at 80% 20%, rgba(0, 102, 204, 0.08) 0%, transparent 50%); pointer-events: none; } @@ -32,16 +32,16 @@ height: 1px; background: linear-gradient(90deg, transparent 0%, - rgba(255, 161, 22, 0.6) 25%, - rgba(0, 102, 204, 0.6) 50%, - rgba(255, 161, 22, 0.6) 75%, + rgba(255, 161, 22, 0.4) 25%, + rgba(0, 102, 204, 0.4) 50%, + rgba(255, 161, 22, 0.4) 75%, transparent 100%); } .header-content { max-width: 1200px; margin: 0 auto; - padding: 0 2rem; + padding: 0 1.5rem; /* Reduced from 2rem */ display: flex; align-items: center; justify-content: center; @@ -53,10 +53,10 @@ text-decoration: none; color: inherit; cursor: pointer; - transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); + transition: all 0.3s ease; /* Simplified transition */ position: relative; - padding: 0.5rem 1rem; - border-radius: 12px; + padding: 0.375rem 0.75rem; /* Reduced from 0.5rem 1rem */ + border-radius: 8px; /* Reduced from 12px */ } .header-link::before { @@ -66,15 +66,14 @@ left: 0; right: 0; bottom: 0; - background: linear-gradient(135deg, rgba(255, 161, 22, 0.1), rgba(0, 102, 204, 0.1)); - border-radius: 12px; + background: linear-gradient(135deg, rgba(255, 161, 22, 0.08), rgba(0, 102, 204, 0.08)); + border-radius: 8px; opacity: 0; - transition: opacity 0.3s ease; + transition: opacity 0.2s ease; } .header-link:hover { - transform: translateY(-2px); - text-shadow: 0 4px 12px rgba(255, 255, 255, 0.3); + transform: translateY(-1px); /* Reduced from -2px */ } .header-link:hover::before { @@ -87,7 +86,7 @@ .header h1 { margin: 0; - font-size: 2rem; + font-size: 1.5rem; /* Reduced from 2rem */ font-weight: 700; background: linear-gradient(135deg, #ffffff 0%, #ffa116 50%, #0099ff 100%); -webkit-background-clip: text; @@ -95,77 +94,68 @@ background-clip: text; text-align: center; line-height: 1.2; - letter-spacing: -0.5px; + letter-spacing: -0.25px; /* Reduced from -0.5px */ position: relative; - padding: 0.5rem 0; + padding: 0.25rem 0; /* Reduced from 0.5rem */ } .header h1::after { content: '🔍'; position: absolute; top: 50%; - right: -2rem; + right: -1.5rem; /* Reduced from -2rem */ transform: translateY(-50%); - font-size: 1.5rem; + font-size: 1.1rem; /* Reduced from 1.5rem */ opacity: 0.8; animation: float 3s ease-in-out infinite; } -/* Code-inspired decoration */ +/* Code-inspired decoration - made smaller */ .header-content::before { content: '{ }'; position: absolute; - left: 2rem; + left: 1.5rem; /* Reduced from 2rem */ top: 50%; transform: translateY(-50%); - font-size: 1.2rem; - color: rgba(255, 161, 22, 0.6); + font-size: 1rem; /* Reduced from 1.2rem */ + color: rgba(255, 161, 22, 0.5); /* More subtle */ font-family: 'Courier New', monospace; font-weight: bold; - opacity: 0.7; + opacity: 0.6; } .header-content::after { content: ''; position: absolute; - right: 2rem; + right: 1.5rem; /* Reduced from 2rem */ top: 50%; transform: translateY(-50%); - font-size: 1.2rem; - color: rgba(0, 102, 204, 0.6); + font-size: 1rem; /* Reduced from 1.2rem */ + color: rgba(0, 102, 204, 0.5); /* More subtle */ font-family: 'Courier New', monospace; font-weight: bold; - opacity: 0.7; + opacity: 0.6; } -/* Floating animation */ +/* Floating animation - more subtle */ @keyframes float { 0%, 100% { transform: translateY(-50%) rotate(0deg); } 50% { - transform: translateY(-50%) rotate(10deg); - } -} - -/* Glow effect on hover */ -@keyframes textGlow { - 0%, 100% { - text-shadow: 0 0 20px rgba(255, 255, 255, 0.3); - } - 50% { - text-shadow: 0 0 30px rgba(255, 161, 22, 0.5), 0 0 40px rgba(0, 102, 204, 0.3); + transform: translateY(-50%) rotate(5deg); /* Reduced rotation */ } } +/* Simplified glow effect */ .header-link:hover h1 { - animation: textGlow 2s ease-in-out infinite; + text-shadow: 0 2px 8px rgba(255, 255, 255, 0.2); } -/* Responsive Design */ +/* Responsive Design - More compact */ @media (max-width: 768px) { .header { - padding: 1rem 0; + padding: 0.5rem 0; /* Reduced from 1rem */ } .header-content { @@ -173,32 +163,36 @@ } .header h1 { - font-size: 1.5rem; + font-size: 1.25rem; /* Reduced from 1.5rem */ } .header h1::after { - right: -1.5rem; - font-size: 1.2rem; + right: -1.25rem; /* Reduced from -1.5rem */ + font-size: 1rem; /* Reduced from 1.2rem */ } .header-content::before, .header-content::after { - display: none; /* Hide code symbols on mobile */ + display: none; } } @media (max-width: 480px) { .header h1 { - font-size: 1.25rem; - letter-spacing: -0.25px; + font-size: 1.1rem; /* Reduced from 1.25rem */ + letter-spacing: -0.1px; } .header h1::after { - display: none; /* Hide emoji on very small screens */ + display: none; } .header-link { - padding: 0.25rem 0.5rem; + padding: 0.2rem 0.4rem; /* More compact */ + } + + .header-content { + padding: 0 0.75rem; } } @@ -218,12 +212,13 @@ .header-link:hover h1 { animation: none; + text-shadow: none; } } /* Dark theme optimization */ @media (prefers-color-scheme: dark) { .header { - background: linear-gradient(135deg, #0a0a0a 0%, #1a0f4d 50%, #0a0a0a 100%); + background: linear-gradient(135deg, #0a0a0a 0%, #1a0f4d 100%); } } \ No newline at end of file diff --git a/src/client/src/styles/layout/Nav.css b/src/client/src/styles/layout/Nav.css index 8905e40..9817eb4 100644 --- a/src/client/src/styles/layout/Nav.css +++ b/src/client/src/styles/layout/Nav.css @@ -1,11 +1,23 @@ +/* styles/layout/Nav.css */ .nav { - background-color: white; - border-bottom: 1px solid #e0e0e0; - padding: 1rem 0; + background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); + border-bottom: 1px solid #e2e8f0; + padding: 0; + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.04); + position: relative; + z-index: 100; + min-height: auto; } .nav-content { max-width: 1200px; margin: 0 auto; - padding: 0 1rem; + padding: 0 1.5rem; + position: relative; + z-index: 101; +} + +/* Remove any extra padding that might be adding space */ +.nav * { + box-sizing: border-box; } \ No newline at end of file diff --git a/src/client/src/styles/layout/ViewToggle.css b/src/client/src/styles/layout/ViewToggle.css index 262796a..2fb0006 100644 --- a/src/client/src/styles/layout/ViewToggle.css +++ b/src/client/src/styles/layout/ViewToggle.css @@ -1,19 +1,25 @@ +.view-toggle-container { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; + gap: 1rem; +} + .view-toggle { display: flex; gap: 0.5rem; - margin-bottom: 1.5rem; - justify-content: flex-end; } .toggle-btn { - padding: 0.5rem 1rem; + padding: 0.4rem 0.8rem; border: 1px solid #e2e8f0; background: white; color: #4a5568; border-radius: 6px; cursor: pointer; transition: all 0.2s ease; - font-size: 0.9rem; + font-size: 0.85rem; } .toggle-btn:hover { @@ -25,4 +31,53 @@ background: #0066cc; color: white; border-color: #0066cc; +} + +.clear-all-btn { + padding: 0.4rem 0.8rem; + border: 1px solid #e2e8f0; + background: #dc3545; + color: white; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s ease; + font-size: 0.85rem; + font-weight: 500; +} + +.clear-all-btn:hover { + background: #c82333; + border-color: #bd2130; + transform: translateY(-1px); +} + +.clear-all-btn:active { + transform: translateY(0); +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .view-toggle-container { + flex-direction: column; + align-items: stretch; + gap: 0.75rem; + } + + .view-toggle { + justify-content: center; + } + + .clear-all-btn { + order: -1; /* Move clear button to top on mobile */ + } + + .toggle-btn { + padding: 0.35rem 0.7rem; + font-size: 0.8rem; + } + + .clear-all-btn { + padding: 0.35rem 0.7rem; + font-size: 0.8rem; + } } \ No newline at end of file