Skip to content

Commit 3e50132

Browse files
committed
feat: command palette, digital rain overlay
1 parent d101ca0 commit 3e50132

File tree

6 files changed

+62
-40
lines changed

6 files changed

+62
-40
lines changed

src/App.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import { ToastProvider } from './context/ToastContext';
66
import ScrollToTop from './components/ScrollToTop';
77
import ContactModal from './components/ContactModal';
88
import GenericModal from './components/GenericModal';
9+
import DigitalRain from './components/DigitalRain';
910
import { AnimationProvider } from './context/AnimationContext'; // Import AnimationProvider
1011

1112
function App() {
1213
const [isModalOpen, setIsModalOpen] = useState(false);
1314
const [isSearchVisible, setIsSearchVisible] = useState(false);
1415
const [isGenericModalOpen, setIsGenericModalOpen] = useState(false);
1516
const [genericModalContent, setGenericModalContent] = useState({ title: '', content: null });
16-
17+
const [isRainActive, setIsRainActive] = useState(false); // State for Digital Rain
1718
const toggleModal = () => {
1819
setIsModalOpen(!isModalOpen);
1920
};
@@ -31,19 +32,22 @@ function App() {
3132
setIsSearchVisible(!isSearchVisible);
3233
window.scrollTo({ top: 0, behavior: 'smooth' }); // Scroll to top
3334
};
35+
const toggleDigitalRain = () => {
36+
setIsRainActive(prev => !prev);
37+
};
3438

3539
return (
3640
<AnimationProvider>
37-
{' '}
38-
{/* Wrap the entire app with AnimationProvider */}
3941
<Router>
42+
<DigitalRain isActive={isRainActive} />
4043
<ScrollToTop />
4144
<ToastProvider>
4245
<Layout
4346
toggleModal={toggleModal}
4447
isSearchVisible={isSearchVisible}
4548
toggleSearch={toggleSearch}
4649
openGenericModal={openGenericModal}
50+
toggleDigitalRain={toggleDigitalRain}
4751
>
4852
<AnimatedRoutes />
4953
</Layout>
@@ -60,5 +64,4 @@ function App() {
6064
</AnimationProvider>
6165
);
6266
}
63-
6467
export default App;

src/components/CommandPalette.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import LiveClock from './LiveClock'; // Import LiveClock
1010
import DigitalRain from './DigitalRain'; // Import DigitalRain
1111
import { filterItems } from '../utils/search'; // Import the search utility
1212

13-
const CommandPalette = ({ isOpen, setIsOpen, openGenericModal }) => {
13+
const CommandPalette = ({ isOpen, setIsOpen, openGenericModal, toggleDigitalRain }) => {
1414
const [searchTerm, setSearchTerm] = useState('');
1515
const [selectedIndex, setSelectedIndex] = useState(0);
1616
const { items, isLoading } = useSearchableData();
@@ -127,10 +127,9 @@ const CommandPalette = ({ isOpen, setIsOpen, openGenericModal }) => {
127127
openGenericModal('Current Time', <LiveClock />);
128128
break;
129129
}
130-
case 'digitalRain': {
131-
openGenericModal('Digital Rain', <DigitalRain />);
130+
case 'digitalRain':
131+
toggleDigitalRain();
132132
break;
133-
}
134133
default:
135134
break;
136135
}
@@ -247,4 +246,4 @@ const CommandPalette = ({ isOpen, setIsOpen, openGenericModal }) => {
247246
);
248247
};
249248

250-
export default CommandPalette;
249+
export default CommandPalette;

src/components/DigitalRain.js

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,87 @@
1-
import React, { useRef, useEffect } from 'react';
1+
import React, { useRef, useEffect, memo } from 'react';
22

3-
const DigitalRain = () => {
3+
const DigitalRain = memo(({ isActive }) => {
44
const canvasRef = useRef(null);
55

66
useEffect(() => {
7+
if (!isActive) return;
8+
79
const canvas = canvasRef.current;
810
const ctx = canvas.getContext('2d');
11+
let animationFrameId;
12+
13+
const resizeCanvas = () => {
14+
canvas.width = window.innerWidth;
15+
canvas.height = window.innerHeight;
16+
};
917

10-
// Set canvas to full width and height of its container
11-
canvas.width = canvas.offsetWidth;
12-
canvas.height = canvas.offsetHeight;
18+
resizeCanvas();
19+
window.addEventListener('resize', resizeCanvas);
1320

14-
const katakana = 'アァカサタナハマヤャラワガザダバパイィキシチニヒミリヰギジヂビピウゥクスツヌフムユュルグズブヅプエェケセテネヘメレヱゲゼデベペオォコソトノホモヨョロヲゴゾドボポヴッン';
21+
const katakana = 'アァカサタナハマヤャラワガザダバパイィキシチニヒミリヰギジヂビピウゥクスツヌフムユュルグズブヅプエェケセテネヘメレヱゲゼデベペオォコソトノホモヨョロヲゴゾドボпоヴッン';
1522
const latin = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
1623
const nums = '0123456789';
1724
const alphabet = katakana + latin + nums;
1825

1926
const fontSize = 16;
2027
const columns = canvas.width / fontSize;
21-
const rainDrops = Array.from({ length: columns }).map(() => 1);
22-
23-
let animationFrameId;
28+
const rainDrops = Array.from({ length: Math.ceil(columns) }).map(() => 1);
2429
let frameCount = 0;
25-
const slowdownFactor = 3; // Higher number = slower rain
30+
const slowdownFactor = 5;
2631

2732
const draw = () => {
28-
ctx.fillStyle = 'rgba(21, 21, 21, 0.05)'; // Semi-transparent black for fading effect
33+
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
2934
ctx.fillRect(0, 0, canvas.width, canvas.height);
30-
31-
ctx.fillStyle = '#0F0'; // Green text
35+
ctx.fillStyle = 'rgba(0, 200, 0, 1.0)';
3236
ctx.font = `${fontSize}px monospace`;
3337

3438
frameCount++;
39+
if (frameCount % slowdownFactor === 0) {
40+
for (let i = 0; i < rainDrops.length; i++) {
41+
const text = alphabet.charAt(Math.floor(Math.random() * alphabet.length));
42+
ctx.fillText(text, i * fontSize, rainDrops[i] * fontSize);
3543

36-
for (let i = 0; i < rainDrops.length; i++) {
37-
const text = alphabet.charAt(Math.floor(Math.random() * alphabet.length));
38-
ctx.fillText(text, i * fontSize, rainDrops[i] * fontSize);
39-
40-
if (frameCount % slowdownFactor === 0) {
4144
if (rainDrops[i] * fontSize > canvas.height && Math.random() > 0.975) {
4245
rainDrops[i] = 0;
4346
}
4447
rainDrops[i]++;
4548
}
4649
}
50+
51+
// Add notification text in the bottom left
52+
const notificationText = "Alt+K to bring command palette";
53+
const fezcode = "fezcode.com";
54+
ctx.font = "16px monospace";
55+
ctx.fillStyle = "rgba(0, 255, 0, 0.05)";
56+
ctx.textAlign = "left";
57+
ctx.fillText(notificationText, 20, canvas.height - 20);
58+
ctx.textAlign = "right";
59+
ctx.fillText(fezcode, canvas.width - 20, canvas.height - 20);
60+
4761
animationFrameId = requestAnimationFrame(draw);
4862
};
63+
4964
draw();
65+
5066
return () => {
67+
window.removeEventListener('resize', resizeCanvas);
5168
cancelAnimationFrame(animationFrameId);
69+
ctx.clearRect(0, 0, canvas.width, canvas.height);
5270
};
53-
}, []);
71+
}, [isActive]);
72+
73+
if (!isActive) return null;
5474

55-
// Style for the canvas to fill the modal body
5675
const canvasStyle = {
57-
display: 'block',
58-
width: '100%',
59-
height: '60vh', // Make it tall inside the modal
76+
position: 'fixed',
77+
top: 0,
78+
left: 0,
79+
width: '100vw',
80+
height: '100vh',
81+
zIndex: 40,
6082
};
6183

62-
return (
63-
<canvas ref={canvasRef} style={canvasStyle}></canvas>
64-
);
65-
};
84+
return <canvas ref={canvasRef} style={canvasStyle}></canvas>;
85+
});
6686

6787
export default DigitalRain;

src/components/Layout.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import CommandPalette from './CommandPalette';
1010

1111
import { DndProvider } from '../context/DndContext';
1212

13-
const Layout = ({ children, toggleModal, isSearchVisible, toggleSearch, openGenericModal }) => {
13+
const Layout = ({ children, toggleModal, isSearchVisible, toggleSearch, openGenericModal, toggleDigitalRain }) => {
1414
const [isSidebarOpen, setIsSidebarOpen] = useState(window.innerWidth > 768);
1515
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
1616
const location = useLocation();
@@ -56,7 +56,7 @@ const Layout = ({ children, toggleModal, isSearchVisible, toggleSearch, openGene
5656

5757
return (
5858
<>
59-
<CommandPalette isOpen={isPaletteOpen} setIsOpen={setIsPaletteOpen} openGenericModal={openGenericModal} />
59+
<CommandPalette isOpen={isPaletteOpen} setIsOpen={setIsPaletteOpen} openGenericModal={openGenericModal} toggleDigitalRain={toggleDigitalRain} />
6060
<div className="bg-gray-950 min-h-screen font-sans flex">
6161
<Sidebar
6262
isOpen={isSidebarOpen}

src/components/Sidebar.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ const Sidebar = ({ isOpen, toggleSidebar, toggleModal, setIsPaletteOpen }) => {
113113
animate={isOpen ? 'open' : 'closed'}
114114
variants={variants}
115115
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
116-
className={`fixed top-0 left-0 h-screen bg-black/30 backdrop-blur-sm text-white w-64 z-50 flex flex-col border-r border-gray-700/50 font-arvo`}
116+
className={`fixed top-0 left-0 h-screen bg-black/30 backdrop-blur-sm text-white w-64 z-20 flex flex-col border-r border-gray-700/50 font-arvo`}
117117
>
118118
{isOpen && (
119119
<div className="p-4 flex justify-between items-center">

src/hooks/useSearchableData.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const useSearchableData = () => {
7777
{ title: 'Go to Latest Log', type: 'command', commandId: 'latestLog' },
7878
{ title: 'Her Daim', type: 'command', commandId: 'herDaim' },
7979
{ title: 'Show Current Time', type: 'command', commandId: 'showTime' },
80-
{ title: 'Start Digital Rain', type: 'command', commandId: 'digitalRain' },
80+
{ title: 'Toggle Digital Rain', type: 'command', commandId: 'digitalRain' },
8181
];
8282

8383
setItems([...staticRoutes, ...customCommands, ...allPosts, ...allProjects, ...allLogs, ...allApps]);

0 commit comments

Comments
 (0)