Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
72 changes: 44 additions & 28 deletions package-lock.json

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

Binary file added public/rabbit.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 32 additions & 32 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -49,12 +49,12 @@ function AppContent() {
},
]);

// Bouncy cats state
const [cats, setCats] = useState<string[]>([]);
// Bouncy rabbits state
const [rabbits, setRabbits] = useState<string[]>([]);

// 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+)
Expand Down Expand Up @@ -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;
Expand All @@ -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 }) => {
Expand Down Expand Up @@ -217,8 +217,8 @@ function AppContent() {
terminalCount={actualTerminalCount}
onTerminalCountChange={handleTerminalCountChange}
onArrangeTerminals={handleArrangeTerminals}
catCount={cats.length}
onRemoveAllCats={handleRemoveAllCats}
rabbitCount={rabbits.length}
onRemoveAllRabbits={handleRemoveAllRabbits}
/>

{/* Terminal Container */}
Expand Down Expand Up @@ -246,12 +246,12 @@ function AppContent() {
))}
</div>

{/* Bouncy Cats - render above everything */}
{cats.map((catId) => (
<BouncyCat
key={catId}
id={catId}
totalCatCount={cats.length}
{/* Bouncy Rabbits - render above everything */}
{rabbits.map((rabbitId) => (
<BounceRabbit
key={rabbitId}
id={rabbitId}
totalRabbitCount={rabbits.length}
/>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -16,7 +16,7 @@ interface Velocity {
y: number;
}

export function BouncyCat({ totalCatCount = 1 }: BouncyCatProps) {
export function BounceRabbit({ totalRabbitCount = 1 }: BounceRabbitProps) {
const [position, setPosition] = useState<Position>(() => ({
x: Math.random() * (window.innerWidth - 100),
y: Math.random() * (window.innerHeight - 100),
Expand All @@ -29,12 +29,12 @@ export function BouncyCat({ totalCatCount = 1 }: BouncyCatProps) {

const animationRef = useRef<number>();
const lastUpdateRef = useRef<number>(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;
Expand All @@ -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
Expand All @@ -72,7 +72,7 @@ export function BouncyCat({ totalCatCount = 1 }: BouncyCatProps) {
});

animationRef.current = requestAnimationFrame(animate);
}, [velocity, totalCatCount]);
}, [velocity, totalRabbitCount]);

useEffect(() => {
animationRef.current = requestAnimationFrame(animate);
Expand All @@ -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),
}));
};

Expand All @@ -109,14 +109,14 @@ export function BouncyCat({ totalCatCount = 1 }: BouncyCatProps) {
}}
>
<img
src="/nyancat.gif"
alt="Bouncy cat"
width={catSize}
height={catSize}
src="/rabbit.gif"
alt="Bouncy rabbit"
width={rabbitSize}
height={rabbitSize}
style={{
imageRendering: 'pixelated', // Keep GIF crisp
}}
/>
</div>
);
}
}
22 changes: 11 additions & 11 deletions src/components/ControlsPanel/ControlsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ interface ControlsPanelProps {
onArrangeTerminals?: () => void;
minTerminals?: number;
maxTerminals?: number;
catCount?: number;
onRemoveAllCats?: () => void;
rabbitCount?: number;
onRemoveAllRabbits?: () => void;
}

export function ControlsPanel({
Expand All @@ -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();
Expand Down Expand Up @@ -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
>
<div className="p-4 space-y-3">
{/* Header */}
Expand Down Expand Up @@ -169,21 +169,21 @@ export function ControlsPanel({
Theme: {themeName}
</button>

{/* Cat Controls (only show if cats exist) */}
{catCount > 0 && onRemoveAllCats && (
{/* Rabbit Controls (only show if rabbits exist) */}
{rabbitCount > 0 && onRemoveAllRabbits && (
<div className="space-y-2">
<div className="text-center">
<span className="text-white font-mono text-xs">
🐱 {catCount} vibe cat{catCount !== 1 ? 's' : ''}
🐰 {rabbitCount} vibe rabbit{rabbitCount !== 1 ? 's' : ''}
</span>
</div>
<button
onClick={onRemoveAllCats}
onClick={onRemoveAllRabbits}
className="w-full bg-orange-600 hover:bg-orange-700 text-white rounded py-2 px-3 text-sm font-medium transition-colors border-0"
style={{ backgroundColor: '#ea580c' }}
aria-label="Remove all cats"
aria-label="Remove all rabbits"
>
Remove Cats
Remove Rabbits
</button>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down