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