diff --git a/.htaccess b/.htaccess index 760a81b..84ad129 100644 --- a/.htaccess +++ b/.htaccess @@ -4,14 +4,22 @@ Options -Indexes RewriteEngine On - # Si fichier/dossier n'existe pas → afficher 404.php + # Force HTTPS (décommenter en production) + # RewriteCond %{HTTPS} off + # RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + + # Si le fichier ou dossier demandé n'existe pas → 404 RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ pages/404.php [L] - # Si c'est un dossier sans index → afficher 404.php + # Dossier sans index → 404 RewriteCond %{REQUEST_FILENAME} -d RewriteCond %{REQUEST_FILENAME}/index.html !-f RewriteCond %{REQUEST_FILENAME}/index.php !-f RewriteRule ^ pages/404.php [L] + +# Page d'erreur Apache native (fallback si mod_rewrite absent) +ErrorDocument 404 /pages/404.php +ErrorDocument 403 /pages/404.php diff --git a/assets/css/main.css b/assets/css/main.css index 7cb36ed..8a8d4f2 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -441,6 +441,19 @@ nav.scrolled { text-align: center; } +/* Le contenu du page-hero est toujours visible dès le chargement */ +.page-hero .reveal, +.page-hero-content { + opacity: 1; + transform: none; + transition: none; + animation: fadeInUp 0.7s ease forwards; +} +.page-hero .reveal.active { + opacity: 1; + transform: none; +} + .hero-content { text-align: center; max-width: 900px; @@ -989,14 +1002,14 @@ section { position: absolute; inset: -4px; background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); - clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); + clip-path: polygon(29.3% 0%, 70.7% 0%, 100% 29.3%, 100% 70.7%, 70.7% 100%, 29.3% 100%, 0% 70.7%, 0% 29.3%); } .team-avatar-inner { position: absolute; inset: 0; background: var(--bg-secondary); - clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); + clip-path: polygon(29.3% 0%, 70.7% 0%, 100% 29.3%, 100% 70.7%, 70.7% 100%, 29.3% 100%, 0% 70.7%, 0% 29.3%); display: flex; align-items: center; justify-content: center; @@ -1415,6 +1428,14 @@ footer { justify-content: center; color: var(--text-secondary); transition: all 0.3s ease; + flex-shrink: 0; +} + +.footer-social a svg { + width: 20px; + height: 20px; + fill: currentColor; + display: block; } .footer-social a:hover { @@ -1423,6 +1444,110 @@ footer { color: white; } +/* ── Partners Section ──────────────────────────────────────────────────────── */ +.partners { + padding: 6rem 2rem; + max-width: 1200px; + margin: 0 auto; +} + +.partners-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1.5rem; + margin-top: 3rem; +} + +.partner-card { + display: flex; + align-items: center; + gap: 1.25rem; + background: var(--card-bg); + border: 1px solid var(--glass-border); + border-radius: 8px; + padding: 1.5rem; + text-decoration: none; + color: var(--text-primary); + transition: all 0.3s ease; +} + +.partner-card:hover { + border-color: var(--accent-primary); + transform: translateY(-3px); + box-shadow: 0 8px 30px rgba(var(--accent-primary-rgb, 142, 36, 170), 0.15); +} + +.partner-avatar { + width: 56px; + height: 56px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + font-family: 'OmegaLarge', sans-serif; + font-size: 1rem; + color: white; + flex-shrink: 0; +} + +.partner-info h4 { + font-family: 'OmegaLarge', sans-serif; + font-size: 1rem; + margin-bottom: 0.25rem; + color: var(--text-primary); +} + +.partner-info p { + font-size: 0.85rem; + color: var(--text-muted); +} + +/* ── Easter Egg ────────────────────────────────────────────────────────────── */ +#easter-egg-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.85); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + animation: fadeIn 0.3s ease; +} + +.easter-egg-box { + background: var(--card-bg); + border: 2px solid var(--accent-primary); + border-radius: 12px; + padding: 3rem 2.5rem; + text-align: center; + max-width: 480px; + width: 90%; + animation: scaleIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.easter-egg-icon { + font-size: 4rem; + margin-bottom: 1rem; + display: block; +} + +.easter-egg-box h2 { + font-family: 'OmegaLarge', sans-serif; + font-size: 1.5rem; + color: var(--accent-light); + margin-bottom: 1rem; +} + +.easter-egg-box p { + color: var(--text-secondary); + margin-bottom: 2rem; +} + +@keyframes scaleIn { + from { transform: scale(0.7); opacity: 0; } + to { transform: scale(1); opacity: 1; } +} + .footer-bottom { max-width: 1200px; margin: 2rem auto 0; @@ -1697,14 +1822,14 @@ footer { position: absolute; inset: -4px; background: rgba(255, 255, 255, 0.3); - clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); + clip-path: polygon(29.3% 0%, 70.7% 0%, 100% 29.3%, 100% 70.7%, 70.7% 100%, 29.3% 100%, 0% 70.7%, 0% 29.3%); } .team-modal-avatar-inner { position: absolute; inset: 0; background: var(--bg-secondary); - clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); + clip-path: polygon(29.3% 0%, 70.7% 0%, 100% 29.3%, 100% 70.7%, 70.7% 100%, 29.3% 100%, 0% 70.7%, 0% 29.3%); display: flex; align-items: center; justify-content: center; @@ -1815,6 +1940,8 @@ footer { .team-modal-social a svg { width: 18px; height: 18px; + fill: currentColor; + display: block; } @media (max-width: 480px) { @@ -2535,50 +2662,9 @@ footer { gap: 0.75rem; } -.project-tech-item { - padding: 0.75rem 1.25rem; - background: var(--glass-bg); - border: 1px solid var(--glass-border); - border-radius: 4px; - color: var(--text-secondary); - font-size: 0.9rem; - display: flex; - align-items: center; - gap: 0.5rem; - transition: all 0.3s ease; -} +/* project-tech-item and project-stats-grid → see end of file */ -.project-tech-item:hover { - border-color: var(--accent-primary); - color: var(--accent-light); -} - -/* Stats Grid in Modal */ -.project-stats-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1rem; -} - -.project-stat-card { - text-align: center; - padding: 1.5rem; - background: var(--glass-bg); - border: 1px solid var(--glass-border); - border-radius: 8px; -} - -.project-stat-card-value { - font-family: 'OmegaLarge', sans-serif; - font-size: 2rem; - color: var(--accent-light); - margin-bottom: 0.25rem; -} - -.project-stat-card-label { - font-size: 0.85rem; - color: var(--text-muted); -} +/* project-stat-card-value / label → see end of file */ @media (max-width: 768px) { .project-modal { @@ -2728,7 +2814,11 @@ footer { } .process-step.active .process-step-icon { - color: white; + color: var(--accent-light); +} + +[data-theme="light"] .process-step.active .process-step-icon { + color: var(--accent-primary); } .process-step-content { @@ -3585,3 +3675,497 @@ footer { font-size: 1.1rem; } } + + +/* ═══════════════════════════════════════════════════════════════════════════ + HOME — Partners carousel + ═══════════════════════════════════════════════════════════════════════════ */ + +.partners-carousel-wrapper { + overflow: hidden; + padding: 2rem 0; + position: relative; + mask-image: linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%); + -webkit-mask-image: linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%); +} + +.partners-carousel-track { + display: flex; + gap: 1.5rem; + width: max-content; + will-change: transform; +} + +.partner-slide { + flex-shrink: 0; + cursor: pointer; +} + +.partner-slide-inner { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.6rem; + background: var(--card-bg); + border: 1px solid var(--glass-border); + border-radius: 10px; + padding: 1.25rem 1.75rem; + width: 160px; + transition: border-color 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease; +} + +.partner-slide:hover .partner-slide-inner { + border-color: var(--accent-primary); + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(142,36,170,0.18); +} + +.partner-logo-img { + width: 72px; + height: 72px; + object-fit: contain; + border-radius: 8px; +} + +.partner-logo-fallback { + width: 72px; + height: 72px; + border-radius: 8px; + align-items: center; + justify-content: center; + font-family: 'OmegaLarge', sans-serif; + font-size: 1.2rem; + color: white; + display: none; +} + +.partner-slide-name { + font-size: 0.82rem; + color: var(--text-secondary); + text-align: center; + font-weight: 500; +} + +/* Partner modal — réutilise team-modal, surcharge avatar */ +.partner-modal-avatar { + background: none !important; + border: none !important; +} + +.team-modal-avatar-photo { + width: 72px; + height: 72px; + border-radius: 10px; + object-fit: contain; + display: block; +} + +.team-modal-avatar-fallback { + width: 72px; + height: 72px; + border-radius: 10px; + align-items: center; + justify-content: center; + font-family: 'OmegaLarge', sans-serif; + font-size: 1.25rem; + color: white; + display: none; +} + +/* ═══════════════════════════════════════════════════════════════════════════ + TEAM — Avatar photo + ═══════════════════════════════════════════════════════════════════════════ */ + +.team-avatar-photo { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 50%; + display: block; +} + +.team-avatar-fallback { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + font-family: 'OmegaLarge', sans-serif; + font-size: 1.5rem; + color: var(--text-primary); +} + +.team-avatar-inner { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + border-radius: 50%; + overflow: hidden; +} + +/* Modal avatar photo */ +.team-modal-avatar-inner { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + overflow: hidden; +} + +/* ═══════════════════════════════════════════════════════════════════════════ + SERVICES — Note + ═══════════════════════════════════════════════════════════════════════════ */ + +.service-note { + font-size: 0.78rem; + color: var(--text-muted); + margin-bottom: 1rem; + line-height: 1.5; + font-style: italic; +} + +.service-note-mark { + color: var(--accent-light); + font-style: normal; + font-weight: 600; +} + +/* ═══════════════════════════════════════════════════════════════════════════ + FAQ — Page + ═══════════════════════════════════════════════════════════════════════════ */ + +.faq-teaser { + padding: 0 2rem 2rem; + max-width: 1200px; + margin: 0 auto; +} + +.faq-teaser-inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 2rem; + background: var(--card-bg); + border: 1px solid var(--glass-border); + border-left: 4px solid var(--accent-primary); + border-radius: 10px; + padding: 2rem 2.5rem; +} + +.faq-teaser-text p.section-tag { + margin-bottom: 0.5rem; +} + +.faq-teaser-text h2 { + font-family: 'OmegaLarge', sans-serif; + font-size: 1.4rem; + color: var(--text-primary); + margin-bottom: 0.6rem; +} + +.faq-teaser-text p:last-child { + color: var(--text-secondary); + font-size: 0.9rem; + max-width: 480px; +} + +.faq-teaser-btn { + white-space: nowrap; + flex-shrink: 0; +} + +.faq-page-section { + padding: 2rem 2rem 5rem; +} + +.faq-page-container { + max-width: 860px; + margin: 0 auto; + display: flex; + flex-direction: column; + gap: 2.5rem; +} + +.faq-category-title { + font-family: 'OmegaLarge', sans-serif; + font-size: 1rem; + color: var(--accent-light); + display: flex; + align-items: center; + gap: 0.6rem; + margin-bottom: 1rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid var(--glass-border); +} + +.faq-category-title svg { + width: 18px; + height: 18px; + flex-shrink: 0; +} + +.faq-list { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.faq-item { + background: var(--card-bg); + border: 1px solid var(--glass-border); + border-radius: 8px; + overflow: hidden; + transition: border-color 0.25s ease; +} + +.faq-item.open { + border-color: var(--accent-primary); +} + +.faq-question { + width: 100%; + background: none; + border: none; + padding: 1.1rem 1.5rem; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + cursor: pointer; + font-size: 0.95rem; + font-weight: 500; + color: var(--text-primary); + text-align: left; + transition: color 0.25s ease; +} + +.faq-question:hover { + color: var(--accent-light); +} + +.faq-chevron { + width: 18px; + height: 18px; + flex-shrink: 0; + color: var(--text-muted); + transition: transform 0.3s ease; +} + +.faq-item.open .faq-chevron { + transform: rotate(180deg); + color: var(--accent-light); +} + +.faq-answer { + max-height: 0; + overflow: hidden; + transition: max-height 0.35s cubic-bezier(0.4,0,0.2,1), padding 0.3s ease; + padding: 0 1.5rem; +} + +.faq-item.open .faq-answer { + max-height: 300px; + padding: 0 1.5rem 1.25rem; +} + +.faq-answer p { + font-size: 0.9rem; + color: var(--text-secondary); + line-height: 1.7; +} + +.faq-answer strong { + color: var(--text-primary); +} + +.faq-cta-section { + background: var(--card-bg); + border-top: 1px solid var(--glass-border); + padding: 4rem 2rem; + text-align: center; +} + +.faq-cta-inner { + max-width: 600px; + margin: 0 auto; +} + +.faq-cta-inner h2 { + font-family: 'OmegaLarge', sans-serif; + font-size: 1.6rem; + margin-bottom: 0.75rem; + color: var(--text-primary); +} + +.faq-cta-inner p { + color: var(--text-secondary); + margin-bottom: 2rem; +} + +.faq-cta-btns { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; +} + +@media (max-width: 768px) { + .faq-teaser-inner { + flex-direction: column; + align-items: flex-start; + } + .faq-teaser-btn { + width: 100%; + text-align: center; + justify-content: center; + } +} + + +/* ═══════════════════════════════════════════════════════════════════════════ + GALLERY — image réelle dans les slides + ═══════════════════════════════════════════════════════════════════════════ */ + +/* Slide sans image chargée → masqué */ +.project-gallery-slide.no-image { + display: none !important; +} + +/* Slide avec image → l'image remplace le gradient */ +.project-gallery-slide.has-image { + background: #000 !important; +} + +.gallery-slide-img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 1; + display: block; +} + +/* Enlève l'overlay gradient quand une vraie image est présente */ +.project-gallery-slide.has-image::before { + display: none; +} + +/* ═══════════════════════════════════════════════════════════════════════════ + MODAL TECH — tech items avec icônes + ═══════════════════════════════════════════════════════════════════════════ */ + +.project-tech-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 0.75rem; +} + +.project-tech-item { + padding: 0.75rem 1rem; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 6px; + color: var(--text-secondary); + font-size: 0.88rem; + display: flex; + align-items: center; + gap: 0.6rem; + transition: all 0.25s ease; + font-weight: 500; +} + +.project-tech-item svg { + width: 16px; + height: 16px; + flex-shrink: 0; + color: var(--accent-light); +} + +.project-tech-item:hover { + border-color: var(--accent-primary); + color: var(--text-primary); + background: rgba(142, 36, 170, 0.08); +} + +/* ═══════════════════════════════════════════════════════════════════════════ + MODAL STATS — grille 2×2 valorisée + ═══════════════════════════════════════════════════════════════════════════ */ + +.project-stats-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; +} + +.project-stat-card { + text-align: center; + padding: 1.5rem 1rem; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 10px; + transition: border-color 0.25s ease, transform 0.25s ease; +} + +.project-stat-card:hover { + border-color: var(--accent-primary); + transform: translateY(-2px); +} + +.project-stat-card-icon { + width: 48px; + height: 48px; + background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 0.85rem; +} + +.project-stat-card-icon svg { + width: 22px; + height: 22px; + color: white; +} + +.project-stat-card-value { + font-family: 'OmegaLarge', sans-serif; + font-size: 2.2rem; + color: var(--accent-light); + line-height: 1; + margin-bottom: 0.4rem; +} + +.project-stat-card-label { + font-size: 0.82rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; +} + + + +/* ═══════════════════════════════════════════════════════════════════════════ + PORTFOLIO — image de couverture dans les cartes + ═══════════════════════════════════════════════════════════════════════════ */ + +.portfolio-cover-img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 1; + display: block; +} + +/* .portfolio-bg conserve son gradient en fallback derrière l'image */ +.portfolio-bg { + position: absolute; + inset: 0; + overflow: hidden; +} diff --git a/assets/data/faq.json b/assets/data/faq.json new file mode 100644 index 0000000..bf71819 --- /dev/null +++ b/assets/data/faq.json @@ -0,0 +1,78 @@ +[ + { + "id": "delais", + "icon": "clock", + "title": "Délais & Process", + "questions": [ + { + "q": "Quels sont les délais habituels pour un bot Discord ?", + "a": "Un bot Discord RP complet demande généralement entre 2 et 4 semaines selon la complexité des systèmes (économie, combat, inventaire…). Nous établissons un planning détaillé dès la validation du devis." + }, + { + "q": "Et pour une map Fortnite ?", + "a": "La création d'une map UEFN prend entre 3 et 6 semaines. Ce délai inclut le design, la construction, les scripts Verse et les tests de performance. Des points réguliers sont organisés pour valider chaque étape." + }, + { + "q": "Comment se déroule le processus du début à la fin ?", + "a": "Notre processus se déroule en 4 étapes : Contact (échange sur vos besoins), Devis (proposition détaillée et validation), Création (développement avec suivi régulier) et Livraison (tests, ajustements et remise des fichiers). Vous êtes impliqué à chaque étape." + } + ] + }, + { + "id": "tarifs", + "icon": "dollar-sign", + "title": "Tarifs & Devis", + "questions": [ + { + "q": "Comment sont calculés vos tarifs ?", + "a": "Chaque projet est unique, nos tarifs sont donc établis sur devis selon la complexité, le nombre de fonctionnalités et les délais souhaités. Le devis est gratuit, détaillé et sans engagement." + }, + { + "q": "Le consulting est-il vraiment gratuit ?", + "a": "Oui, la première consultation est entièrement gratuite et sans engagement. Elle permet de cerner vos besoins, évaluer votre projet et vous orienter vers la solution la plus adaptée, qu'elle soit payante ou non." + }, + { + "q": "Y a-t-il des frais cachés ?", + "a": "Non. Le devis que nous soumettons est exhaustif. Les éventuels ajouts de fonctionnalités en cours de projet font l'objet d'un avenant écrit et validé par les deux parties avant toute réalisation." + } + ] + }, + { + "id": "revisions", + "icon": "refresh-cw", + "title": "Révisions & Support", + "questions": [ + { + "q": "Que signifient les « révisions illimitées* » en graphisme ?", + "a": "Les révisions illimitées s'appliquent aux ajustements dans le cadre du brief initial : couleurs, proportions, typo, espacement. Elles ne couvrent pas les changements de direction créative complète (nouveau concept, refonte totale), qui sont facturés séparément." + }, + { + "q": "Que se passe-t-il si mon bot plante après livraison ?", + "a": "Un mois de support est inclus après chaque livraison. Durant cette période, tout bug lié à notre code est corrigé gratuitement et en priorité. Passé ce délai, un contrat de maintenance peut être négocié." + }, + { + "q": "Puis-je modifier moi-même le projet après livraison ?", + "a": "Oui, vous recevez les fichiers sources complets. Nous livrons également une documentation pour vous permettre d'effectuer des modifications de base. Pour des évolutions plus complexes, notre équipe reste disponible." + } + ] + }, + { + "id": "technique", + "icon": "code", + "title": "Technique", + "questions": [ + { + "q": "Quelles technologies utilisez-vous pour les bots Discord ?", + "a": "Nos bots sont développés principalement en Python avec discord.py, couplés à une base de données MySQL. Pour l'hébergement, nous pouvons vous guider vers des solutions adaptées (VPS, serveur dédié) ou gérer l'hébergement pour vous." + }, + { + "q": "Les maps Fortnite sont-elles compatibles avec les dernières mises à jour du jeu ?", + "a": "Oui. Nous développons exclusivement via UEFN (Unreal Editor for Fortnite), l'outil officiel d'Epic Games, ce qui garantit une compatibilité optimale et l'accès aux dernières fonctionnalités disponibles." + }, + { + "q": "Pouvez-vous reprendre un projet existant ?", + "a": "Oui, nous réalisons des audits de projets existants. Après analyse du code ou de la map, nous vous soumettons un rapport et un plan d'action. C'est d'ailleurs inclus dans notre offre de consulting gratuit." + } + ] + } +] diff --git a/assets/data/partners.json b/assets/data/partners.json new file mode 100644 index 0000000..b628b9d --- /dev/null +++ b/assets/data/partners.json @@ -0,0 +1,50 @@ +[ + { + "name": "victorias-vice", + "displayName": "Victoria's Vice", + "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"], + "stats": [ + { "icon": "users", "value": "70", "label": "Joueurs max" }, + { "icon": "home", "value": "30+", "label": "Bâtiments" }, + { "icon": "play", "value": "30+", "label": "Sessions" } + ] + }, + { + "name": "partenaire-2", + "displayName": "Partenaire 2", + "description": "Description courte du partenaire.", + "longDesc": "Description longue du partenaire à renseigner.", + "gradient": "linear-gradient(135deg, var(--accent-primary), var(--accent-secondary))", + "url": "#", + "discord": "#", + "since": "2025", + "services": ["Service 1", "Service 2"], + "stats": [ + { "icon": "star", "value": "?", "label": "Stat 1" }, + { "icon": "zap", "value": "?", "label": "Stat 2" }, + { "icon": "heart", "value": "?", "label": "Stat 3" } + ] + }, + { + "name": "partenaire-3", + "displayName": "Partenaire 3", + "description": "Description courte du partenaire.", + "longDesc": "Description longue du partenaire à renseigner.", + "gradient": "linear-gradient(135deg, #1a3a5e, #3a5e8b)", + "url": "#", + "discord": "#", + "since": "2025", + "services": ["Service 1", "Service 2"], + "stats": [ + { "icon": "star", "value": "?", "label": "Stat 1" }, + { "icon": "zap", "value": "?", "label": "Stat 2" }, + { "icon": "heart", "value": "?", "label": "Stat 3" } + ] + } +] diff --git a/assets/data/projects.json b/assets/data/projects.json index 43fb019..28897f1 100644 --- a/assets/data/projects.json +++ b/assets/data/projects.json @@ -7,21 +7,113 @@ "gradient": "linear-gradient(135deg, var(--accent-primary), var(--accent-secondary))", "year": "2025", "duration": "3 semaines", - "features": ["Système d'absences staff", "Systèmes de sécurité et logs", "Gestion de sanctions", "Système de convocations", "Tickets avec stats", "Et bien plus à venir"], - "tech": ["Python", "Discord.py", "MySQL", "Bach"], - "stats": [{ "value": "1075+", "label": "Utilisateurs" }, { "value": "150+", "label": "Commandes" }, { "value": "97%", "label": "Uptime" }] + "features": [ + "Système d'absences staff", + "Systèmes de sécurité et logs", + "Gestion de sanctions", + "Système de convocations", + "Tickets avec stats", + "Et bien plus à venir" + ], + "tech": [ + { + "name": "Python", + "icon": "code" + }, + { + "name": "Discord.py", + "icon": "message-circle" + }, + { + "name": "MySQL", + "icon": "database" + }, + { + "name": "Bash", + "icon": "terminal" + } + ], + "stats": [ + { + "icon": "users", + "value": "1075+", + "label": "Utilisateurs" + }, + { + "icon": "terminal", + "value": "150+", + "label": "Commandes" + }, + { + "icon": "activity", + "value": "97%", + "label": "Uptime" + }, + { + "icon": "clock", + "value": "3", + "label": "Semaines de dev" + } + ], + "images": "assets/images/projects/kuby/" }, { "id": 2, "category": "map", "title": "Victoria's Vice", - "desc": "Victoria's Vice est serveur RP whitelist Fortnite, Victoria’s Vice, qui est basé sur un thème italien ! Plongez-vous dans un univers authentique où chaque partie est une immersion dans les rues, la culture, et l’ambiance unique italienne.", + "desc": "Victoria's Vice est serveur RP whitelist Fortnite, Victoria's Vice, qui est basé sur un thème italien ! Plongez-vous dans un univers authentique où chaque partie est une immersion dans les rues, la culture, et l'ambiance unique italienne.", "gradient": "linear-gradient(135deg, #5E2040, #8b3a62)", "year": "2025", "duration": "4 mois", - "features": ["économie réaliste", "30+ bâtiments", "Système jour/nuit", "Serveur Whitelist", "Métiers légaux variés", "Bot RP Vicy exclusif"], - "tech": ["Fortnite Creative", "UEFN", "Verse", "Texturing"], - "stats": [{ "value": "30+", "label": "Sessions" }, { "value": "30+", "label": "Bâtiments" }, { "value": "70", "label": "Joueurs max" }] + "features": [ + "économie réaliste", + "30+ 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": "30+", + "label": "Sessions" + }, + { + "icon": "home", + "value": "30+", + "label": "Bâtiments" + }, + { + "icon": "users", + "value": "70", + "label": "Joueurs max" + }, + { + "icon": "calendar", + "value": "4", + "label": "Mois de dev" + } + ], + "images": "assets/images/projects/victorias-vice/" }, { "id": 3, @@ -31,9 +123,55 @@ "gradient": "linear-gradient(135deg, #3a1f5e, #8b3a62)", "year": "2024", "duration": "", - "features": ["Économie réaliste", "Gestion ticket Whitelist", "Carte d'identités", "Prise de RDV", "système de comptes avec RIB", "Fonctionnalités bonus"], - "tech": ["Python", "Discord.py", "MySQL", "Pillow"], - "stats": [{ "value": "300+", "label": "Utilisateurs" }, { "value": "100+", "label": "Commandes" }, { "value": "60+", "label": "RIB générés" }] + "features": [ + "Économie réaliste", + "Gestion ticket Whitelist", + "Carte d'identités", + "Prise de RDV", + "système de comptes avec RIB", + "Fonctionnalités bonus" + ], + "tech": [ + { + "name": "Python", + "icon": "code" + }, + { + "name": "Discord.py", + "icon": "message-circle" + }, + { + "name": "MySQL", + "icon": "database" + }, + { + "name": "Pillow", + "icon": "image" + } + ], + "stats": [ + { + "icon": "users", + "value": "300+", + "label": "Utilisateurs" + }, + { + "icon": "terminal", + "value": "100+", + "label": "Commandes" + }, + { + "icon": "credit-card", + "value": "60+", + "label": "RIB générés" + }, + { + "icon": "activity", + "value": "99%", + "label": "Uptime" + } + ], + "images": "assets/images/projects/vicy/" }, { "id": 4, @@ -42,10 +180,49 @@ "desc": "Identité visuelle complète incluant logo, bannières, emojis personnalisés et templates pour Omega Kube.", "gradient": "linear-gradient(135deg, #2d1b5e, #5E2040)", "year": "2024", - "duration": "1 semaines", - "features": ["Logo principal", "charte graphique", "Bannières", "Animations After effect"], - "tech": ["Photoshop", "Illustrator", "After Effects", "Figma"], - "stats": [{ "value": "5+", "label": "Éléments" }, { "value": "2h", "label": "After Effects" }, { "value": "3h", "label": "Illustrator" }] + "duration": "1 semaine", + "features": [ + "Logo principal", + "Charte graphique", + "Bannières", + "Animations After Effects" + ], + "tech": [ + { + "name": "Photoshop", + "icon": "edit" + }, + { + "name": "Illustrator", + "icon": "pen-tool" + }, + { + "name": "After Effects", + "icon": "film" + }, + { + "name": "Figma", + "icon": "pen-tool" + } + ], + "stats": [ + { + "icon": "layers", + "value": "5+", + "label": "Éléments" + }, + { + "icon": "clock", + "value": "2h", + "label": "After Effects" + }, + { + "icon": "pen-tool", + "value": "3h", + "label": "Illustrator" + } + ], + "images": "assets/images/projects/branding-omega-kube/" }, { "id": 5, @@ -55,8 +232,44 @@ "gradient": "linear-gradient(135deg, #1a3a5e, #3a5e8b)", "year": "2026", "duration": "4 mois", - "features": ["Intégration de SCP", "Cinématiques", "Modeles 3D", "Site Souterrain", "Terraforming exterieur"], - "tech": ["Fortnite Creative", "UEFN", "Custom Props"], - "stats": [{ "value": "6+", "label": "Secteurs" }, { "value": "15+", "label": "SCP" }, { "value": "30+", "label": "Systèmes" }] + "features": [ + "Intégration de SCP", + "Cinématiques", + "Modèles 3D", + "Site Souterrain", + "Terraforming extérieur" + ], + "tech": [ + { + "name": "Fortnite Creative", + "icon": "map" + }, + { + "name": "UEFN", + "icon": "layers" + }, + { + "name": "Custom Props", + "icon": "box" + } + ], + "stats": [ + { + "icon": "grid", + "value": "6+", + "label": "Secteurs" + }, + { + "icon": "shield", + "value": "15+", + "label": "SCP" + }, + { + "icon": "cpu", + "value": "30+", + "label": "Systèmes" + } + ], + "images": "assets/images/projects/site-omega/" } -] +] \ No newline at end of file diff --git a/assets/data/services.json b/assets/data/services.json index 2cf98c3..4306138 100644 --- a/assets/data/services.json +++ b/assets/data/services.json @@ -14,21 +14,68 @@ "title": "Bots Discord RP", "description": "Des bots entièrement personnalisés pour animer et gérer vos serveurs Role Play. Nous développons des systèmes complets adaptés à votre univers et à vos mécaniques de jeu.", "highlights": [ - { "value": "2-4", "label": "Semaines de dev" }, - { "value": "1 mois","label": "Support inclus" }, - { "value": "100%", "label": "Personnalisable" } + { + "value": "2-4", + "label": "Semaines de dev" + }, + { + "value": "1 mois", + "label": "Support inclus" + }, + { + "value": "100%", + "label": "Personnalisable" + } ], "features": [ - { "icon": "dollar-sign", "title": "Économie virtuelle", "desc": "Monnaie, banque, transactions" }, - { "icon": "briefcase", "title": "Système de métiers", "desc": "Jobs, skills, progression" }, - { "icon": "shield", "title": "Combat & Stats", "desc": "PvP, PvE, attributs" }, - { "icon": "box", "title": "Inventaire avancé", "desc": "Items, crafting, échanges" }, - { "icon": "flag", "title": "Factions & Guildes", "desc": "Clans, territoires, guerres" }, - { "icon": "settings", "title": "Panel Admin", "desc": "Interface web de gestion" } + { + "icon": "dollar-sign", + "title": "Économie virtuelle", + "desc": "Monnaie, banque, transactions" + }, + { + "icon": "briefcase", + "title": "Système de métiers", + "desc": "Jobs, skills, progression" + }, + { + "icon": "shield", + "title": "Combat & Stats", + "desc": "PvP, PvE, attributs" + }, + { + "icon": "box", + "title": "Inventaire avancé", + "desc": "Items, crafting, échanges" + }, + { + "icon": "flag", + "title": "Factions & Guildes", + "desc": "Clans, territoires, guerres" + }, + { + "icon": "settings", + "title": "Panel Admin", + "desc": "Interface web de gestion" + } ], "cta": [ - { "label": "Demander un devis", "href": "index.html#contact", "style": "primary", "icon": "message-circle" }, - { "label": "Voir nos réalisations","href": "realisations.html", "style": "secondary","icon": "eye" } + { + "label": "Demander un devis", + "href": "index.html#contact", + "style": "primary", + "icon": "message-circle" + }, + { + "label": "Voir nos réalisations", + "href": "realisations.html", + "style": "secondary", + "icon": "eye" + } + ], + "note": [ + {"value": "*","label": "Support inclus 1 mois après livraison, prolongeable sur devis"}, + {"value": "✓","label": "Développement 100% personnalisé selon vos besoins"} ] }, { @@ -36,31 +83,86 @@ "nav": { "label": "Maps Fortnite", "icon": "map", - "stamp": { "type": "new", "text": "New" } + "stamp": { + "type": "new", + "text": "New" + } }, "visual": { "accentFrom": "#1a5e8b", "accentTo": "#3a8bb5", - "stamp": { "type": "new", "text": "✦ New" } + "stamp": { + "type": "new", + "text": "✦ New" + } }, "title": "Maps Fortnite RP", "description": "Des environnements immersifs créés sur mesure pour vos sessions Role Play. De la ville moderne au village médiéval, nous donnons vie à tous les univers.", "highlights": [ - { "value": "3-6", "label": "Semaines de création" }, - { "value": "50+", "label": "Joueurs supportés" }, - { "value": "UEFN", "label": "Compatible" } + { + "value": "3-6", + "label": "Semaines de création" + }, + { + "value": "50+", + "label": "Joueurs supportés" + }, + { + "value": "UEFN", + "label": "Compatible" + } ], "features": [ - { "icon": "home", "title": "Bâtiments détaillés", "desc": "Intérieurs accessibles et déco" }, - { "icon": "sun", "title": "Cycle jour/nuit", "desc": "Ambiance dynamique" }, - { "icon": "zap", "title": "Zones interactives", "desc": "Scripts et triggers" }, - { "icon": "cpu", "title": "Optimisation", "desc": "Performance garantie" }, - { "icon": "map-pin", "title": "Points d'intérêt", "desc": "Lieux uniques et mémorables" }, - { "icon": "edit-3", "title": "Modifications", "desc": "Ajustements post-livraison" } + { + "icon": "home", + "title": "Bâtiments détaillés", + "desc": "Intérieurs accessibles et déco" + }, + { + "icon": "sun", + "title": "Cycle jour/nuit", + "desc": "Ambiance dynamique" + }, + { + "icon": "zap", + "title": "Zones interactives", + "desc": "Scripts et triggers" + }, + { + "icon": "cpu", + "title": "Optimisation", + "desc": "Performance garantie" + }, + { + "icon": "map-pin", + "title": "Points d'intérêt", + "desc": "Lieux uniques et mémorables" + }, + { + "icon": "edit-3", + "title": "Modifications", + "desc": "Ajustements post-livraison" + } ], "cta": [ - { "label": "Demander un devis", "href": "index.html#contact", "style": "primary", "icon": "message-circle" }, - { "label": "Voir nos maps", "href": "realisations.html", "style": "secondary", "icon": "eye" } + { + "label": "Demander un devis", + "href": "index.html#contact", + "style": "primary", + "icon": "message-circle" + }, + { + "label": "Voir nos maps", + "href": "realisations.html", + "style": "secondary", + "icon": "eye" + } + ], + "note": [ + { + "value": "*", + "label": "Modifications post-livraison incluses pendant 2 semaines" + } ] }, { @@ -78,21 +180,70 @@ "title": "Créations Graphiques", "description": "Une identité visuelle forte et cohérente pour vos projets. Du logo aux bannières, nous créons des visuels qui marquent les esprits et attirent votre communauté.", "highlights": [ - { "value": "1-2", "label": "Semaines" }, - { "value": "∞", "label": "Révisions" }, - { "value": "HD", "label": "Qualité" } + { + "value": "1-2", + "label": "Semaines" + }, + { + "value": "∞", + "label": "Révisions" + }, + { + "value": "HD", + "label": "Qualité" + } ], "features": [ - { "icon": "hexagon", "title": "Logo & Branding", "desc": "Identité visuelle complète" }, - { "icon": "layout", "title": "Bannières Discord", "desc": "Server banner, splash, icône" }, - { "icon": "smile", "title": "Emojis custom", "desc": "Pack emojis personnalisés" }, - { "icon": "grid", "title": "Icônes & Assets", "desc": "Items, skills, badges" }, - { "icon": "play-circle","title": "Animations", "desc": "GIFs et motion design" }, - { "icon": "book-open", "title": "Guide de style", "desc": "Charte graphique fournie" } + { + "icon": "hexagon", + "title": "Logo & Branding", + "desc": "Identité visuelle complète" + }, + { + "icon": "layout", + "title": "Bannières Discord", + "desc": "Server banner, splash, icône" + }, + { + "icon": "smile", + "title": "Emojis custom", + "desc": "Pack emojis personnalisés" + }, + { + "icon": "grid", + "title": "Icônes & Assets", + "desc": "Items, skills, badges" + }, + { + "icon": "play-circle", + "title": "Animations", + "desc": "GIFs et motion design" + }, + { + "icon": "book-open", + "title": "Guide de style", + "desc": "Charte graphique fournie" + } ], "cta": [ - { "label": "Demander un devis", "href": "index.html#contact", "style": "primary", "icon": "message-circle" }, - { "label": "Voir nos créations", "href": "realisations.html", "style": "secondary", "icon": "eye" } + { + "label": "Demander un devis", + "href": "index.html#contact", + "style": "primary", + "icon": "message-circle" + }, + { + "label": "Voir nos créations", + "href": "realisations.html", + "style": "secondary", + "icon": "eye" + } + ], + "note": [ + { + "value": "*", + "label": "Révisions illimitées sous conditions, voir FAQ" + } ] }, { @@ -100,31 +251,85 @@ "nav": { "label": "Consulting", "icon": "users", - "stamp": { "type": "free", "text": "Gratuit" } + "stamp": { + "type": "free", + "text": "Gratuit" + } }, "visual": { "accentFrom": "#3a8b5e", "accentTo": "#5ab57a", - "stamp": { "type": "free", "text": "✓ Gratuit", "animated": true } + "stamp": { + "type": "free", + "text": "✓ Gratuit", + "animated": true + } }, "title": "Consulting & Suivie", - "titleStamp": { "type": "free", "text": "Gratuit" }, + "titleStamp": { + "type": "free", + "text": "Gratuit" + }, "description": "Bénéficiez de notre expérience pour structurer, lancer et développer votre projet RP. Conseils stratégiques et accompagnement personnalisé.", "highlights": [ - { "value": "50+", "label": "Projets accompagnés" }, - { "value": "3 ans", "label": "D'expérience" }, - { "value": "24/7", "label": "Support Discord" } + { + "value": "50+", + "label": "Projets accompagnés" + }, + { + "value": "3 ans", + "label": "D'expérience" + }, + { + "value": "24/7", + "label": "Support Discord" + } ], "features": [ - { "icon": "search", "title": "Audit serveur", "desc": "Analyse de l'existant" }, - { "icon": "trending-up", "title": "Stratégie croissance","desc": "Plan de développement" }, - { "icon": "user-check", "title": "Formation staff", "desc": "Équipe modération" }, - { "icon": "heart", "title": "Expérience joueur", "desc": "Optimisation UX" }, - { "icon": "calendar", "title": "Planning events", "desc": "Calendrier d'animations" }, - { "icon": "dollar-sign", "title": "Monétisation", "desc": "Conseils revenus" } + { + "icon": "search", + "title": "Audit serveur", + "desc": "Analyse de l'existant" + }, + { + "icon": "trending-up", + "title": "Stratégie croissance", + "desc": "Plan de développement" + }, + { + "icon": "user-check", + "title": "Formation staff", + "desc": "Équipe modération" + }, + { + "icon": "heart", + "title": "Expérience joueur", + "desc": "Optimisation UX" + }, + { + "icon": "calendar", + "title": "Planning events", + "desc": "Calendrier d'animations" + }, + { + "icon": "dollar-sign", + "title": "Monétisation", + "desc": "Conseils revenus" + } ], "cta": [ - { "label": "Prendre rendez-vous", "href": "index.html#contact", "style": "primary", "icon": "message-circle" } + { + "label": "Prendre rendez-vous", + "href": "index.html#contact", + "style": "primary", + "icon": "message-circle" + } + ], + "note": [ + { + "value": "✓", + "label": "Première consultation gratuite et sans engagement" + } ] }, { @@ -132,32 +337,91 @@ "nav": { "label": "Pack Complet", "icon": "package", - "stamp": { "type": "popular", "text": "Populaire" } + "stamp": { + "type": "popular", + "text": "Populaire" + } }, "visual": { "accentFrom": "#8b3a8b", "accentTo": "#b55ab5", - "stamp": { "type": "popular", "text": "★ Populaire", "animated": true } + "stamp": { + "type": "popular", + "text": "★ Populaire", + "animated": true + } }, "title": "Pack Serveur Complet", - "titleStamp": { "type": "promo", "text": "-20%" }, + "titleStamp": { + "type": "promo", + "text": "-20%" + }, "description": "La solution tout-en-un pour lancer votre projet RP. Bot personnalisé, map Fortnite, identité visuelle et accompagnement : tout est inclus pour un lancement réussi.", "highlights": [ - { "value": "-20%", "label": "VS services séparés" }, - { "value": "3 mois", "label": "Support inclus" }, - { "value": "Clé en main","label": "Prêt à lancer" } + { + "value": "-20%", + "label": "VS services séparés" + }, + { + "value": "3 mois", + "label": "Support inclus" + }, + { + "value": "Clé en main", + "label": "Prêt à lancer" + } ], "features": [ - { "icon": "code", "title": "Bot Discord RP", "desc": "Système complet personnalisé" }, - { "icon": "map", "title": "Map Fortnite", "desc": "Environnement sur mesure" }, - { "icon": "image", "title": "Pack graphique", "desc": "Logo, bannières, emojis" }, - { "icon": "server", "title": "Config Discord", "desc": "Serveur prêt à l'emploi" }, - { "icon": "book", "title": "Documentation", "desc": "Guides et tutoriels" }, - { "icon": "headphones", "title": "Support 3 mois", "desc": "Accompagnement lancement" } + { + "icon": "code", + "title": "Bot Discord RP", + "desc": "Système complet personnalisé" + }, + { + "icon": "map", + "title": "Map Fortnite", + "desc": "Environnement sur mesure" + }, + { + "icon": "image", + "title": "Pack graphique", + "desc": "Logo, bannières, emojis" + }, + { + "icon": "server", + "title": "Config Discord", + "desc": "Serveur prêt à l'emploi" + }, + { + "icon": "book", + "title": "Documentation", + "desc": "Guides et tutoriels" + }, + { + "icon": "headphones", + "title": "Support 3 mois", + "desc": "Accompagnement lancement" + } ], "cta": [ - { "label": "Demander un devis pack", "href": "index.html#contact", "style": "primary", "icon": "message-circle" }, - { "label": "Voir nos packs livrés", "href": "realisations.html", "style": "secondary", "icon": "eye" } + { + "label": "Demander un devis pack", + "href": "index.html#contact", + "style": "primary", + "icon": "message-circle" + }, + { + "label": "Voir nos packs livrés", + "href": "realisations.html", + "style": "secondary", + "icon": "eye" + } + ], + "note": [ + { + "value": "*", + "label": "Réduction de 20% appliquée automatiquement vs services séparés" + } ] } -] +] \ No newline at end of file diff --git a/assets/js/home.js b/assets/js/home.js index 8be00d0..2d8eb16 100644 --- a/assets/js/home.js +++ b/assets/js/home.js @@ -1,36 +1,238 @@ -// Home page specific JS: team carousel + modal +// Home page specific JS: team carousel + modal + portfolio preview + partners + konami document.addEventListener('DOMContentLoaded', () => { - // Fetch team data and render + + // ── Portfolio preview — même cartes que la page réalisations ────────────────── + const homePortfolioGrid = document.getElementById('home-portfolio-grid'); + if (homePortfolioGrid) { + fetch('assets/data/projects.json') + .then(res => res.json()) + .then(projects => { + const first3 = projects.slice(0, 3); + const delayClasses = ['', ' reveal-delay-1', ' reveal-delay-2']; + const categoryLabels = { bot: 'Bot Discord', map: 'Fortnite Map', graphisme: 'Graphisme' }; + + homePortfolioGrid.innerHTML = first3.map((p, i) => { + const imgSrc = (p.images || '') + '1.png'; + return ` +
+
+ ${p.title} +
+
+ ${categoryLabels[p.category] || p.category} +

${p.title}

+

${p.desc.substring(0, 80)}${p.desc.length > 80 ? '…' : ''}

+
+
+ `; + }).join(''); + + setTimeout(() => { + homePortfolioGrid.querySelectorAll('.reveal').forEach(el => el.classList.add('active')); + if (window.feather && feather.replace) try { feather.replace(); } catch(e) {} + }, 80); + + // Délégation : attend que openProjectModal soit défini par realisations.js + 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); + } + }); + }) + .catch(err => console.error('Failed to load projects for home preview', err)); + } + + // ── Partners — carousel + modal ───────────────────────────────────────────── + const partnersTrack = document.getElementById('partners-track'); + const partnerModalOverlay = document.getElementById('partner-modal-overlay'); + + if (partnersTrack) { + fetch('assets/data/partners.json') + .then(res => res.json()) + .then(partners => { + if (!partners.length) { + const section = document.getElementById('partenaires'); + if (section) section.style.display = 'none'; + return; + } + + // Générer les slides (tripler pour garantir boucle sans trou) + const copies = [...partners, ...partners, ...partners]; + partnersTrack.innerHTML = copies.map((p, i) => { + const imgSrc = `assets/images/partners/${p.name}.png`; + return ` +
+
+ ${p.displayName} + + ${p.displayName} +
+
+ `; + }).join(''); + + // Défilement infini — loopWidth mesuré une seule fois après rendu complet + const track = partnersTrack; + let isPaused = false; + let scrollPos = 0; + let loopWidth = 0; + let animFrame; + const SPEED = 0.6; + + function measureLoop() { + // Mesure la largeur exacte d'un bloc (1/3 des slides) + // On utilise offsetWidth de chaque slide réel + gap pour éviter les arrondis de scrollWidth + const slides = Array.from(track.querySelectorAll('.partner-slide')); + const perGroup = partners.length; + if (slides.length < perGroup) return 0; + // Largeur d'un groupe = somme des (offsetWidth + gap) des premiers slides + const style = window.getComputedStyle(track); + const gap = parseFloat(style.gap) || 24; + let w = 0; + for (let i = 0; i < perGroup; i++) { + w += slides[i].offsetWidth + gap; + } + return Math.round(w); + } + + function autoScroll() { + if (!isPaused) { + scrollPos += SPEED; + if (loopWidth > 0 && scrollPos >= loopWidth) { + scrollPos -= loopWidth; + } + track.style.transform = `translateX(-${scrollPos}px)`; + } + animFrame = requestAnimationFrame(autoScroll); + } + + // Attendre que les slides soient rendues et mesurées + requestAnimationFrame(() => { + requestAnimationFrame(() => { + loopWidth = measureLoop(); + animFrame = requestAnimationFrame(autoScroll); + }); + }); + + // Re-mesurer au resize pour rester précis + let resizeTO; + window.addEventListener('resize', () => { + clearTimeout(resizeTO); + resizeTO = setTimeout(() => { loopWidth = measureLoop(); }, 150); + }); + + track.parentElement.addEventListener('mouseenter', () => { isPaused = true; }); + track.parentElement.addEventListener('mouseleave', () => { isPaused = false; }); + + // Click → modal + track.querySelectorAll('.partner-slide').forEach(slide => { + const open = () => { + const idx = parseInt(slide.dataset.partnerIdx, 10); + openPartnerModal(partners[idx]); + }; + slide.addEventListener('click', open); + slide.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(); } }); + }); + + function openPartnerModal(p) { + isPaused = true; + const imgSrc = `assets/images/partners/${p.name}.png`; + document.getElementById('partner-modal-logo-img').src = imgSrc; + document.getElementById('partner-modal-logo-img').alt = p.displayName; + document.getElementById('partner-modal-logo-img').onerror = function() { + this.style.display = 'none'; + document.getElementById('partner-modal-logo-fallback').style.display = 'flex'; + document.getElementById('partner-modal-logo-fallback').style.background = p.gradient; + document.getElementById('partner-modal-logo-fallback').textContent = p.displayName.substring(0,2).toUpperCase(); + }; + document.getElementById('partner-modal-logo-fallback').style.display = 'none'; + document.getElementById('partner-modal-name').textContent = p.displayName; + document.getElementById('partner-modal-desc').textContent = p.longDesc; + document.getElementById('partner-modal-since').textContent = p.since; + document.getElementById('partner-modal-services').innerHTML = (p.services || []).map(s => `${s}`).join(''); + document.getElementById('partner-modal-stats').innerHTML = (p.stats || []).map(s => ` +
+
${s.value}
+
${s.label}
+
+ `).join(''); + const links = document.getElementById('partner-modal-links'); + links.innerHTML = ''; + if (p.url && p.url !== '#') { + links.innerHTML += ` Site web`; + } + if (p.discord && p.discord !== '#') { + links.innerHTML += ` + + Discord + `; + } + if (window.feather) feather.replace(); + partnerModalOverlay.classList.add('active'); + document.body.style.overflow = 'hidden'; + } + + if (partnerModalOverlay) { + const closeBtn = document.getElementById('partner-modal-close'); + closeBtn && closeBtn.addEventListener('click', closePartnerModal); + partnerModalOverlay.addEventListener('click', e => { if (e.target === partnerModalOverlay) closePartnerModal(); }); + document.addEventListener('keydown', e => { + if (e.key === 'Escape' && partnerModalOverlay.classList.contains('active')) closePartnerModal(); + }); + } + + function closePartnerModal() { + partnerModalOverlay.classList.remove('active'); + document.body.style.overflow = ''; + isPaused = false; + } + }) + .catch(err => { + const section = document.getElementById('partenaires'); + if (section) section.style.display = 'none'; + }); + } + + // ── Team section ───────────────────────────────────────────────────────────── fetch('assets/data/team.json') .then(res => res.json()) - .then(members => { - initTeamSection(members); - }) - .catch(err => { - console.error('Failed to load team data', err); - }); + .then(members => { initTeamSection(members); }) + .catch(err => { console.error('Failed to load team data', err); }); + // Konami + easter egg → définis au niveau top-level du script (voir bas de fichier) + + // ── initTeamSection ────────────────────────────────────────────────────────── function initTeamSection(members) { const carousel = document.getElementById('team-carousel'); const dotsContainer = document.getElementById('carousel-dots'); const prevBtn = document.getElementById('carousel-prev'); const nextBtn = document.getElementById('carousel-next'); - // Render cards - carousel.innerHTML = members.map((m, i) => ` -
-
-
${m.avatar}
+ carousel.innerHTML = members.map((m, i) => { + const imgSrc = `assets/images/team/${m.id}.png`; + return ` +
+
+
+ ${m.name} + +
+
+

${m.name}

+

${m.role}

+

${(m.bio || '').split('.').shift()}.

-

${m.name}

-

${m.role}

-

${(m.bio || '').split('.').shift()}.

-
- `).join(''); + `; + }).join(''); - // Carousel logic - const cardsContainer = carousel; // the track - let cards = carousel.querySelectorAll('.team-card'); + let cards = Array.from(carousel.querySelectorAll('.team-card')); let currentIndex = 0; let cardsPerView = 3; let autoPlayInterval; @@ -41,10 +243,11 @@ document.addEventListener('DOMContentLoaded', () => { else cardsPerView = 3; } + function getTotalDots() { return Math.max(1, cards.length - cardsPerView + 1); } + function createDots() { dotsContainer.innerHTML = ''; - const totalDots = Math.max(1, Math.ceil(cards.length / cardsPerView)); - for (let i = 0; i < totalDots; i++) { + for (let i = 0; i < getTotalDots(); i++) { const dot = document.createElement('div'); dot.classList.add('carousel-dot'); if (i === 0) dot.classList.add('active'); @@ -54,65 +257,49 @@ document.addEventListener('DOMContentLoaded', () => { } function updateCarousel() { - cards = carousel.querySelectorAll('.team-card'); - if (cards.length === 0) return; - const cardStyle = window.getComputedStyle(cards[0]); - const gap = 32; // matches previous implementation + cards = Array.from(carousel.querySelectorAll('.team-card')); + if (!cards.length) return; + const gap = 32; const cardWidth = cards[0].offsetWidth + gap; carousel.style.transform = `translateX(-${currentIndex * cardWidth}px)`; - - const dots = dotsContainer.querySelectorAll('.carousel-dot'); - const activeDotIndex = Math.floor(currentIndex / cardsPerView); - dots.forEach((dot, i) => dot.classList.toggle('active', i === activeDotIndex)); + Array.from(dotsContainer.querySelectorAll('.carousel-dot')).forEach((d, i) => d.classList.toggle('active', i === currentIndex)); } - function goToSlide(dotIndex) { - currentIndex = dotIndex * cardsPerView; - if (currentIndex > cards.length - cardsPerView) currentIndex = Math.max(0, cards.length - cardsPerView); - updateCarousel(); - resetAutoPlay(); + function goToSlide(index) { + currentIndex = Math.min(index, Math.max(0, cards.length - cardsPerView)); + updateCarousel(); resetAutoPlay(); } function nextSlide() { - currentIndex++; - if (currentIndex > cards.length - cardsPerView) currentIndex = 0; + const max = Math.max(0, cards.length - cardsPerView); + currentIndex = currentIndex >= max ? 0 : currentIndex + 1; updateCarousel(); } function prevSlide() { - currentIndex--; - if (currentIndex < 0) currentIndex = Math.max(0, cards.length - cardsPerView); + const max = Math.max(0, cards.length - cardsPerView); + currentIndex = currentIndex <= 0 ? max : currentIndex - 1; updateCarousel(); } - function startAutoPlay() { - clearInterval(autoPlayInterval); - autoPlayInterval = setInterval(nextSlide, 4000); - } - - function resetAutoPlay() { - clearInterval(autoPlayInterval); - startAutoPlay(); - } + function startAutoPlay() { clearInterval(autoPlayInterval); autoPlayInterval = setInterval(nextSlide, 4000); } + function resetAutoPlay() { clearInterval(autoPlayInterval); startAutoPlay(); } prevBtn && prevBtn.addEventListener('click', () => { prevSlide(); resetAutoPlay(); }); nextBtn && nextBtn.addEventListener('click', () => { nextSlide(); resetAutoPlay(); }); - carousel.addEventListener('mouseenter', () => clearInterval(autoPlayInterval)); carousel.addEventListener('mouseleave', startAutoPlay); - updateCardsPerView(); - createDots(); - startAutoPlay(); - - window.addEventListener('resize', () => { - updateCardsPerView(); - createDots(); - currentIndex = 0; - updateCarousel(); + let resizeTimer; + const ro = new ResizeObserver(() => { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(() => { updateCardsPerView(); currentIndex = 0; createDots(); updateCarousel(); }, 100); }); + ro.observe(carousel.parentElement || document.body); - // Modal logic + updateCardsPerView(); createDots(); updateCarousel(); startAutoPlay(); + + // Modal const modalOverlay = document.getElementById('team-modal-overlay'); const modalClose = document.getElementById('team-modal-close'); const modalAvatar = document.getElementById('modal-avatar'); @@ -124,29 +311,33 @@ document.addEventListener('DOMContentLoaded', () => { const modalSocial = document.getElementById('modal-social'); function openMemberModal(index) { - const member = members[index]; - if (!member) return; - modalAvatar.textContent = member.avatar || member.name.charAt(0); - modalName.textContent = member.name; - modalRole.textContent = member.role; - modalBio.textContent = member.bio; - - modalStats.innerHTML = (member.stats || []).map(stat => ` + const m = members[index]; + if (!m) return; + // Avatar avec image si dispo + const imgSrc = `assets/images/team/${m.id}.png`; + modalAvatar.innerHTML = ` + ${m.name} + ${m.avatar || m.name.charAt(0)} + `; + modalName.textContent = m.name; + modalRole.textContent = m.role; + modalBio.textContent = m.bio; + modalStats.innerHTML = (m.stats || []).map(s => `
-
${stat.value}
-
${stat.label}
+
${s.value}
+
${s.label}
`).join(''); - - modalSkills.innerHTML = (member.skills || []).map(s => `${s}`).join(''); - + modalSkills.innerHTML = (m.skills || []).map(s => `${s}`).join(''); modalSocial.innerHTML = ` - - + + + + + + `; - - if (window.feather && feather.replace) feather.replace(); - modalOverlay.classList.add('active'); document.body.style.overflow = 'hidden'; } @@ -156,16 +347,68 @@ document.addEventListener('DOMContentLoaded', () => { document.body.style.overflow = ''; } - // Attach click listeners to generated cards carousel.querySelectorAll('.team-card').forEach(card => { - card.addEventListener('click', () => { - const idx = parseInt(card.dataset.member, 10); - openMemberModal(idx); - }); + const openCard = () => { const idx = parseInt(card.dataset.member, 10); openMemberModal(idx); }; + card.addEventListener('click', openCard); + card.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openCard(); } }); }); modalClose && modalClose.addEventListener('click', closeMemberModal); - modalOverlay && modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) closeMemberModal(); }); - document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && modalOverlay.classList.contains('active')) closeMemberModal(); }); + modalOverlay && modalOverlay.addEventListener('click', e => { if (e.target === modalOverlay) closeMemberModal(); }); + document.addEventListener('keydown', e => { if (e.key === 'Escape' && modalOverlay && modalOverlay.classList.contains('active')) closeMemberModal(); }); } -}); \ No newline at end of file +}); + +// ═══════════════════════════════════════════════════════════════════════════ +// EASTER EGG — top-level (hors DOMContentLoaded pour garantir l'attachement) +// ═══════════════════════════════════════════════════════════════════════════ + +(function () { + var KONAMI = ['ArrowUp','ArrowUp','ArrowDown','ArrowDown','ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a']; + var progress = 0; + + // PC : Konami Code — capture:true pour passer avant tout autre listener + window.addEventListener('keydown', function(e) { + var key = e.key.length === 1 ? e.key.toLowerCase() : e.key; + if (key === KONAMI[progress]) { + progress++; + if (progress === KONAMI.length) { + progress = 0; + triggerEasterEgg(); + } + } else { + progress = (key === KONAMI[0]) ? 1 : 0; + } + }, true); // capture phase — reçu avant tout stopPropagation + + // Mobile : 10 taps sur le logo navbar OU logo footer + var tapCount = 0; + var tapTimer = null; + + document.addEventListener('click', function(e) { + if (!e.target.closest('.logo') && !e.target.closest('.logo-mini') && !e.target.closest('.omega-symbol')) return; + if (!('ontouchstart' in window) && navigator.maxTouchPoints === 0) return; + tapCount++; + clearTimeout(tapTimer); + tapTimer = setTimeout(function() { tapCount = 0; }, 600); + if (tapCount >= 10) { tapCount = 0; triggerEasterEgg(); } + }); + + function triggerEasterEgg() { + if (document.getElementById('easter-egg-overlay')) return; + var egg = document.createElement('div'); + egg.id = 'easter-egg-overlay'; + egg.setAttribute('role', 'dialog'); + egg.setAttribute('aria-modal', 'true'); + egg.innerHTML = '
🎮

GG\u00a0! Tu as trouvé l\u2019easter egg\u00a0!

La vraie question c\u2019est\u00a0: combien de temps tu as mis\u00a0?

'; + document.body.appendChild(egg); + var closeBtn = document.getElementById('easter-egg-close'); + closeBtn && closeBtn.focus(); + var remove = function() { egg.remove(); }; + closeBtn && closeBtn.addEventListener('click', remove); + egg.addEventListener('click', function(e) { if (e.target === egg) remove(); }); + window.addEventListener('keydown', function esc(e) { + if (e.key === 'Escape') { remove(); window.removeEventListener('keydown', esc); } + }, { once: true }); + } +})(); diff --git a/assets/js/main.js b/assets/js/main.js index 9e7fe37..e34b113 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -86,6 +86,15 @@ document.addEventListener('DOMContentLoaded', async () => { // wait for includes to be loaded so subsequent initialization can bind to elements inside them await loadIncludes(); + // Déclenche les reveals maintenant que tous les includes sont dans le DOM + setTimeout(() => { + document.querySelectorAll('.reveal:not(.active)').forEach(el => { + if (el.getBoundingClientRect().top < window.innerHeight - 50) { + el.classList.add('active'); + } + }); + }, 0); + const html = document.documentElement; function getPreferredTheme() { @@ -367,27 +376,27 @@ document.addEventListener('DOMContentLoaded', async () => { window.addEventListener('hashchange', wrappedSetActive); window.addEventListener('popstate', wrappedSetActive); - // Reveal on scroll - const revealElements = document.querySelectorAll('.reveal'); + // Reveal on scroll — re-query après chargement des includes function revealOnScroll() { - if (!revealElements.length) return; - revealElements.forEach(el => { - const elementTop = el.getBoundingClientRect().top; - if (elementTop < window.innerHeight - 100) { + const els = document.querySelectorAll('.reveal:not(.active)'); + els.forEach(el => { + if (el.getBoundingClientRect().top < window.innerHeight - 50) { el.classList.add('active'); } }); } - window.addEventListener('scroll', revealOnScroll); + // Déclenche immédiatement après includes + au scroll + au load + revealOnScroll(); + window.addEventListener('scroll', revealOnScroll, { passive: true }); window.addEventListener('load', revealOnScroll); - // Contact form (if exists) - const contactForm = document.getElementById('contact-form'); - if (contactForm) { - contactForm.addEventListener('submit', function (e) { - e.preventDefault(); - alert('Merci pour votre message ! Nous vous répondrons très bientôt.'); - this.reset(); - }); - } + // Contact form (if exists) c'est en double + // const contactForm = document.getElementById('contact-form'); + // if (contactForm) { + // contactForm.addEventListener('submit', function (e) { + // e.preventDefault(); + // alert('Merci pour votre message ! Nous vous répondrons très bientôt.'); + // this.reset(); + // }); + // } }); \ No newline at end of file diff --git a/assets/js/pages/realisations.js b/assets/js/pages/realisations.js index eb95d55..6904fb4 100644 --- a/assets/js/pages/realisations.js +++ b/assets/js/pages/realisations.js @@ -1,7 +1,9 @@ -// Page script for /pages/realisations.html +// Page script for /pages/realisations.html et index.html document.addEventListener('DOMContentLoaded', async () => { - const dataUrl = '../assets/data/projects.json'; + // Fonctionne depuis /pages/ et depuis / (index.html) + const isRoot = !window.location.pathname.includes('/pages/'); + const dataUrl = isRoot ? 'assets/data/projects.json' : '../assets/data/projects.json'; let allProjects = []; try { @@ -133,35 +135,80 @@ document.addEventListener('DOMContentLoaded', async () => { portfolioGrid.scrollIntoView({ behavior: 'smooth', block: 'start' }); } + // ── Modal tab switching ────────────────────────────────────────────────────── + const modalTabs = document.querySelectorAll('.project-modal-tab'); + modalTabs.forEach(tab => { + tab.addEventListener('click', () => { + const tabId = tab.dataset.tab; + modalTabs.forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + document.querySelectorAll('.project-modal-tab-content').forEach(c => { + c.classList.toggle('active', c.id === 'tab-' + tabId); + }); + }); + }); + + function resetModalTabs() { + modalTabs.forEach((t, i) => t.classList.toggle('active', i === 0)); + document.querySelectorAll('.project-modal-tab-content').forEach((c, i) => c.classList.toggle('active', i === 0)); + } + function openProjectModal(projectId) { const project = allProjects.find(p => p.id === Number(projectId)); if (!project) return; + resetModalTabs(); + modalCategory.textContent = getCategoryLabel(project.category); modalTitle.textContent = project.title; modalDesc.textContent = project.desc; - modalMeta.innerHTML = ` ${project.year} ${project.duration}`; + modalMeta.innerHTML = ` ${project.year}${project.duration ? ` ${project.duration}` : ''}`; modalFeatures.innerHTML = project.features.map(f => `
${f}
`).join(''); - modalTech.innerHTML = project.tech.map(t => `
${t}
`).join(''); + + // Tech avec icônes (support ancien format string et nouveau format {name, icon}) + modalTech.innerHTML = project.tech.map(t => { + const name = typeof t === 'object' ? t.name : t; + const icon = typeof t === 'object' ? (t.icon || 'code') : 'code'; + return `
${name}
`; + }).join(''); + + // Stats en grille 2×2 avec icônes modalStats.innerHTML = project.stats.map(s => `
+
${s.value}
${s.label}
`).join(''); - // gallery slides (use gradient as placeholder slides like original) - gallerySlides.innerHTML = ` - - - - `; + // 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 += ` + `; + } + gallerySlides.innerHTML = slidesHtml; // reset gallery state galleryIndex = 0; updateGallery(); + updateGalleryDots(); if (modalOverlay) modalOverlay.classList.add('active'); @@ -176,16 +223,60 @@ document.addEventListener('DOMContentLoaded', async () => { // Gallery helpers let galleryIndex = 0; + function getVisibleSlides() { + 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); + } + function updateGallery() { - const slides = gallerySlides ? Array.from(gallerySlides.children) : []; - slides.forEach((s, i) => { + 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}%)`; s.classList.toggle('active', i === galleryIndex); }); + // Clamp index + if (galleryIndex >= visible.length) galleryIndex = Math.max(0, visible.length - 1); } - function prevSlide() { galleryIndex = Math.max(0, galleryIndex - 1); updateGallery(); } - function nextSlide() { const slides = gallerySlides ? gallerySlides.children.length : 0; galleryIndex = Math.min(slides - 1, galleryIndex + 1); updateGallery(); } + 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) => + `` + ).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 prevSlide() { + const visible = getVisibleSlides(); + galleryIndex = galleryIndex <= 0 ? visible.length - 1 : galleryIndex - 1; + updateGallery(); + } + function nextSlide() { + const visible = getVisibleSlides(); + galleryIndex = galleryIndex >= visible.length - 1 ? 0 : galleryIndex + 1; + updateGallery(); + } // Event bindings filterTabs.forEach(tab => tab.addEventListener('click', (e) => { @@ -222,6 +313,60 @@ document.addEventListener('DOMContentLoaded', async () => { window.openProjectModal = openProjectModal; window.goToPage = goToPage; + // ── Projets phares (bento grid) — 3 premiers projets du JSON ─────────────── + const featuredBento = document.getElementById('featured-bento-grid'); + if (featuredBento) { + const featured = allProjects.slice(0, 3); + const categoryLabels = { bot: 'Bot Discord', map: 'Fortnite Map', graphisme: 'Graphisme' }; + + featuredBento.innerHTML = featured.map((p, i) => { + const isReverse = i % 2 === 1 ? ' reverse' : ''; + const statsHtml = (p.stats || []).slice(0, 4).map((s) => ` +
+
+
${s.value}
+
${s.label}
+
+ `).join(''); + const featuresHtml = (p.features || []).slice(0, 6).map(f => `${f}`).join(''); + const divider = i < featured.length - 1 ? '
' : ''; + + return ` +
+
+
+ ${categoryLabels[p.category] || p.category} +

${p.title}

+

${p.desc.substring(0, 100)}${p.desc.length > 100 ? '…' : ''}

+
+
+
${statsHtml}
+
+

Description

+

${p.desc}

+
${featuresHtml}
+
+
+ ${divider} + `; + }).join(''); + + // Replace feather icons + if (window.feather && feather.replace) { try { feather.replace(); } catch(e) {} } + + // Click to open modal + featuredBento.querySelectorAll('.bento-project').forEach(el => { + const open = () => openProjectModal(el.getAttribute('data-project-id')); + el.addEventListener('click', open); + el.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(); } }); + }); + + // Reveal animation + setTimeout(() => { + featuredBento.querySelectorAll('.reveal').forEach(el => el.classList.add('active')); + }, 80); + } + // Initial render renderProjects(); renderPagination(); diff --git a/assets/js/pages/services.js b/assets/js/pages/services.js index 577cc4c..cfb0c25 100644 --- a/assets/js/pages/services.js +++ b/assets/js/pages/services.js @@ -68,6 +68,11 @@
`).join(''); } + function renderNote(note) { + if (!note || !note.length) return ''; + return `

${note.map(n => `${n.value} ${n.label}
`).join(' ')}

`; + } + function renderCtas(ctas) { return ctas.map(c => ` @@ -97,6 +102,9 @@
${renderFeatures(service.features, accentFrom, accentTo)}
+
+ ${renderNote(service.note)} +
${renderCtas(service.cta)}
diff --git a/components/contact.html b/components/contact.html new file mode 100644 index 0000000..3b7fc64 --- /dev/null +++ b/components/contact.html @@ -0,0 +1,133 @@ + +
+
+ +

Parlons de votre projet

+

Un projet RP en tête ? Contactez-nous !

+
+
+ + +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+
+
+ + diff --git a/components/footer.html b/components/footer.html index b407fe5..35c3fe7 100644 --- a/components/footer.html +++ b/components/footer.html @@ -21,6 +21,7 @@ À propos Services Réalisations + FAQ Contact
-
3
-
Ans d'expérience
+
+
Ans d'exp.
@@ -33,4 +33,10 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/components/index/services.html b/components/index/services.html index 1704f45..b527af9 100644 --- a/components/index/services.html +++ b/components/index/services.html @@ -10,8 +10,8 @@
-

Bots Discord RP

-

Des bots entièrement personnalisés pour animer et gérer vos serveurs RP.

+

Bots Discord

+

Des bots entièrement personnalisés pour animer et gérer touts vos serveurs.