This commit is contained in:
José Henrique 2025-01-22 20:58:52 -03:00
commit d25dae734c
20 changed files with 6328 additions and 0 deletions

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

@ -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

@ -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

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

@ -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

@ -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;

@ -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;

@ -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">&gt; 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;

@ -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;

@ -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

@ -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

@ -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

@ -0,0 +1 @@
/// <reference types="vite/client" />

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

@ -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

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

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

@ -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'],
},
});