Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 38 additions & 22 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
import AboutMe from "./components/AboutMe.jsx";
import Education from "./components/Education.jsx";
import Experience from "./components/Experience.jsx";
import Skills from "./components/Skills.jsx";
import Projects from "./components/Projects.jsx";
import Navbar from "./components/Navbar.jsx";

import Navbar from './components/Navbar'
import AboutMe from './components/AboutMe'
import Skills from './components/Skills'
import Education from './components/Education'
import Experience from './components/Experience'
import Projects from './components/Projects'
import {LanguageProvider} from "./contexts/LanguageProvider.jsx";

function App() {
return (
<>
<LanguageProvider>
{/* Mobile: no grid, Tablet+: grid with sidebar column */}
{/* Using minmax(0, 1fr) for content column to allow proper shrinking */}
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-blue-900 to-slate-900 md:grid md:grid-cols-[80px_minmax(0,1fr)] lg:grid-cols-[100px_minmax(0,1fr)]">

{/* Sidebar Column - only exists on desktop */}
<aside className="hidden md:block">
<Navbar />
</aside>

{/* Main Content Column */}
<main className="min-w-0">
{/* Mobile navbar (shows outside grid) */}
<div className="md:hidden">
<Navbar />
</div>

<AboutMe />
<Skills />
<Education />
<Experience />
<Projects />
</main>

</div>
</LanguageProvider>
</>

return (
<>
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-blue-900 to-slate-900">
<Navbar />
<div className="relative z-10">
<AboutMe />
<Skills />
<Education />
<Experience />
<Projects />
</div>
</div>
</>
)
)
}

export default App
export default App
Binary file added src/assets/images/cimf_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 25 additions & 23 deletions src/components/AboutMe.jsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,56 @@
import profileImg from '../assets/images/cropped-profile.jpg'
import resumePdf from '../assets/resume.pdf'

import { FaLinkedin, FaGithub, FaFileDownload } from 'react-icons/fa'
import SectionArrow from "./SectionArrow.jsx";

import SectionArrow from "./SectionArrow.jsx"
import { useLanguage } from '../contexts/useLanguage.js'
import { getText } from '../utils/translationHelpers.js'
import { aboutData } from './data/about.js'

function AboutMe() {
const { language } = useLanguage()

return (
<section id="about" className="min-h-screen flex flex-col items-center justify-between">
<section id="about" className="min-h-screen flex flex-col items-center justify-between pt-16 md:pt-0">
<div className="flex-grow flex items-center justify-center w-full">
<div className="max-w-6xl mx-auto px-4">
<div className="grid md:grid-cols-2 gap-8 items-center">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-2 gap-6 md:gap-8 items-center">
<div>
<h1 className="text-6xl font-extrabold font-mono mb-6 text-white">Luan Tran</h1>
<p className="mb-6 text-white">
I'm a Master's student in Applied Computer Science with industry experience in automation and web development at Broadsign, Ericsson, and Matrox. My projects include deploying automated CI/CD workflows, multimodal medical imaging applications, and automated language proficiency assessment systems.
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-extrabold font-mono mb-4 sm:mb-6 text-white">
{aboutData.name}
</h1>
<p className="mb-4 sm:mb-6 text-sm sm:text-base text-white">
{getText(aboutData.description1, language)}
</p>
<p className="mb-6 text-white">
I'm currently researching LLM-based agents for linguistic education and seeking opportunities in software development and machine learning.
<p className="mb-4 sm:mb-6 text-sm sm:text-base text-white">
{getText(aboutData.description2, language)}
</p>
<div className="flex justify-between items-center">
<div className="flex flex-row sm:flex-row justify-between items-center gap-4">
<ul className="flex gap-4">
<li>
<a href="https://linkedin.com/in/theluantran" target="_blank" rel="noopener noreferrer">
<FaLinkedin className="text-4xl text-white hover:text-blue-400 transition-colors" />
<FaLinkedin className="text-3xl sm:text-4xl text-white hover:text-blue-400 transition-colors" />
</a>
</li>
<li>
<a href="https://github.com/luantran" target="_blank" rel="noopener noreferrer">
<FaGithub className="text-4xl text-white hover:text-gray-400 transition-colors" />
<FaGithub className="text-3xl sm:text-4xl text-white hover:text-gray-400 transition-colors" />
</a>
</li>
</ul>

<a
href={resumePdf}
href={aboutData.resume}
download
className="flex items-center gap-2 bg-white text-blue-900 px-4 py-2 rounded-lg hover:bg-blue-100 transition-colors font-semibold"
className="flex items-center gap-2 bg-white text-blue-900 px-4 py-2 rounded-lg hover:bg-blue-100 transition-colors font-semibold text-sm sm:text-base"
>
<FaFileDownload className="text-xl" />
<span>Download CV</span>
<FaFileDownload className="text-lg sm:text-xl" />
<span>{getText(aboutData.downloadCV, language)}</span>
</a>
</div>
</div>
<div className="flex items-center justify-center h-screen">
<div className="flex items-center justify-center mt-8 md:mt-0">
<img
src={profileImg}
src={aboutData.image}
alt="Luan Tran profile picture"
className="h-1/2 rounded-lg"
className="h-64 sm:h-80 md:h-96 lg:h-[500px] rounded-lg"
/>
</div>
</div>
Expand Down
98 changes: 62 additions & 36 deletions src/components/Education.jsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,98 @@
import SectionArrow from "./SectionArrow.jsx";
import {educationData} from "./data/education.js"
import { educationData } from "./data/education.js";
import { useLanguage } from '../contexts/useLanguage.js';
import { getText } from '../utils/translationHelpers';

const EducationItem = ({ data }) => {
const { university, logo, degree, date, description, gpa } = data
const EducationItem = ({ data, index, language }) => {
const { university, logo, degree, date } = data;

return (
<div className="group relative my-[10px] flex w-1/2 justify-end pr-[22px] odd:justify-start odd:self-end odd:pl-[22px] odd:pr-0 sm:pr-[30px] sm:odd:pl-[30px]">
<div className={`group relative ${index > 0 ? 'my-[5px] sm:-mt-10 md:-mt-12 lg:-mt-14 xl:-mt-16' : 'my-[5px] sm:my-[3px]'} flex w-full sm:w-1/2 justify-start sm:justify-end sm:pr-[22px] sm:odd:justify-start sm:odd:self-end sm:odd:pl-[22px] sm:odd:pr-0 md:pr-[30px] md:odd:pl-[30px]`}>

<div className="
relative flex w-full flex-row items-center gap-6 rounded-lg bg-white/10 px-8 py-6 shadow-lg border border-gray-700
group-odd:flex-row-reverse
after:absolute
after:right-[-7.5px]
after:top-[calc(50%-7.5px)]
after:h-4 after:w-4
after:rotate-45
after:content-normal
after:bg-white
after:border-r
after:border-b
after:border-gray-700
relative flex w-full flex-col sm:flex-row items-start sm:items-center gap-3 sm:gap-4 md:gap-6 rounded-lg bg-white/10 px-4 sm:px-5 md:px-8 py-3 sm:py-4 md:py-6 shadow-lg border border-gray-700
sm:group-odd:flex-row-reverse
after:content-none
sm:after:content-normal
sm:after:absolute
sm:after:right-[-7.5px]
sm:after:top-[calc(50%-7.5px)]
sm:after:h-4 sm:after:w-4
sm:after:rotate-45
sm:after:bg-white
sm:after:border-r
sm:after:border-b
sm:after:border-gray-700

group-odd:after:left-[-7.5px]
group-odd:after:right-auto
group-odd:after:border-l
group-odd:after:border-t
group-odd:after:border-r-0
group-odd:after:border-b-0">
sm:group-odd:after:left-[-7.5px]
sm:group-odd:after:right-auto
sm:group-odd:after:border-l
sm:group-odd:after:border-t
sm:group-odd:after:border-r-0
sm:group-odd:after:border-b-0">

{/* University Logo */}
<img
src={logo}
alt={`${university} logo`}
className="w-16 h-16 object-contain flex-shrink-0"
alt={`${getText(university, language)} logo`}
className="w-10 h-10 sm:w-12 sm:h-12 md:w-14 md:h-14 lg:w-16 lg:h-16 object-contain flex-shrink-0"
/>

{/* Content */}
<div className="flex flex-col text-left group-odd:text-right">
<h3 className="font-bold text-3xl text-white">{university}</h3>
<p className="text-base text-xl text-gray-300 mt-1">{degree}</p>
<time className="text-lg text-gray-400 mt-2">{date}</time>
<div className="flex flex-col text-left sm:group-odd:text-right min-w-0 flex-1">
<h3
className="font-bold text-white leading-tight break-words"
style={{ fontSize: 'clamp(1rem, 2.5vw + 0.5rem, 1.875rem)' }}
>
{getText(university, language)}
</h3>
<p
className="text-gray-300 mt-0.5 sm:mt-1 break-words"
style={{ fontSize: 'clamp(0.75rem, 1.5vw + 0.3rem, 1.25rem)' }}
>
{getText(degree, language)}
</p>
<time
className="text-gray-400 mt-0.5 sm:mt-1 md:mt-2 break-words"
style={{ fontSize: 'clamp(0.7rem, 1.2vw + 0.25rem, 1.125rem)' }}
>
{date}
</time>
</div>

{/* Timeline dot */}
<span className="absolute -right-8 top-[calc(50%-10px)] z-50 h-5 w-5 rounded-[50%] border-[3px] border-slate-400 bg-white group-odd:-left-8 group-odd:right-auto sm:-right-10 sm:group-odd:-left-10" />
<span className="hidden sm:block absolute -right-8 top-[calc(50%-10px)] z-50 h-5 w-5 rounded-[50%] border-[3px] border-slate-400 bg-white group-odd:-left-8 group-odd:right-auto md:-right-10 md:group-odd:-left-10" />
</div>
</div>
)
);
}

function Education() {
const { language } = useLanguage();

const sectionTitle = {
en: "Education",
fr: "Éducation"
};

return (
<section id="education" className="min-h-screen flex flex-col items-center justify-between">
<div className="flex-grow flex items-center justify-center w-full">
<div className="max-w-screen-xl mx-auto px-4 min-w-6xl">
<h2 className="text-5xl font-bold text-white text-center mb-12">
Education
<div className="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8 w-full">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-white text-center mb-8 sm:mb-12">
{getText(sectionTitle, language)}
</h2>
<div className="relative my-10 flex flex-col after:absolute after:left-[calc(50%_-_2px)] after:h-full after:w-1 after:content-normal after:bg-slate-400">
{/* Timeline */}
<div className="relative my-6 sm:my-10 flex flex-col after:absolute after:left-[calc(50%_-_2px)] after:h-full after:w-1 after:bg-slate-400 after:opacity-0 sm:after:opacity-100">
{educationData.map((data, idx) => (
<EducationItem data={data} key={idx} />
<EducationItem data={data} index={idx} language={language} key={idx} />
))}
</div>
</div>
</div>
<SectionArrow targetSection="experience" />
</section>
)
);
}

export default Education
67 changes: 42 additions & 25 deletions src/components/Experience.jsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,42 @@
import SectionArrow from "./SectionArrow.jsx";
import {experienceData} from "./data/experience.js";

const ExperienceItem = ({ data }) => {
import { experienceData } from "./data/experience.js";
import { useLanguage } from '../contexts/useLanguage.js';
import { getText, getArray } from '../utils/translationHelpers';

const ExperienceItem = ({ data, language }) => {
return (
<div className="relative pl-12 pb-12">
<div className="absolute left-2.5 top-1.5 w-3 h-3 bg-green-500 rounded-full border-2 border-gray-900"></div>

<div className="bg-white/10 border border-gray-700 rounded-lg p-6 hover:border-gray-600 transition-colors">
<div className="flex items-start justify-between mb-3">
<div>
<span className="commit-hash text-yellow-400 text-sm">{data.hash}</span>
<h3 className="text-3xl font-semibold text-white mt-1">{data.title}</h3>
<h4 className="mt-1"><span className="px-2 py-1 bg-blue-500/20 text-blue-400 rounded text-md">{data.company}
</span> </h4>
<div className="relative pl-8 sm:pl-10 md:pl-12 pb-8 sm:pb-10 md:pb-12">
<div className="absolute left-2 sm:left-2.5 top-1.5 w-2.5 h-2.5 sm:w-3 sm:h-3 bg-green-500 rounded-full border-2 border-gray-900"></div>

<div className="bg-white/10 border border-gray-700 rounded-lg p-4 sm:p-5 md:p-6 hover:border-gray-600 transition-colors">
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between mb-3 gap-2">
<div className="min-w-0 flex-1">
<span className="commit-hash text-yellow-400 text-xs sm:text-sm">{data.hash}</span>
<h3 className="text-xl sm:text-2xl md:text-3xl font-semibold text-white mt-1 break-words">
{getText(data.title, language)}
</h3>
<h4 className="mt-1">
<span className="px-2 py-1 bg-blue-500/20 text-blue-400 rounded text-xs sm:text-sm md:text-md inline-block">
{data.company}
</span>
</h4>
</div>
<span className="text-gray-400 text-lg">{data.period}</span>
<span className="text-gray-400 text-sm sm:text-base md:text-lg whitespace-nowrap">
{getText(data.period, language)}
</span>
</div>
<div className="space-y-2 mb-4">
{data.achievements.map((achievementPoint, index) => (
<div key={index} className="text-green-400 text-md commit-hash">
<div className="space-y-1.5 sm:space-y-2 mb-3 sm:mb-4">
{getArray(data.achievements, language).map((achievementPoint, index) => (
<div key={index} className="text-green-400 text-xs sm:text-sm md:text-md commit-hash break-words">
<span className="mr-2">+</span>{achievementPoint}
</div>
))}
</div>
<div className="flex flex-wrap gap-2 pt-4 border-t border-white/50">
<div className="flex flex-wrap gap-1.5 sm:gap-2 pt-3 sm:pt-4 border-t border-white/50">
{data.tech.map((techPoint, index) => (
<span key={index} className="px-3 py-1 bg-gray-700 text-gray-300 rounded-full text-xs">{techPoint}</span>
<span key={index} className="px-2 sm:px-3 py-0.5 sm:py-1 bg-gray-700 text-gray-300 rounded-full text-[10px] sm:text-xs">
{techPoint}
</span>
))}
</div>
</div>
Expand All @@ -35,19 +45,26 @@ const ExperienceItem = ({ data }) => {
}

function Experience() {
const { language } = useLanguage();

const sectionTitle = {
en: "Work Experience",
fr: "Expérience Professionnelle"
};

return (
<section id="experience" className="min-h-screen flex flex-col items-center justify-between">
<div className="flex-grow flex items-center justify-center w-full ">
<div className="max-w-6xl mx-auto mb-16">
<h2 className="text-5xl font-bold text-white text-center mb-12">
Work Experience
<div className="flex-grow flex items-center justify-center w-full">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 mb-12 sm:mb-16 w-full">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-white text-center mb-8 sm:mb-12">
{getText(sectionTitle, language)}
</h2>

<div className="relative">
<div className="absolute left-4 top-0 bottom-0 w-0.5 bg-gray-700"></div>
<div className="absolute left-3 sm:left-3.5 md:left-4 top-0 bottom-0 w-1 sm:w-1.5 bg-gray-300"></div>

{experienceData.map((experience) => (
<ExperienceItem key={experience.hash} data={experience} />
<ExperienceItem key={experience.hash} data={experience} language={language} />
))}
</div>
</div>
Expand Down
Loading