Skip to content

Commit 096af38

Browse files
let claude freestyle
1 parent dc5ecad commit 096af38

File tree

11 files changed

+488
-58
lines changed

11 files changed

+488
-58
lines changed

index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<title>scott-term</title>
6+
<meta name="description" content="Scott's interactive terminal portfolio - A unique developer portfolio experience" />
7+
<meta name="keywords" content="portfolio, terminal, developer, web developer, interactive" />
8+
<title>scott@terminal:~$</title>
9+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>💻</text></svg>" />
710
</head>
811
<body>
912
<div id="root"></div>

src/App.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
11
import React, { useState } from 'react';
22
import Terminal from './components/Terminal';
3+
import ParticleBackground from './components/ParticleBackground';
34
import './App.css';
45
import CommandHandler from './commands/CommandHandler';
56

67
const commandHandler = new CommandHandler();
78

9+
const welcomeBanner = `
10+
╔══════════════════════════════════════════════════════════════╗
11+
║ ║
12+
║ ███████╗ ██████╗ ██████╗ ████████╗████████╗ ███╗ ███╗║
13+
║ ██╔════╝██╔════╝██╔═══██╗╚══██╔══╝╚══██╔══╝ ████╗ ████║║
14+
║ ███████╗██║ ██║ ██║ ██║ ██║ ██╔████╔██║║
15+
║ ╚════██║██║ ██║ ██║ ██║ ██║ ██║╚██╔╝██║║
16+
║ ███████║╚██████╗╚██████╔╝ ██║ ██║ ██║ ╚═╝ ██║║
17+
║ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝║
18+
║ ║
19+
║ Welcome to Scott's Terminal Portfolio ║
20+
║ ║
21+
╚══════════════════════════════════════════════════════════════╝
22+
23+
Type 'help' or 'ls' to see available commands
24+
Type 'neofetch' to see system info
25+
`;
26+
827
const App: React.FC = () => {
928
const [userPrompts, setUserPrompts] = useState<
1029
{ input: string; output: string }[]
11-
>([]);
30+
>([{ input: '', output: welcomeBanner }]);
1231
const [terminalDeleted, setTerminalDeleted] = useState<boolean>(false);
1332
const handleCommand = (input: string) => {
1433
const result = commandHandler.handleCommand(input);
@@ -28,6 +47,7 @@ const App: React.FC = () => {
2847
};
2948
return (
3049
<div className="App">
50+
<ParticleBackground />
3151
{!terminalDeleted && (
3252
<Terminal
3353
userPrompts={userPrompts}

src/commands/CommandHandler.test.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ describe("CommandHandler", () => {
44
it("returns correct output for known commands", () => {
55
const commandHandler = new CommandHandler();
66

7-
expect(commandHandler.handleCommand("help")).toMatchInlineSnapshot(`
8-
"Available commands:
9-
- help: Show available commands
10-
- clear: A command to clear the screen
11-
- neofetch: Display system information and React logo ASCII art
12-
- about: Display information about the user
13-
- history: Display employment history
14-
- education: Display education information
15-
- spin: Weeeeeeeeeeeeeeeeeeeeee"
7+
expect(commandHandler.handleCommand("help")).toMatchInlineSnapshot(`
8+
"Available commands:
9+
- help: Show available commands
10+
- ls: List all available commands
11+
- whoami: Display current user information
12+
- clear: A command to clear the screen
13+
- neofetch: Display system information and React logo ASCII art
14+
- about: Display information about the user
15+
- history: Display employment history
16+
- education: Display education information
17+
- spin: Weeeeeeeeeeeeeeeeeeeeee"
1618
`);
1719
expect(commandHandler.handleCommand("clear")).toEqual("clear");
1820
expect(commandHandler.handleCommand("neofetch")).toContain("OS:");

src/commands/CommandHandler.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ import EmploymentHistoryCommand from "./EmploymentHistoryCommand";
77
import EducationCommand from "./EducationCommand";
88
import SpinCommand from "./SpinCommand";
99
import RmRfCommand from "./RmRfCommand";
10+
import LsCommand from "./LsCommand";
11+
import WhoAmICommand from "./WhoAmICommand";
1012

1113
class CommandHandler {
1214
commands: Command[];
1315

1416
constructor() {
1517
this.commands = [
1618
new HelpCommand(),
19+
new LsCommand(),
20+
new WhoAmICommand(),
1721
new ClearCommand(),
1822
new NeoFetchCommand(),
1923
new AboutCommand(),
@@ -30,13 +34,13 @@ class CommandHandler {
3034
}
3135
const command = this.commands.find((cmd) => cmd.name === commandName.toLowerCase());
3236
if (command) {
33-
if (command instanceof HelpCommand) {
37+
if (command instanceof HelpCommand || command instanceof LsCommand) {
3438
return command.execute(this.commands);
3539
}
3640
return command.execute();
3741
}
3842

39-
return `Unknown command: '${commandName}'. Type 'help' for available commands.`;
43+
return `Unknown command: '${commandName}'. Type 'help' or 'ls' for available commands.`;
4044
}
4145
}
4246

src/commands/LsCommand.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Command from './Command';
2+
3+
class LsCommand extends Command {
4+
name = 'ls';
5+
description = 'List all available commands';
6+
7+
execute(commands?: Command[]): string {
8+
if (!commands) {
9+
return 'Error: Commands list not available.';
10+
}
11+
12+
const commandList = commands
13+
.filter((command) => !command.secret)
14+
.map((cmd) => ` ${cmd.name.padEnd(20)} ${cmd.description}`)
15+
.join('\n');
16+
17+
return `Available commands:\n\n${commandList}\n\nTip: Type 'help' for more information about using these commands.`;
18+
}
19+
}
20+
21+
export default LsCommand;

src/commands/WhoAmICommand.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Command from './Command';
2+
3+
class WhoAmICommand extends Command {
4+
name = 'whoami';
5+
description = 'Display current user information';
6+
7+
execute(): string {
8+
const browserName = this.getBrowserName();
9+
const platform = window.navigator.platform;
10+
const language = window.navigator.language;
11+
const screenRes = `${window.screen.width}x${window.screen.height}`;
12+
const connection = (navigator as any).connection;
13+
const connectionType = connection ? connection.effectiveType : 'unknown';
14+
15+
return `
16+
╭─────────────────────────────────────────╮
17+
│ USER INFORMATION │
18+
╰─────────────────────────────────────────╯
19+
20+
User Agent : ${browserName}
21+
Platform : ${platform}
22+
Language : ${language}
23+
Screen : ${screenRes}
24+
Connection : ${connectionType}
25+
Location : ${Intl.DateTimeFormat().resolvedOptions().timeZone}
26+
27+
You are viewing Scott's terminal portfolio
28+
from ${browserName} on ${platform}
29+
`;
30+
}
31+
32+
getBrowserName(): string {
33+
const userAgent = window.navigator.userAgent;
34+
if (userAgent.includes('Chrome')) return 'Chrome';
35+
if (userAgent.includes('Firefox')) return 'Firefox';
36+
if (userAgent.includes('Safari')) return 'Safari';
37+
if (userAgent.includes('Opera') || userAgent.includes('OPR')) return 'Opera';
38+
if (userAgent.includes('Edge') || userAgent.includes('Edg')) return 'Edge';
39+
if (userAgent.includes('Trident')) return 'Internet Explorer';
40+
return 'Unknown Browser';
41+
}
42+
}
43+
44+
export default WhoAmICommand;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.particle-background {
2+
position: fixed;
3+
top: 0;
4+
left: 0;
5+
width: 100%;
6+
height: 100%;
7+
z-index: -1;
8+
pointer-events: none;
9+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import React, { useEffect, useRef } from 'react';
2+
import './ParticleBackground.css';
3+
4+
interface Particle {
5+
x: number;
6+
y: number;
7+
size: number;
8+
speedX: number;
9+
speedY: number;
10+
opacity: number;
11+
}
12+
13+
const ParticleBackground: React.FC = () => {
14+
const canvasRef = useRef<HTMLCanvasElement>(null);
15+
16+
useEffect(() => {
17+
const canvas = canvasRef.current;
18+
if (!canvas) return;
19+
20+
const ctx = canvas.getContext('2d');
21+
if (!ctx) return;
22+
23+
const resizeCanvas = () => {
24+
canvas.width = window.innerWidth;
25+
canvas.height = window.innerHeight;
26+
};
27+
28+
resizeCanvas();
29+
window.addEventListener('resize', resizeCanvas);
30+
31+
const particles: Particle[] = [];
32+
const particleCount = 50;
33+
34+
for (let i = 0; i < particleCount; i++) {
35+
particles.push({
36+
x: Math.random() * canvas.width,
37+
y: Math.random() * canvas.height,
38+
size: Math.random() * 2 + 1,
39+
speedX: (Math.random() - 0.5) * 0.5,
40+
speedY: (Math.random() - 0.5) * 0.5,
41+
opacity: Math.random() * 0.5 + 0.2,
42+
});
43+
}
44+
45+
const animate = () => {
46+
ctx.clearRect(0, 0, canvas.width, canvas.height);
47+
48+
particles.forEach((particle, index) => {
49+
particle.x += particle.speedX;
50+
particle.y += particle.speedY;
51+
52+
if (particle.x < 0 || particle.x > canvas.width) particle.speedX *= -1;
53+
if (particle.y < 0 || particle.y > canvas.height) particle.speedY *= -1;
54+
55+
ctx.beginPath();
56+
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
57+
ctx.fillStyle = `rgba(142, 192, 124, ${particle.opacity})`;
58+
ctx.fill();
59+
60+
for (let j = index + 1; j < particles.length; j++) {
61+
const dx = particles[j].x - particle.x;
62+
const dy = particles[j].y - particle.y;
63+
const distance = Math.sqrt(dx * dx + dy * dy);
64+
65+
if (distance < 120) {
66+
ctx.beginPath();
67+
ctx.strokeStyle = `rgba(142, 192, 124, ${0.15 * (1 - distance / 120)})`;
68+
ctx.lineWidth = 1;
69+
ctx.moveTo(particle.x, particle.y);
70+
ctx.lineTo(particles[j].x, particles[j].y);
71+
ctx.stroke();
72+
}
73+
}
74+
});
75+
76+
requestAnimationFrame(animate);
77+
};
78+
79+
animate();
80+
81+
return () => {
82+
window.removeEventListener('resize', resizeCanvas);
83+
};
84+
}, []);
85+
86+
return <canvas ref={canvasRef} className="particle-background" />;
87+
};
88+
89+
export default ParticleBackground;

0 commit comments

Comments
 (0)