Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9ac0836
Uses new PR but fixes bugs in Youtube video links.
impxcts May 5, 2026
ea0f793
white screen fix caused by missing getCuratedVideosForTopics export i…
impxcts May 5, 2026
4e7de00
fixed bug with pre-algebra videos not appearing for fractions and ord…
impxcts May 5, 2026
a8591ea
Added complete selection of videos for each topic in Statistics II.
impxcts May 5, 2026
2447bc5
Changed button names to better reflect the functionality.
impxcts May 5, 2026
68e523d
Added new Galaxy theme as a fun addition.
impxcts May 6, 2026
86d939f
Added videos for Physics, Statistics II, and fixed bugs with Pre-aleg…
impxcts May 6, 2026
706db01
fixed 'white-screen' issue caused by missing getCuratedVideosForTopic…
impxcts May 6, 2026
89787cc
Renamed CURATED_SUBJECT_VIDEOS to SUBJECT_VIDEOS to fix naming consis…
impxcts May 6, 2026
e7e7599
Export getYouTubeVideoId for video ID extraction.
impxcts May 6, 2026
c7d4f72
Add animated compile butto n with shimmer and success flash effects.
impxcts May 6, 2026
2f766f6
FIx normalizeCuratedVideo to support URL entries and categoryMatches …
impxcts May 6, 2026
fc2152b
found another issue and fixed fallback video category assignment in g…
impxcts May 6, 2026
aea6230
Test for fixing frontend issue.
impxcts May 6, 2026
3734545
Fixed bug where fallback videos were assigned to wrong section.
impxcts May 6, 2026
dccb96d
fix: update subjectVideos import path from components test.
impxcts May 6, 2026
efe4045
surface autosave state to users with real-time status text. This incl…
impxcts May 6, 2026
a4e11de
Fixed the CreateCheatSheetTest.jsx and now uses the code from the pre…
impxcts May 6, 2026
b07da48
fixed a naming issue in subjectVideos.test.js
impxcts May 6, 2026
c84c57d
fixed some of the naming issues with topics.
impxcts May 6, 2026
ef15b7c
Added all the videos associated with each sub-topic within Statistics I.
impxcts May 6, 2026
2d08ce6
Added all YouTube videos for Physics II
impxcts May 6, 2026
561a34d
Added videos for physics 1 for the following topics: work, energy pow…
impxcts May 6, 2026
3553a70
Added the videos for calculus III, which includes vector formulas, pa…
impxcts May 6, 2026
d964d55
Added full video integration for calculus: parametric, polar, power &…
impxcts May 6, 2026
d806f3f
Added all videos for calculus 1, which includes basic antiderivatives…
impxcts May 6, 2026
56b133d
added a new red theme as a small touch.
impxcts May 6, 2026
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
6 changes: 3 additions & 3 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

154 changes: 154 additions & 0 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,33 @@
--shadow-inset: inset 0 1px 3px rgba(0,0,0,0.5);
}

/* new crimson/red theme */
[data-theme="crimson"] {
--bg: #0d0608;
--text: #f5e6e8;
--text-muted: #a87880;
--panel-bg: #1a0a0d;
--border: #3d1018;
--border-subtle: #2a0b10;
--primary: #c0392b;
--primary-hover: #96281b;
--card-bg: #1a0a0d;
--box-bg: #120608;
--input-bg: #0a0305;
--input-border: #5c1520;
--input-text: #f5e6e8;
--btn-primary: #c0392b;
--btn-primary-hover: #96281b;
--btn-download: #8e2020;
--btn-download-hover: #6b1818;
--btn-clear: #e74c3c;
--btn-clear-hover: #c0392b;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.5);
--shadow-md: 0 4px 12px rgba(180,0,30,0.2);
--shadow-lg: 0 8px 24px rgba(180,0,30,0.25);
--shadow-inset: inset 0 1px 3px rgba(0,0,0,0.6);
}

/* cool grey theme */
[data-theme="coolGrey"] {
--bg: #2e3440;
Expand Down Expand Up @@ -236,6 +263,33 @@
[data-theme="neon"] .class-btn.active {
box-shadow: 0 0 10px rgba(0,245,255,0.5);
}
/* Galaxy Theme */
[data-theme="galaxy"] {
--bg: #0a0614;
--text: #e8e0f5;
--text-muted: #8b7aa8;
--panel-bg: #110d1f;
--border: #2d1f4a;
--border-subtle: #1a1230;
--primary: #7c3aed;
--primary-hover: #6d28d9;
--card-bg: #110d1f;
--box-bg: #0d0a1a;
--input-bg: #080512;
--input-border: #3d2566;
--input-text: #e8e0f5;
--btn-primary: #7c3aed;
--btn-primary-hover: #6d28d9;
--btn-download: #5b21b6;
--btn-download-hover: #4c1d95;
--btn-clear: #db2777;
--btn-clear-hover: #be185d;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.5);
--shadow-md: 0 4px 12px rgba(124,58,237,0.25);
--shadow-lg: 0 8px 24px rgba(124,58,237,0.3);
--shadow-inset: inset 0 1px 3px rgba(0,0,0,0.6);
}

/* ==========================================================================
BASE STYLES
========================================================================== */
Expand Down Expand Up @@ -3019,3 +3073,103 @@ three - column responsive
justify-content: center;
}
}

/* ── Animated Compile Button ── */
.btn-compile {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
transition: all var(--transition-base);
}

.btn-compile-icon {
display: inline-block;
transition: transform 0.3s ease;
}

.btn-compile:hover:not(:disabled) .btn-compile-icon {
transform: scale(1.3) rotate(-10deg);
}

/* Pulse ring on click */
.btn-compile::after {
content: '';
position: absolute;
inset: 0;
border-radius: var(--radius-md);
background: rgba(255, 255, 255, 0.15);
opacity: 0;
transform: scale(0.8);
transition: none;
}

.btn-compile:active::after {
opacity: 1;
transform: scale(1);
transition: transform 0.15s ease, opacity 0.15s ease;
}

/* Compiling state — shimmer sweep */
.btn-compile.is-compiling {
cursor: not-allowed;
background: linear-gradient(
90deg,
var(--btn-primary) 0%,
#6ba3f9 40%,
#a5c8ff 50%,
#6ba3f9 60%,
var(--btn-primary) 100%
);
background-size: 200% 100%;
animation: compile-shimmer 1.4s linear infinite;
}

@keyframes compile-shimmer {
0% { background-position: 200% center; }
100% { background-position: -200% center; }
}

/* Compiling state — spinning icon */
.btn-compile.is-compiling .btn-compile-icon {
animation: compile-spin 0.8s linear infinite;
}

@keyframes compile-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

/* Success flash — add/remove a class via JS after compile */
.btn-compile.compile-success {
background: linear-gradient(180deg, #34d399 0%, var(--btn-download) 100%);
animation: compile-success-flash 0.6s ease forwards;
}

@keyframes compile-success-flash {
0% { transform: scale(1); }
40% { transform: scale(1.04); }
70% { transform: scale(0.97); }
100% { transform: scale(1); }
}

.save-status {
font-size: 12px;
opacity: 0.75;
margin-left: 10px;
transition: opacity 0.2s ease;
}

.save-status.saving {
color: #888;
}

.save-status.offline {
color: #d97706;
}

.save-status.saved {
color: #16a34a;
}
4 changes: 3 additions & 1 deletion frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ const THEMES = [
{ id: 'miami', label: '🌴 Miami 🐬' },
{ id: 'forest', label: '🌲 Forest' },
{ id: 'coolGrey', label: '❄️ Cool Grey'},
{ id: 'neon', label: '🩵 neon'}
{ id: 'neon', label: '🩵 neon'},
{ id: 'galaxy', label: '🌌 Galaxy' },
{id: 'crimson', label: '❤️ Red' },
];

function App() {
Expand Down
68 changes: 61 additions & 7 deletions frontend/src/components/CreateCheatSheet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ function FormulaReorderPanel({ groupedFormulas, onReorderClass, onReorderFormula
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
);

const [expandedGroups, setExpandedGroups] = React.useState({});
const [expandedGroups, setExpandedGroups] = useState({});

const handleDragEnd = (event) => {
const { active, over } = event;
Expand Down Expand Up @@ -267,6 +267,7 @@ function FormulaReorderPanel({ groupedFormulas, onReorderClass, onReorderFormula
}
};


const toggleGroup = (className) => {
setExpandedGroups(prev => ({ ...prev, [className]: !prev[className] }));
};
Expand Down Expand Up @@ -683,7 +684,7 @@ const LatexEditor = ({ content, onChange, isModified, compileError }) => {
value={content}
onChange={(e) => onChange(e.target.value)}
onScroll={handleScroll}
placeholder='Select classes and categories above, then click "Compile PDF" to see the LaTeX code here.'
placeholder='Select classes and categories above, then click "GET CHEAT SHEET" to see the LaTeX code here.'
className={`textarea-field ${isModified ? 'modified' : ''}`}
rows={15}
spellCheck="false"
Expand Down Expand Up @@ -1035,6 +1036,8 @@ const CreateCheatSheet = ({ onSave, onReset, onRestoreSnapshot, initialData, isS
const [rightPanelVisible, setRightPanelVisible] = useState(true);
const [panelLayout, setPanelLayout] = useState(() => loadPanelLayout());
const [videoSearchRequest, setVideoSearchRequest] = useState(null);
const [saveStatus, setSaveStatus] = useState('idle');
const [lastSavedAt, setLastSavedAt] = useState(null);
const [classesCollapseSignal, setClassesCollapseSignal] = useState(0);
const pendingPanelLayoutRef = useRef(panelLayout);
const hasCollapsedLeftPanelOnceRef = useRef(false);
Expand All @@ -1043,6 +1046,7 @@ const CreateCheatSheet = ({ onSave, onReset, onRestoreSnapshot, initialData, isS
const modalDialogRef = useRef(null);
const appBodyRef = useRef(null);
const centerPanelRef = useRef(null);
const compileBtnRef = useRef(null);
const snapshots = useMemo(() => [...(initialData?.compileHistory || [])].reverse(), [initialData?.compileHistory]);
const selectedClassNames = useMemo(
() => classesData.filter((cls) => selectedClasses[cls.name]).map((cls) => cls.name),
Expand Down Expand Up @@ -1094,6 +1098,31 @@ const CreateCheatSheet = ({ onSave, onReset, onRestoreSnapshot, initialData, isS
}
}, []);

const getSaveStatusText = () => {
if (saveStatus === 'saving') return 'Saving...';
if (saveStatus === 'offline') return 'Offline changes pending'
if (saveStatus === 'saved' && lastSavedAt) {
const diff = Date.now() - lastSavedAt;
const minutes = Math.floor(diff / 60000);
if (minutes < 1) return 'Saved just now';
if (minutes === 1) return 'Saved 1 min ago';
return `Saved ${minutes} min ago`;
}
return '';
};

useEffect(() => {
if(!initialData) return
if(initialData.title) setTitle(initialData.title);
if (initialData.content){
handleContentChange(initialData.content);
}
if (initialData.columns) setColumns(initialData.columns);
if(initialData.fontSize) setFontSize(initialData.fontSize);
if (initialData.spacing) setSpacing(initialData.spacing);
if (initialData.margins) setMargins(initialData.margins);
}, [initialData]);

useEffect(() => {
const hasCompiledBefore = Boolean(initialData?.compileHistory?.length || pdfBlob || content.trim());
if (hasCompiledBefore) return;
Expand Down Expand Up @@ -1194,7 +1223,8 @@ const CreateCheatSheet = ({ onSave, onReset, onRestoreSnapshot, initialData, isS
}

lastAutoSavedPdfRef.current = pdfBlob;

setSaveStatus('saving');
setLastSavedAt(Date.now());
onSave({
title,
content,
Expand All @@ -1215,11 +1245,18 @@ const CreateCheatSheet = ({ onSave, onReset, onRestoreSnapshot, initialData, isS
selectedFormulas: getSelectedFormulasList(),
compiledAt: new Date().toISOString(),
},
}, false).catch((error) => {
}, false)
.then(() => {
setSaveStatus('saved');
setLastSavedAt(Date.now());
}).catch((error) => {
console.error('Failed to autosave compiled sheet', error);
setSaveStatus('offline');
});
}, [columns, compileError, content, contentSource, fontSize, getSelectedFormulasList, margins, onSave, pdfBlob, spacing, title]);



const startResize = useCallback((panel) => (event) => {
event.preventDefault();

Expand Down Expand Up @@ -1300,7 +1337,16 @@ const CreateCheatSheet = ({ onSave, onReset, onRestoreSnapshot, initialData, isS

const workspaceSplitTemplate = `minmax(${LATEX_PANEL_MIN_WIDTH}px, ${panelLayout.latexWidth}px) 10px minmax(${MIN_PREVIEW_WIDTH}px, 1fr)`;
const previewLayoutSignature = `${appBodyGridTemplate}|${workspaceSplitTemplate}|${leftPanelVisible}|${rightPanelVisible}|${showLatex}`;

useEffect(() => {
if (!pdfBlob || isCompiling) return;
const btn = compileBtnRef.current;
if (!btn) return;
btn.classList.add('compile-success');
const timer = setTimeout(() => {
btn.classList.remove('compile-success');
}, 600);
return () => clearTimeout(timer);
}, [pdfBlob, isCompiling]);
const handleCompileClick = () => {
if (!hasCollapsedLeftPanelOnceRef.current) {
// First compile: keep controls reachable while reclaiming preview space.
Expand Down Expand Up @@ -1328,7 +1374,7 @@ const CreateCheatSheet = ({ onSave, onReset, onRestoreSnapshot, initialData, isS
};

const handleSave = async (e) => {
e.preventDefault();
e?.preventDefault?.();
await onSave({
title,
content,
Expand Down Expand Up @@ -1405,12 +1451,17 @@ const CreateCheatSheet = ({ onSave, onReset, onRestoreSnapshot, initialData, isS
{/* Footer buttons */}
<div className="left-panel-footer">
<button
ref={compileBtnRef}
type="button"
onClick={handleCompileClick}
className="btn-compile"
disabled={isCompiling}
aria-label="Compile PDF"
>
{isCompiling ? 'Compiling…' : 'Compile PDF'}
<span className="btn-compile-icon">{isCompiling ? '↻' : '⚡'}</span>
<span className="btn-compile-text">
{isCompiling ? 'Compiling…' : 'GET CHEAT SHEET'}
</span>
</button>

<div className="button-row">
Expand Down Expand Up @@ -1502,6 +1553,9 @@ const CreateCheatSheet = ({ onSave, onReset, onRestoreSnapshot, initialData, isS
</div>

<div className="workspace-topbar-group workspace-topbar-group-end">
<span className="save-status">
{getSaveStatusText()}
</span>
<button
type="button"
className="btn-toggle-panel"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/CreateCheatSheet.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -403,4 +403,4 @@ describe('CreateCheatSheet Component', () => {
expect(mockClearSelections).toHaveBeenCalled();
expect(mockReset).toHaveBeenCalled();
});
});
});
Loading
Loading