Merge pull request 'add of mobile images, change style and update README.md' (#12) from feature/scrollingImages into dev
Reviewed-on: #12
@ -3,7 +3,8 @@
|
||||
## For V1 :
|
||||
- Revoir les titres de section (+espace au dessus des titres)
|
||||
- Revoir ma section experiences
|
||||
- Mettre un défilement de plusieurs images sur les projets
|
||||
- Ajouter un logo
|
||||
- Mettre mon nom prénom en titre de site
|
||||
|
||||
## For V2 :
|
||||
- Mettre la base de donnée en place
|
||||
|
||||
BIN
public/assets/images/SAE_C++/SAE_C++_1.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
public/assets/images/SAE_C++/SAE_C++_2.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
public/assets/images/SAE_C++/SAE_C++_3.png
Normal file
|
After Width: | Height: | Size: 643 KiB |
BIN
public/assets/images/codev/codev_1.png
Normal file
|
After Width: | Height: | Size: 923 KiB |
BIN
public/assets/images/codev/codev_2.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
public/assets/images/codev/codev_3.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
public/assets/images/codev/codev_4.png
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
public/assets/images/codev/codev_5.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 163 KiB |
BIN
public/assets/images/proxmox/proxmox_1.png
Normal file
|
After Width: | Height: | Size: 232 KiB |
BIN
public/assets/images/proxmox/proxmox_2.png
Normal file
|
After Width: | Height: | Size: 800 KiB |
BIN
public/assets/images/proxmox/proxmox_3.png
Normal file
|
After Width: | Height: | Size: 312 KiB |
BIN
public/assets/images/proxmox/proxmox_4.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
public/assets/images/proxmox/proxmox_5.png
Normal file
|
After Width: | Height: | Size: 328 KiB |
@ -24,7 +24,7 @@ const NavBar = () => {
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
handleScroll(); // check initial
|
||||
handleScroll();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
|
||||
@ -1,13 +1,40 @@
|
||||
import SingleProject from "./SingleProject.jsx";
|
||||
import "../styles/Projects.css"
|
||||
function Projects() {
|
||||
const desc = "A platform for creating and sharing code snippets with a clean and intuitive design. It allows you to create, share, and discover code snippets with ease."
|
||||
const codevProject = {
|
||||
title: "Codev",
|
||||
image: "codev",
|
||||
color: "orange",
|
||||
nbImage: 5,
|
||||
skills: ['HTML', 'CSS', 'PHP', 'JavaScript', 'SQL'],
|
||||
description: "A collaborative platform where developers can create, share, and grow coding projects. Built for solo makers or teams, it features real-time messaging and a centralized workspace."
|
||||
}
|
||||
const SAECProject = {
|
||||
title: "SAE C++",
|
||||
image: "SAE_C++",
|
||||
color: "purple",
|
||||
nbImage: 3,
|
||||
skills: ['C++', 'Git'],
|
||||
description: "A C++ program developed for creating and managing treasure hunt records, with a web interface that allows browsing and consulting the entries."
|
||||
}
|
||||
const proxmoxProject = {
|
||||
title: "Proxmox",
|
||||
image: "proxmox",
|
||||
color: "green",
|
||||
nbImage: 5,
|
||||
skills: [],
|
||||
description: "A Proxmox-based infrastructure that consolidates my personal cloud, home automation, code hosting, media services and web hosting into a single, self-hosted environment."
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<section id="projects-section">
|
||||
<h1>Projects</h1>
|
||||
<SingleProject image="landscape" title="Codev" description={desc} skills={['HTML', 'CSS', 'PHP', 'JavaScript', 'SQL']} color="orange" />
|
||||
<SingleProject image="landscape" title="SAE C++" description={desc} skills={['C++', 'Git']} color="purple" />
|
||||
<SingleProject image="proxmox" title="Proxmox" description={desc} skills={[]} color="green" />
|
||||
<div className="projects-section-list">
|
||||
<SingleProject image={codevProject.image} title={codevProject.title} description={codevProject.description} skills={codevProject.skills} color={codevProject.color} nbImage={codevProject.nbImage}/>
|
||||
<SingleProject image={SAECProject.image} title={SAECProject.title} description={SAECProject.description} skills={SAECProject.skills} color={SAECProject.color} nbImage={SAECProject.nbImage}/>
|
||||
<SingleProject image={proxmoxProject.image} title={proxmoxProject.title} description={proxmoxProject.description} skills={proxmoxProject.skills} color={proxmoxProject.color} nbImage={proxmoxProject.nbImage}/>
|
||||
</div>
|
||||
<div className="show-more-container">
|
||||
<p className="show-more-link">
|
||||
Show more
|
||||
|
||||
@ -1,32 +1,79 @@
|
||||
import SkillCard from './SkillCard';
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import SkillCard from "./SkillCard";
|
||||
import "../styles/SingleProject.css";
|
||||
function SingleProject({image, title, description, skills, color}) {
|
||||
|
||||
function SingleProject({ image, title, description, skills, color, nbImage }) {
|
||||
const [imageID, setImageID] = useState(1);
|
||||
const [isFading, setIsFading] = useState(true);
|
||||
const intervalRef = useRef(null);
|
||||
|
||||
const handleChangeImage = (direction) => {
|
||||
if (nbImage <= 1) return;
|
||||
|
||||
setIsFading(false);
|
||||
|
||||
setTimeout(() => {
|
||||
setImageID((prevID) =>
|
||||
direction === 1
|
||||
? (prevID % nbImage) + 1
|
||||
: prevID === 1 ? nbImage : prevID - 1
|
||||
);
|
||||
setIsFading(true);
|
||||
}, 300);
|
||||
|
||||
clearInterval(intervalRef.current);
|
||||
startAutoSlide();
|
||||
};
|
||||
|
||||
const startAutoSlide = () => {
|
||||
intervalRef.current = setInterval(() => {
|
||||
handleChangeImage(1);
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
startAutoSlide();
|
||||
return () => clearInterval(intervalRef.current);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="single-project">
|
||||
<div className="single-project-left">
|
||||
<img src={`/public/assets/images/${image}.png`} alt={image}/>
|
||||
</div>
|
||||
<div className="single-project-middle">
|
||||
<div aria-hidden="true" className={`single-project-line color-${color}`}></div>
|
||||
</div>
|
||||
<div className="single-project-right">
|
||||
<h3 className="single-project-title">{title}</h3>
|
||||
<p className="single-project-description">
|
||||
{description}
|
||||
</p>
|
||||
<ul className="single-project-skills-list">
|
||||
{skills.map(skill => (
|
||||
<li><SkillCard text={skill}/></li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="single-project-link">
|
||||
Learn more
|
||||
</p>
|
||||
<button onClick={() => handleChangeImage(-1)} className="arrow preview">{'<'}</button>
|
||||
|
||||
<img
|
||||
src={`/public/assets/images/${image}/${image}_${imageID}.png`}
|
||||
alt={image}
|
||||
className={isFading ? 'fade-in' : 'fade-out'}
|
||||
/>
|
||||
|
||||
<button onClick={() => handleChangeImage(1)} className="arrow next">{'>'}</button>
|
||||
</div>
|
||||
|
||||
<div className="single-project-right">
|
||||
<div className="single-project-right-top">
|
||||
<div className={`single-project-line color-${color}`}></div>
|
||||
<h3 className="single-project-title">{title}</h3>
|
||||
</div>
|
||||
|
||||
<div className="single-project-right-bottom">
|
||||
<p className="single-project-description" style={{ whiteSpace: "pre-line" }}>
|
||||
{description}
|
||||
</p>
|
||||
|
||||
<ul className="single-project-skills-list">
|
||||
{skills.map((skill) => (
|
||||
<li key={skill}>
|
||||
<SkillCard text={skill} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<p className="single-project-link">Learn more</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default SingleProject
|
||||
export default SingleProject;
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
background-color: #0D0D0D;
|
||||
}
|
||||
|
||||
@ -26,4 +26,8 @@
|
||||
color: #1E1E1E;
|
||||
transform: translateY(-3px);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.projects-section-list {
|
||||
display: flex;
|
||||
}
|
||||
@ -1,11 +1,12 @@
|
||||
.single-project {
|
||||
display: flex;
|
||||
width: 80%;
|
||||
flex-direction: column;
|
||||
width: 29%;
|
||||
margin: 2rem auto;
|
||||
background-color: #1E1E1E;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease-in-out;
|
||||
border-radius: 12px;
|
||||
background-color: #1E1E1E;
|
||||
}
|
||||
|
||||
.single-project:hover {
|
||||
@ -14,15 +15,25 @@
|
||||
}
|
||||
|
||||
.single-project-left {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 55%;
|
||||
width: 100%; /* largeur fixe de la left */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.single-project-left img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%; /* occupe toute la largeur du conteneur */
|
||||
height: auto; /* hauteur ajustée automatiquement pour garder l’intégralité */
|
||||
display: block;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
opacity: 1;
|
||||
object-fit: contain; /* force l’image à remplir le conteneur (déformation possible) */
|
||||
}
|
||||
|
||||
.single-project-left img.fade-out {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.single-project-right {
|
||||
@ -31,6 +42,41 @@
|
||||
justify-content: space-between;
|
||||
padding: 2rem 2rem 2rem 0rem;
|
||||
align-items: flex-start;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.single-project-right-top {
|
||||
display: flex;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
.single-project-right-bottom {
|
||||
margin-left: 2rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.single-project-line {
|
||||
border-radius: 38px;
|
||||
min-width: 1.5rem;
|
||||
height: 0.25rem;
|
||||
margin-top: 1.5rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.color-orange {
|
||||
background-color: #D95F46;
|
||||
}
|
||||
|
||||
.color-purple {
|
||||
background-color: #a646d9;
|
||||
}
|
||||
|
||||
.color-green {
|
||||
background-color: #36a837;
|
||||
}
|
||||
|
||||
.single-project-title {
|
||||
@ -55,11 +101,11 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
.single-project-link {
|
||||
color: #B0B0B0;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
margin-bottom : 0;
|
||||
}
|
||||
|
||||
.single-project-link:hover {
|
||||
@ -67,26 +113,35 @@
|
||||
color: #EAEAEA;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.single-project-middle {
|
||||
padding: 2rem 0.5rem;
|
||||
.arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: #EAEAEA;
|
||||
font-size: 35px;
|
||||
display: none;
|
||||
transition: all 0.3s ease;
|
||||
text-shadow: 0 0 6px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.single-project-line {
|
||||
border-radius: 38px;
|
||||
min-width: 1.5rem;
|
||||
height: 0.25rem;
|
||||
margin-top: 1.5rem;
|
||||
margin-left: 2rem;
|
||||
.arrow:hover {
|
||||
color: #D95F46;
|
||||
opacity: 0.8;
|
||||
transform: translateY(-50%) scale(1.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.color-orange{
|
||||
background-color: #D95F46;
|
||||
.arrow.preview {
|
||||
left: 10px;
|
||||
}
|
||||
.color-purple{
|
||||
background-color: #a646d9;
|
||||
|
||||
.arrow.next {
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
/* Affichage des flèches au survol de la zone image */
|
||||
.single-project-left:hover .arrow {
|
||||
display: flex;
|
||||
}
|
||||
.color-green{
|
||||
background-color: #36a837;
|
||||
}
|
||||