origin
This commit is contained in:
commit
d25dae734c
24
.gitignore
vendored
Executable file
24
.gitignore
vendored
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
28
eslint.config.js
Executable file
28
eslint.config.js
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import globals from 'globals';
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ ignores: ['dist'] },
|
||||||
|
{
|
||||||
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
13
index.html
Executable file
13
index.html
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + React + TS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
5548
package-lock.json
generated
Executable file
5548
package-lock.json
generated
Executable file
File diff suppressed because it is too large
Load Diff
37
package.json
Executable file
37
package.json
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "vite-react-typescript-starter",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
|
"lucide-react": "^0.344.0",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-markdown": "^9.0.3",
|
||||||
|
"remark-gfm": "^4.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.9.1",
|
||||||
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
|
"@types/react": "^18.3.5",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
|
"autoprefixer": "^10.4.18",
|
||||||
|
"eslint": "^9.9.1",
|
||||||
|
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.11",
|
||||||
|
"globals": "^15.9.0",
|
||||||
|
"postcss": "^8.4.35",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "^5.5.3",
|
||||||
|
"typescript-eslint": "^8.3.0",
|
||||||
|
"vite": "^5.4.2"
|
||||||
|
}
|
||||||
|
}
|
6
postcss.config.js
Executable file
6
postcss.config.js
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
18
public/content/projects.md
Normal file
18
public/content/projects.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# My Projects
|
||||||
|
|
||||||
|
## Project 1: Personal Portfolio
|
||||||
|
- **Tech Stack:** React, TypeScript, Tailwind CSS
|
||||||
|
- **Description:** A retro TV-themed portfolio website showcasing my work and skills
|
||||||
|
- [View Source](https://github.com/username/portfolio)
|
||||||
|
|
||||||
|
## Project 2: Task Manager
|
||||||
|
- **Tech Stack:** Node.js, Express, MongoDB
|
||||||
|
- **Description:** RESTful API for managing tasks and projects
|
||||||
|
- [Live Demo](https://demo.example.com)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Currently Working On
|
||||||
|
* Cloud infrastructure optimization
|
||||||
|
* Open source contributions
|
||||||
|
* Machine learning side projects
|
106
src/App.tsx
Executable file
106
src/App.tsx
Executable file
@ -0,0 +1,106 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Terminal } from 'lucide-react';
|
||||||
|
import TerminalButton from './components/TerminalButton';
|
||||||
|
import ProfileContent from './components/ProfileContent';
|
||||||
|
import ProjectsContent from './components/ProjectsContent';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [activeSection, setActiveSection] = React.useState('about');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-900 flex items-center justify-center overflow-hidden">
|
||||||
|
{/* TV Container */}
|
||||||
|
<div className="w-[95%] max-w-[1400px] relative h-[80vh]">
|
||||||
|
{/* TV Frame */}
|
||||||
|
<div className="absolute inset-0 tv-frame">
|
||||||
|
{/* Screen Container with Convex Effect */}
|
||||||
|
<div className="relative w-full h-full rounded-[20px] overflow-hidden tv-screen-container">
|
||||||
|
{/* Convex Screen Effect */}
|
||||||
|
<div className="absolute inset-0 tv-screen-convex"></div>
|
||||||
|
|
||||||
|
{/* Screen Content */}
|
||||||
|
<div className="relative h-full w-full bg-[#003300] p-6 font-mono text-[#00FF00] overflow-hidden z-10">
|
||||||
|
<div className="animate-pulse mb-4">
|
||||||
|
<Terminal className="inline-block mr-2" />
|
||||||
|
<span className="text-sm">echo "Hey there!"</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Split Screen Container */}
|
||||||
|
<div className="flex gap-8 h-[calc(100%-3rem)]">
|
||||||
|
{/* Left Column - Navigation */}
|
||||||
|
<div className="w-64 space-y-3 overflow-y-auto custom-scrollbar">
|
||||||
|
<TerminalButton
|
||||||
|
onClick={() => setActiveSection('about')}
|
||||||
|
isSelected={activeSection === 'about'}
|
||||||
|
>
|
||||||
|
ABOUT
|
||||||
|
</TerminalButton>
|
||||||
|
<TerminalButton
|
||||||
|
onClick={() => setActiveSection('projects')}
|
||||||
|
isSelected={activeSection === 'projects'}
|
||||||
|
>
|
||||||
|
PROJECTS
|
||||||
|
</TerminalButton>
|
||||||
|
<TerminalButton
|
||||||
|
onClick={() => setActiveSection('skills')}
|
||||||
|
isSelected={activeSection === 'skills'}
|
||||||
|
>
|
||||||
|
SKILLS
|
||||||
|
</TerminalButton>
|
||||||
|
<TerminalButton
|
||||||
|
onClick={() => setActiveSection('contact')}
|
||||||
|
isSelected={activeSection === 'contact'}
|
||||||
|
>
|
||||||
|
CONTACT
|
||||||
|
</TerminalButton>
|
||||||
|
<TerminalButton
|
||||||
|
onClick={() => setActiveSection('resume')}
|
||||||
|
isSelected={activeSection === 'resume'}
|
||||||
|
>
|
||||||
|
RESUME
|
||||||
|
</TerminalButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Vertical Separator */}
|
||||||
|
<div className="w-px h-full bg-[#00FF00] opacity-30"></div>
|
||||||
|
|
||||||
|
{/* Right Column - Content */}
|
||||||
|
<div className="flex-1 overflow-y-auto pr-4 custom-scrollbar">
|
||||||
|
{activeSection === 'about' && <ProfileContent />}
|
||||||
|
{activeSection === 'projects' && (
|
||||||
|
<ProjectsContent markdownPath="/content/projects.md" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scan Line Effect */}
|
||||||
|
<div className="absolute inset-0 pointer-events-none z-20" style={{
|
||||||
|
background: 'linear-gradient(rgba(0, 17, 0, 0.1) 50%, rgba(0, 17, 0, 0.2) 50%)',
|
||||||
|
backgroundSize: '100% 4px'
|
||||||
|
}}></div>
|
||||||
|
|
||||||
|
{/* Screen Glare */}
|
||||||
|
<div className="screen-glare"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* TV Controls */}
|
||||||
|
<div className="absolute -bottom-10 right-20 flex space-x-6">
|
||||||
|
<div className="w-8 h-8 bg-gray-700 rounded-full shadow-inner"></div>
|
||||||
|
<div className="w-8 h-8 bg-gray-700 rounded-full shadow-inner"></div>
|
||||||
|
<div className="w-8 h-8 bg-gray-700 rounded-full shadow-inner"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* TV Speaker Grills */}
|
||||||
|
<div className="absolute -bottom-6 left-20 flex space-x-1.5">
|
||||||
|
{[...Array(10)].map((_, i) => (
|
||||||
|
<div key={i} className="w-1.5 h-6 bg-gray-700 rounded-full opacity-50"></div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
43
src/components/GlitchyLink.tsx
Normal file
43
src/components/GlitchyLink.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
interface GlitchyLinkProps {
|
||||||
|
text: string;
|
||||||
|
url: string;
|
||||||
|
type?: 'email' | 'link';
|
||||||
|
}
|
||||||
|
|
||||||
|
const GlitchyLink: React.FC<GlitchyLinkProps> = ({ text, url, type = 'link' }) => {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const handleClick = async (e: React.MouseEvent) => {
|
||||||
|
if (type === 'email') {
|
||||||
|
e.preventDefault();
|
||||||
|
if (copied) return;
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
onClick={handleClick}
|
||||||
|
className="group relative inline-block hover:text-green-400 transition-colors"
|
||||||
|
>
|
||||||
|
<span className="relative inline-block animate-glitch-1">
|
||||||
|
{copied ? 'Copied!' : text}
|
||||||
|
</span>
|
||||||
|
<span className="absolute top-0 left-0 w-full h-full opacity-0 group-hover:opacity-70 group-hover:animate-glitch-1">
|
||||||
|
{copied ? 'Copied!' : text}
|
||||||
|
</span>
|
||||||
|
<span className="absolute top-0 left-0 w-full h-full opacity-0 group-hover:opacity-70 group-hover:animate-glitch-2">
|
||||||
|
{copied ? 'Copied!' : text}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GlitchyLink;
|
60
src/components/ProfileContent.tsx
Executable file
60
src/components/ProfileContent.tsx
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Mail, Github, Linkedin, User, Code, BookOpen, Building, MapPin } from 'lucide-react';
|
||||||
|
import GlitchyLink from './GlitchyLink';
|
||||||
|
|
||||||
|
const ProfileContent: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex-1 overflow-auto">
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<User className="w-6 h-6" />
|
||||||
|
<h2 className="text-2xl font-bold">José Henrique Ivanchechen</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Code className="w-6 h-6" />
|
||||||
|
<p className="text-lg">Software Engineer</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Building className="w-6 h-6" />
|
||||||
|
<p className="text-lg">EposNow</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<MapPin className="w-6 h-6" />
|
||||||
|
<p className="text-lg">Curitiba, PR - Brazil</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<BookOpen className="w-6 h-6" />
|
||||||
|
<p className="text-lg">5+ Years Experience</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-12 space-y-6">
|
||||||
|
<h3 className="text-xl font-semibold">> Contact Info:</h3>
|
||||||
|
<div className="space-y-4 pl-6">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Mail className="w-5 h-5" />
|
||||||
|
<GlitchyLink
|
||||||
|
text="jose.henrique.ivan@gmail.com"
|
||||||
|
url="mailto:jose.henrique.ivan@gmail.com"
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Github className="w-5 h-5" />
|
||||||
|
<GlitchyLink text="@ivanch" url="https://github.com/ivanch" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Linkedin className="w-5 h-5" />
|
||||||
|
<GlitchyLink text="/in/joseivanch" url="https://linkedin.com/in/joseivanch" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfileContent;
|
46
src/components/ProjectsContent.tsx
Normal file
46
src/components/ProjectsContent.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
import remarkGfm from 'remark-gfm';
|
||||||
|
import { FileWarning } from 'lucide-react';
|
||||||
|
|
||||||
|
interface ProjectsContentProps {
|
||||||
|
markdownPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProjectsContent: React.FC<ProjectsContentProps> = ({ markdownPath }) => {
|
||||||
|
const [content, setContent] = React.useState<string>('');
|
||||||
|
const [error, setError] = React.useState<string>('');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetch(markdownPath)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) throw new Error('Failed to load content');
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then(text => setContent(text))
|
||||||
|
.catch(err => setError(err.message));
|
||||||
|
}, [markdownPath]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="flex-1 flex items-center justify-center">
|
||||||
|
<div className="text-center space-y-4">
|
||||||
|
<FileWarning className="w-12 h-12 mx-auto" />
|
||||||
|
<p className="text-lg">Error loading content: {error}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="prose prose-invert prose-green max-w-none">
|
||||||
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||||
|
{content}
|
||||||
|
</ReactMarkdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectsContent;
|
34
src/components/TerminalButton.tsx
Executable file
34
src/components/TerminalButton.tsx
Executable file
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ChevronRight } from 'lucide-react';
|
||||||
|
|
||||||
|
interface TerminalButtonProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
onClick?: () => void;
|
||||||
|
isSelected?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TerminalButton: React.FC<TerminalButtonProps> = ({ children, onClick, isSelected }) => {
|
||||||
|
const playKeyPress = () => {
|
||||||
|
const audio = new Audio('data:audio/wav;base64,UklGRnQGAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YU8GAACBhYqFbF1fdH2Dg4R/gIKFi4SAgX98eoCFhQAAhIWKhWxdX3R9g4OEf4CChYuEgIF/fHqAhYUAAP//');
|
||||||
|
audio.volume = 0.2;
|
||||||
|
audio.play().catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = (e: React.MouseEvent) => {
|
||||||
|
playKeyPress();
|
||||||
|
onClick?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={`terminal-button group ${isSelected ? 'terminal-button-selected' : ''}`}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<div className="glare-effect"></div>
|
||||||
|
<ChevronRight className="inline-block mr-2 w-4 h-4 transition-transform group-hover:translate-x-1" />
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TerminalButton;
|
252
src/index.css
Executable file
252
src/index.css
Executable file
@ -0,0 +1,252 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'VT323';
|
||||||
|
src: url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.typing-effect {
|
||||||
|
border-right: 2px solid #00FF00;
|
||||||
|
animation: cursor-blink 1s step-end infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-frame {
|
||||||
|
@apply rounded-[50px] p-8;
|
||||||
|
background: linear-gradient(145deg, #4a4a4a, #2a2a2a);
|
||||||
|
box-shadow:
|
||||||
|
20px 20px 60px #1a1a1a,
|
||||||
|
-20px -20px 60px #3a3a3a,
|
||||||
|
inset 0 2px 20px rgba(255, 255, 255, 0.1);
|
||||||
|
border: 12px solid #1a1a1a;
|
||||||
|
border-radius: 50px;
|
||||||
|
transform: perspective(1000px) rotateX(2deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-frame::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -12px;
|
||||||
|
left: -12px;
|
||||||
|
right: -12px;
|
||||||
|
bottom: -12px;
|
||||||
|
background: linear-gradient(145deg, #3a3a3a, #1a1a1a);
|
||||||
|
border-radius: 60px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-frame::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 40%;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(255, 255, 255, 0.1) 0%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
border-radius: 40px 40px 0 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-screen-container {
|
||||||
|
background: #000;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 50px rgba(0, 0, 0, 0.5),
|
||||||
|
inset 0 0 100px rgba(0, 0, 0, 0.3);
|
||||||
|
transform: perspective(1000px) rotateX(1deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-screen-convex {
|
||||||
|
content: '';
|
||||||
|
background: radial-gradient(
|
||||||
|
circle at center,
|
||||||
|
transparent 0%,
|
||||||
|
rgba(0, 0, 0, 0) 50%,
|
||||||
|
rgba(0, 0, 0, 0.3) 100%
|
||||||
|
);
|
||||||
|
border-radius: 50% / 5%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tv-screen-convex::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: radial-gradient(
|
||||||
|
circle at 50% 0%,
|
||||||
|
rgba(255, 255, 255, 0.1) 0%,
|
||||||
|
rgba(255, 255, 255, 0.05) 25%,
|
||||||
|
transparent 50%
|
||||||
|
);
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-button {
|
||||||
|
@apply relative w-full px-6 py-3 font-mono text-lg
|
||||||
|
transition-all duration-150 overflow-hidden
|
||||||
|
border border-[#00FF00]/30 select-none;
|
||||||
|
font-family: 'VT323', 'Courier New', monospace;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(0, 25, 0, 0.9) 0%,
|
||||||
|
rgba(0, 20, 0, 0.95) 100%
|
||||||
|
);
|
||||||
|
color: #4AFF4A;
|
||||||
|
text-shadow: 0 0 8px rgba(74, 255, 74, 0.5);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 15px rgba(0, 255, 0, 0.1),
|
||||||
|
0 0 2px rgba(0, 255, 0, 0.5),
|
||||||
|
0 0 5px rgba(0, 255, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-button-selected {
|
||||||
|
@apply text-[110%] font-bold;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(0, 35, 0, 0.95) 0%,
|
||||||
|
rgba(0, 30, 0, 1) 100%
|
||||||
|
);
|
||||||
|
color: #7FFF7F;
|
||||||
|
text-shadow: 0 0 12px rgba(74, 255, 74, 0.8);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 25px rgba(0, 255, 0, 0.2),
|
||||||
|
0 0 4px rgba(0, 255, 0, 0.6),
|
||||||
|
0 0 8px rgba(0, 255, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-button::before {
|
||||||
|
content: '';
|
||||||
|
@apply absolute inset-0 opacity-0 transition-opacity duration-150;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(0, 255, 0, 0.1) 0%,
|
||||||
|
rgba(0, 255, 0, 0.05) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-button:hover {
|
||||||
|
color: #7FFF7F;
|
||||||
|
text-shadow: 0 0 8px rgba(74, 255, 74, 0.8);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 20px rgba(0, 255, 0, 0.2),
|
||||||
|
0 0 4px rgba(0, 255, 0, 0.6),
|
||||||
|
0 0 8px rgba(0, 255, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-button:hover::before {
|
||||||
|
@apply opacity-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-button:active {
|
||||||
|
@apply transform scale-[0.98];
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(0, 35, 0, 0.95) 0%,
|
||||||
|
rgba(0, 30, 0, 1) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-button::after {
|
||||||
|
content: '';
|
||||||
|
@apply absolute inset-0 pointer-events-none;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(0, 255, 0, 0.03),
|
||||||
|
rgba(0, 255, 0, 0.03) 1px,
|
||||||
|
transparent 1px,
|
||||||
|
transparent 2px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glare-effect {
|
||||||
|
@apply absolute inset-0 pointer-events-none;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent 0%,
|
||||||
|
rgba(74, 255, 74, 0.1) 25%,
|
||||||
|
rgba(74, 255, 74, 0.1) 75%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
animation: glare 3s ease-in-out infinite;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screen-glare {
|
||||||
|
@apply absolute inset-0 pointer-events-none z-30;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent 0%,
|
||||||
|
rgba(255, 255, 255, 0.03) 45%,
|
||||||
|
rgba(255, 255, 255, 0.07) 50%,
|
||||||
|
rgba(255, 255, 255, 0.03) 55%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
animation: horizontal-glare 4s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cursor-blink {
|
||||||
|
from, to { border-color: transparent }
|
||||||
|
50% { border-color: #00FF00 }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glare {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-200%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(200%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes horizontal-glare {
|
||||||
|
from {
|
||||||
|
transform: translateX(-200%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(200%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar for the terminal */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #001100;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #00FF00;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #00CC00;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbar */
|
||||||
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background: rgba(0, 51, 0, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background: #00FF00;
|
||||||
|
border-radius: 4px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #00CC00;
|
||||||
|
}
|
10
src/main.tsx
Executable file
10
src/main.tsx
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
import { StrictMode } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import App from './App.tsx';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>
|
||||||
|
);
|
1
src/vite-env.d.ts
vendored
Executable file
1
src/vite-env.d.ts
vendored
Executable file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
39
tailwind.config.js
Executable file
39
tailwind.config.js
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
keyframes: {
|
||||||
|
glitch: {
|
||||||
|
'0%, 100%': { transform: 'translateX(0)' },
|
||||||
|
'20%': { transform: 'translateX(-2px)' },
|
||||||
|
'40%': { transform: 'translateX(2px)' },
|
||||||
|
'60%': { transform: 'skewX(2deg)' },
|
||||||
|
'80%': { transform: 'skewX(-2deg)' },
|
||||||
|
},
|
||||||
|
'glitch-1': {
|
||||||
|
'0%, 100%': { clip: 'rect(44px, 450px, 56px, 0)' },
|
||||||
|
'20%': { clip: 'rect(12px, 450px, 32px, 0)' },
|
||||||
|
'40%': { clip: 'rect(62px, 450px, 84px, 0)' },
|
||||||
|
'60%': { clip: 'rect(22px, 450px, 11px, 0)' },
|
||||||
|
'80%': { clip: 'rect(59px, 450px, 71px, 0)' },
|
||||||
|
},
|
||||||
|
'glitch-2': {
|
||||||
|
'0%, 100%': { clip: 'rect(24px, 450px, 36px, 0)' },
|
||||||
|
'20%': { clip: 'rect(82px, 450px, 92px, 0)' },
|
||||||
|
'40%': { clip: 'rect(32px, 450px, 44px, 0)' },
|
||||||
|
'60%': { clip: 'rect(72px, 450px, 81px, 0)' },
|
||||||
|
'80%': { clip: 'rect(19px, 450px, 31px, 0)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
'glitch': 'glitch 1s infinite',
|
||||||
|
'glitch-1': 'glitch-1 0.8s infinite',
|
||||||
|
'glitch-2': 'glitch-2 0.8s infinite',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
require('@tailwindcss/typography'),
|
||||||
|
],
|
||||||
|
};
|
24
tsconfig.app.json
Executable file
24
tsconfig.app.json
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
7
tsconfig.json
Executable file
7
tsconfig.json
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
22
tsconfig.node.json
Executable file
22
tsconfig.node.json
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
10
vite.config.ts
Executable file
10
vite.config.ts
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ['lucide-react'],
|
||||||
|
},
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user