feat: update portfolio with Simmonds Ltd design, light/dark theme, improved navigation

- Add Simmonds Ltd inspired design with grid patterns
- Light/Dark theme toggle (default: light)
- Center VNDK logo on main landing page
- Move view mode toggle to Selected Works section
- Highlight Download CV button with phosphor green
- Remove duplicate view toggles from nav bar
- Update Professional Journey with longer descriptions
- Update README with new features
This commit is contained in:
Khoa.vo 2026-04-25 23:03:54 +07:00
parent c586f24432
commit bb9e8c9b81
5 changed files with 932 additions and 476 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
node_modules/
dist/
.DS_Store
.env
*.local

View file

@ -1,11 +1,33 @@
# Portfolio # KHOA.VO Portfolio
My personal portfolio website built with React, Vite, and Tailwind CSS. Features dual-mode navigation - a creative editorial design view and a developer terminal interface, along with a specialized Print-Ready A4 CV crafted in a high-contrast editorial aesthetic. Personal portfolio website featuring dual personas (Creative & IT), inspired by Simmonds Ltd design aesthetics.
## Live Site ## Live Site
- **Main**: https://khoavo.myds.me - **Main**: https://khoavo.myds.me
- **Creative Works**: https://portfolio.khoavo.myds.me - **Creative Works**: https://portfolio.khoavo.myds.me (redirects to main)
## Features
### Creative Side
- **Three viewing modes**: Grid, List, Minimal
- **Image effects**: Grayscale + pixelated + blur → Full color on hover
- **Enhanced project modal**: Keyboard navigation (ESC, Arrow keys)
- **Professional Journey**: Longer, more detailed for HR/readability
### IT Side
- **Retro desktop UI**: Draggable windows
- **CRT screen effects**: Scanlines, vignette
- **Idle screensaver**: 5s timeout with animated logo
### Design
- **Simmonds Ltd inspired**: Dark/light theme, grid patterns, phosphor green accents
- **Typography**: Syne (display) + IBM Plex Mono
- **Default theme**: Light
### Print CV
- Separate print-optimized A4 format
- Concise professional journey
## Tech Stack ## Tech Stack
@ -28,22 +50,9 @@ npm run dev
npm run build npm run build
``` ```
## Preview ## Print CV
```bash Press the "Download CV" button or use `window.print()` to generate the PDF CV.
npm run preview
```
## Deployment
Deployed on Synology NAS via Docker.
## AI Agent Optimization (AIO)
This portfolio is heavily optimized for Agentic SEO. It includes:
- **JSON-LD Structured Data**: Explicitly defining `Person`, `jobTitle`, `knowsAbout`, and `seeks` (seeking job opportunity) metadata injected securely into `<head>`.
- **AI-Friendly `robots.txt`**: Access explicitly granted to major LLM scrapers such as `GPTBot`, `PerplexityBot`, `ClaudeBot`, and `Google-Extended`.
- **`llms.txt` Endpoint**: Available directly at `/llms.txt`, exposing an exact markdown-structured context endpoint purely for LLM ingestors to natively understand my professional profile.
## License ## License

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,84 @@
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Newsreader:ital,opsz,wght@0,6..72,200..800;1,6..72,200..800&family=JetBrains+Mono:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
:root {
--bg-primary: #F4F4F2;
--bg-secondary: #EBEBE9;
--text-primary: #1A1A1A;
--text-secondary: #4A4A4A;
--text-muted: #8E8E8E;
--accent: #1A1A1A;
--accent-subtle: rgba(26, 26, 26, 0.05);
--border: rgba(0, 0, 0, 0.1);
--grid-color: rgba(0, 0, 0, 0.03);
--accent-it: #00FF94;
}
[data-theme="dark"] {
--bg-primary: #0D0D0D;
--bg-secondary: #141414;
--text-primary: #EAEAEA;
--text-secondary: #A0A0A0;
--text-muted: #606060;
--accent: #EAEAEA;
--accent-subtle: rgba(234, 234, 234, 0.05);
--border: rgba(255, 255, 255, 0.1);
}
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
} }
body { body {
background-color: #FAFAFA; background-color: var(--bg-primary);
color: var(--text-primary);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
transition: background-color 0.5s cubic-bezier(0.4, 0, 0.2, 1), color 0.5s cubic-bezier(0.4, 0, 0.2, 1);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
.font-serif { .noise-overlay {
font-family: 'Playfair Display', Georgia, serif; position: fixed;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.06;
z-index: 9999;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
} }
.font-mono { .font-creative-sans {
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; font-family: 'Inter', sans-serif;
}
.font-creative-serif {
font-family: 'Newsreader', serif;
}
.font-it-mono {
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
}
/* Legacy mappings to prevent breakage during transition */
.font-display { @apply font-creative-sans; }
.font-serif { @apply font-creative-serif; }
.font-mono { @apply font-it-mono; }
.font-body { @apply font-it-mono; }
/* Grid background pattern */
.bg-grid-pattern {
background-image: linear-gradient(rgba(0,0,0,0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(0,0,0,0.03) 1px, transparent 1px);
background-size: 60px 60px;
}
.bg-dotted-pattern {
background-image: radial-gradient(rgba(0,0,0,0.1) 1px, transparent 1px);
background-size: 20px 20px;
} }
/* Screen: hide print-only version */ /* Screen: hide print-only version */
@ -72,17 +132,14 @@ body {
color: black !important; color: black !important;
} }
/* Hide the main interactive portfolio */
.app-main { .app-main {
display: none !important; display: none !important;
} }
/* Hide the preview overlay */
.fixed.inset-0 { .fixed.inset-0 {
display: none !important; display: none !important;
} }
/* Show only the print version */
.print-only-version { .print-only-version {
display: block !important; display: block !important;
visibility: visible !important; visibility: visible !important;
@ -126,7 +183,6 @@ body {
flex-direction: column !important; flex-direction: column !important;
} }
/* Cover Page */
.pdf-content .cover-page { .pdf-content .cover-page {
background: #111827 !important; background: #111827 !important;
display: flex !important; display: flex !important;
@ -185,7 +241,6 @@ body {
white-space: nowrap !important; white-space: nowrap !important;
} }
/* Sections */
.pdf-content .section { .pdf-content .section {
margin-bottom: 7mm !important; margin-bottom: 7mm !important;
} }
@ -201,7 +256,6 @@ body {
letter-spacing: 0.05em !important; letter-spacing: 0.05em !important;
} }
/* Summary */
.pdf-content .summary-section { .pdf-content .summary-section {
background: #f9fafb !important; background: #f9fafb !important;
padding: 5mm !important; padding: 5mm !important;
@ -215,7 +269,6 @@ body {
margin: 0 !important; margin: 0 !important;
} }
/* Experience */
.pdf-content .experience-list { .pdf-content .experience-list {
display: flex !important; display: flex !important;
flex-direction: column !important; flex-direction: column !important;
@ -274,7 +327,6 @@ body {
margin-bottom: 1.5mm !important; margin-bottom: 1.5mm !important;
} }
/* Skills */
.pdf-content .skills-grid { .pdf-content .skills-grid {
display: grid !important; display: grid !important;
grid-template-columns: 1fr 1fr !important; grid-template-columns: 1fr 1fr !important;
@ -301,7 +353,6 @@ body {
line-height: 1.5 !important; line-height: 1.5 !important;
} }
/* Portfolio */
.pdf-content .portfolio-grid { .pdf-content .portfolio-grid {
display: flex !important; display: flex !important;
flex-direction: column !important; flex-direction: column !important;
@ -357,7 +408,6 @@ body {
margin: 0 !important; margin: 0 !important;
} }
/* Contact Page */
.pdf-content .contact-page { .pdf-content .contact-page {
background: #111827 !important; background: #111827 !important;
display: flex !important; display: flex !important;
@ -429,7 +479,7 @@ body {
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: #0a0a0a; background: #0A0A0A;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
@ -439,4 +489,89 @@ body {
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: #00D9FF; background: #00D9FF;
}
/* Image effects - Grayscale + pixelated to color */
.grayscale-blur {
filter: grayscale(80%) blur(1px);
image-rendering: pixelated;
transition: all 0.5s ease-out;
}
.group:hover .grayscale-blur,
.grayscale-blur:hover {
filter: grayscale(0%) blur(0);
image-rendering: auto;
}
/* CRT Screen Effects */
.crt-screen::before {
content: '';
position: fixed;
inset: 0;
background: repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0.15),
rgba(0, 0, 0, 0.15) 1px,
transparent 1px,
transparent 2px
);
pointer-events: none;
z-index: 1000;
}
.crt-screen::after {
content: '';
position: fixed;
inset: 0;
background: radial-gradient(ellipse at center, transparent 0%, rgba(0, 0, 0, 0.3) 100%);
pointer-events: none;
z-index: 1001;
}
@keyframes crt-flicker {
0%, 100% { opacity: 1; }
92% { opacity: 1; }
93% { opacity: 0.8; }
94% { opacity: 1; }
}
.crt-flicker {
animation: crt-flicker 0.15s infinite;
}
@keyframes crt-scanline {
0% { transform: translateY(-100%); }
100% { transform: translateY(100%); }
}
.crt-scanline::after {
content: '';
position: fixed;
left: 0;
right: 0;
height: 4px;
background: rgba(0, 255, 148, 0.1);
animation: crt-scanline 4s linear infinite;
pointer-events: none;
z-index: 1002;
}
/* Screensaver animations */
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
.animate-blink {
animation: blink 1s step-end infinite;
}
.animate-float {
animation: float 2s ease-in-out infinite;
} }

View file

@ -4,8 +4,52 @@ export default {
"./index.html", "./index.html",
"./src/**/*.{js,ts,jsx,tsx}", "./src/**/*.{js,ts,jsx,tsx}",
], ],
darkMode: 'class',
theme: { theme: {
extend: {}, extend: {
colors: {
bg: {
primary: 'var(--bg-primary)',
secondary: 'var(--bg-secondary)',
},
text: {
primary: 'var(--text-primary)',
secondary: 'var(--text-secondary)',
muted: 'var(--text-muted)',
},
accent: {
DEFAULT: 'var(--accent)',
subtle: 'var(--accent-subtle)',
it: '#00FF94',
itSubtle: 'var(--accent-it-subtle)',
},
},
fontFamily: {
display: ['Syne', 'sans-serif'],
body: ['IBM Plex Mono', 'monospace'],
mono: ['JetBrains Mono', 'monospace'],
serif: ['Playfair Display', 'Georgia', 'serif'],
},
typography: {
display: {
fontSize: ['clamp(3rem, 10vw, 10rem)', { lineHeight: '0.9', letterSpacing: '-0.02em', fontWeight: '800' }],
},
huge: {
fontSize: ['clamp(2rem, 6vw, 6rem)', { lineHeight: '1', letterSpacing: '-0.02em', fontWeight: '700' }],
},
},
backgroundImage: {
'grid-pattern': 'linear-gradient(var(--grid-color) 1px, transparent 1px), linear-gradient(90deg, var(--grid-color) 1px, transparent 1px)',
'dotted-pattern': 'radial-gradient(var(--border) 1px, transparent 1px)',
},
backgroundSize: {
'grid': '60px 60px',
'dotted': '20px 20px',
},
transitionDuration: {
'400': '400ms',
},
},
}, },
plugins: [], plugins: [],
} }