all updates + fix bug
|
|
@ -1130,7 +1130,7 @@ section {
|
|||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
opacity: 0.4;
|
||||
/* opacity retiré — géré par portfolio-content gradient */
|
||||
}
|
||||
|
||||
.portfolio-content {
|
||||
|
|
@ -1140,7 +1140,9 @@ section {
|
|||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 2rem;
|
||||
background: linear-gradient(180deg, transparent 0%, var(--bg-deep) 100%);
|
||||
/* Gradient sombre uniquement en bas pour lisibilité du texte sans masquer l'image */
|
||||
background: linear-gradient(180deg, transparent 55%, rgba(10,10,15,0.92) 100%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.portfolio-tag {
|
||||
|
|
@ -1169,9 +1171,7 @@ section {
|
|||
}
|
||||
|
||||
.portfolio-item p {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
margin-top: 0.5rem;
|
||||
display: none; /* Description masquée dans les cartes — visible uniquement dans la modal */
|
||||
}
|
||||
|
||||
/* Portfolio Detail */
|
||||
|
|
@ -2118,13 +2118,12 @@ footer {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Pas d'overlay sur le bento — juste gradient bas pour lisibilité texte */
|
||||
.bento-project .bento-image::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, rgba(139, 58, 98, 0.3), rgba(45, 27, 51, 0.5));
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Gradient sombre en bas pour le texte */
|
||||
.bento-project .bento-image::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
|
@ -2132,7 +2131,9 @@ footer {
|
|||
left: 0;
|
||||
right: 0;
|
||||
height: 50%;
|
||||
background: linear-gradient(to top, var(--bg-deep), transparent);
|
||||
background: linear-gradient(to top, rgba(10,10,15,0.9) 0%, transparent 100%);
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bento-image-content {
|
||||
|
|
@ -2140,7 +2141,7 @@ footer {
|
|||
bottom: 2rem;
|
||||
left: 2rem;
|
||||
right: 2rem;
|
||||
z-index: 1;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.bento-image-content .portfolio-tag {
|
||||
|
|
@ -2462,19 +2463,43 @@ footer {
|
|||
}
|
||||
|
||||
.project-gallery-slides {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
|
||||
.project-gallery-slide {
|
||||
min-width: 100%;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.35s ease, transform 0.35s ease;
|
||||
transform: translateX(100%);
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
||||
/* Slide à gauche de la courante */
|
||||
.project-gallery-slide.slide-left {
|
||||
transform: translateX(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Slide à droite de la courante */
|
||||
.project-gallery-slide.slide-right {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Slide active — visible */
|
||||
.project-gallery-slide.active {
|
||||
opacity: 1;
|
||||
transform: translateX(0) !important;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.project-gallery-slide::before {
|
||||
|
|
@ -2700,12 +2725,12 @@ footer {
|
|||
position: absolute;
|
||||
inset: 0;
|
||||
background: transparent;
|
||||
z-index: 1;
|
||||
z-index: 3;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.portfolio-item:hover::before {
|
||||
background: rgba(139, 58, 98, 0.1);
|
||||
background: rgba(139, 58, 98, 0.08);
|
||||
}
|
||||
|
||||
.portfolio-item:active {
|
||||
|
|
@ -4171,13 +4196,85 @@ footer {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
z-index: 1;
|
||||
z-index: 1; /* au-dessus du gradient .portfolio-bg */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* .portfolio-bg conserve son gradient en fallback derrière l'image */
|
||||
/* .portfolio-bg — override pour les cartes avec image */
|
||||
.portfolio-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
/* gradient retiré ici — défini dans la règle principale */
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
BENTO + PORTFOLIO — image de couverture réelle
|
||||
═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
.bento-cover-img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
z-index: 1; /* au-dessus du gradient de fond */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* bento-image-content — z-index géré dans la règle principale ligne ~2141 */
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
GALLERY — iframe YouTube
|
||||
═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
.yt-wrapper {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.gallery-slide-yt-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
GALLERY — bouton mute vidéo
|
||||
═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
.gallery-mute-btn {
|
||||
position: absolute;
|
||||
bottom: 3rem; /* au-dessus des dots */
|
||||
right: 1rem;
|
||||
z-index: 10;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
transition: background 0.2s ease;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.gallery-mute-btn:hover {
|
||||
background: rgba(139, 58, 98, 0.8);
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
/* Icône son coupé = visible par défaut (muted=true au départ) */
|
||||
.gallery-mute-btn .icon-muted { display: block; }
|
||||
.gallery-mute-btn .icon-unmuted { display: none; }
|
||||
|
||||
/* Quand le son est activé */
|
||||
.gallery-mute-btn.unmuted .icon-muted { display: none; }
|
||||
.gallery-mute-btn.unmuted .icon-unmuted { display: block; }
|
||||
|
|
|
|||
|
|
@ -1,50 +1,113 @@
|
|||
[
|
||||
{
|
||||
"name": "victorias-vice",
|
||||
"displayName": "Victoria's Vice",
|
||||
"displayName": "Victoria's Vice 2",
|
||||
"description": "Serveur RP whitelist Fortnite sur thème italien. L'une de nos collaborations les plus abouties.",
|
||||
"longDesc": "Victoria's Vice est un serveur RP whitelist Fortnite immersif basé sur une esthétique italienne unique. OmégaKube a développé l'intégralité de la map UEFN, le bot RP Vicy et l'identité visuelle du projet.",
|
||||
"gradient": "linear-gradient(135deg, #5E2040, #8b3a62)",
|
||||
"url": "#",
|
||||
"discord": "#",
|
||||
"since": "2025",
|
||||
"services": ["Map UEFN", "Bot Discord", "Branding"],
|
||||
"services": [
|
||||
"Map UEFN",
|
||||
"Bot Discord",
|
||||
"Branding"
|
||||
],
|
||||
"stats": [
|
||||
{ "icon": "users", "value": "70", "label": "Joueurs max" },
|
||||
{ "icon": "home", "value": "30+", "label": "Bâtiments" },
|
||||
{ "icon": "play", "value": "30+", "label": "Sessions" }
|
||||
]
|
||||
{
|
||||
"icon": "users",
|
||||
"value": "70",
|
||||
"label": "Joueurs max"
|
||||
},
|
||||
{
|
||||
"icon": "home",
|
||||
"value": "30+",
|
||||
"label": "Bâtiments"
|
||||
},
|
||||
{
|
||||
"icon": "play",
|
||||
"value": "30+",
|
||||
"label": "Sessions"
|
||||
}
|
||||
],
|
||||
"social": {
|
||||
"discord": "https://discord.gg/8D564jm2SD",
|
||||
"twitter": "",
|
||||
"instagram": "",
|
||||
"youtube": "https://www.youtube.com/@VictoriasVice",
|
||||
"tiktok": "",
|
||||
"website": "#"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "partenaire-2",
|
||||
"displayName": "Partenaire 2",
|
||||
"description": "Description courte du partenaire.",
|
||||
"longDesc": "Description longue du partenaire à renseigner.",
|
||||
"name": "red-dust-redemption",
|
||||
"displayName": "Red Dust Redemption",
|
||||
"description": "Serveur RP whitelist sur le thème far-west.",
|
||||
"longDesc": "Serveur RP whitelist sur le thème du far-west.",
|
||||
"gradient": "linear-gradient(135deg, var(--accent-primary), var(--accent-secondary))",
|
||||
"url": "#",
|
||||
"discord": "#",
|
||||
"since": "2025",
|
||||
"services": ["Service 1", "Service 2"],
|
||||
"services": [
|
||||
"Sessions RP Red Dust Redemption"
|
||||
],
|
||||
"stats": [
|
||||
{ "icon": "star", "value": "?", "label": "Stat 1" },
|
||||
{ "icon": "zap", "value": "?", "label": "Stat 2" },
|
||||
{ "icon": "heart", "value": "?", "label": "Stat 3" }
|
||||
]
|
||||
{
|
||||
"icon": "star",
|
||||
"value": "?",
|
||||
"label": "Stat 1"
|
||||
},
|
||||
{
|
||||
"icon": "zap",
|
||||
"value": "?",
|
||||
"label": "Stat 2"
|
||||
},
|
||||
{
|
||||
"icon": "heart",
|
||||
"value": "?",
|
||||
"label": "Stat 3"
|
||||
}
|
||||
],
|
||||
"social": {
|
||||
"discord": "#",
|
||||
"twitter": "",
|
||||
"instagram": "",
|
||||
"youtube": "",
|
||||
"tiktok": "",
|
||||
"website": "#"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "partenaire-3",
|
||||
"displayName": "Partenaire 3",
|
||||
"description": "Description courte du partenaire.",
|
||||
"name": "rickroll",
|
||||
"displayName": "Rick Astley",
|
||||
"description": "Official Video 4K Remaster",
|
||||
"longDesc": "Description longue du partenaire à renseigner.",
|
||||
"gradient": "linear-gradient(135deg, #1a3a5e, #3a5e8b)",
|
||||
"url": "#",
|
||||
"discord": "#",
|
||||
"since": "2025",
|
||||
"services": ["Service 1", "Service 2"],
|
||||
"services": [
|
||||
"Service 1",
|
||||
"Service 2"
|
||||
],
|
||||
"stats": [
|
||||
{ "icon": "star", "value": "?", "label": "Stat 1" },
|
||||
{ "icon": "zap", "value": "?", "label": "Stat 2" },
|
||||
{ "icon": "heart", "value": "?", "label": "Stat 3" }
|
||||
]
|
||||
{
|
||||
"icon": "star",
|
||||
"value": "?",
|
||||
"label": "Stat 1"
|
||||
},
|
||||
{
|
||||
"icon": "zap",
|
||||
"value": "?",
|
||||
"label": "Stat 2"
|
||||
},
|
||||
{
|
||||
"icon": "heart",
|
||||
"value": "?",
|
||||
"label": "Stat 3"
|
||||
}
|
||||
],
|
||||
"social": {
|
||||
"discord": "#",
|
||||
"twitter": "",
|
||||
"instagram": "",
|
||||
"youtube": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||
"tiktok": "",
|
||||
"website": "#"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
@ -55,7 +55,18 @@
|
|||
"label": "Semaines de dev"
|
||||
}
|
||||
],
|
||||
"images": "assets/images/projects/kuby/"
|
||||
"images": "assets/images/projects/kuby/",
|
||||
"files": [
|
||||
"imgk-1.png",
|
||||
"https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
],
|
||||
"social": {
|
||||
"discord": "",
|
||||
"instagram": "",
|
||||
"tiktok": "",
|
||||
"youtube": "",
|
||||
"twitter": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
|
|
@ -113,7 +124,26 @@
|
|||
"label": "Mois de dev"
|
||||
}
|
||||
],
|
||||
"images": "assets/images/projects/victorias-vice/"
|
||||
"images": "assets/images/projects/victorias-vice-1/",
|
||||
"files": [
|
||||
"imgvv1-1.jpg",
|
||||
"imgvv1-2.jpg",
|
||||
"imgvv1-3.jpg",
|
||||
"imgvv1-4.jpg",
|
||||
"imgvv1-5.jpg",
|
||||
"imgvv1-6.jpg",
|
||||
"imgvv1-7.jpg",
|
||||
"imgvv1-8.jpg",
|
||||
"TrailerOfficielVV1.mp4",
|
||||
"yt:xiCdHA-Z7uM"
|
||||
],
|
||||
"social": {
|
||||
"discord": "https://discord.gg/8D564jm2SD",
|
||||
"instagram": "",
|
||||
"tiktok": "",
|
||||
"youtube": "",
|
||||
"twitter": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
|
|
@ -171,7 +201,18 @@
|
|||
"label": "Uptime"
|
||||
}
|
||||
],
|
||||
"images": "assets/images/projects/vicy/"
|
||||
"images": "assets/images/projects/vicy/",
|
||||
"files": [
|
||||
"imgvicy-1.png",
|
||||
"yt:dQw4w9WgXcQ"
|
||||
],
|
||||
"social": {
|
||||
"discord": "https://discord.gg/8D564jm2SD",
|
||||
"instagram": "",
|
||||
"tiktok": "",
|
||||
"youtube": "",
|
||||
"twitter": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
|
|
@ -222,7 +263,15 @@
|
|||
"label": "Illustrator"
|
||||
}
|
||||
],
|
||||
"images": "assets/images/projects/branding-omega-kube/"
|
||||
"images": "assets/images/projects/branding-omega-kube/",
|
||||
"files": ["logo.png"],
|
||||
"social": {
|
||||
"discord": "https://discord.gg/FwvNkvu5dy",
|
||||
"instagram": "",
|
||||
"tiktok": "",
|
||||
"youtube": "",
|
||||
"twitter": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
|
|
@ -270,6 +319,164 @@
|
|||
"label": "Systèmes"
|
||||
}
|
||||
],
|
||||
"images": "assets/images/projects/site-omega/"
|
||||
}
|
||||
"images": "assets/images/projects/site-omega/",
|
||||
"files": [
|
||||
"imgso-1.png",
|
||||
"imgso-2.png",
|
||||
"imgso-3.png",
|
||||
"imgso-4.png",
|
||||
"imgso-5.png",
|
||||
"imgso-6.png",
|
||||
"imgso-7.png",
|
||||
"imgso-8.png",
|
||||
"imgso-9.jpg"
|
||||
],
|
||||
"social": {
|
||||
"discord": "",
|
||||
"instagram": "",
|
||||
"tiktok": "",
|
||||
"youtube": "",
|
||||
"twitter": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"category": "map",
|
||||
"title":"Victoria's Vice 2",
|
||||
"desc": "Suite de Victoria's Vice, avec une map plus grande, plus de bâtiments, plus de systèmes et une immersion encore plus poussée dans l'univers italien.",
|
||||
"gradient": "linear-gradient(135deg, #5E2040, #8b3a62)",
|
||||
"year": "2026",
|
||||
"duration": "en cour...",
|
||||
"features": [
|
||||
"économie réaliste",
|
||||
"50+ bâtiments",
|
||||
"Système jour/nuit",
|
||||
"Serveur Whitelist",
|
||||
"Métiers légaux variés",
|
||||
"Bot RP Vicy exclusif"
|
||||
],
|
||||
"tech": [
|
||||
{
|
||||
"name": "Fortnite Creative",
|
||||
"icon": "map"
|
||||
},
|
||||
{
|
||||
"name": "UEFN",
|
||||
"icon": "layers"
|
||||
},
|
||||
{
|
||||
"name": "Verse",
|
||||
"icon": "cpu"
|
||||
},
|
||||
{
|
||||
"name": "Texturing",
|
||||
"icon": "image"
|
||||
}
|
||||
],
|
||||
"stats": [
|
||||
{
|
||||
"icon": "play",
|
||||
"value": "en cour...",
|
||||
"label": "Sessions"
|
||||
},
|
||||
{
|
||||
"icon": "home",
|
||||
"value": "en cour...",
|
||||
"label": "Bâtiments"
|
||||
},
|
||||
{
|
||||
"icon": "users",
|
||||
"value": "en cour...",
|
||||
"label": "Joueurs max"
|
||||
},
|
||||
{
|
||||
"icon": "calendar",
|
||||
"value": "en cour...",
|
||||
"label": "Mois de dev"
|
||||
}
|
||||
],
|
||||
"images": "assets/images/projects/victorias-vice-2/",
|
||||
"files": [
|
||||
"imgvv2-1.jpg",
|
||||
"imgvv2-2.jpg",
|
||||
"imgvv2-3.jpg",
|
||||
"https://www.youtube.com/watch?v=Lx-1FWOJN9E"
|
||||
],
|
||||
"social": {
|
||||
"discord": "https://discord.gg/AM3e58cZvX",
|
||||
"instagram": "",
|
||||
"tiktok": "",
|
||||
"youtube": "",
|
||||
"twitter": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"category": "bot",
|
||||
"title": "Beta",
|
||||
"desc": "En cour de développement chez Omega Kube.",
|
||||
"gradient": "linear-gradient(135deg, #000000, #434343)",
|
||||
"year": "2026",
|
||||
"duration": "en cour...",
|
||||
"features": [
|
||||
"en cour..."
|
||||
],
|
||||
"tech": [
|
||||
{
|
||||
"name": "Python",
|
||||
"icon": "code"
|
||||
},
|
||||
{
|
||||
"name": "Discord.py",
|
||||
"icon": "message-circle"
|
||||
},
|
||||
{
|
||||
"name": "MySQL",
|
||||
"icon": "database"
|
||||
},
|
||||
{
|
||||
"name": "Pillow",
|
||||
"icon": "image"
|
||||
},
|
||||
{
|
||||
"name": "Autre tech",
|
||||
"icon": "tool"
|
||||
}
|
||||
],
|
||||
"stats": [
|
||||
{
|
||||
"icon": "users",
|
||||
"value": "en cour...",
|
||||
"label": "Utilisateurs"
|
||||
},
|
||||
{
|
||||
"icon": "terminal",
|
||||
"value": "en cour...",
|
||||
"label": "Commandes"
|
||||
},
|
||||
{
|
||||
"icon": "credit-card",
|
||||
"value": "en cour...",
|
||||
"label": "RP menés"
|
||||
},
|
||||
{
|
||||
"icon": "activity",
|
||||
"value": "en cour...",
|
||||
"label": "Uptime"
|
||||
}
|
||||
],
|
||||
"images": "assets/images/projects/beta/",
|
||||
"files": [
|
||||
"imgb-1.png",
|
||||
"imgb-2.png",
|
||||
"yt:dQw4w9WgXcQ"
|
||||
],
|
||||
"social": {
|
||||
"discord": "https://discord.gg/FwvNkvu5dy",
|
||||
"instagram": "",
|
||||
"tiktok": "",
|
||||
"youtube": "",
|
||||
"twitter": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -6,11 +6,37 @@
|
|||
"avatar": "N",
|
||||
"bio": "Nixfix donne vie aux identités visuelles des projets RP. Passionné de design graphique, il excelle aussi bien dans la création de logos que dans la gestion de projets. Son style unique allie une esthétique gaming affirmée à un véritable professionnalisme, afin de proposer des visuels percutants et cohérents.",
|
||||
"stats": [
|
||||
{ "value": "6", "label": "Ans d'exp." },
|
||||
{ "value": "15+", "label": "Projets" },
|
||||
{ "value": "★★★", "label": "Expert" }
|
||||
{
|
||||
"value": "6",
|
||||
"label": "Ans d'exp."
|
||||
},
|
||||
{
|
||||
"value": "15+",
|
||||
"label": "Projets"
|
||||
},
|
||||
{
|
||||
"value": "★★★",
|
||||
"label": "Expert"
|
||||
}
|
||||
],
|
||||
"skills": ["Polyvalent", "Gestion de projets", "Illustrator", "After Effects", "Figma", "UEFN", "Photoshop"]
|
||||
"skills": [
|
||||
"Polyvalent",
|
||||
"Gestion de projets",
|
||||
"Illustrator",
|
||||
"After Effects",
|
||||
"Figma",
|
||||
"UEFN",
|
||||
"Photoshop",
|
||||
"Verse"
|
||||
],
|
||||
"social": {
|
||||
"discord": "",
|
||||
"twitter": "",
|
||||
"instagram": "",
|
||||
"youtube": "",
|
||||
"tiktok": "",
|
||||
"github": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "353942721997832195",
|
||||
|
|
@ -19,76 +45,146 @@
|
|||
"avatar": "M",
|
||||
"bio": "Je suis un développeur UEFN et Builder Minecraft polyvalent avec une solide expérience dans la création de systèmes pour divers Serveurs RP. J'ai travaillé sur des projets de plus ou moins grande envergure sur diverses plateformes de jeux. J'ai aussi une certaine expérience en gestion de projet et je me montre polyvalent pour beaucoup de tâches",
|
||||
"stats": [
|
||||
{ "value": "6", "label": "Ans d'exp." },
|
||||
{ "value": "25+", "label": "Systèmes" },
|
||||
{ "value": "∞", "label": "Fan de Louis de Funès" }
|
||||
{
|
||||
"value": "6",
|
||||
"label": "Ans d'exp."
|
||||
},
|
||||
{
|
||||
"value": "25+",
|
||||
"label": "Systèmes"
|
||||
},
|
||||
{
|
||||
"value": "∞",
|
||||
"label": "Fan de Louis de Funès"
|
||||
}
|
||||
],
|
||||
"skills": ["Polyvalent", "Gestion de projets", "UEFN", "Builder Minecraft"]
|
||||
"skills": [
|
||||
"Polyvalent",
|
||||
"Gestion de projets",
|
||||
"UEFN",
|
||||
"Builder Minecraft"
|
||||
],
|
||||
"social": {
|
||||
"discord": "",
|
||||
"twitter": "",
|
||||
"instagram": "",
|
||||
"youtube": "",
|
||||
"tiktok": "",
|
||||
"github": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "971446412690722826",
|
||||
"name": "Gameur",
|
||||
"role": "Développeur Principal",
|
||||
"role": "Agent - Développeur Principal",
|
||||
"avatar": "G",
|
||||
"bio": "Avec plus de 3 ans d'expérience dans le self-hosting, j'ai créé des bots pour des communautés de toutes tailles, allant de petits serveurs à des projets d'envergure. Mon expertise couvre la création de commandes personnalisées, l'intégration d'API tierces et la gestion de bases de données pour offrir des fonctionnalités avancées et une expérience utilisateur fluide.",
|
||||
"stats": [
|
||||
{ "value": "3", "label": "Ans d'exp." },
|
||||
{ "value": "50+", "label": "Projets" },
|
||||
{ "value": "★★★★", "label": "Intermédiaire - Avançé" }
|
||||
{
|
||||
"value": "3",
|
||||
"label": "Ans d'exp."
|
||||
},
|
||||
{
|
||||
"value": "50+",
|
||||
"label": "Projets"
|
||||
},
|
||||
{
|
||||
"value": "★★★★",
|
||||
"label": "Intermédiaire - Avançé"
|
||||
}
|
||||
],
|
||||
"skills": ["Python", "Discord.py", "API REST", "Docker", "self-hosting", "Linux", "Gestion de projets"]
|
||||
},
|
||||
{
|
||||
"id": "1324439906046574693",
|
||||
"name": "Lowei",
|
||||
"role": "Développeur Backend",
|
||||
"avatar": "L",
|
||||
"bio": "Salut ! Je suis un développeur backend passionné par la création de systèmes robustes, performants et dans l'automatisation de tâches. N'hésite pas à checker mes réseaux ci-dessous !",
|
||||
"stats": [
|
||||
{ "value": "5", "label": "Ans d'exp." },
|
||||
{ "value": "20+", "label": "Systèmes" },
|
||||
{ "value": "∞", "label": "Disponibilité" }
|
||||
"skills": [
|
||||
"Python",
|
||||
"Discord.py",
|
||||
"API REST",
|
||||
"Docker",
|
||||
"self-hosting",
|
||||
"Linux",
|
||||
"Gestion de projets"
|
||||
],
|
||||
"skills": ["Fortnite Creative", "UEFN", "Level Design", "3D", "Lighting", "Scripting"]
|
||||
"social": {
|
||||
"discord": "",
|
||||
"twitter": "",
|
||||
"instagram": "",
|
||||
"youtube": "",
|
||||
"tiktok": "",
|
||||
"github": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "561485865562406914",
|
||||
"name": "KIRITO_KMA",
|
||||
"role": "All Master & Développeur",
|
||||
"role": "Agent - All Master & Développeur",
|
||||
"avatar": "K",
|
||||
"bio": "Au-delà des horizons et par-delà les temps",
|
||||
"stats": [
|
||||
{ "value": "4", "label": "Ans d'exp." },
|
||||
{ "value": "50+", "label": "Projets menés" },
|
||||
{ "value": "1000%", "label": "Maîtrise du shifoumi" }
|
||||
{
|
||||
"value": "4",
|
||||
"label": "Ans d'exp."
|
||||
},
|
||||
{
|
||||
"value": "50+",
|
||||
"label": "Projets menés"
|
||||
},
|
||||
{
|
||||
"value": "1000%",
|
||||
"label": "Maîtrise du shifoumi"
|
||||
}
|
||||
],
|
||||
"skills": ["Dev Full-Stack", "SEO", "Map-Maker", "Bot Discord", "Scénariste", "Shifoumi", "RedStone Minecraft"]
|
||||
"skills": [
|
||||
"Dev Full-Stack",
|
||||
"SEO",
|
||||
"Map-Maker",
|
||||
"Bot Discord",
|
||||
"Scénariste",
|
||||
"Shifoumi",
|
||||
"RedStone Minecraft"
|
||||
],
|
||||
"social": {
|
||||
"discord": "",
|
||||
"twitter": "",
|
||||
"instagram": "",
|
||||
"youtube": "",
|
||||
"tiktok": "",
|
||||
"github": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "983747660265693258",
|
||||
"name": "Mr Reiko",
|
||||
"role": "Développeur UEFN",
|
||||
"avatar": "R",
|
||||
"bio": "Lucas est l'architecte invisible derrière les systèmes les plus robustes d'OmégaKube. Spécialisé dans les bases de données et les API, il conçoit des systèmes d'économie capables de gérer des millions de transactions sans ralentissement.",
|
||||
"id": "1324439906046574693",
|
||||
"name": "Lowei",
|
||||
"role": "Agent - Développeur Backend",
|
||||
"avatar": "L",
|
||||
"bio": "Salut ! Je suis un développeur backend passionné par la création de systèmes robustes, performants et dans l'automatisation de tâches. N'hésite pas à checker mes réseaux ci-dessous !",
|
||||
"stats": [
|
||||
{ "value": "8", "label": "Ans d'exp." },
|
||||
{ "value": "30+", "label": "Systèmes" },
|
||||
{ "value": "99.9%", "label": "Uptime" }
|
||||
{
|
||||
"value": "5",
|
||||
"label": "Ans d'exp."
|
||||
},
|
||||
{
|
||||
"value": "20+",
|
||||
"label": "Systèmes"
|
||||
},
|
||||
{
|
||||
"value": "∞",
|
||||
"label": "Disponibilité"
|
||||
}
|
||||
],
|
||||
"skills": ["Javascript", "HTML", "C++", "Verse", "Python", "UEFN", "Blender"]
|
||||
},
|
||||
{
|
||||
"id": "1013376581458210986",
|
||||
"name": "Kayrix",
|
||||
"role": "Développer UEFN",
|
||||
"avatar": "K",
|
||||
"bio": "Je suis polyvalent et curieux, je cherche à toucher à tout sur l'UEFN. En parallèle, je suis en études de cinéma d'animation 3D et jeux vidéo, avec des compétences en modélisation 3D, animation et environnements sur Unreal Engine. Je développe aussi mes propres projets RP depuis un moment, ce qui m'a permis d'acquérir une bonne expérience, tout en restant dans une démarche d'apprentissage et d'amélioration continue.",
|
||||
"stats": [
|
||||
{ "value": "5", "label": "Ans d'exp." },
|
||||
{ "value": "40+", "label": "Scénarios" },
|
||||
{ "value": "∞", "label": "Créativité" }
|
||||
"skills": [
|
||||
"Python",
|
||||
"HTML",
|
||||
"CSS",
|
||||
"C, C++",
|
||||
"Java",
|
||||
"Blender"
|
||||
],
|
||||
"skills": ["UEFN", "Maya", "Polyvalent", "Gestion de projets"]
|
||||
"social": {
|
||||
"discord": "",
|
||||
"twitter": "",
|
||||
"instagram": "",
|
||||
"youtube": "",
|
||||
"tiktok": "",
|
||||
"github": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1151495567256584253",
|
||||
|
|
@ -97,10 +193,34 @@
|
|||
"avatar": "K",
|
||||
"bio": "Kuby est le gardien de la qualité du moindre octets au quetta-octets chez OmégaKube. Aucune commande ne lui échappe. Il execute méticuleusement chaque instructions, chaque commande, chaque interaction pour garantir une expérience sans accroc aux utilisateurs finaux.",
|
||||
"stats": [
|
||||
{ "value": "2", "label": "Yotta d'exp sur Discord" },
|
||||
{ "value": "500+", "label": "Commande réalisées" },
|
||||
{ "value": "0", "label": "Vulnerabilités" }
|
||||
{
|
||||
"value": "2",
|
||||
"label": "Yotta d'exp sur Discord"
|
||||
},
|
||||
{
|
||||
"value": "500+",
|
||||
"label": "Commande réalisées"
|
||||
},
|
||||
{
|
||||
"value": "0",
|
||||
"label": "Vulnerabilités"
|
||||
}
|
||||
],
|
||||
"skills": ["Moderation", "Events", "Documentation", "Serveur Discord", "Administration","Bot"]
|
||||
"skills": [
|
||||
"Moderation",
|
||||
"Events",
|
||||
"Documentation",
|
||||
"Serveur Discord",
|
||||
"Administration",
|
||||
"Bot"
|
||||
],
|
||||
"social": {
|
||||
"discord": "",
|
||||
"twitter": "",
|
||||
"instagram": "",
|
||||
"youtube": "",
|
||||
"tiktok": "",
|
||||
"github": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
BIN
assets/images/partners/red-dust-redemption.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
assets/images/partners/rickroll.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
assets/images/partners/victorias-vice.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
assets/images/projects/beta/imgb-1.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
assets/images/projects/beta/imgb-2.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
assets/images/projects/branding-omega-kube/logo.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
assets/images/projects/kuby/imgk-1.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
BIN
assets/images/projects/kuby/imgk-1bis.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
assets/images/projects/site-omega/imgso-1.png
Normal file
|
After Width: | Height: | Size: 4.2 MiB |
BIN
assets/images/projects/site-omega/imgso-2.png
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
assets/images/projects/site-omega/imgso-3.png
Normal file
|
After Width: | Height: | Size: 4.4 MiB |
BIN
assets/images/projects/site-omega/imgso-4.png
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
assets/images/projects/site-omega/imgso-5.png
Normal file
|
After Width: | Height: | Size: 4.7 MiB |
BIN
assets/images/projects/site-omega/imgso-6.png
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
BIN
assets/images/projects/site-omega/imgso-7.png
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
BIN
assets/images/projects/site-omega/imgso-8.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
assets/images/projects/site-omega/imgso-9.jpg
Normal file
|
After Width: | Height: | Size: 437 KiB |
BIN
assets/images/projects/victorias-vice-1/TrailerOfficielVV1.mp4
Normal file
BIN
assets/images/projects/victorias-vice-1/imgvv1-1.jpg
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
assets/images/projects/victorias-vice-1/imgvv1-2.jpg
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
assets/images/projects/victorias-vice-1/imgvv1-3.jpg
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
assets/images/projects/victorias-vice-1/imgvv1-4.jpg
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
assets/images/projects/victorias-vice-1/imgvv1-5.jpg
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
assets/images/projects/victorias-vice-1/imgvv1-6.jpg
Normal file
|
After Width: | Height: | Size: 302 KiB |
BIN
assets/images/projects/victorias-vice-1/imgvv1-7.jpg
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
assets/images/projects/victorias-vice-1/imgvv1-8.jpg
Normal file
|
After Width: | Height: | Size: 600 KiB |
BIN
assets/images/projects/victorias-vice-2/imgvv2-1.jpg
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
assets/images/projects/victorias-vice-2/imgvv2-2.jpg
Normal file
|
After Width: | Height: | Size: 478 KiB |
BIN
assets/images/projects/victorias-vice-2/imgvv2-3.jpg
Normal file
|
After Width: | Height: | Size: 720 KiB |
BIN
assets/images/projects/vicy/imgvicy-1.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -12,16 +12,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
const categoryLabels = { bot: 'Bot Discord', map: 'Fortnite Map', graphisme: 'Graphisme' };
|
||||
|
||||
homePortfolioGrid.innerHTML = first3.map((p, i) => {
|
||||
const imgSrc = (p.images || '') + '1.png';
|
||||
const firstFile = Array.isArray(p.files) && p.files.length > 0 ? p.files[0] : null;
|
||||
const imgSrc = firstFile ? (p.images || '') + firstFile : null;
|
||||
return `
|
||||
<div class="portfolio-item reveal${delayClasses[i]}" data-project-id="${p.id}" role="button" tabindex="0" aria-label="Voir le projet ${p.title}">
|
||||
<div class="portfolio-bg" style="background: ${p.gradient};">
|
||||
<img src="${imgSrc}" alt="${p.title}" class="portfolio-cover-img" onerror="this.style.display='none'">
|
||||
${imgSrc ? `<img src="${imgSrc}" alt="${p.title}" class="portfolio-cover-img" onerror="this.style.display='none'">` : ''}
|
||||
</div>
|
||||
<div class="portfolio-content">
|
||||
<span class="portfolio-tag">${categoryLabels[p.category] || p.category}</span>
|
||||
<h4>${p.title}</h4>
|
||||
<p>${p.desc.substring(0, 80)}${p.desc.length > 80 ? '…' : ''}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -32,13 +32,24 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
if (window.feather && feather.replace) try { feather.replace(); } catch(e) {}
|
||||
}, 80);
|
||||
|
||||
// Délégation : attend que openProjectModal soit défini par realisations.js
|
||||
// Délégation globale sur document — fonctionne même si realisations.js
|
||||
// se charge après home.js (ordre des scripts defer non garanti)
|
||||
homePortfolioGrid.addEventListener('click', e => {
|
||||
const item = e.target.closest('.portfolio-item[data-project-id]');
|
||||
if (!item) return;
|
||||
const id = item.getAttribute('data-project-id');
|
||||
if (typeof window.openProjectModal === 'function') {
|
||||
window.openProjectModal(id);
|
||||
} else {
|
||||
// Retry : realisations.js pas encore initialisé, on attend
|
||||
const wait = setInterval(() => {
|
||||
if (typeof window.openProjectModal === 'function') {
|
||||
clearInterval(wait);
|
||||
window.openProjectModal(id);
|
||||
}
|
||||
}, 50);
|
||||
// Abandon après 3 secondes
|
||||
setTimeout(() => clearInterval(wait), 3000);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -163,16 +174,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
</div>
|
||||
`).join('');
|
||||
const links = document.getElementById('partner-modal-links');
|
||||
links.innerHTML = '';
|
||||
if (p.url && p.url !== '#') {
|
||||
links.innerHTML += `<a href="${p.url}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary"><i data-feather="external-link"></i> Site web</a>`;
|
||||
}
|
||||
if (p.discord && p.discord !== '#') {
|
||||
links.innerHTML += `<a href="${p.discord}" target="_blank" rel="noopener noreferrer" class="btn btn-primary">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z"/></svg>
|
||||
Discord
|
||||
</a>`;
|
||||
}
|
||||
// Réseaux sociaux depuis le champ social du partenaire
|
||||
links.innerHTML = typeof window.renderSocialLinks === 'function'
|
||||
? window.renderSocialLinks(p.social)
|
||||
: '';
|
||||
if (window.feather) feather.replace();
|
||||
partnerModalOverlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
|
@ -302,13 +307,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
// Modal
|
||||
const modalOverlay = document.getElementById('team-modal-overlay');
|
||||
const modalClose = document.getElementById('team-modal-close');
|
||||
const modalAvatar = document.getElementById('modal-avatar');
|
||||
const modalName = document.getElementById('modal-name');
|
||||
const modalRole = document.getElementById('modal-role');
|
||||
const modalBio = document.getElementById('modal-bio');
|
||||
const modalStats = document.getElementById('modal-stats');
|
||||
const modalSkills = document.getElementById('modal-skills');
|
||||
const modalSocial = document.getElementById('modal-social');
|
||||
const modalAvatar = document.getElementById('team-modal-avatar');
|
||||
const modalName = document.getElementById('team-modal-name');
|
||||
const modalRole = document.getElementById('team-modal-role');
|
||||
const modalBio = document.getElementById('team-modal-bio');
|
||||
const modalStats = document.getElementById('team-modal-stats');
|
||||
const modalSkills = document.getElementById('team-modal-skills');
|
||||
const modalSocial = document.getElementById('team-modal-social');
|
||||
|
||||
function openMemberModal(index) {
|
||||
const m = members[index];
|
||||
|
|
@ -330,14 +335,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
</div>
|
||||
`).join('');
|
||||
modalSkills.innerHTML = (m.skills || []).map(s => `<span class="team-modal-skill">${s}</span>`).join('');
|
||||
modalSocial.innerHTML = `
|
||||
<a href="https://discord.gg/FwvNkvu5dy" target="_blank" rel="noopener noreferrer" aria-label="Discord">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>
|
||||
</a>
|
||||
<a href="#" target="_blank" rel="noopener noreferrer" aria-label="X (Twitter)">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.746l7.73-8.835L1.254 2.25H8.08l4.259 5.631 5.905-5.631zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
|
||||
</a>
|
||||
`;
|
||||
// Réseaux sociaux depuis le JSON du membre (+ fallback discord global)
|
||||
const socialHtml = typeof window.renderSocialLinks === 'function'
|
||||
? window.renderSocialLinks(m.social, (window.OK_SOCIAL || {}).discord)
|
||||
: '';
|
||||
modalSocial.innerHTML = socialHtml;
|
||||
modalOverlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,58 @@
|
|||
// OmégaKube - shared scripts
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// CONFIGURATION GLOBALE — liens réseaux sociaux
|
||||
// Modifier ici pour mettre à jour partout sur le site
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
window.OK_SOCIAL = {
|
||||
discord: 'https://discord.gg/FwvNkvu5dy',
|
||||
fortnite: 'https://www.fortnite.com/@omega_kube_team',
|
||||
twitter: 'https://x.com/Omegakube',
|
||||
instagram: 'https://www.instagram.com/omegakube/',
|
||||
youtube: 'https://www.youtube.com/@OmegaKube',
|
||||
tiktok: 'https://www.tiktok.com/@omegakube',
|
||||
facebook: '#',
|
||||
bluesky: '#',
|
||||
email: 'contact@omegakube.fr'
|
||||
};
|
||||
|
||||
// SVGs des réseaux — utilisé partout dans les modals
|
||||
const SVG_ICONS = {
|
||||
discord: '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>',
|
||||
fortnite: '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M6 3h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm0 2v8h12V5H6zm2 2h2v1h1V7h2v4h-2V9h-1v2H8V7zm7 1a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm2 2a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-4 4l-2 4h2l1-2 1 2h2l-2-4h-2z"/></svg>',
|
||||
twitter: '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.746l7.73-8.835L1.254 2.25H8.08l4.259 5.631 5.905-5.631zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>',
|
||||
instagram: '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/></svg>',
|
||||
youtube: '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>',
|
||||
tiktok: '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M19.59 6.69a4.83 4.83 0 0 1-3.77-4.25V2h-3.45v13.67a2.89 2.89 0 0 1-2.88 2.5 2.89 2.89 0 0 1-2.89-2.89 2.89 2.89 0 0 1 2.89-2.89c.28 0 .54.04.79.1V9.01a6.32 6.32 0 0 0-.79-.05 6.34 6.34 0 0 0-6.34 6.34 6.34 6.34 0 0 0 6.34 6.34 6.34 6.34 0 0 0 6.33-6.34V8.69a8.17 8.17 0 0 0 4.77 1.52V6.75a4.85 4.85 0 0 1-1-.06z"/></svg>',
|
||||
github: '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M12 0C5.374 0 0 5.373 0 12c0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0 1 12 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z"/></svg>',
|
||||
website: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>',
|
||||
};
|
||||
|
||||
/**
|
||||
* Génère le HTML des liens sociaux pour les modals
|
||||
* @param {Object} social - objet {discord, twitter, instagram, ...}
|
||||
* @param {string} [discordFallback] - lien discord global si pas de lien perso
|
||||
* @returns {string} HTML des icônes sociales
|
||||
*/
|
||||
window.renderSocialLinks = function(social, discordFallback) {
|
||||
const S = social || {};
|
||||
const G = window.OK_SOCIAL || {};
|
||||
const entries = [
|
||||
{ key: 'discord', url: S.discord || discordFallback || G.discord },
|
||||
{ key: 'fortnite', url: S.fortnite || G.fortnite },
|
||||
{ key: 'twitter', url: S.twitter },
|
||||
{ key: 'instagram', url: S.instagram },
|
||||
{ key: 'youtube', url: S.youtube },
|
||||
{ key: 'tiktok', url: S.tiktok },
|
||||
{ key: 'github', url: S.github },
|
||||
{ key: 'website', url: S.website },
|
||||
];
|
||||
return entries
|
||||
.filter(e => e.url && e.url !== '#' && e.url !== '')
|
||||
.map(e => `<a href="${e.url}" target="_blank" rel="noopener noreferrer" aria-label="${e.key}">${SVG_ICONS[e.key] || ''}</a>`)
|
||||
.join('');
|
||||
};
|
||||
|
||||
// Feather icons loader (centralized)
|
||||
// loads https://unpkg.com/feather-icons when needed and exposes window.ensureFeather()
|
||||
const FEATHER_URL = 'https://unpkg.com/feather-icons';
|
||||
|
|
@ -86,6 +139,27 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
// wait for includes to be loaded so subsequent initialization can bind to elements inside them
|
||||
await loadIncludes();
|
||||
|
||||
// Hydrate les liens sociaux (data-social) avec les URLs de OK_SOCIAL
|
||||
(function hydrateSocialLinks() {
|
||||
const S = window.OK_SOCIAL || {};
|
||||
const map = {
|
||||
'discord': S.discord,
|
||||
'twitter': S.twitter,
|
||||
'instagram': S.instagram,
|
||||
'youtube': S.youtube,
|
||||
'tiktok': S.tiktok,
|
||||
'facebook': S.facebook,
|
||||
'bluesky': S.bluesky,
|
||||
'email-link': S.email ? 'mailto:' + S.email : '#',
|
||||
};
|
||||
document.querySelectorAll('[data-social]').forEach(el => {
|
||||
const key = el.getAttribute('data-social');
|
||||
if (map[key] && map[key] !== '#') {
|
||||
el.setAttribute('href', map[key]);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// Déclenche les reveals maintenant que tous les includes sont dans le DOM
|
||||
setTimeout(() => {
|
||||
document.querySelectorAll('.reveal:not(.active)').forEach(el => {
|
||||
|
|
|
|||
|
|
@ -57,19 +57,24 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
}
|
||||
|
||||
function renderProjects() {
|
||||
if (!portfolioGrid) return; // pas sur la page réalisations
|
||||
const filtered = getFilteredProjects();
|
||||
const projects = getProjectsForPage(filtered, currentPage);
|
||||
|
||||
portfolioGrid.innerHTML = projects.map(project => `
|
||||
<div class="portfolio-item reveal" data-project-id="${project.id}">
|
||||
<div class="portfolio-bg" style="background: ${project.gradient};"></div>
|
||||
<div class="portfolio-content">
|
||||
<span class="portfolio-tag">${getCategoryLabel(project.category)}</span>
|
||||
<h4>${project.title}</h4>
|
||||
<p>${project.desc.substring(0, 80)}...</p>
|
||||
portfolioGrid.innerHTML = projects.map(project => {
|
||||
const prefix = isRoot ? '' : '../';
|
||||
const firstFile = Array.isArray(project.files) && project.files.length > 0 ? project.files[0] : null;
|
||||
const coverImg = firstFile ? `<img src="${prefix}${project.images}${firstFile}" alt="${project.title}" class="portfolio-cover-img" onerror="this.style.display='none'">` : '';
|
||||
return `
|
||||
<div class="portfolio-item reveal" data-project-id="${project.id}">
|
||||
<div class="portfolio-bg" style="background: ${project.gradient};">${coverImg}</div>
|
||||
<div class="portfolio-content">
|
||||
<span class="portfolio-tag">${getCategoryLabel(project.category)}</span>
|
||||
<h4>${project.title}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// activate reveals & icons
|
||||
setTimeout(() => {
|
||||
|
|
@ -83,6 +88,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
}
|
||||
|
||||
function renderPagination() {
|
||||
if (!paginationContainer) return; // pas sur la page réalisations
|
||||
const filtered = getFilteredProjects();
|
||||
const totalPages = getTotalPages(filtered);
|
||||
|
||||
|
|
@ -183,35 +189,75 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
</div>
|
||||
`).join('');
|
||||
|
||||
// Gallery : charger les images du dossier via convention de nommage
|
||||
// images/projects/{slug}/1.png, 2.png... avec fallback gradient
|
||||
const imgPath = project.images || '';
|
||||
const imgExtensions = ['png', 'jpg', 'jpeg', 'webp'];
|
||||
|
||||
// On tente de charger jusqu'à 6 images numérotées depuis le dossier
|
||||
const maxImages = 6;
|
||||
let slidesHtml = '';
|
||||
|
||||
// Toujours créer au moins 1 slide gradient (fallback)
|
||||
// Les images réelles se chargent avec onerror pour masquer si absentes
|
||||
for (let i = 1; i <= maxImages; i++) {
|
||||
slidesHtml += `
|
||||
<div class="project-gallery-slide" data-img-idx="${i}" style="background: ${project.gradient};">
|
||||
<img src="${imgPath}${i}.png" alt="${project.title} ${i}"
|
||||
class="gallery-slide-img"
|
||||
onerror="this.parentElement.classList.add('no-image')"
|
||||
onload="this.parentElement.classList.add('has-image')">
|
||||
</div>`;
|
||||
}
|
||||
gallerySlides.innerHTML = slidesHtml;
|
||||
// Gallery : utilise project.files (noms réels) avec fallback gradient si vide
|
||||
const imgPath = (isRoot ? '' : '../') + (project.images || '');
|
||||
const files = Array.isArray(project.files) ? project.files : [];
|
||||
|
||||
// reset gallery state
|
||||
if (files.length > 0) {
|
||||
// Images réelles nommées explicitement dans le JSON
|
||||
// Supporte : fichier local, mp4 local, URL YouTube, shortcode yt:VIDEO_ID
|
||||
gallerySlides.innerHTML = files.map((filename, i) => {
|
||||
const isLocalVideo = /\.(mp4|webm|ogg)$/i.test(filename);
|
||||
|
||||
// Détecte YouTube : URL complète ou shortcode yt:ID
|
||||
const ytMatch = filename.match(/(?:yt:|youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
|
||||
|
||||
if (ytMatch) {
|
||||
const videoId = ytMatch[1];
|
||||
return `
|
||||
<div class="project-gallery-slide slide-right gallery-slide-youtube" data-img-idx="${i}" style="background:${project.gradient};">
|
||||
<div class="yt-wrapper">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/${videoId}?rel=0&modestbranding=1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
class="gallery-slide-yt-iframe"
|
||||
title="Vidéo YouTube ${project.title}"
|
||||
loading="lazy">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
if (isLocalVideo) {
|
||||
return `
|
||||
<div class="project-gallery-slide slide-right" data-img-idx="${i}" style="background:${project.gradient};">
|
||||
<video class="gallery-slide-img gallery-video" src="${imgPath}${filename}" muted loop autoplay playsinline></video>
|
||||
<button class="gallery-mute-btn" aria-label="Activer le son" title="Activer le son" onclick="(function(btn){var v=btn.closest('.project-gallery-slide').querySelector('video');v.muted=!v.muted;btn.classList.toggle('unmuted',!v.muted);btn.setAttribute('aria-label',v.muted?'Activer le son':'Couper le son');})(this)">
|
||||
<svg class="icon-muted" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/></svg>
|
||||
<svg class="icon-unmuted" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="project-gallery-slide slide-right" data-img-idx="${i}" style="background:${project.gradient};">
|
||||
<img src="${imgPath}${filename}" alt="${project.title} ${i + 1}"
|
||||
class="gallery-slide-img"
|
||||
onerror="this.parentElement.classList.add('no-image');this.style.display='none'">
|
||||
</div>`;
|
||||
}).join('');
|
||||
} else {
|
||||
// Fallback : une slide gradient unique
|
||||
gallerySlides.innerHTML = `
|
||||
<div class="project-gallery-slide has-image" style="background:${project.gradient};"></div>`;
|
||||
}
|
||||
|
||||
// reset gallery + affichage immédiat
|
||||
galleryIndex = 0;
|
||||
updateGallery();
|
||||
updateGalleryDots();
|
||||
|
||||
if (modalOverlay) modalOverlay.classList.add('active');
|
||||
|
||||
// Réseaux sociaux du projet
|
||||
const projectSocialEl = document.getElementById('modal-project-social');
|
||||
if (projectSocialEl) {
|
||||
projectSocialEl.innerHTML = typeof window.renderSocialLinks === 'function'
|
||||
? window.renderSocialLinks(project.social)
|
||||
: '';
|
||||
}
|
||||
|
||||
if (window.feather && feather.replace) {
|
||||
try { feather.replace(); } catch (e) {}
|
||||
}
|
||||
|
|
@ -223,88 +269,100 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
|
||||
// Gallery helpers
|
||||
let galleryIndex = 0;
|
||||
function getVisibleSlides() {
|
||||
function getAllSlides() {
|
||||
if (!gallerySlides) return [];
|
||||
// Retourne uniquement les slides qui ont une image chargée OU qui sont la première (fallback gradient)
|
||||
const all = Array.from(gallerySlides.children);
|
||||
const withImg = all.filter(s => s.classList.contains('has-image'));
|
||||
return withImg.length > 0 ? withImg : [all[0]].filter(Boolean);
|
||||
return Array.from(gallerySlides.children).filter(s => !s.classList.contains('no-image'));
|
||||
}
|
||||
|
||||
function updateGallery() {
|
||||
const visible = getVisibleSlides();
|
||||
const all = gallerySlides ? Array.from(gallerySlides.children) : [];
|
||||
// Cache toutes les slides non visibles
|
||||
all.forEach(s => {
|
||||
if (!visible.includes(s)) { s.style.display = 'none'; }
|
||||
else { s.style.display = ''; }
|
||||
});
|
||||
// Positionne les slides visibles
|
||||
visible.forEach((s, i) => {
|
||||
s.style.transform = `translateX(${(i - galleryIndex) * 100}%)`;
|
||||
const slides = getAllSlides();
|
||||
if (!slides.length) return;
|
||||
if (galleryIndex >= slides.length) galleryIndex = 0;
|
||||
if (galleryIndex < 0) galleryIndex = slides.length - 1;
|
||||
slides.forEach((s, i) => {
|
||||
// Le CSS gère tout via .active — on ne touche JAMAIS à style.transform
|
||||
s.classList.toggle('active', i === galleryIndex);
|
||||
// Juste la direction (gauche/droite) via une classe data
|
||||
if (i < galleryIndex) {
|
||||
s.classList.add('slide-left');
|
||||
s.classList.remove('slide-right');
|
||||
} else if (i > galleryIndex) {
|
||||
s.classList.add('slide-right');
|
||||
s.classList.remove('slide-left');
|
||||
} else {
|
||||
s.classList.remove('slide-left', 'slide-right');
|
||||
}
|
||||
});
|
||||
// Clamp index
|
||||
if (galleryIndex >= visible.length) galleryIndex = Math.max(0, visible.length - 1);
|
||||
}
|
||||
|
||||
function updateGalleryDots() {
|
||||
const galleryDots = document.getElementById('gallery-dots');
|
||||
if (!galleryDots) return;
|
||||
// Attend que les images se chargent avant de créer les dots
|
||||
setTimeout(() => {
|
||||
const visible = getVisibleSlides();
|
||||
galleryDots.innerHTML = visible.map((_, i) =>
|
||||
`<div class="gallery-dot${i === 0 ? ' active' : ''}" data-idx="${i}"></div>`
|
||||
).join('');
|
||||
galleryDots.querySelectorAll('.gallery-dot').forEach(dot => {
|
||||
dot.addEventListener('click', () => {
|
||||
galleryIndex = parseInt(dot.dataset.idx);
|
||||
updateGallery();
|
||||
galleryDots.querySelectorAll('.gallery-dot').forEach((d, i) => d.classList.toggle('active', i === galleryIndex));
|
||||
});
|
||||
const slides = getAllSlides();
|
||||
galleryDots.innerHTML = slides.map((_, i) =>
|
||||
`<div class="gallery-dot${i === 0 ? ' active' : ''}" data-idx="${i}"></div>`
|
||||
).join('');
|
||||
galleryDots.querySelectorAll('.gallery-dot').forEach(dot => {
|
||||
dot.addEventListener('click', () => {
|
||||
galleryIndex = parseInt(dot.dataset.idx);
|
||||
updateGallery();
|
||||
galleryDots.querySelectorAll('.gallery-dot').forEach((d, i) =>
|
||||
d.classList.toggle('active', i === galleryIndex));
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
function syncDots() {
|
||||
const galleryDots = document.getElementById('gallery-dots');
|
||||
if (!galleryDots) return;
|
||||
galleryDots.querySelectorAll('.gallery-dot').forEach((d, i) =>
|
||||
d.classList.toggle('active', i === galleryIndex));
|
||||
}
|
||||
|
||||
function prevSlide() {
|
||||
const visible = getVisibleSlides();
|
||||
galleryIndex = galleryIndex <= 0 ? visible.length - 1 : galleryIndex - 1;
|
||||
galleryIndex = galleryIndex <= 0 ? getAllSlides().length - 1 : galleryIndex - 1;
|
||||
updateGallery();
|
||||
syncDots();
|
||||
}
|
||||
function nextSlide() {
|
||||
const visible = getVisibleSlides();
|
||||
galleryIndex = galleryIndex >= visible.length - 1 ? 0 : galleryIndex + 1;
|
||||
galleryIndex = galleryIndex >= getAllSlides().length - 1 ? 0 : galleryIndex + 1;
|
||||
updateGallery();
|
||||
syncDots();
|
||||
}
|
||||
|
||||
// Event bindings
|
||||
filterTabs.forEach(tab => tab.addEventListener('click', (e) => {
|
||||
filterTabs.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
currentFilter = tab.dataset.filter || 'all';
|
||||
currentPage = 1;
|
||||
renderProjects();
|
||||
renderPagination();
|
||||
}));
|
||||
// Event bindings — uniquement sur la page réalisations
|
||||
if (filterTabs.length) {
|
||||
filterTabs.forEach(tab => tab.addEventListener('click', (e) => {
|
||||
filterTabs.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
currentFilter = tab.dataset.filter || 'all';
|
||||
currentPage = 1;
|
||||
renderProjects();
|
||||
renderPagination();
|
||||
}));
|
||||
}
|
||||
|
||||
paginationContainer.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('button[data-page]');
|
||||
if (!btn) return;
|
||||
const page = Number(btn.getAttribute('data-page'));
|
||||
goToPage(page);
|
||||
});
|
||||
if (paginationContainer) {
|
||||
paginationContainer.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('button[data-page]');
|
||||
if (!btn) return;
|
||||
const page = Number(btn.getAttribute('data-page'));
|
||||
goToPage(page);
|
||||
});
|
||||
}
|
||||
|
||||
portfolioGrid.addEventListener('click', (e) => {
|
||||
const item = e.target.closest('.portfolio-item');
|
||||
if (!item) return;
|
||||
const id = item.getAttribute('data-project-id');
|
||||
openProjectModal(id);
|
||||
});
|
||||
if (portfolioGrid) {
|
||||
portfolioGrid.addEventListener('click', (e) => {
|
||||
const item = e.target.closest('.portfolio-item');
|
||||
if (!item) return;
|
||||
const id = item.getAttribute('data-project-id');
|
||||
openProjectModal(id);
|
||||
});
|
||||
}
|
||||
|
||||
if (modalClose) modalClose.addEventListener('click', closeProjectModal);
|
||||
if (modalOverlay) modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) closeProjectModal(); });
|
||||
window.addEventListener('keydown', (e) => { if (e.key === 'Escape' && modalOverlay.classList.contains('active')) closeProjectModal(); });
|
||||
window.addEventListener('keydown', (e) => { if (e.key === 'Escape' && modalOverlay && modalOverlay.classList.contains('active')) closeProjectModal(); });
|
||||
|
||||
if (galleryPrev) galleryPrev.addEventListener('click', prevSlide);
|
||||
if (galleryNext) galleryNext.addEventListener('click', nextSlide);
|
||||
|
|
@ -331,9 +389,16 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
const featuresHtml = (p.features || []).slice(0, 6).map(f => `<span class="bento-feature">${f}</span>`).join('');
|
||||
const divider = i < featured.length - 1 ? '<div class="bento-divider"></div>' : '';
|
||||
|
||||
const prefix = isRoot ? '' : '../';
|
||||
const firstFile = Array.isArray(p.files) && p.files.length > 0 ? p.files[0] : null;
|
||||
const bentoCover = firstFile
|
||||
? `<img src="${prefix}${p.images}${firstFile}" alt="${p.title}" class="bento-cover-img" onerror="this.style.display='none'">`
|
||||
: '';
|
||||
|
||||
return `
|
||||
<div class="bento-project${isReverse} reveal" data-project-id="${p.id}" style="cursor:pointer;" role="button" tabindex="0" aria-label="Voir le projet ${p.title}">
|
||||
<div class="bento-image" style="background: ${p.gradient};">
|
||||
${bentoCover}
|
||||
<div class="bento-image-content">
|
||||
<span class="portfolio-tag">${categoryLabels[p.category] || p.category}</span>
|
||||
<h3>${p.title}</h3>
|
||||
|
|
|
|||