Initial commit

This commit is contained in:
2026-02-03 19:36:22 +01:00
commit 56db6fac8c
6 changed files with 1117 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

264
assets/css/styles.css Normal file
View File

@@ -0,0 +1,264 @@
:root {
--color-primary: #2563eb;
--color-secondary: #0f172a;
--color-accent: #3b82f6;
}
* {
font-family: 'Inter', sans-serif;
}
.gradient-bg {
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
}
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.smooth-scroll {
scroll-behavior: smooth;
}
.btn-primary {
background-color: var(--color-primary);
transition: all 0.3s ease;
}
.btn-primary:hover {
background-color: var(--color-accent);
transform: scale(1.05);
}
.nav-link {
position: relative;
transition: color 0.3s ease;
}
.nav-link::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 2px;
background-color: var(--color-primary);
transition: width 0.3s ease;
}
.nav-link:hover::after {
width: 100%;
}
.section-title {
position: relative;
display: inline-block;
}
.section-title::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 4px;
background-color: var(--color-primary);
border-radius: 2px;
}
/* Animations et reveals */
@media (prefers-reduced-motion: reduce) {
.card-hover,
.btn-primary,
.animate-fade-in,
.reveal {
transition: none !important;
animation: none !important;
}
}
.reveal {
opacity: 0;
transform: translateY(12px);
transition: opacity 0.6s ease, transform 0.6s ease;
will-change: opacity, transform;
}
.animate-fade-in {
opacity: 1 !important;
transform: translateY(0) !important;
}
/* Header scrolled state */
.header-scrolled {
background-color: rgba(255, 255, 255, 0.98);
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
backdrop-filter: blur(6px);
}
/* Service icon micro-interaction */
.service-icon {
transition: transform 0.35s ease;
display: inline-block;
}
.card-hover:hover .service-icon {
transform: translateY(-6px) rotate(-6deg) scale(1.05);
}
/* Buttons */
.btn-primary {
box-shadow: 0 8px 20px rgba(37, 99, 235, 0.12);
}
.btn-primary:focus {
outline: 3px solid rgba(37, 99, 235, 0.12);
outline-offset: 2px;
}
/* Ensure avatars have a minimum height to avoid layout shift */
#team-container .aspect-square {
min-height: 260px;
}
/* Background gradient + subtle noise */
body {
background-image: radial-gradient(circle at 10% 10%, rgba(59, 130, 246, 0.06), transparent 10%),
radial-gradient(circle at 90% 90%, rgba(99, 102, 241, 0.04), transparent 12%),
linear-gradient(180deg, #fbfdff 0%, #f8fafc 100%);
background-attachment: fixed;
}
/* Animated floating blobs */
.bg-blob {
position: absolute;
filter: blur(40px);
opacity: 0.18;
transform-origin: center;
}
.blob-1 {
width: 420px;
height: 420px;
left: -80px;
top: -60px;
background: linear-gradient(135deg, rgba(37, 99, 235, 0.9), rgba(59, 130, 246, 0.6));
animation: floatY 8s ease-in-out infinite;
}
.blob-2 {
width: 300px;
height: 300px;
right: -60px;
bottom: -80px;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.9), rgba(99, 102, 241, 0.6));
animation: floatX 9s ease-in-out infinite;
}
@keyframes floatY {
0% {
transform: translateY(0)
}
50% {
transform: translateY(18px)
}
100% {
transform: translateY(0)
}
}
@keyframes floatX {
0% {
transform: translateX(0)
}
50% {
transform: translateX(-18px)
}
100% {
transform: translateX(0)
}
}
/* Hero illustration container */
.hero-illustration {
position: relative;
width: 100%;
height: 420px;
display: flex;
align-items: center;
justify-content: center;
}
.hero-illustration svg {
width: 100%;
height: 100%;
max-width: 560px;
}
/* Testimonials */
.testimonials {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.9));
padding: 4rem 0;
border-radius: 12px;
}
.testimonial-slide {
display: none;
}
.testimonial-slide.active {
display: block;
}
.testimonials .controls {
display: flex;
gap: 12px;
align-items: center;
justify-content: center;
margin-top: 1rem
}
.testimonials .dot {
width: 10px;
height: 10px;
border-radius: 999px;
background: rgba(15, 23, 42, 0.12);
}
.testimonials .dot.active {
background: var(--color-primary);
box-shadow: 0 6px 18px rgba(37, 99, 235, 0.14);
}
/* CTA micro-animation */
.cta-anim {
transition: transform 0.35s cubic-bezier(.2, .9, .2, 1), box-shadow 0.35s;
}
.cta-anim:hover {
transform: translateY(-6px);
box-shadow: 0 18px 36px rgba(37, 99, 235, 0.14);
}
/* Background blobs and hero micro styles */
.bg-blob {
will-change: transform, opacity;
}
/* Hero responsive */
@media (max-width: 768px) {
.hero-illustration {
height: 320px;
}
}

442
assets/js/app.js Normal file
View File

@@ -0,0 +1,442 @@
// Application JavaScript pour MRM & Co
// Génération dynamique des sections depuis config.js
document.addEventListener('DOMContentLoaded', function () {
// Initialisation
initializeColors();
renderServices();
renderTeam();
updateContactInfo();
setCurrentYear();
initializeMobileMenu();
initializeContactForm();
initializeSmoothScroll();
initializeHeaderScroll();
initializeHeroParallax();
initializeTestimonials();
});
/**
* Applique les couleurs personnalisées depuis la configuration
*/
function initializeColors() {
if (typeof config !== 'undefined' && config.colors) {
document.documentElement.style.setProperty('--color-primary', config.colors.primary);
document.documentElement.style.setProperty('--color-secondary', config.colors.secondary);
document.documentElement.style.setProperty('--color-accent', config.colors.accent);
}
}
/**
* Génère dynamiquement la section Services
*/
function renderServices() {
const servicesContainer = document.getElementById('services-container');
if (!servicesContainer || typeof config === 'undefined' || !config.services) {
console.error('Configuration des services non trouvée');
return;
}
servicesContainer.innerHTML = config.services.map((service, idx) => {
var iconId = 'icon-' + (service.icon || 'web');
return `
<div class="card-hover bg-white p-8 rounded-xl shadow-lg reveal" style="transition-delay:${idx * 60}ms">
<div class="mb-4 text-4xl text-indigo-600">
<svg class="service-icon w-12 h-12 text-primary" aria-hidden="true"><use href="#${iconId}"></use></svg>
</div>
<h3 class="text-xl font-bold text-gray-900 mb-3">${service.title}</h3>
<p class="text-gray-600 leading-relaxed">${service.description}</p>
</div>
`;
}).join('');
}
/**
* Génère dynamiquement la section Équipe
*/
function renderTeam() {
const teamContainer = document.getElementById('team-container');
if (!teamContainer || typeof config === 'undefined' || !config.team) {
console.error('Configuration de l\'équipe non trouvée');
return;
}
teamContainer.innerHTML = config.team.map(function (member, idx) {
// fallback avatar via ui-avatars (utilisé si l'image externe échoue)
var primaryColor = (config.colors && config.colors.primary) ? config.colors.primary.replace('#', '') : '2563eb';
var avatarFallback = 'https://ui-avatars.com/api/?name=' + encodeURIComponent(member.name) + '&background=ffffff&color=' + primaryColor + '&size=512';
return `
<div class="card-hover bg-white rounded-xl shadow-lg overflow-hidden">
<div class="aspect-square overflow-hidden bg-gray-100">
<img src="${member.image}"
alt="${member.name}"
loading="lazy"
data-fallback="${avatarFallback}"
class="w-full h-full object-cover transition-transform duration-300 hover:scale-110">
</div>
<div class="p-6">
<h3 class="text-xl font-bold text-gray-900 mb-1">${member.name}</h3>
<p class="text-sm font-semibold mb-3" style="color: var(--color-primary)">${member.role}</p>
<p class="text-gray-600 leading-relaxed mb-4">${member.description}</p>
<div class="flex space-x-3">
<a href="${member.linkedin}"
class="text-gray-400 hover:text-blue-600 transition text-xl"
aria-label="LinkedIn de ${member.name}">
🔗
</a>
</div>
</div>
</div>
`;
}).map((html, i) => html.replace('card-hover', 'card-hover reveal')).join('');
// Attacher les handlers d'erreur d'image après insertion pour éviter les attributs inline
attachImageFallbacks();
}
function attachImageFallbacks() {
document.querySelectorAll('#team-container img[data-fallback]').forEach(function (img) {
if (img.__fallbackAttached) return;
img.addEventListener('error', function () {
var fb = img.getAttribute('data-fallback');
if (fb) img.src = fb;
});
img.__fallbackAttached = true;
});
}
/**
* Met à jour les informations de contact depuis la configuration
*/
function updateContactInfo() {
if (typeof config === 'undefined' || !config.company) {
return;
}
// Mise à jour du hero
const heroDesc = document.getElementById('hero-description');
if (heroDesc && config.company.description) {
heroDesc.textContent = config.company.description;
}
// Mise à jour des coordonnées
const emailEl = document.getElementById('contact-email');
if (emailEl && config.company.email) {
emailEl.textContent = config.company.email;
}
const addressEl = document.getElementById('contact-address');
if (addressEl && config.company.address) {
addressEl.textContent = config.company.address;
}
// Mise à jour du footer
const footerDesc = document.getElementById('footer-description');
if (footerDesc && config.company.tagline) {
footerDesc.textContent = config.company.tagline;
}
}
/**
* Initialise le menu mobile responsive
*/
function initializeMobileMenu() {
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
if (mobileMenuBtn && mobileMenu) {
mobileMenuBtn.addEventListener('click', function () {
mobileMenu.classList.toggle('hidden');
});
// Fermer le menu lors du clic sur un lien
const mobileLinks = mobileMenu.querySelectorAll('a');
mobileLinks.forEach(link => {
link.addEventListener('click', function () {
mobileMenu.classList.add('hidden');
});
});
}
}
/**
* Gestion du formulaire de contact
*/
function initializeContactForm() {
const contactForm = document.getElementById('contact-form');
if (contactForm) {
contactForm.addEventListener('submit', function (e) {
e.preventDefault();
// Récupération des données du formulaire
const formData = {
name: document.getElementById('name').value,
email: document.getElementById('email').value,
message: document.getElementById('message').value
};
// Simulation d'envoi (à remplacer par un vrai backend)
console.log('Formulaire soumis:', formData);
// Message de confirmation
alert('Merci pour votre message ! Nous vous répondrons dans les plus brefs délais.');
// Réinitialisation du formulaire
contactForm.reset();
});
}
}
/**
* Scroll fluide pour les ancres de navigation
*/
function initializeSmoothScroll() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const targetId = this.getAttribute('href');
if (targetId === '#') return;
const targetElement = document.querySelector(targetId);
if (targetElement) {
const headerOffset = 80; // Hauteur du header fixe
const elementPosition = targetElement.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
}
});
});
}
/**
* Animation au scroll pour les éléments
*/
function initializeScrollAnimations() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-fade-in');
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.12
});
// Observer tous les éléments avec la classe reveal
document.querySelectorAll('.reveal').forEach(el => {
observer.observe(el);
});
}
// Initialiser les animations au scroll après le chargement
window.addEventListener('load', initializeScrollAnimations);
/**
* Insère l'année courante dans le footer
*/
function setCurrentYear() {
var el = document.getElementById('current-year');
if (el) {
el.textContent = new Date().getFullYear();
}
}
/**
* Change l'apparence du header quand on scroll
*/
function initializeHeaderScroll() {
var header = document.querySelector('header');
if (!header) return;
function onScroll() {
if (window.scrollY > 18) header.classList.add('header-scrolled');
else header.classList.remove('header-scrolled');
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
}
/**
* Micro-parallax for hero illustration
*/
function initializeHeroParallax() {
var hero = document.getElementById('hero-illustration');
if (!hero) return;
hero.addEventListener('mousemove', function (e) {
var rect = hero.getBoundingClientRect();
var x = (e.clientX - rect.left) / rect.width - 0.5; // -0.5..0.5
var y = (e.clientY - rect.top) / rect.height - 0.5;
var blobs = hero.querySelectorAll('.bg-blob');
blobs.forEach(function (b, i) {
var depth = (i === 0) ? 18 : -12;
b.style.transform = 'translate(' + (-x * depth) + 'px,' + (-y * depth) + 'px)';
});
});
// reset on leave
hero.addEventListener('mouseleave', function () {
var blobs = hero.querySelectorAll('.bg-blob');
blobs.forEach(function (b) { b.style.transform = ''; });
});
}
/**
* Testimonials slider simple
*/
function initializeTestimonials() {
var slides = Array.from(document.querySelectorAll('#testimonial-slides .testimonial-slide'));
if (!slides.length) return;
var dotsContainer = document.getElementById('testimonial-dots');
var current = 0;
var autoplay = true;
var timer = null;
function goTo(idx) {
slides.forEach((s, i) => s.classList.toggle('active', i === idx));
Array.from(dotsContainer.children).forEach((d, i) => d.classList.toggle('active', i === idx));
current = idx;
}
function next() { goTo((current + 1) % slides.length); }
function prev() { goTo((current - 1 + slides.length) % slides.length); }
// dots
slides.forEach((s, i) => {
var dot = document.createElement('div');
dot.className = 'dot' + (i === 0 ? ' active' : '');
dot.addEventListener('click', function () { goTo(i); resetTimer(); });
dotsContainer.appendChild(dot);
});
document.getElementById('next-testimonial').addEventListener('click', function () { next(); resetTimer(); });
document.getElementById('prev-testimonial').addEventListener('click', function () { prev(); resetTimer(); });
function startTimer() { if (timer) clearInterval(timer); timer = setInterval(next, 5000); }
function resetTimer() { if (autoplay) startTimer(); }
// pause on hover
var container = document.querySelector('.testimonials');
container.addEventListener('mouseenter', function () { autoplay = false; if (timer) clearInterval(timer); });
container.addEventListener('mouseleave', function () { autoplay = true; startTimer(); });
// start
goTo(0);
startTimer();
}
/**
* Initialisation du canvas d'arrière-plan interactif
*/
function initializeBackgroundCanvas() {
if (typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
var canvas = document.getElementById('bg-canvas');
if (!canvas) return;
var ctx = canvas.getContext('2d');
var DPR = window.devicePixelRatio || 1;
var width, height;
var mouse = { x: -9999, y: -9999 };
function resize() {
width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
canvas.width = Math.floor(width * DPR);
canvas.height = Math.floor(height * DPR);
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
}
window.addEventListener('resize', resize);
resize();
canvas.addEventListener('mousemove', function (e) {
mouse.x = e.clientX;
mouse.y = e.clientY;
});
canvas.addEventListener('mouseleave', function () { mouse.x = -9999; mouse.y = -9999; });
// particles/blobs
var blobs = [];
var count = Math.max(6, Math.min(28, Math.floor((width * height) / 200000)));
for (var i = 0; i < count; i++) {
blobs.push({
x: Math.random() * width,
y: Math.random() * height,
r: 80 + Math.random() * 260,
vx: (Math.random() - 0.5) * 0.2,
vy: (Math.random() - 0.5) * 0.2,
hue: 210 + Math.random() * 60
});
}
function drawBlob(b) {
var grd = ctx.createRadialGradient(b.x, b.y, b.r * 0.15, b.x, b.y, b.r);
var h = b.hue;
grd.addColorStop(0, 'hsla(' + h + ',90%,70%,0.20)');
grd.addColorStop(0.4, 'hsla(' + (h + 30) + ',80%,60%,0.12)');
grd.addColorStop(1, 'hsla(' + (h + 60) + ',70%,50%,0.02)');
ctx.fillStyle = grd;
ctx.beginPath();
ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
ctx.fill();
}
function step() {
ctx.clearRect(0, 0, width, height);
// subtle background overlay
ctx.globalCompositeOperation = 'source-over';
for (var i = 0; i < blobs.length; i++) {
var b = blobs[i];
// move
b.x += b.vx;
b.y += b.vy;
// slight attraction to mouse
if (mouse.x > -9998) {
var dx = mouse.x - b.x;
var dy = mouse.y - b.y;
var dist = Math.sqrt(dx * dx + dy * dy) + 1;
var force = Math.min(80 / dist, 0.9);
b.vx += dx * 0.0008 * force;
b.vy += dy * 0.0008 * force;
}
// slow down velocity
b.vx *= 0.995;
b.vy *= 0.995;
// wrap
if (b.x < -b.r) b.x = width + b.r;
if (b.x > width + b.r) b.x = -b.r;
if (b.y < -b.r) b.y = height + b.r;
if (b.y > height + b.r) b.y = -b.r;
drawBlob(b);
}
// small radial highlight near mouse for interactivity
if (mouse.x > -9998) {
var radial = ctx.createRadialGradient(mouse.x, mouse.y, 0, mouse.x, mouse.y, 180);
radial.addColorStop(0, 'rgba(255,255,255,0.06)');
radial.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle = radial;
ctx.fillRect(mouse.x - 180, mouse.y - 180, 360, 360);
}
requestAnimationFrame(step);
}
step();
}
// lancer le background après DOM ready
window.addEventListener('load', function () { initializeBackgroundCanvas(); });

62
assets/js/config.js Normal file
View File

@@ -0,0 +1,62 @@
// Configuration du site MRM & Co
const config = {
// Couleurs principales du site
colors: {
primary: '#2563eb', // Bleu moderne
secondary: '#0f172a', // Bleu foncé
accent: '#3b82f6' // Bleu clair
},
// Informations de l'entreprise
company: {
name: 'MRM & Co',
tagline: 'Votre partenaire technologique de confiance',
description: 'Solutions informatiques innovantes pour propulser votre entreprise vers le futur',
email: 'contact@mrm.fr',
address: 'Nîmes, France'
},
// Services proposés
services: [
{
title: 'Développement web',
description: 'Création de sites web modernes, performants et responsives, optimisés pour la conversion et la performance.',
icon: 'web'
},
{
title: 'Développement logiciel',
description: 'Conception et réalisation de logiciels sur-mesure (desktop, backend, APIs) adaptés à vos processus métier.',
icon: 'software'
},
{
title: 'Développement mobile',
description: 'Applications mobiles natives et cross-platform performantes, avec expérience utilisateur soignée.',
icon: 'mobile'
}
],
// Membres de l'équipe
team: [
{
name: 'Maxence MINARRO',
role: 'CEO (Directeur Général) & Co-fondateur',
description: '',
image: 'assets/avatars/maxence-minarro.jpeg',
linkedin: '#'
},
{
name: 'Titouan ROGER',
role: 'CTO (Directeur Technique) & Co-fondateur',
description: 'Développeur FullStack passionné par les nouvelles technologies et l\'innovation.',
image: 'assets/avatars/titouan-roger.jpeg',
linkedin: 'https://www.linkedin.com/in/titouan-roger/'
},
{
name: 'Florian MOUTOUVIRIN',
role: 'CMO (Directeur Marketing) & Co-fondateur',
description: '',
image: 'assets/avatars/florian-moutouvirin.jpeg',
linkedin: 'https://www.linkedin.com/in/florian-moutouvirin/'
}
]
};