/* Public portfolio pages */ /* Project Detail Page */ function ProjectDetail({ project, onClose, tweaks }) { const primary = tweaks.primaryColor; const [backHovered, setBackHovered] = React.useState(false); return (
{project.category}

{project.title}

{project.summary}

{[ ['Role', project.role], ['Timeline', project.duration], ['Impact', project.impact], ].map(([label, value]) => (
{label}
{value}
))}
{project.title}

{project.description}

); } function DetailSection({ title, color, children }) { return (

{title}

{children}
); } function DetailList({ items, color }) { return (
{items.map((item, i) => (
{item}
))}
); } /* Hero Section */ function HeroSection({ onNavigate, tweaks }) { const primary = tweaks.primaryColor; return (
{/* Decorative elements */} {/* Gradient orb */}
Hello, I'm Ethan

DATA
ANALYST

I work with data to understand what is really happening, then turn those findings into reports, dashboards, and practical tools that help teams make better decisions.

Ethan avatar
{/* Scroll indicator */}
); } /* About Section */ function AboutSection({ tweaks }) { const primary = tweaks.primaryColor; return (

About me

I'm a Data Analyst with a strong background in business intelligence, data engineering, and predictive analytics. Previously worked as an ERP Implementation Analyst, where I gained deep expertise in enterprise systems and data workflows.

I specialize in turning raw data into strategic insights, building automated pipelines, and creating dashboards that empower decision-makers at every level.

); } function TechChip({ tech }) { return (
{tech.mark} {tech.label}
); } function TechMarquee({ tweaks }) { const row1 = [ { label: 'Python', mark: 'Py', tone: '#3776AB' }, { label: 'SQL Server', mark: 'SQL', tone: '#CC2927' }, { label: 'Power BI', mark: 'PBI', tone: '#F2C811' }, { label: 'Databricks', mark: 'DB', tone: '#FF3621' }, { label: 'Odoo', mark: 'Odo', tone: '#714B67' }, { label: 'PostgreSQL', mark: 'PG', tone: '#336791' }, { label: 'dbt', mark: 'dbt', tone: '#FF694B' }, { label: 'Prefect', mark: 'Pf', tone: '#1E40AF' }, ]; const row2 = [ { label: 'Apps Script', mark: 'AS', tone: '#34A853' }, { label: 'Git', mark: 'Git', tone: '#F05032' }, { label: 'C#', mark: 'C#', tone: '#7F52FF' }, { label: 'Golang', mark: 'Go', tone: '#00ADD8' }, { label: 'SQLite', mark: 'Lite', tone: '#0F80CC' }, { label: 'Azure Data Studio', mark: 'ADS', tone: '#0078D4' }, { label: 'NotebookLM', mark: 'NB', tone: '#4285F4' }, { label: 'Claude', mark: 'AI', tone: '#D97706' }, ]; const d1 = [...row1, ...row1]; const d2 = [...row2, ...row2]; return (
{/* Fade left/right edges */}
{d1.map((t, i) => )}
{d2.map((t, i) => )}
); } function SkillsSection({ tweaks }) { const primary = tweaks.primaryColor; const skillGroups = [ { title: 'Languages', icon: 'M8 9l-4 3 4 3M16 9l4 3-4 3M14 4l-4 16', items: [ { label: 'Python', mark: 'Py', tone: '#3776AB' }, { label: 'Apps Script', mark: 'AS', tone: '#34A853' }, { label: 'C#', mark: 'C#', tone: '#7F52FF' }, { label: 'Golang', mark: 'Go', tone: '#00ADD8' }, ], }, { title: 'Databases', icon: 'M4 6c0-1.7 3.6-3 8-3s8 1.3 8 3-3.6 3-8 3-8-1.3-8-3zm0 0v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3', items: [ { label: 'SQL Server', mark: 'SQL', tone: '#CC2927' }, { label: 'PostgreSQL', mark: 'PG', tone: '#336791' }, { label: 'SQLite', mark: 'Lite', tone: '#0F80CC' }, ], }, { title: 'Tools', icon: 'M6 3v6a3 3 0 003 3h6a3 3 0 013 3v6M6 9a3 3 0 100-6 3 3 0 000 6zM18 21a3 3 0 100-6 3 3 0 000 6z', items: [ { label: 'Git', mark: 'Git', tone: '#F05032' }, { label: 'Azure Data Studio', mark: 'ADS', tone: '#0078D4' }, ], }, { title: 'AI', icon: 'M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8L12 3zM5 17l.9 2.1L8 20l-2.1.9L5 23l-.9-2.1L2 20l2.1-.9L5 17zM19 15l.7 1.6L21 17l-1.3.4L19 19l-.7-1.6L17 17l1.3-.4L19 15z', items: [ { label: 'NotebookLM', mark: 'LM', tone: '#4285F4' }, { label: 'Claude', mark: 'Cl', tone: '#D97757' }, { label: 'Codex', mark: 'Cx', tone: '#10A37F' }, { label: 'Gemini', mark: 'Ge', tone: '#8E75FF' }, ], }, ]; return (

Skills

{skillGroups.map(group => ( ))}
); } function SkillGroupCard({ group, accent }) { return (

{group.title}

{group.items.map(item => )}
); } function SkillItem({ item, accent }) { const [hovered, setHovered] = React.useState(false); return (
setHovered(true)} onMouseLeave={() => setHovered(false)} style={{ display: 'flex', alignItems: 'center', gap: 10, minHeight: 46, padding: '8px 10px', borderRadius: 14, background: hovered ? `${accent}10` : 'rgba(var(--theme-bg-rgb),0.26)', border: `1px solid ${hovered ? accent + '44' : 'rgba(var(--theme-text-rgb),0.08)'}`, transition: 'background 0.22s ease, border-color 0.22s ease, transform 0.22s ease', transform: hovered ? 'translateY(-1px)' : 'none', }}> 2 ? 10 : 12, lineHeight: 1, flexShrink: 0, }}>{item.mark} {item.label}
); } /* ─── Stack Card (sticky-stacking experience/education card) ─── */ function StackCard({ item, index, type, color }) { const [hovered, setHovered] = React.useState(false); return (
setHovered(true)} onMouseLeave={() => setHovered(false)} style={{ position: 'relative', padding: '36px 40px', borderRadius: 24, overflow: 'hidden', background: hovered ? `linear-gradient(135deg, ${color}0E, rgba(var(--theme-surface-rgb),0.55))` : `rgba(var(--theme-surface-rgb), ${0.18 + index * 0.06})`, border: `1px solid ${hovered ? color + '2E' : 'rgba(var(--theme-text-rgb),0.07)'}`, boxShadow: hovered ? `0 32px 64px rgba(0,0,0,0.28), 0 0 0 1px ${color}12` : `0 ${8 + index * 4}px ${28 + index * 8}px rgba(0,0,0,${0.14 + index * 0.04})`, backdropFilter: 'blur(14px)', transition: 'all 0.4s cubic-bezier(0.25,0.46,0.45,0.94)', }}> {/* Content */}
{item.logo && (
{item.logoAlt
)}

{item.role || item.degree}

{item.company || item.school}
{item.period}

{item.description}

{item.highlights && (
{item.highlights.map((h, idx) => ( {h} ))}
)}
); } /* Experience & Education Section */ function ExperienceSection({ tweaks }) { const primary = tweaks.primaryColor; const experiences = [ { role: 'Data Analyst', company: 'Tan Thanh Wood Joint Stocks Company', logo: 'assets/tan-thanh-logo.png', logoAlt: 'Tan Thanh Group logo', period: '9/2025 - Present', description: 'Gathered and analyzed business requirements from cross-functional departments to develop and optimize internal ERP applications. Managed the full software development lifecycle including requirements analysis, BPMN process design, stakeholder review, development, UAT, and production release.', highlights: ['ERP Modules', 'BPMN Design', 'Process Digitization', 'UAT'], }, { role: 'Developer (Remote)', company: 'Motico Company Limited', logo: 'assets/motico-logo.png', logoAlt: 'Motico logo', period: '3/2026 - Present', description: 'Received business requirements directly from the General Director to design and build CRM and task management workflows. Focused on customer pipeline tracking, follow-up activities, task assignment, progress status, and internal collaboration features for daily operations.', highlights: ['CRM Workflow', 'Customer Pipeline', 'Task Management', 'Progress Tracking'], }, { role: 'Intern', company: 'VNPT Can Tho', logo: 'assets/vnpt-logo.png', logoAlt: 'VNPT logo', period: '5/2024 - 8/2024', description: 'Supported healthcare management system development under mentor guidance, gaining hands-on exposure to professional software development practices.', highlights: ['Healthcare System', 'Software Development'], }, ]; const education = [ { degree: 'Software Engineering', school: 'Can Tho University', logo: 'assets/can-tho-university-logo.png', logoAlt: 'Can Tho University logo', period: '2019 - 2024', description: 'Focused on software engineering, database systems, and data science. Graduated with a GPA of 7.76/10.', }, ]; return (
{/* Experience */}

Work Experience

{experiences.map((exp, i) => ( ))}
{/* Education */}

Education

{education.map((edu, i) => ( ))}
); } function TimelineCard({ item, type, color, isLast }) { const [hovered, setHovered] = React.useState(false); return (
setHovered(true)} onMouseLeave={() => setHovered(false)}> {/* Dot */}
{/* Card */}
{item.logo && (
{item.logoAlt
)}

{item.role || item.degree}

{item.company || item.school}
{item.period}

{item.description}

{item.highlights && (
{item.highlights.map((h, i) => ( {h} ))}
)}
); } /* Projects Section */ function ProjectsSection({ onSelectProject, tweaks }) { const primary = tweaks.primaryColor; const [filter, setFilter] = React.useState('All'); const [hoveredFilter, setHoveredFilter] = React.useState(null); const categories = ['All', ...Array.from(new Set(PROJECTS.map(p => p.category)))]; const filtered = filter === 'All' ? PROJECTS : PROJECTS.filter(p => p.category === filter); return (

My recent works

{categories.map(c => { const active = filter === c; const hovered = hoveredFilter === c; return ( )})}
{filtered.map((p, i) => ( ))}
); } /* Contact Section */ function ContactSection({ tweaks }) { const primary = tweaks.primaryColor; const [form, setForm] = React.useState({ name: '', email: '', message: '' }); const [sent, setSent] = React.useState(false); const [focused, setFocused] = React.useState(null); const [submitHovered, setSubmitHovered] = React.useState(false); const handleSubmit = (e) => { e.preventDefault(); setSent(true); setTimeout(() => setSent(false), 3000); setForm({ name: '', email: '', message: '' }); }; const inputStyle = (field) => ({ width: '100%', padding: '16px 24px', borderRadius: 16, background: 'rgba(var(--theme-surface-rgb),0.5)', border: `2px solid ${focused === field ? primary : 'transparent'}`, color: 'var(--theme-text)', fontFamily: 'Poppins, sans-serif', fontSize: 16, outline: 'none', transition: 'all 0.3s', boxSizing: 'border-box', }); return (

Got a project
in mind?

Let's discuss how data-driven solutions can help your business grow. I'm always open to new opportunities and collaborations.

setForm({...form, name: e.target.value})} placeholder="Name" style={inputStyle('name')} onFocus={() => setFocused('name')} onBlur={() => setFocused(null)}/>
setForm({...form, email: e.target.value})} placeholder="Email" type="email" style={inputStyle('email')} onFocus={() => setFocused('email')} onBlur={() => setFocused(null)}/>