diff --git a/package-lock.json b/package-lock.json index 646bbea..175b99c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2778,6 +2778,19 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -6186,6 +6199,19 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -6662,12 +6688,13 @@ "dev": true }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -7057,6 +7084,19 @@ "node": ">=8.10.0" } }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -8026,18 +8066,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinypool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.0.tgz", @@ -8775,18 +8803,6 @@ } } }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/public/rabbit.gif b/public/rabbit.gif new file mode 100644 index 0000000..db6e0c4 Binary files /dev/null and b/public/rabbit.gif differ diff --git a/src/App.tsx b/src/App.tsx index 6f9c84a..c7764f3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,7 +4,7 @@ import { AppProvider } from './contexts/AppContext'; import { ThemeProvider } from './contexts/ThemeContext'; import { TerminalWindow } from './components/TerminalWindow/TerminalWindow'; import { ControlsPanel } from './components/ControlsPanel/ControlsPanel'; -import { BouncyCat } from './components/BouncyCat/BouncyCat'; +import { BounceRabbit } from './components/BounceRabbit/BounceRabbit'; import { AsciiTyper } from './pages/AsciiTyper'; import { useState } from 'react'; import { Analytics } from "@vercel/analytics/react" @@ -49,12 +49,12 @@ function AppContent() { }, ]); - // Bouncy cats state - const [cats, setCats] = useState([]); + // Bouncy rabbits state + const [rabbits, setRabbits] = useState([]); - - // Use actual count for cats, not rendered count - allow up to 1000 cats - // const expectedCatCount = Math.min(Math.floor(actualTerminalCount / 5), 1000); + + // Use actual count for rabbits, not rendered count - allow up to 1000 rabbits + // const expectedRabbitCount = Math.min(Math.floor(actualTerminalCount / 5), 1000); const handleTerminalCountChange = (count: number) => { // Update the actual terminal count (can go to 10,000+) @@ -92,21 +92,21 @@ function AppContent() { setHighestZIndex(newZ); setNextTerminalId(newNextId); - // Update cats based on new actual terminal count (max 1000 cats) - const newExpectedCatCount = Math.min(Math.floor(count / 5), 1000); - setCats(prevCats => { - if (newExpectedCatCount > prevCats.length) { - // Add new cats - const newCats = [...prevCats]; - for (let i = prevCats.length; i < newExpectedCatCount; i++) { - newCats.push(`cat-${Date.now()}-${i}`); + // Update rabbits based on new actual terminal count (max 1000 rabbits) + const newExpectedRabbitCount = Math.min(Math.floor(count / 5), 1000); + setRabbits(prevRabbits => { + if (newExpectedRabbitCount > prevRabbits.length) { + // Add new rabbits + const newRabbits = [...prevRabbits]; + for (let i = prevRabbits.length; i < newExpectedRabbitCount; i++) { + newRabbits.push(`rabbit-${Date.now()}-${i}`); } - return newCats; - } else if (newExpectedCatCount < prevCats.length) { - // Remove excess cats if terminals are removed - return prevCats.slice(0, newExpectedCatCount); + return newRabbits; + } else if (newExpectedRabbitCount < prevRabbits.length) { + // Remove excess rabbits if terminals are removed + return prevRabbits.slice(0, newExpectedRabbitCount); } - return prevCats; + return prevRabbits; }); return updated; @@ -121,16 +121,16 @@ function AppContent() { setTerminals((prev) => { const updated = prev.filter(terminal => terminal.id !== terminalId); - // Update cats when terminal count changes (max 1000 cats) - const newExpectedCatCount = Math.min(Math.floor(newCount / 5), 1000); - setCats(prevCats => prevCats.slice(0, newExpectedCatCount)); + // Update rabbits when terminal count changes (max 1000 rabbits) + const newExpectedRabbitCount = Math.min(Math.floor(newCount / 5), 1000); + setRabbits(prevRabbits => prevRabbits.slice(0, newExpectedRabbitCount)); return updated; }); }; - const handleRemoveAllCats = () => { - setCats([]); + const handleRemoveAllRabbits = () => { + setRabbits([]); }; const handlePositionChange = (terminalId: string, position: { x: number; y: number }) => { @@ -217,8 +217,8 @@ function AppContent() { terminalCount={actualTerminalCount} onTerminalCountChange={handleTerminalCountChange} onArrangeTerminals={handleArrangeTerminals} - catCount={cats.length} - onRemoveAllCats={handleRemoveAllCats} + rabbitCount={rabbits.length} + onRemoveAllRabbits={handleRemoveAllRabbits} /> {/* Terminal Container */} @@ -246,12 +246,12 @@ function AppContent() { ))} - {/* Bouncy Cats - render above everything */} - {cats.map((catId) => ( - ( + ))} diff --git a/src/components/BouncyCat/BouncyCat.tsx b/src/components/BounceRabbit/BounceRabbit.tsx similarity index 78% rename from src/components/BouncyCat/BouncyCat.tsx rename to src/components/BounceRabbit/BounceRabbit.tsx index 4ca37c5..f540bec 100644 --- a/src/components/BouncyCat/BouncyCat.tsx +++ b/src/components/BounceRabbit/BounceRabbit.tsx @@ -1,9 +1,9 @@ import { useState, useEffect, useRef, useCallback } from 'react'; -interface BouncyCatProps { +interface BounceRabbitProps { id: string; onRemove?: (id: string) => void; - totalCatCount?: number; + totalRabbitCount?: number; } interface Position { @@ -16,7 +16,7 @@ interface Velocity { y: number; } -export function BouncyCat({ totalCatCount = 1 }: BouncyCatProps) { +export function BounceRabbit({ totalRabbitCount = 1 }: BounceRabbitProps) { const [position, setPosition] = useState(() => ({ x: Math.random() * (window.innerWidth - 100), y: Math.random() * (window.innerHeight - 100), @@ -29,12 +29,12 @@ export function BouncyCat({ totalCatCount = 1 }: BouncyCatProps) { const animationRef = useRef(); const lastUpdateRef = useRef(0); - const catSize = 64; // Assumed cat GIF size + const rabbitSize = 64; // Assumed rabbit GIF size - // Throttle animation updates to reduce CPU usage - more throttling with more cats + // Throttle animation updates to reduce CPU usage - more throttling with more rabbits const animate = useCallback((timestamp: number) => { - // Dynamic throttling based on cat count: more cats = lower frame rate - const throttleTime = totalCatCount > 50 ? 50 : totalCatCount > 20 ? 33 : 16; + // Dynamic throttling based on rabbit count: more rabbits = lower frame rate + const throttleTime = totalRabbitCount > 50 ? 50 : totalRabbitCount > 20 ? 33 : 16; if (timestamp - lastUpdateRef.current < throttleTime) { animationRef.current = requestAnimationFrame(animate); return; @@ -52,15 +52,15 @@ export function BouncyCat({ totalCatCount = 1 }: BouncyCatProps) { let newVelocity = { ...velocity }; // Left and right walls - if (newPosition.x <= 0 || newPosition.x >= window.innerWidth - catSize) { + if (newPosition.x <= 0 || newPosition.x >= window.innerWidth - rabbitSize) { newVelocity.x = -newVelocity.x; - newPosition.x = Math.max(0, Math.min(newPosition.x, window.innerWidth - catSize)); + newPosition.x = Math.max(0, Math.min(newPosition.x, window.innerWidth - rabbitSize)); } // Top and bottom walls - if (newPosition.y <= 0 || newPosition.y >= window.innerHeight - catSize) { + if (newPosition.y <= 0 || newPosition.y >= window.innerHeight - rabbitSize) { newVelocity.y = -newVelocity.y; - newPosition.y = Math.max(0, Math.min(newPosition.y, window.innerHeight - catSize)); + newPosition.y = Math.max(0, Math.min(newPosition.y, window.innerHeight - rabbitSize)); } // Update velocity if it changed @@ -72,7 +72,7 @@ export function BouncyCat({ totalCatCount = 1 }: BouncyCatProps) { }); animationRef.current = requestAnimationFrame(animate); - }, [velocity, totalCatCount]); + }, [velocity, totalRabbitCount]); useEffect(() => { animationRef.current = requestAnimationFrame(animate); @@ -88,8 +88,8 @@ export function BouncyCat({ totalCatCount = 1 }: BouncyCatProps) { useEffect(() => { const handleResize = () => { setPosition(prevPosition => ({ - x: Math.min(prevPosition.x, window.innerWidth - catSize), - y: Math.min(prevPosition.y, window.innerHeight - catSize), + x: Math.min(prevPosition.x, window.innerWidth - rabbitSize), + y: Math.min(prevPosition.y, window.innerHeight - rabbitSize), })); }; @@ -109,14 +109,14 @@ export function BouncyCat({ totalCatCount = 1 }: BouncyCatProps) { }} > Bouncy cat ); -} \ No newline at end of file +} diff --git a/src/components/ControlsPanel/ControlsPanel.tsx b/src/components/ControlsPanel/ControlsPanel.tsx index c857638..6a40c23 100644 --- a/src/components/ControlsPanel/ControlsPanel.tsx +++ b/src/components/ControlsPanel/ControlsPanel.tsx @@ -8,8 +8,8 @@ interface ControlsPanelProps { onArrangeTerminals?: () => void; minTerminals?: number; maxTerminals?: number; - catCount?: number; - onRemoveAllCats?: () => void; + rabbitCount?: number; + onRemoveAllRabbits?: () => void; } export function ControlsPanel({ @@ -18,8 +18,8 @@ export function ControlsPanel({ onArrangeTerminals, minTerminals = 1, maxTerminals = 10000, - catCount = 0, - onRemoveAllCats + rabbitCount = 0, + onRemoveAllRabbits }: ControlsPanelProps) { const [isVisible, setIsVisible] = useState(true); const { themeName, setTheme, getThemeNames } = useTheme(); @@ -82,7 +82,7 @@ export function ControlsPanel({ className={`fixed top-4 left-4 bg-gray-800 border border-gray-600 rounded-lg shadow-lg transition-transform duration-300 ${ isVisible ? 'translate-x-0' : '-translate-x-full' }`} - style={{ zIndex: 10001 }} // Ensure controls are always above cats and terminals + style={{ zIndex: 10001 }} // Ensure controls are always above rabbits and terminals >
{/* Header */} @@ -169,21 +169,21 @@ export function ControlsPanel({ Theme: {themeName} - {/* Cat Controls (only show if cats exist) */} - {catCount > 0 && onRemoveAllCats && ( + {/* Rabbit Controls (only show if rabbits exist) */} + {rabbitCount > 0 && onRemoveAllRabbits && (
- 🐱 {catCount} vibe cat{catCount !== 1 ? 's' : ''} + 🐰 {rabbitCount} vibe rabbit{rabbitCount !== 1 ? 's' : ''}
)} diff --git a/src/components/ControlsPanel/__tests__/ControlsPanel.test.tsx b/src/components/ControlsPanel/__tests__/ControlsPanel.test.tsx index eb49911..d8bfe87 100644 --- a/src/components/ControlsPanel/__tests__/ControlsPanel.test.tsx +++ b/src/components/ControlsPanel/__tests__/ControlsPanel.test.tsx @@ -34,7 +34,7 @@ describe('ControlsPanel', () => { expect(screen.getByText('Arrange')).toBeInTheDocument(); expect(screen.getByText(/Theme:/)).toBeInTheDocument(); expect(screen.getByText(/Made with ❤️ by/)).toBeInTheDocument(); - expect(screen.getByRole('link', { name: /use the vibe typer/i })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: /vibe typer/i })).toBeInTheDocument(); }); it('should call onTerminalCountChange when + button is clicked', () => {