diff --git a/.woodpecker.yml b/.woodpecker.yml
index 1553b0b..ed91729 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,123 +1,124 @@
steps:
- build:
- image: node:22
- commands:
- - npm ci
- - npm run build
- - echo "VERSION=$(cat version.txt)" > .env
+ build:
+ image: node:22
+ commands:
+ - npm ci
+ - npm run build
+ - echo "VERSION=$(cat version.txt)" > .env
- clear-from-host:
- image: appleboy/drone-ssh
- settings:
- host:
- from_secret: ssh_host
- username:
- from_secret: ssh_username
- key:
- from_secret: ssh_key
- port: 2332
- script:
- - cd /home/mnisyif/docker-containers/mnisyif/frontend
- - rm -rf data index-*.js index-*.css logos papers pp projects
- - find . -maxdepth 1 -type f -delete
- - echo "Target directory cleared, resumes folder preserved"
+ clear-from-host:
+ image: appleboy/drone-ssh
+ settings:
+ host:
+ from_secret: ssh_host
+ username:
+ from_secret: ssh_username
+ key:
+ from_secret: ssh_key
+ port: 2332
+ script:
+ - cd /home/mnisyif/docker-containers/mnisyif/frontend
+ - rm -rf data index-*.js index-*.css logos papers pp projects
+ - find . -maxdepth 1 -type f -delete
+ - echo "Target directory cleared, resumes folder preserved"
copy-to-host:
- image: appleboy/drone-scp
- settings:
- host:
- from_secret: ssh_host
- username:
- from_secret: ssh_username
- key:
- from_secret: ssh_key
- port: 2332
- target: /home/mnisyif/docker-containers/mnisyif/frontend
- source:
- - dist/
- - nginx.conf
- - version.txt
- deploy:
- image: appleboy/drone-ssh
- settings:
- host:
- from_secret: ssh_host
- username:
- from_secret: ssh_username
- key:
- from_secret: ssh_key
- port: 2332
- script:
- - cd /home/mnisyif/docker-containers/mnisyif/frontend
- - VERSION=$(cat version.txt)
- - echo "Nginx configuration:"
- - cat nginx.conf
- - echo "Contents of dist directory:"
- - ls -la dist
- # Stop and remove the existing container if it exists
- - docker stop frontend || true
- - docker rm frontend || true
- # Run the new container with the current version, mounting the files
- - >
- docker run -d --name frontend -p 5173:80
- -v /home/mnisyif/docker-containers/mnisyif/frontend/dist:/usr/share/nginx/html:ro
- -v /home/mnisyif/docker-containers/mnisyif/frontend/nginx.conf:/etc/nginx/nginx.conf:ro
- nginx:alpine
- # Tag the running container with the version
- - docker tag nginx:alpine frontend:$VERSION
- - echo "Deployment completed"
+ image: appleboy/drone-scp
+ settings:
+ host:
+ from_secret: ssh_host
+ username:
+ from_secret: ssh_username
+ key:
+ from_secret: ssh_key
+ port: 2332
+ target: /home/mnisyif/docker-containers/mnisyif/frontend
+ source:
+ - dist/
+ - nginx.conf
+ - version.txt
- confirm-deployment:
- image: appleboy/drone-ssh
- settings:
- host:
- from_secret: ssh_host
- username:
- from_secret: ssh_username
- key:
- from_secret: ssh_key
- port: 2332
- script:
- - cd /home/mnisyif/docker-containers/mnisyif/frontend
- - VERSION=$(cat version.txt)
- - echo "Confirming deployment for version: $VERSION"
- - docker ps -a
- - if ! docker ps | grep -q frontend-$VERSION; then
- echo "Container failed to start";
- docker logs frontend-$VERSION;
- exit 1;
- fi
- - echo "Container is running, checking Nginx configuration..."
- - docker exec frontend-$VERSION nginx -t || { echo "Nginx configuration test failed"; exit 1; }
- - echo "Listing contents of /usr/share/nginx/html"
- - docker exec frontend-$VERSION ls -la /usr/share/nginx/html
- - echo "Listing contents of /usr/share/nginx/html/resumes"
- - docker exec frontend-$VERSION ls -la /usr/share/nginx/html/resumes || echo "Resumes directory not found"
- - echo "Checking HTTP response..."
- - curl -I http://localhost:5173 || { echo "HTTP request failed"; exit 1; }
- - echo "Deployment confirmed successfully"
+ deploy:
+ image: appleboy/drone-ssh
+ settings:
+ host:
+ from_secret: ssh_host
+ username:
+ from_secret: ssh_username
+ key:
+ from_secret: ssh_key
+ port: 2332
+ script:
+ - cd /home/mnisyif/docker-containers/mnisyif/frontend
+ - VERSION=$(cat version.txt)
+ - echo "Nginx configuration:"
+ - cat nginx.conf
+ - echo "Contents of dist directory:"
+ - ls -la dist
+ # Stop and remove the existing container if it exists
+ - docker stop frontend || true
+ - docker rm frontend || true
+ # Run the new container with the current version, mounting the files
+ - >
+ docker run -d --name frontend -p 5173:80
+ -v /home/mnisyif/docker-containers/mnisyif/frontend/dist:/usr/share/nginx/html:ro
+ -v /home/mnisyif/docker-containers/mnisyif/frontend/nginx.conf:/etc/nginx/nginx.conf:ro
+ nginx:alpine
+ # Tag the running container with the version
+ - docker tag nginx:alpine frontend:$VERSION
+ - echo "Deployment completed"
- cleanup:
- image: appleboy/drone-ssh
- settings:
- host:
- from_secret: ssh_host
- username:
- from_secret: ssh_username
- key:
- from_secret: ssh_key
- port: 2332
- script:
- - echo "Performing cleanup..."
- - docker system prune -f --volumes
- - >
- for img in $(docker images frontend --format "{{.Tag}}" | grep -v $(cat /home/mnisyif/docker-containers/mnisyif/frontend/version.txt)); do
- docker rmi frontend:$img || true;
- done
- - echo "Cleanup completed"
+ confirm-deployment:
+ image: appleboy/drone-ssh
+ settings:
+ host:
+ from_secret: ssh_host
+ username:
+ from_secret: ssh_username
+ key:
+ from_secret: ssh_key
+ port: 2332
+ script:
+ - echo "Verifying deployment..."
+ # Verify the container is running
+ - docker ps | grep frontend || { echo "Container failed to start"; exit 1; }
+ # Display container logs
+ - docker logs frontend
+ # Test Nginx configuration
+ - docker exec frontend nginx -t
+ # Check Nginx process
+ - docker exec frontend ps aux | grep nginx
+ # Check contents of /usr/share/nginx/html in the container
+ - docker exec frontend ls -la /usr/share/nginx/html
+ # Perform a simple HTTP request to check if the server is responding
+ - curl -I http://localhost:5173 || { echo "HTTP request failed"; exit 1; }
+ - echo "Deployment confirmed successfully"
-trigger:
- branch:
- - master
- event:
- - push
+ cleanup:
+ image: appleboy/drone-ssh
+ settings:
+ host:
+ from_secret: ssh_host
+ username:
+ from_secret: ssh_username
+ key:
+ from_secret: ssh_key
+ port: 2332
+ script:
+ - echo "Performing cleanup..."
+ - docker system prune -f --volumes
+ - >
+ for img in $(docker images frontend --format "{{.Tag}}" | grep -v $(cat /home/mnisyif/docker-containers/mnisyif/frontend/version.txt)); do
+ docker rmi frontend:$img || true
+ done
+ - echo "Cleanup completed"
+
+# trigger:
+# branch:
+# - master
+# event:
+# - push
+
+when:
+ - branch: master
+ event: push
diff --git a/index.html b/index.html
index c4e0c9c..b0b7d4c 100644
--- a/index.html
+++ b/index.html
@@ -2,7 +2,7 @@
-
+
Murtadha Nisyif | Portfolio
diff --git a/public/assets/data/projectsData.json b/public/assets/data/projectsData.json
index 4d0bbd4..cfc9097 100644
--- a/public/assets/data/projectsData.json
+++ b/public/assets/data/projectsData.json
@@ -2,52 +2,147 @@
{
"id": 1,
"title": "PaperKeypad",
- "category": "Misc",
+ "category": ["Misc"],
"images": ["/assets/projects/keypad0.jpg"],
"description": "Ever need to use a keyboard, but you got only your phone and a printer, PaperKeypad is a keypad that is made of paper.",
"technologies": ["Java", "JavaFX", "Android Studio"],
"features": ["Mobile sensor manipulation", "Responsive design"],
- "githubLink": "https://github.com/betato/PaperKeypad"
+ "githubLink": "https://github.com/betato/PaperKeypad",
+ "date": 2019
},
{
"id": 2,
"title": "StonkBot",
- "category": "Misc",
+ "category": ["Misc"],
"images": ["/assets/projects/stonkbot0.jpg"],
"description": "The fear of losing money is common among first-time and seasoned investors alike. This inspired the creation of Stonk Bot, a fantasy trading platform that can be implemented in Discord.",
"technologies": ["Python", "VS Code", "Matplotlib", "Financial Modeling Prep API", "Discord API"],
"features": ["Buy shares", "Sell shares", "View stock information", "View personal portfolio", "View leaderboard"],
"githubLink": "https://github.com/aidanbruneel/stonkbot",
- "liveLink": "https://discord.com/invite/tQNkk7v7R8"
+ "liveLink": "https://discord.com/invite/tQNkk7v7R8",
+ "date": 2022
},
{
"id": 3,
"title": "Car Model Classification",
- "category": "Machine Learning",
+ "category": ["Machine Learning"],
"images": ["/assets/projects/carmodelclass0.png"],
"description": "Developing a computer vision application to identify a vehicle model from a given image is an interesting and challenging problem to solve. Challenge of this problem is that different vehicle models can appear very similar and the same vehicle can look different and hard to identify depending on lighting conditions, angle and many other factors. In this project, I decided to train a Convolutional Neural Network(CNN) to generate a model that can identify a given vehicle model.",
"technologies": ["Python", "Tensorflow", "CNN", "Deep learning", "ResNet", "EfficientNet", "Stanford Cars Dataset"],
"features": ["Buy shares", "Sell shares", "View stock information", "View personal portfolio", "View leaderboard"],
- "githubLink": "https://github.com/mnisyif/carClassificationModel"
+ "githubLink": "https://github.com/mnisyif/carClassificationModel",
+ "date": 2022
},
{
"id": 4,
"title": "Memory Allocation Simulations",
- "category": "Misc",
+ "category": ["Misc"],
"images": ["/assets/projects/memallc0.png"],
"description": "This implementation uses doubly linked list to simulate memory allocation given 4 different memory management algorithms",
"technologies": ["C", "CMake", "Data structures"],
"features": ["First fit", "Best fit", "Next fit", "Worst fit"],
- "githubLink": "https://github.com/mnisyif/MemoryAllocationAlgorithm/tree/main"
+ "githubLink": "https://github.com/mnisyif/MemoryAllocationAlgorithm/tree/main",
+ "date": 2022
},
{
"id": 5,
- "title": "Portfolio Website",
- "category": "Web Development",
- "images": ["/assets/projects/memallc0.png"],
- "description": "This implementation uses doubly linked list to simulate memory allocation given 4 different memory management algorithms",
- "technologies": ["C", "CMake", "Data structures"],
- "features": ["First fit", "Best fit", "Next fit", "Worst fit"],
- "githubLink": "https://github.com/mnisyif/MemoryAllocationAlgorithm/tree/main"
- }
+ "title": "Transformer-based Semantic Transcoding",
+ "category": ["Machine Learning"],
+ "images": ["/assets/projects/semantic01.png"],
+ "description": "Developed PyTorch models for E2E semantic transcoding, deployed on Xilinx SoC boards using Vitis AI™",
+ "technologies": ["PyTorch", "Vitis AI", "Xilinx SoC", "Machine Learning", "C++"],
+ "features": ["E2E semantic transcoding", "Hardware acceleration", "SoC deployment", "C++ deployment"],
+ "githubLink": "https://github.com/mnisyif/masters-research",
+ "date": 2024
+ },
+ {
+ "id": 6,
+ "title": "Clean Architecture C# Backend",
+ "category": ["Web Development"],
+ "images": ["/assets/projects/clean_architecture_backend.png"],
+ "description": "Engineered a scalable portfolio website backend using C#, adhering to Clean Architecture principles and implementing CI/CD pipeline for efficient deployment",
+ "technologies": ["C#", "Clean Architecture", "CI/CD", "REST Api"],
+ "features": ["Scalable backend", "Clean Architecture implementation", "Automated deployment", "RESTful"],
+ "githubLink": "https://github.com/mnisyif/portfolio-backend",
+ "date": 2024
+ },
+ {
+ "id": 7,
+ "title": "DevOps Homelab Maestro",
+ "category": ["DevOps"],
+ "images": ["/assets/projects/homelab_maestro.png"],
+ "description": "Orchestrating a robust homelab environment with Docker containers, Kubernetes clusters, Ceph distributed storage, and CI/CD pipelines for seamless application deployment",
+ "technologies": ["Docker", "Kubernetes", "Ceph", "CI/CD"],
+ "features": ["Containerized applications", "Orchestration", "Distributed storage", "Automated deployment"],
+ "date": 2023
+ },
+ {
+ "id": 8,
+ "title": "RL Dynamic Noise Cancelling",
+ "category": ["Machine Learning"],
+ "images": ["/assets/projects/noise_cancelling.png"],
+ "description": "Implemented real-time Automatic Noise Filtering using Reinforcement Learning and Dynamic Sparse Training in PyTorch",
+ "technologies": ["PyTorch", "Reinforcement Learning", "Dynamic Sparse Training", "Jupyter Notebooks"],
+ "features": ["Real-time filtering", "Automatic noise cancellation", "Sparse training", "Interactive development"],
+ "githubLink": "https://github.com/mnisyif/rl-noise-cancelling",
+ "date": 2023
+ },
+ {
+ "id": 9,
+ "title": "Real-Time Text-to-Braille",
+ "category": ["Embedded Systems"],
+ "images": ["/assets/projects/braille01.jpg","/assets/projects/braille02.jpg"],
+ "description": "Built a Raspberry Pi device for real-time image-to-Braille conversion, enhancing accessibility for the deaf-blind community",
+ "technologies": ["Raspberry Pi", "Image Processing", "OCR", "Python"],
+ "features": ["Real-time conversion", "Low-cost OCR algorithm", "Lookup table for Braille conversion", "Accessibility enhancement"],
+ "date": 2023
+ },
+ {
+ "id": 10,
+ "title": "ZAMAZ UTI Diagnosis",
+ "category": ["Embedded Systems"],
+ "images": ["/assets/projects/zamaz01.jpg","/assets/projects/zamaz02.jpg"],
+ "description": "Developed a Raspberry Pi-based system for automated urine test analysis, achieving 16x faster results than standard methods",
+ "technologies": ["Raspberry Pi", "Python", "Image Processing", "Healthcare Technology"],
+ "features": ["Automated analysis", "Pixel-based concentration calculation", "E. Coli and Staph bacteria detection", "Rapid results"],
+ "date": 2022
+ },
+ {
+ "id": 11,
+ "title": "HAM10K Image Classification with Deep Networks",
+ "category": ["Machine Learning"],
+ "images": ["/assets/projects/ham10k_classification.png"],
+ "description": "Developed and compared three deep learning models (MLP+PCA, DCNN, RegNetY-320) for skin cancer classification using the HAM10000 dataset, achieving 96.89% accuracy with RegNetY-320",
+ "technologies": ["PyTorch", "Deep Learning", "CNN", "RegNet", "PCA", "Python"],
+ "features": ["Multi-model comparison", "Data balancing and augmentation", "High accuracy classification", "Medical image analysis"],
+ "githubLink": "https://github.com/nithinprasad94/ENGG6600_DL_Final_Project",
+ "liveLink":"https://youtu.be/zHbRmIn7gPo",
+ "date": 2023
+ },
+ {
+ "id": 12,
+ "title": "Heart Disease Prediction Web App",
+ "category": ["Machine Learning", "Web Development"],
+ "images": ["/assets/projects/heartdis01.png"],
+ "description": "Developed a Flask-based web application that predicts the likelihood of heart disease using machine learning models. The app processes user input, applies feature encoding and scaling, and provides instant predictions.",
+ "technologies": [
+ "Python",
+ "Flask",
+ "NumPy",
+ "Scikit-learn",
+ "Pickle",
+ "HTML",
+ "Machine Learning"
+ ],
+ "features": [
+ "User-friendly web interface for input",
+ "Real-time prediction using pre-trained model",
+ "Feature encoding and scaling",
+ "Integration of multiple ML preprocessing steps",
+ "Handling of both categorical and numerical inputs"
+ ],
+ "githubLink": "https://github.com/zeyadghulam/engg6600-assignment3",
+ "date":2023
+ }
+
]
diff --git a/public/assets/logos/favicon.png b/public/assets/logos/favicon.png
new file mode 100644
index 0000000..97af622
Binary files /dev/null and b/public/assets/logos/favicon.png differ
diff --git a/public/assets/logos/favicon.svg b/public/assets/logos/favicon.svg
new file mode 100644
index 0000000..f1f5a57
--- /dev/null
+++ b/public/assets/logos/favicon.svg
@@ -0,0 +1,30 @@
+
+
+
diff --git a/public/assets/pp/pp.jpg b/public/assets/pp/pp.jpg
index 1f57648..3226c9b 100644
Binary files a/public/assets/pp/pp.jpg and b/public/assets/pp/pp.jpg differ
diff --git a/public/assets/projects/braille01.jpg b/public/assets/projects/braille01.jpg
new file mode 100644
index 0000000..5bb6647
Binary files /dev/null and b/public/assets/projects/braille01.jpg differ
diff --git a/public/assets/projects/braille02.jpg b/public/assets/projects/braille02.jpg
new file mode 100644
index 0000000..8454940
Binary files /dev/null and b/public/assets/projects/braille02.jpg differ
diff --git a/public/assets/projects/heartdis01.png b/public/assets/projects/heartdis01.png
new file mode 100644
index 0000000..31f095d
Binary files /dev/null and b/public/assets/projects/heartdis01.png differ
diff --git a/public/assets/projects/semantic01.png b/public/assets/projects/semantic01.png
new file mode 100644
index 0000000..e9ec5fa
Binary files /dev/null and b/public/assets/projects/semantic01.png differ
diff --git a/public/assets/projects/zamaz01.jpg b/public/assets/projects/zamaz01.jpg
new file mode 100644
index 0000000..bf7df69
Binary files /dev/null and b/public/assets/projects/zamaz01.jpg differ
diff --git a/public/assets/projects/zamaz02.jpg b/public/assets/projects/zamaz02.jpg
new file mode 100644
index 0000000..021be5b
Binary files /dev/null and b/public/assets/projects/zamaz02.jpg differ
diff --git a/public/assets/resumes/Murtadha.pdf b/public/assets/resumes/Murtadha.pdf
deleted file mode 100644
index afa9fbd..0000000
Binary files a/public/assets/resumes/Murtadha.pdf and /dev/null differ
diff --git a/src/App.jsx b/src/App.jsx
index 5c030a1..a79b249 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -13,6 +13,8 @@ import InfoSection from "./shared/components/info/InfoSection";
import styles from "./App.module.css";
+import { fetchEducationData, fetchExperienceData, fetchPersonalData, fetchProjectsData } from "./utils/dataFetcher";
+
function App() {
const [educationData, setEducationData] = useState([]);
const [experienceData, setExperienceData] = useState([]);
@@ -20,35 +22,21 @@ function App() {
const [personalData, setPersonalData] = useState([]);
useEffect(() => {
- const fetchEducationData = async () => {
- const response = await fetch("/assets/data/educationData.json");
- const data = await response.json();
- setEducationData(data);
+ const fetchData = async () => {
+ const education = await fetchEducationData();
+ setEducationData(education);
+
+ const experience = await fetchExperienceData();
+ setExperienceData(experience);
+
+ const projects = await fetchProjectsData();
+ setProjectsData(projects);
+
+ const personal = await fetchPersonalData();
+ setPersonalData(personal);
};
-
- const fetchExperienceData = async () => {
- const response = await fetch("/assets/data/experienceData.json");
- const data = await response.json();
- setExperienceData(data);
- };
-
- const fetchProjectsData = async () => {
- const response = await fetch("/assets/data/projectsData.json");
- const data = await response.json();
- setProjectsData(data);
- };
-
- const fetchPersonalData = async () => {
- const response = await fetch("/assets/data/personalData.json");
- const data = await response.json();
- setPersonalData(data);
- // console.log(data)
- };
-
- fetchEducationData();
- fetchExperienceData();
- fetchProjectsData();
- fetchPersonalData();
+
+ fetchData();
}, []);
return (
diff --git a/src/components/projects/Projects.jsx b/src/components/projects/Projects.jsx
index 6d55663..bba1c0c 100644
--- a/src/components/projects/Projects.jsx
+++ b/src/components/projects/Projects.jsx
@@ -17,13 +17,16 @@ function Projects({ title, data }) {
}, []);
const categories = useMemo(() => {
- const cats = new Set(data.map((project) => project.category));
+ const cats = new Set(data.flatMap((project) => project.category));
return ["All", ...Array.from(cats)];
}, [data]);
- const filteredProjects = useMemo(() => {
- if (activeFilter === "All") return data;
- return data.filter((project) => project.category === activeFilter);
+ const sortedAndFilteredProjects = useMemo(() => {
+ let filteredProjects = activeFilter === "All"
+ ? data
+ : data.filter((project) => project.category.includes(activeFilter));
+
+ return filteredProjects.sort((a, b) => b.date - a.date);
}, [data, activeFilter]);
const handleFilterClick = (category) => {
@@ -41,14 +44,23 @@ function Projects({ title, data }) {
{title}
{categories.map((category) => (
-
- {filteredProjects.map((project) => (
-
+ {sortedAndFilteredProjects.map((project) => (
+
))}
{selectedProject && }
@@ -56,4 +68,4 @@ function Projects({ title, data }) {
);
}
-export default Projects;
+export default Projects;
\ No newline at end of file
diff --git a/src/components/projects/projectCard/ProjectCard.jsx b/src/components/projects/projectCard/ProjectCard.jsx
index 3a967b8..efecf1c 100644
--- a/src/components/projects/projectCard/ProjectCard.jsx
+++ b/src/components/projects/projectCard/ProjectCard.jsx
@@ -1,7 +1,44 @@
-import React from "react";
+import React, { useRef, useEffect, useState } from "react";
import styles from "./ProjectCard.module.css";
function ProjectCard({ project, onClick, className }) {
+ const [truncatedDescription, setTruncatedDescription] = useState(project.description);
+ const descriptionRef = useRef(null);
+
+ const formatList = (items) => {
+ return items.map((item, index, arr) => (
+
+ {item}
+ {index < arr.length - 1 && , }
+
+ ));
+ };
+
+ useEffect(() => {
+ const truncateDescription = () => {
+ const element = descriptionRef.current;
+ if (!element) return;
+
+ const maxHeight = parseInt(window.getComputedStyle(element).lineHeight) * 4; // 4 lines
+ let text = project.description;
+ element.textContent = text;
+
+ while (element.scrollHeight > maxHeight && text.length > 0) {
+ text = text.slice(0, -1);
+ element.textContent = text + '...';
+ }
+
+ setTruncatedDescription(element.textContent);
+ };
+
+ truncateDescription();
+ window.addEventListener('resize', truncateDescription);
+
+ return () => {
+ window.removeEventListener('resize', truncateDescription);
+ };
+ }, [project.description]);
+
return (
onClick(project)}>
@@ -9,11 +46,17 @@ function ProjectCard({ project, onClick, className }) {
{project.title}
-
{project.category}
-
{project.description}
+
+ {formatList(project.category)}
+
+
{truncatedDescription}
+
+ {formatList(project.technologies)}
+
+ {/*
{project.date}
*/}
);
}
-export default ProjectCard;
+export default ProjectCard;
\ No newline at end of file
diff --git a/src/components/projects/projectCard/ProjectCard.module.css b/src/components/projects/projectCard/ProjectCard.module.css
index 0431321..29f06ad 100644
--- a/src/components/projects/projectCard/ProjectCard.module.css
+++ b/src/components/projects/projectCard/ProjectCard.module.css
@@ -5,9 +5,10 @@
overflow: hidden;
transition: transform 0.3s ease;
cursor: pointer;
- height: 450px; /* Fixed height for the card */
display: flex;
flex-direction: column;
+ height: auto;
+ min-height: 450px;
}
.card:hover {
@@ -32,7 +33,6 @@
display: flex;
flex-direction: column;
padding: 1rem;
- overflow: hidden;
}
.title {
@@ -42,7 +42,7 @@
color: #333;
}
-.category {
+.categories {
font-size: 0.8rem;
color: #666;
margin-bottom: 0.5rem;
@@ -51,10 +51,24 @@
.description {
font-size: 0.9rem;
color: #666;
- flex-grow: 1;
+ margin-bottom: 0.5rem;
overflow: hidden;
- display: -webkit-box;
- -webkit-line-clamp: 4; /* Adjust this number to show more or fewer lines */
- -webkit-box-orient: vertical;
- text-overflow: ellipsis;
+ line-height: 1.4;
+ max-height: calc(1.4em * 4); /* 4 lines of text */
}
+
+.technologies {
+ font-size: 0.8rem;
+ color: #0066cc;
+ margin-bottom: 0.5rem;
+}
+
+.date {
+ font-size: 0.8rem;
+ color: #999;
+ margin-top: auto;
+}
+
+.separator {
+ margin: 0 2px;
+}
\ No newline at end of file
diff --git a/src/shared/components/resumeDownloader/ResumeDownloader.jsx b/src/shared/components/resumeDownloader/ResumeDownloader.jsx
index b5da1fe..acbce79 100644
--- a/src/shared/components/resumeDownloader/ResumeDownloader.jsx
+++ b/src/shared/components/resumeDownloader/ResumeDownloader.jsx
@@ -8,7 +8,7 @@ const ResumeDownloader = ({ resumeLink }) => {
const link = document.createElement("a");
link.href = resumeLink;
console.log(link.href);
- link.download = "Murtadha.pdf";
+ link.download = "Murtadha_Nisyif_Resume.pdf";
link.target = "_blank";
link.rel = "noopener noreferrer";
diff --git a/src/utils/dataFetcher.js b/src/utils/dataFetcher.js
new file mode 100644
index 0000000..5298702
--- /dev/null
+++ b/src/utils/dataFetcher.js
@@ -0,0 +1,19 @@
+export const fetchEducationData = async () => {
+ const response = await fetch("/assets/data/educationData.json");
+ return await response.json();
+};
+
+export const fetchExperienceData = async () => {
+ const response = await fetch("/assets/data/experienceData.json");
+ return await response.json();
+};
+
+export const fetchProjectsData = async () => {
+ const response = await fetch("/assets/data/projectsData.json");
+ return await response.json();
+};
+
+export const fetchPersonalData = async () => {
+ const response = await fetch("/assets/data/personalData.json");
+ return await response.json();
+};
\ No newline at end of file
diff --git a/version.txt b/version.txt
index aac2dac..7fd0b1e 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-0.12.1
\ No newline at end of file
+0.12.4
\ No newline at end of file