commit b216a187bd5565f4d7dd3b329bb2b6a95d183908 Author: mrtoine Date: Fri Sep 12 10:57:48 2025 +0200 first commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..e47155a Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/Static/.DS_Store b/Static/.DS_Store new file mode 100644 index 0000000..998e1a1 Binary files /dev/null and b/Static/.DS_Store differ diff --git a/Static/css/admin.css b/Static/css/admin.css new file mode 100644 index 0000000..32c9cff --- /dev/null +++ b/Static/css/admin.css @@ -0,0 +1,1130 @@ +:root { + --primary-color: #3a86ff; + --secondary-color: #8338ec; + --accent-color: #ff006e; + --dark-color: #1a1a2e; + --light-color: #f8f9fa; + --gray-color: #6c757d; + --success-color: #06d6a0; + --header-color: #09092d; + --header-color-variant: #6576a3; + + --gradient-header: linear-gradient(135deg, #4158D0, #C850C0, #FFCC70); + --font-main: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + --font-heading: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + --transition-speed: 0.3s; + --border-radius: 8px; +} + +/* ===== RESET & BASE STYLES ===== */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + color: #333; + line-height: 1.6; +} + +/* ===== LOGIN PAGE STYLES (PRESERVED) ===== */ +body.login-page { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + background-image: url("../img/bg-login.jpg"); + background-size: cover; + background-position: center; + margin: 0; +} + +section.login { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 300px; + padding: 20px; + background-color: rgba(255, 255, 255, 0.4); + backdrop-filter: blur(10px); + border: 1px solid lightslategrey; + border-radius: 15px; + margin: auto; +} + +section.login form { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + gap: 1rem; +} + +section.login input[type="text"], +section.login input[type="password"] { + border: 1px solid lightslategrey; + border-radius: 5px; + width: 100%; + padding: 0.75rem; + font-family: inherit; + font-size: 1rem; + resize: vertical; + text-align: center; +} + +section.login .btn { + width: 100%; + padding: 0.75rem; + font-family: inherit; + font-size: 1rem; + resize: vertical; + cursor: pointer; + background-color: #06d6a0; + border: 1px solid #10b981; + color: #185742; + border-radius: 5px; + transition: background-color 0.3s ease; +} + +section.login .btn:hover { + background-color: #10b981; +} + +/* ===== MODERN ADMIN LAYOUT ===== */ +.admin-container { + display: flex; + min-height: 100vh; +} + +/* ===== NAVIGATION SIDEBAR ===== */ +nav { + width: 280px; + background: linear-gradient(180deg, #1e293b 0%, #334155 100%); + box-shadow: 4px 0 20px rgba(0, 0, 0, 0.1); + position: fixed; + height: 100vh; + left: 0; + top: 0; + z-index: 1000; + transition: transform 0.3s ease; +} + +nav ul { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + height: 100%; +} + +nav ul li.title { + background: linear-gradient(135deg, #3b82f6, #8b5cf6); + color: white; + padding: 1.5rem; + font-size: 1.4rem; + font-weight: 700; + text-align: center; + border-bottom: 2px solid rgba(255, 255, 255, 0.1); + margin-bottom: 1rem; +} + +nav ul li.nav-item { + margin: 0.25rem 0.75rem; + border-radius: 12px; + transition: all 0.3s ease; +} + +nav ul li.nav-item:hover { + background: rgba(255, 255, 255, 0.1); + transform: translateX(5px); +} + +nav ul li.nav-item a { + color: #e2e8f0; + text-decoration: none; + padding: 1rem 1.25rem; + display: block; + border-radius: 12px; + font-weight: 500; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +nav ul li.nav-item a:hover { + color: #60a5fa; + background: rgba(96, 165, 250, 0.1); +} + +nav ul li.nav-item a::before { + content: ''; + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 3px; + background: #60a5fa; + transform: scaleY(0); + transition: transform 0.3s ease; +} + +nav ul li.nav-item a:hover::before { + transform: scaleY(1); +} + +/* ===== MAIN CONTENT AREA ===== */ +section:not(.login) { + margin-left: 280px; + padding: 2rem; + flex: 1; + background: rgba(255, 255, 255, 0.05); + backdrop-filter: blur(10px); + border-radius: 20px 0 0 0; + margin-top: 0; +} + +/* ===== DASHBOARD STYLES ===== */ +.dashboard { + max-width: 1400px; + margin: 0 auto; +} + +.dashboard h1 { + font-size: 3rem; + font-weight: 800; + background: #c8e7ff; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: 0.5rem; + text-align: center; +} + +.welcome-message { + font-size: 1.2rem; + color: rgba(255, 255, 255, 0.8); + text-align: center; + margin-bottom: 3rem; + font-weight: 300; +} + +/* ===== DASHBOARD GRID ===== */ +.dashboard-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; + margin-bottom: 3rem; +} + +.dashboard-card { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); + border-radius: 20px; + padding: 2rem; + border: 1px solid rgba(255, 255, 255, 0.2); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease, box-shadow 0.3s ease; + position: relative; + overflow: hidden; + will-change: transform; +} + +.dashboard-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, #667eea, #764ba2); + transform: scaleX(0); + transition: transform 0.3s ease; + transform-origin: left; +} + +.dashboard-card:hover::before { + transform: scaleX(1); +} + +.dashboard-card:hover { + transform: translateY(-8px); + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15); +} + +.dashboard-card h3 { + font-size: 1.5rem; + margin-bottom: 1rem; + color: #1e293b; + font-weight: 700; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.dashboard-card p { + color: #64748b; + margin-bottom: 1.5rem; + line-height: 1.7; + font-size: 1rem; +} + +/* ===== MODERN BUTTONS ===== */ +.btn-primary, .btn-success, .btn-secondary, .btn-warning, .btn-danger { + padding: 1rem 2rem; + border-radius: 5px; + text-decoration: none; + font-weight: 600; + text-align: center; + display: inline-block; + transition: transform 0.2s ease, box-shadow 0.2s ease; + cursor: pointer; + border: none; + font-size: 1rem; + position: relative; + overflow: hidden; + text-transform: uppercase; + letter-spacing: 0.5px; + will-change: transform; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4); +} + +.btn-primary:hover { + transform: translateY(-3px); + box-shadow: 0 15px 40px rgba(102, 126, 234, 0.6); +} + +.btn-secondary { + background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%); + color: white; + box-shadow: 0 10px 30px rgba(107, 114, 128, 0.4); +} + +.btn-secondary:hover { + transform: translateY(-3px); + box-shadow: 0 15px 40px rgba(107, 114, 128, 0.6); +} + +.btn-success { + background: linear-gradient(135deg, #85f887 0%, #1daf4c 100%); + color: white; + box-shadow: 0 10px 30px rgba(126, 234, 102, 0.4); +} + +.btn-success:hover { + transform: translateY(-3px); + box-shadow: 0 15px 40px rgba(126, 234, 102, 0.4); +} + +.btn-warning { + background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%); + color: white; + box-shadow: 0 10px 30px rgba(251, 191, 36, 0.4); +} + +.btn-warning:hover { + transform: translateY(-3px); + box-shadow: 0 15px 40px rgba(251, 191, 36, 0.6); +} + +.btn-danger { + background: linear-gradient(135deg, #f43f5e 0%, #e11d48 100%); + color: white; + box-shadow: 0 10px 30px rgba(244, 63, 94, 0.4); +} + +.btn-danger:hover { + transform: translateY(-3px); + box-shadow: 0 15px 40px rgba(244, 63, 94, 0.6); +} + +.btn-small { + padding: 0.5rem 1rem; + font-size: 0.9rem; +} + +.btn-small:hover { + transform: translateY(-2px); + box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3); +} + + + +/* ===== STATISTICS SECTION ===== */ +.stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 1.5rem; +} + +.stat-item { + background: rgba(102, 126, 234, 0.1); + border-radius: 16px; + padding: 1.5rem; + text-align: center; + border: 2px solid rgba(102, 126, 234, 0.2); + transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease; + will-change: transform; +} + +.stat-item:hover { + background: rgba(102, 126, 234, 0.2); + border-color: rgba(102, 126, 234, 0.4); + transform: scale(1.03); +} + +.stat-number { + font-size: 2.5rem; + font-weight: 900; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + display: block; + margin-bottom: 0.5rem; +} + +.stat-label { + font-size: 0.9rem; + color: #64748b; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* ===== QUICK ACTIONS ===== */ +.quick-actions { + display: flex; + flex-direction: column; + gap: 1rem; +} + +/* ===== RECENT ACTIVITY ===== */ +.recent-activity { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); + border-radius: 20px; + padding: 2rem; + border: 1px solid rgba(255, 255, 255, 0.2); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); +} + +.recent-activity h3 { + font-size: 1.5rem; + margin-bottom: 1.5rem; + color: #1e293b; + font-weight: 700; + text-align: center; +} + +.activity-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.activity-item { + background: linear-gradient(135deg, rgba(102, 126, 234, 0.05), rgba(118, 75, 162, 0.05)); + border-radius: 16px; + padding: 1.5rem; + border-left: 4px solid transparent; + border-image: linear-gradient(135deg, #667eea, #764ba2) 1; + transition: background 0.3s ease, transform 0.3s ease; + position: relative; + overflow: hidden; + will-change: transform; +} + +.activity-item::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 4px; + background: linear-gradient(135deg, #667eea, #764ba2); +} + +.activity-item:hover { + background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1)); + transform: translateX(8px); +} + +.activity-date { + font-size: 0.9rem; + color: #667eea; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 0.5rem; + display: block; +} + +.activity-desc { + color: #1e293b; + font-weight: 500; + font-size: 1rem; +} + +/* ===== CARDS PROJECTS ===== */ +.projects-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + gap: 2rem; + margin: 2rem 0; +} + +.card-projects { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); + border-radius: 20px; + padding: 0; + border: 1px solid rgba(255, 255, 255, 0.2); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease, box-shadow 0.3s ease; + overflow: hidden; + will-change: transform; + position: relative; +} + +.card-projects::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, #667eea, #764ba2); + transform: scaleX(0); + transition: transform 0.3s ease; + transform-origin: left; + z-index: 2; +} + +.card-projects:hover::before { + transform: scaleX(1); +} + +.card-projects:hover { + transform: translateY(-8px); + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15); +} + +.card-title { + height: 150px; + background: linear-gradient(135deg, #b3ea66 0%, #3193a4 100%); + margin: 0; + display: flex; + align-items: center; + justify-content: center; + color: white; + text-align: center; + position: relative; + overflow: hidden; +} + +.card-title::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.1); + opacity: 0; + transition: opacity 0.3s ease; +} + +.card-projects:hover .card-title::after { + opacity: 1; +} + +.card-title h3 { + font-size: 1.4rem; + font-weight: 700; + margin: 0; + padding: 1rem; + z-index: 1; + position: relative; +} + +.card-projects > h3 { + font-size: 1.3rem; + color: #1e293b; + margin: 1.5rem 1.5rem 1rem 1.5rem; + font-weight: 700; +} + +.card-projects > p { + color: #64748b; + line-height: 1.6; + margin: 0 1.5rem 1rem 1.5rem; + font-size: 0.95rem; +} + +.card-projects > p:last-of-type { + margin-bottom: 1.5rem; +} + +.card-projects > p strong { + color: #1e293b; + font-weight: 600; +} + +.card-projects .btn { + margin: 0 1.5rem 1.5rem 1.5rem; + padding: 0.8rem 1.5rem; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + text-decoration: none; + border-radius: 50px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + font-size: 0.9rem; + transition: transform 0.2s ease, box-shadow 0.2s ease; + display: inline-block; + border: none; + cursor: pointer; +} + +.card-projects .btn:hover { + transform: translateY(-2px); + box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4); +} + +.card-tags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 1.5rem; +} + +.card-projects .action-btn { + margin: 0.5rem; +} + +span.tag { + background: rgba(102, 126, 234, 0.1); + color: #667eea; + padding: 0.5rem 1rem; + border-radius: 12px; + font-size: 0.9rem; + font-weight: 600; + transition: background-color 0.3s ease, transform 0.3s ease; +} + +/* ===== PROJECT ACTIONS ===== */ +.projects-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + flex-wrap: wrap; + gap: 1rem; +} + +.projects-actions h1 { + margin: 0; + flex: 1; + min-width: 200px; +} + +.projects-actions .btn-success { + flex-shrink: 0; +} + +/* ===== PROJECT FORM STYLES ===== */ +.form-project { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); + border-radius: 20px; + padding: 2rem; + margin-bottom: 2rem; + border: 1px solid rgba(255, 255, 255, 0.2); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + position: relative; + overflow: hidden; +} + +.form-project::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, #667eea, #764ba2); +} + +.form-project.hidden { + display: none; +} + +.form-project form { + display: grid; + gap: 1.5rem; + max-width: 800px; + margin: 0 auto; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.form-group label { + font-weight: 600; + color: #1e293b; + font-size: 1rem; + margin-bottom: 0.25rem; + position: relative; +} + +.form-group label::after { + content: ''; + position: absolute; + bottom: -2px; + left: 0; + width: 30px; + height: 2px; + background: linear-gradient(90deg, #667eea, #764ba2); + border-radius: 1px; +} + +.form-group input, +.form-group textarea { + padding: 1rem 1.25rem; + border: 2px solid rgba(102, 126, 234, 0.1); + border-radius: 12px; + font-size: 1rem; + font-family: inherit; + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(10px); + transition: all 0.3s ease; + color: #1e293b; + resize: vertical; +} + +.form-group input:focus, +.form-group textarea:focus { + outline: none; + border-color: #667eea; + background: rgba(255, 255, 255, 0.95); + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); + transform: translateY(-1px); +} + +.form-group input::placeholder, +.form-group textarea::placeholder { + color: #94a3b8; + font-style: italic; +} + +.form-group textarea { + min-height: 120px; + max-height: 300px; + line-height: 1.6; +} + +/* Styles pour les formulaires */ +.form-help { + font-size: 0.8rem; + color: #6b7280; + margin-top: 0.25rem; + font-style: italic; + display: block; +} + +/* Form responsive design */ +@media (max-width: 768px) { + .form-project { + padding: 1.5rem; + margin: 1rem; + border-radius: 16px; + } + + .form-project form { + gap: 1.25rem; + } + + .form-group input, + .form-group textarea { + padding: 0.875rem 1rem; + font-size: 0.95rem; + } +} + +/* ===== RESPONSIVE DESIGN ===== */ +@media (max-width: 1024px) { + nav { + transform: translateX(-100%); + } + + nav.open { + transform: translateX(0); + } + + section { + margin-left: 0; + padding: 1rem; + } + + .dashboard h1 { + font-size: 2.5rem; + } +} + +@media (max-width: 768px) { + .dashboard-grid { + grid-template-columns: 1fr; + gap: 1.5rem; + } + + .dashboard h1 { + font-size: 2rem; + } + + .stats { + grid-template-columns: repeat(2, 1fr); + } + + .activity-item { + padding: 1rem; + } + + .activity-item:hover { + transform: translateX(5px); + } +} + +@media (max-width: 480px) { + section { + padding: 0.5rem; + } + + .dashboard-card { + padding: 1.5rem; + } + + .stats { + grid-template-columns: 1fr; + } + + .btn-primary, btn-success, .btn-secondary, .btn-danger { + padding: 0.8rem 1.5rem; + font-size: 0.9rem; + } +} + +.hidden { + display: none; +} + +/* ===== TECHNOLOGIES MANAGER STYLES ===== */ +.technologies-grid { + background: #f8f9fa; + border-radius: var(--border-radius); + padding: 1.5rem; + border: 1px solid #dee2e6; +} + +.tech-controls { + margin-bottom: 1.5rem; + padding: 1rem; + background: white; + border-radius: var(--border-radius); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.tech-search { + position: relative; + margin-bottom: 1rem; +} + +.tech-search input { + width: 100%; + padding: 0.75rem 2.5rem 0.75rem 1rem; + border: 2px solid #e9ecef; + border-radius: 25px; + font-size: 0.9rem; + transition: all var(--transition-speed); +} + +.tech-search input:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(58, 134, 255, 0.1); + outline: none; +} + +.search-icon { + position: absolute; + right: 1rem; + top: 50%; + transform: translateY(-50%); + color: var(--gray-color); + font-size: 1.1rem; +} + +.tech-quick-actions { + display: flex; + gap: 0.5rem; + margin-bottom: 1rem; + flex-wrap: wrap; +} + +.btn-quick { + padding: 0.5rem 1rem; + border: 1px solid var(--primary-color); + background: white; + color: var(--primary-color); + border-radius: 20px; + font-size: 0.8rem; + cursor: pointer; + transition: all var(--transition-speed); +} + +.btn-quick:hover { + background: var(--primary-color); + color: white; + transform: translateY(-2px); +} + +.selected-count { + text-align: center; + font-weight: 600; + padding: 0.5rem; + border-radius: var(--border-radius); +} + +.count-empty { + color: var(--gray-color); + background: #f8f9fa; +} + +.count-low { + color: #ffc107; + background: #fff3cd; +} + +.count-medium { + color: #28a745; + background: #d1edda; +} + +.count-high { + color: #dc3545; + background: #f8d7da; +} + +.tech-categories { + display: grid; + gap: 1rem; +} + +.tech-category { + background: white; + border-radius: var(--border-radius); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + overflow: hidden; + transition: all var(--transition-speed); +} + +.tech-category:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.category-header { + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + color: white; + padding: 1rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + user-select: none; + transition: all var(--transition-speed); +} + +.category-header:hover { + background: linear-gradient(135deg, var(--secondary-color), var(--primary-color)); +} + +.category-header h4 { + margin: 0; + font-size: 1rem; + font-weight: 600; +} + +.category-toggle { + font-size: 1.2rem; + transition: transform var(--transition-speed); +} + +.category-count { + font-size: 0.8rem; + opacity: 0.8; +} + +.category-items { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 0.5rem; + padding: 1rem; + background: #fafbfc; +} + +.tech-item { + display: flex; + align-items: center; + padding: 0.5rem; + background: white; + border-radius: 6px; + transition: all var(--transition-speed); + cursor: pointer; + border: 2px solid transparent; +} + +.tech-item:hover { + background: #e3f2fd; + transform: translateY(-1px); +} + +.tech-checkbox { + margin-right: 0.5rem; + transform: scale(1.2); + cursor: pointer; +} + +.tech-checkbox:checked + .tech-label { + color: var(--primary-color); + font-weight: 600; +} + +.tech-item:has(.tech-checkbox:checked) { + background: linear-gradient(135deg, #e3f2fd, #f3e5f5); + border-color: var(--primary-color); + box-shadow: 0 2px 4px rgba(58, 134, 255, 0.2); +} + +.tech-label { + font-size: 0.9rem; + cursor: pointer; + transition: all var(--transition-speed); + flex: 1; +} + +/* ===== RESPONSIVE DESIGN ===== */ +@media (max-width: 768px) { + .tech-categories { + grid-template-columns: 1fr; + } + + .category-items { + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 0.3rem; + padding: 0.5rem; + } + + .tech-item { + padding: 0.3rem; + } + + .tech-quick-actions { + justify-content: center; + } + + .btn-quick { + flex: 1; + min-width: 0; + } +} + +@media (max-width: 480px) { + .category-items { + grid-template-columns: 1fr; + } + + .tech-quick-actions { + flex-direction: column; + } + + .category-header { + padding: 0.75rem; + } + + .category-header h4 { + font-size: 0.9rem; + } +} + +/* ===== ANIMATIONS ===== */ +.tech-item { + animation: fadeInUp 0.3s ease-out; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.tech-category { + animation: slideIn 0.5s ease-out; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* ===== DARK THEME SUPPORT ===== */ +.dark-theme .technologies-grid { + background: #2d2d2d; + border-color: #444; +} + +.dark-theme .tech-controls { + background: #3a3a3a; +} + +.dark-theme .tech-search input { + background: #2d2d2d; + border-color: #555; + color: #e0e0e0; +} + +.dark-theme .tech-item { + background: #3a3a3a; + color: #e0e0e0; +} + +.dark-theme .tech-item:hover { + background: #4a4a4a; +} + +.dark-theme .category-items { + background: #2d2d2d; +} \ No newline at end of file diff --git a/Static/css/style-secondaire.css b/Static/css/style-secondaire.css new file mode 100644 index 0000000..91d6510 --- /dev/null +++ b/Static/css/style-secondaire.css @@ -0,0 +1,710 @@ +/* Réinitialisation de base */ +*, *::before, *::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* Variables pour palette de couleurs et typographie */ +:root { + /* Palette principale - Couleurs professionnelles et tech */ + --primary-color: #2563eb; /* Bleu royal - Couleur principale */ + --secondary-color: #0e84a3; /* Bleu-vert - Couleur secondaire */ + --accent-color: #0ea5e9; /* Cyan vif - Pour accents et CTA */ + --dark-color: #0f172a; /* Bleu très foncé - Fond et texte */ + --light-color: #f8fafc; /* Blanc cassé - Fond et contraste */ + --gray-color: #64748b; /* Gris slate - Texte secondaire */ + --success-color: #10b981; /* Vert émeraude - Succès/validation */ + --header-color: #0f172a; /* Bleu foncé pour le texte du header */ + --header-color-variant: #38bdf8; /* Bleu ciel pour accentuation */ + + /* Dégradés professionnels */ + --gradient-header: linear-gradient(135deg, #1e3a8a, #0369a1, #0284c7); + --gradient-card: linear-gradient(to bottom right, rgba(15, 23, 42, 0.03), rgba(14, 165, 233, 0.05)); + --gradient-accent: linear-gradient(90deg, #0ea5e9, #2563eb); + + /* Typographie améliorée */ + --font-main: 'Inter', 'Segoe UI', system-ui, sans-serif; + --font-heading: 'Montserrat', 'Segoe UI', system-ui, sans-serif; + --font-code: 'JetBrains Mono', 'Cascadia Code', monospace; + + /* Variables d'interface */ + --transition-speed: 0.3s; + --border-radius: 8px; + --card-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + --box-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); +} + +/* Style général du corps */ +html { + scroll-behavior: smooth; +} + +body { + font-family: var(--font-main); + line-height: 1.6; + color: var(--dark-color); + background-color: var(--light-color); + overflow-x: hidden; +} + +/* En-tête */ +header { + background: var(--gradient-header); + background-size: 200% 200%; + animation: gradientAnimation 15s ease infinite; + color: white; + padding: 5rem 1rem; + text-align: center; + min-height: 280px; + display: flex; + flex-direction: column; + justify-content: center; + position: relative; + overflow: hidden; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + perspective: 1000px; +} + +@keyframes gradientAnimation { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +/* Animations et éléments pour l'en-tête */ +header::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 10%, transparent 70%); + transform: rotate(30deg); + pointer-events: none; + animation: shimmer 15s infinite linear; + z-index: 1; +} + +/* Vagues animées */ +header::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 70px; + background: url('data:image/svg+xml;utf8,'); + background-size: cover; + background-repeat: no-repeat; + filter: drop-shadow(0px -5px 5px rgba(0,0,0,0.1)); + z-index: 2; +} + +/* Effet de superposition en dégradé */ +header .overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: radial-gradient(circle at center, transparent 0%, rgba(0,0,0,0.2) 100%); + z-index: 1; +} + +/* Particules animées */ +@keyframes float { + 0% { transform: translateY(0) rotate(0deg) scale(1); opacity: 0.8; } + 25% { transform: translateY(-15px) rotate(5deg) scale(0.9); opacity: 0.6; } + 50% { transform: translateY(-25px) rotate(10deg) scale(0.8); opacity: 0.4; } + 75% { transform: translateY(-15px) rotate(5deg) scale(0.9); opacity: 0.6; } + 100% { transform: translateY(0) rotate(0deg) scale(1); opacity: 0.8; } +} + +@keyframes shimmer { + 0% { transform: rotate(30deg) translateY(0) scale(1); opacity: 0.5; } + 50% { transform: rotate(25deg) translateY(-15px) scale(1.05); opacity: 0.7; } + 100% { transform: rotate(30deg) translateY(0) scale(1); opacity: 0.5; } +} + +@keyframes pulse { + 0% { transform: scale(1); opacity: 0.8; } + 50% { transform: scale(1.05); opacity: 1; } + 100% { transform: scale(1); opacity: 0.8; } +} + +.particles { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + pointer-events: none; + z-index: 1; +} + +.particle { + position: absolute; + display: block; + border-radius: 50%; + background-color: rgba(255, 255, 255, 0.4); + box-shadow: 0 0 20px 5px rgba(255, 255, 255, 0.3); + pointer-events: none; + z-index: 1; + backdrop-filter: blur(1px); + filter: blur(1px); +} + +.particle.star { + clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%); + background-color: rgba(255, 255, 255, 0.9); + box-shadow: 0 0 20px 10px rgba(255, 255, 255, 0.4); + filter: blur(0); +} + +/* Animation des éléments du header */ +header h1 { + position: relative; + z-index: 2; + font-size: 3.8rem; + font-weight: 800; + margin-bottom: 0.5rem; + text-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + animation: fadeInDown 1s both, pulse 5s infinite ease-in-out 1s; + letter-spacing: 1px; + transform: translateZ(50px); +} + +header h1::after { + content: ''; + position: absolute; + background: white; + left: 50%; + transform: translateX(-50%); + width: 100px; + height: 4px; + bottom: -10px; + animation: expandWidth 1.5s ease forwards; + border-radius: var(--border-radius); + box-shadow: 0 2px 10px rgba(255, 255, 255, 0.5); +} + +header p { + position: relative; + z-index: 2; + font-size: 1.6rem; + color: rgba(255, 255, 255, 0.95); + font-weight: 300; + margin-top: 1.2rem; + animation: fadeInUp 1s 0.5s both; + text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + transform: translateZ(30px); +} + +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes expandWidth { + from { width: 0; } + to { width: 100px; } +} + +/* Styles du contenu du header */ +.header-content { + position: relative; + z-index: 10; + max-width: 800px; + margin: 0 auto; + background: rgba(255, 255, 255, 0.05); + backdrop-filter: blur(10px); + border-radius: 16px; + padding: 2rem; + border: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); + transform-style: preserve-3d; +} + +/* Effet de frappe au clavier */ +.typing-text { + display: inline-block; + white-space: nowrap; + border-right: none; + font-family: var(--font-main); + background: linear-gradient(to right, var(--accent-color), var(--header-color-variant)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + font-weight: 600; + letter-spacing: 0.5px; +} + +.cursor { + display: inline-block; + width: 3px; + height: 1.6em; + background-color: white; + margin-left: 3px; + animation: blink 1s step-end infinite; + box-shadow: 0 0 10px rgba(255, 255, 255, 0.8); + vertical-align: middle; + position: relative; + top: -2px; +} + +@keyframes blink { + from, to { opacity: 1; box-shadow: 0 0 15px rgba(255, 255, 255, 0.8); } + 50% { opacity: 0; box-shadow: none; } +} + +/* Navigation */ +nav { + background-color: var(--dark-color); + position: sticky; + top: 0; + z-index: 1000; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +nav ul { + display: flex; + justify-content: center; + list-style-type: none; + padding: 0; +} + +nav ul li { + position: relative; +} + +nav ul li a { + display: inline-block; + padding: 1rem 1.5rem; + text-decoration: none; + font-weight: 500; + transition: all var(--transition-speed) ease; + cursor: pointer; +} + +nav ul li::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + width: 0; + height: 2px; + background-color: var(--accent-color); + transition: all var(--transition-speed) ease; + transform: translateX(-50%); +} + +nav ul li:hover::after { + width: 80%; +} + +nav ul li:hover a { + color: var(--accent-color); +} + +/* Styles pour les pages actives et scrolled navbar */ +nav.scrolled { + background-color: rgba(26, 26, 46, 0.95); + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + padding: 5px 10px; + transition: all 0.3s ease; +} + +nav ul li.active::after { + width: 80%; +} + +nav ul li.active a { + color: var(--accent-color); +} + +/* Contenu principal */ +main { + max-width: 1200px; + margin: 2rem auto; + padding: 0 1rem; +} + +.main-content { + background-color: white; + border-radius: var(--border-radius); + padding: 2rem; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); + transition: transform var(--transition-speed); +} + +/* Titres */ +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-heading); + margin-bottom: 1rem; + color: var(--dark-color); + font-weight: 700; + line-height: 1.2; +} + +h1 { + font-size: 2.5rem; + margin-bottom: 1.5rem; + position: relative; + display: inline-block; +} + +h1::after { + content: ''; + position: absolute; + bottom: -10px; + left: 0; + width: 80px; + height: 4px; + background: var(--accent-color); +} + +h2 { + font-size: 2rem; +} + +h3 { + font-size: 1.75rem; +} + +/* Paragraphes */ +p { + margin-bottom: 1.5rem; + color: var(--gray-color); +} + +/* Liens */ +a { + color: var(--primary-color); + text-decoration: none; + transition: color var(--transition-speed); +} + +a:hover { + color: var(--secondary-color); +} + +/* Pied de page */ +footer { + background-color: var(--dark-color); + color: var(--light-color); + padding: 2rem 1rem; + text-align: center; + margin-top: 3rem; +} + +footer p { + color: var(--light-color); + opacity: 0.8; + margin-bottom: 0; +} + +/* Styles pour la visualisation des compétences */ +.skill-card { + background-color: var(--light-color); + padding: 1.2rem; + border-radius: var(--border-radius); + box-shadow: 0 2px 4px rgba(0,0,0,0.05); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.skill-card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 15px rgba(0,0,0,0.1); +} + +.skill-bar { + height: 6px; + background-color: #e0e0e0; + margin-top: 1rem; + border-radius: 3px; + overflow: hidden; +} + +.skill-bar-fill { + height: 100%; + border-radius: 3px; +} + +.lang-circle { + position: relative; + width: 100px; + height: 100px; + border-radius: 50%; + margin: 10px auto; +} + +.lang-circle-inner { + position: absolute; + top: 10px; + left: 10px; + width: 80px; + height: 80px; + border-radius: 50%; + background-color: var(--light-color); + display: flex; + align-items: center; + justify-content: center; +} + +.service-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + color: white; + border-radius: 8px; + margin-right: 10px; +} + +.experience-timeline { + position: relative; + padding-left: 30px; + margin-left: 20px; + border-left: 2px solid var(--primary-color); +} + +.timeline-dot { + position: absolute; + width: 20px; + height: 20px; + background-color: var(--primary-color); + border-radius: 50%; + left: -41px; + top: 0; +} + +/* Media queries pour le responsive */ +@media (max-width: 768px) { + nav ul { + flex-direction: column; + align-items: center; + } + + nav ul li { + width: 100%; + text-align: center; + } + + nav ul li a { + padding: 0.75rem 1rem; + } + + h1 { + font-size: 2rem; + } + + h2 { + font-size: 1.75rem; + } + + h3 { + font-size: 1.5rem; + } +} + +/* Animations et effets */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.main-content { + animation: fadeIn 0.5s ease-in-out; +} + +/* Animations supplémentaires */ +@keyframes slideInUp { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.section { + animation: slideInUp 0.5s ease-out forwards; +} + +/* Style pour les sections de contenu */ +.section { + margin-bottom: 3rem; + padding: 1.8rem; + border-radius: var(--border-radius); + background-color: white; + box-shadow: var(--card-shadow); + transition: all var(--transition-speed); + border: 1px solid rgba(0, 0, 0, 0.03); + position: relative; + overflow: hidden; +} + +.section:hover { + transform: translateY(-5px); + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.12); +} + +.section::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 4px; + height: 0; + background: var(--gradient-accent); + transition: height 0.5s ease; +} + +.section:hover::before { + height: 100%; +} + +/* Styles pour des cartes technologiques */ +.tech-card { + background: var(--gradient-card); + padding: 1.5rem; + border-radius: var(--border-radius); + border: 1px solid rgba(0, 0, 0, 0.05); + display: flex; + align-items: center; + gap: 1.5rem; + margin-bottom: 1.5rem; + transition: all var(--transition-speed); +} + +.tech-card:hover { + transform: translateY(-3px); + box-shadow: var(--box-shadow); +} + +.tech-icon { + width: 50px; + height: 50px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 12px; + font-size: 1.5rem; + color: white; + background: var(--gradient-accent); +} + +/* Boutons */ +.btn { + display: inline-block; + padding: 0.75rem 1.5rem; + background: var(--gradient-accent); + color: white; + border-radius: 8px; + border: none; + cursor: pointer; + transition: all var(--transition-speed); + font-weight: 600; + font-size: 0.9rem; + letter-spacing: 0.5px; + box-shadow: var(--box-shadow); + position: relative; + overflow: hidden; + z-index: 1; +} + +.btn::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transition: left 0.7s ease; + z-index: -1; +} + +.btn:hover { + transform: translateY(-3px); + box-shadow: 0 6px 15px rgba(37, 99, 235, 0.3); +} + +.btn:hover::before { + left: 100%; +} + +/* Style des formulaires */ +input, textarea { + transition: border-color var(--transition-speed); +} + +input:focus, textarea:focus { + outline: none; + border-color: var(--primary-color) !important; + box-shadow: 0 0 0 2px rgba(58, 134, 255, 0.2); +} + +/* Mode sombre */ +.dark-theme { + --light-color: #1a1a2e; + --dark-color: #f8f9fa; + --gray-color: #e0e0e0; + background-color: #121212; +} + +.dark-theme .main-content { + background-color: #252525; +} + +.dark-theme .section, +.dark-theme [style*="background-color: var(--light-color)"] { + background-color: #2d2d2d !important; + color: var(--dark-color); +} + +.dark-theme p { + color: #e0e0e0; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: var(--primary-color); + border-radius: 10px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--secondary-color); +} \ No newline at end of file diff --git a/Static/css/style.css b/Static/css/style.css new file mode 100644 index 0000000..18fa1d4 --- /dev/null +++ b/Static/css/style.css @@ -0,0 +1,837 @@ +/* Réinitialisation de base */ +*, *::before, *::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* Variables pour palette de couleurs et typographie */ +:root { + --primary-color: #3a86ff; + --secondary-color: #8338ec; + --accent-color: #ff006e; + --dark-color: #2f2f2f; + --light-color: #f8f9fa; + --gray-color: #6c757d; + --success-color: #06d6a0; + --header-color: #09092d; + --header-color-variant: #6576a3; + + --gradient-header: linear-gradient(135deg, #4158D0, #C850C0, #FFCC70); + --font-main: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + --font-heading: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + --transition-speed: 0.3s; + --border-radius: 8px; +} + +/* Style général du corps */ +html { + scroll-behavior: smooth; +} + +body { + font-family: var(--font-main); + line-height: 1.6; + color: var(--dark-color); + background-color: var(--light-color); + overflow-x: hidden; +} + +/* En-tête */ +header { + background: var(--gradient-header); + background-size: 200% 200%; + animation: gradientAnimation 15s ease infinite; + color: white; + padding: 5rem 1rem; + text-align: center; + min-height: 280px; + display: flex; + flex-direction: column; + justify-content: center; + position: relative; + overflow: hidden; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + perspective: 1000px; +} + +@keyframes gradientAnimation { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +/* Animations et éléments pour l'en-tête */ +header::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 10%, transparent 70%); + transform: rotate(30deg); + pointer-events: none; + animation: shimmer 15s infinite linear; + z-index: 1; +} + +/* Vagues animées */ +header::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 70px; + background: url('data:image/svg+xml;utf8,'); + background-size: cover; + background-repeat: no-repeat; + filter: drop-shadow(0px -5px 5px rgba(0,0,0,0.1)); + z-index: 2; +} + +/* Effet de superposition en dégradé */ +header .overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: radial-gradient(circle at center, transparent 0%, rgba(0,0,0,0.2) 100%); + z-index: 1; +} + +/* Particules animées */ +@keyframes float { + 0% { transform: translateY(0) rotate(0deg) scale(1); opacity: 0.8; } + 25% { transform: translateY(-15px) rotate(5deg) scale(0.9); opacity: 0.6; } + 50% { transform: translateY(-25px) rotate(10deg) scale(0.8); opacity: 0.4; } + 75% { transform: translateY(-15px) rotate(5deg) scale(0.9); opacity: 0.6; } + 100% { transform: translateY(0) rotate(0deg) scale(1); opacity: 0.8; } +} + +@keyframes shimmer { + 0% { transform: rotate(30deg) translateY(0) scale(1); opacity: 0.5; } + 50% { transform: rotate(25deg) translateY(-15px) scale(1.05); opacity: 0.7; } + 100% { transform: rotate(30deg) translateY(0) scale(1); opacity: 0.5; } +} + +@keyframes pulse { + 0% { transform: scale(1); opacity: 0.8; } + 50% { transform: scale(1.05); opacity: 1; } + 100% { transform: scale(1); opacity: 0.8; } +} + +.particles { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + pointer-events: none; + z-index: 1; +} + +.particle { + position: absolute; + display: block; + border-radius: 50%; + background-color: rgba(255, 255, 255, 0.4); + box-shadow: 0 0 20px 5px rgba(255, 255, 255, 0.3); + pointer-events: none; + z-index: 1; + backdrop-filter: blur(1px); + filter: blur(1px); +} + +.particle.star { + clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%); + background-color: rgba(255, 255, 255, 0.9); + box-shadow: 0 0 20px 10px rgba(255, 255, 255, 0.4); + filter: blur(0); +} + +/* Animation des éléments du header */ +header h1 { + position: relative; + z-index: 2; + font-size: 3.8rem; + font-weight: 800; + margin-bottom: 0.5rem; + text-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + animation: fadeInDown 1s both, pulse 5s infinite ease-in-out 1s; + letter-spacing: 1px; + transform: translateZ(50px); +} + +header h1::after { + content: ''; + position: absolute; + background: white; + left: 50%; + transform: translateX(-50%); + width: 100px; + height: 4px; + bottom: -10px; + animation: expandWidth 1.5s ease forwards; + border-radius: var(--border-radius); + box-shadow: 0 2px 10px rgba(255, 255, 255, 0.5); +} + +header p { + position: relative; + z-index: 2; + font-size: 1.6rem; + color: rgba(255, 255, 255, 0.95); + font-weight: 300; + margin-top: 1.2rem; + animation: fadeInUp 1s 0.5s both; + text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + transform: translateZ(30px); +} + +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes expandWidth { + from { width: 0; } + to { width: 100px; } +} + +/* Styles du contenu du header */ +.header-content { + position: relative; + z-index: 10; + max-width: 800px; + margin: 0 auto; +} + +/* Effet de frappe au clavier */ +.typing-text { + display: inline-block; + white-space: nowrap; + border-right: none; + font-family: var(--font-main); + background: linear-gradient(to right, var(--header-color), var(--header-color-variant)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + font-weight: 500; +} + +.cursor { + display: inline-block; + width: 3px; + height: 1.6em; + background-color: white; + margin-left: 3px; + animation: blink 1s step-end infinite; + box-shadow: 0 0 10px rgba(255, 255, 255, 0.8); + vertical-align: middle; + position: relative; + top: -2px; +} + +@keyframes blink { + from, to { opacity: 1; box-shadow: 0 0 15px rgba(255, 255, 255, 0.8); } + 50% { opacity: 0; box-shadow: none; } +} + +/* Navigation */ +nav { + background-color: var(--dark-color); + position: sticky; + top: 0; + z-index: 1000; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +nav ul { + display: flex; + justify-content: center; + list-style-type: none; + padding: 0; +} + +nav ul li { + position: relative; +} + +nav ul li a { + display: inline-block; + padding: 1rem 1.5rem; + text-decoration: none; + font-weight: 500; + transition: all var(--transition-speed) ease; + cursor: pointer; +} + +nav ul li::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + width: 0; + height: 2px; + background-color: var(--accent-color); + transition: all var(--transition-speed) ease; + transform: translateX(-50%); +} + +nav ul li:hover::after { + width: 80%; +} + +nav ul li:hover a { + color: var(--accent-color); +} + +/* Styles pour les pages actives et scrolled navbar */ +nav.scrolled { + background-color: rgba(26, 26, 46, 0.95); + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + padding: 5px 10px; + transition: all 0.3s ease; +} + +nav ul li.active::after { + width: 80%; +} + +nav ul li.active a { + color: var(--accent-color); +} + +/* Contenu principal */ +main { + max-width: 1200px; + margin: 2rem auto; + padding: 0 1rem; +} + +.main-content { + background-color: white; + border-radius: var(--border-radius); + padding: 2rem; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); + transition: transform var(--transition-speed); +} + +/* Titres */ +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-heading); + margin-bottom: 1rem; + color: var(--dark-color); + font-weight: 700; + line-height: 1.2; +} + +h1 { + font-size: 2.5rem; + margin-bottom: 1.5rem; + position: relative; + display: inline-block; +} + +h1::after { + content: ''; + position: absolute; + bottom: -10px; + left: 0; + width: 80px; + height: 4px; + background: var(--accent-color); +} + +h2 { + font-size: 2rem; +} + +h3 { + font-size: 1.75rem; +} + +/* Paragraphes */ +p { + margin-bottom: 1.5rem; +} + +/* Liens */ +a { + color: var(--primary-color); + text-decoration: none; + transition: color var(--transition-speed); +} + +a:hover { + color: var(--secondary-color); +} + +/* Pied de page */ +footer { + background-color: #2d2d2d; + color: var(--light-color); + padding: 2rem 1rem; + text-align: center; + margin-top: 3rem; +} + +footer p { + color: var(--dark-color); + opacity: 0.8; + margin-bottom: 0; +} + +/* Styles pour la visualisation des compétences */ +.skill-card { + background-color: var(--light-color); + padding: 1.2rem; + border-radius: var(--border-radius); + box-shadow: 0 2px 4px rgba(0,0,0,0.05); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.skill-card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 15px rgba(0,0,0,0.1); +} + +.skill-bar { + height: 6px; + background-color: #e0e0e0; + margin-top: 1rem; + border-radius: 3px; + overflow: hidden; +} + +.skill-bar-fill { + height: 100%; + border-radius: 3px; +} + +.lang-circle { + position: relative; + width: 100px; + height: 100px; + border-radius: 50%; + margin: 10px auto; +} + +.lang-circle-inner { + position: absolute; + top: 10px; + left: 10px; + width: 80px; + height: 80px; + border-radius: 50%; + background-color: var(--light-color); + display: flex; + align-items: center; + justify-content: center; +} + +.service-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + color: white; + border-radius: 8px; + margin-right: 10px; +} + +.experience-timeline { + position: relative; + padding-left: 30px; + margin-left: 20px; + border-left: 2px solid var(--primary-color); +} + +.timeline-dot { + position: absolute; + width: 20px; + height: 20px; + background-color: var(--primary-color); + border-radius: 50%; + left: -41px; + top: 0; +} + + +/* Media queries pour le responsive */ +@media (max-width: 768px) { + nav ul { + flex-direction: column; + align-items: center; + } + + nav ul li { + width: 100%; + text-align: center; + } + + nav ul li a { + padding: 0.75rem 1rem; + } + + h1 { + font-size: 2rem; + } + + h2 { + font-size: 1.75rem; + } + + h3 { + font-size: 1.5rem; + } +} + +/* Animations et effets */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.main-content { + animation: fadeIn 0.5s ease-in-out; +} + +/* Animations supplémentaires */ +@keyframes slideInUp { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.section { + animation: slideInUp 0.5s ease-out forwards; +} + +/* Style pour les sections de contenu */ +.section { + margin-bottom: 3rem; + padding: 1.5rem; + border-radius: var(--border-radius); + background-color: white; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); + transition: transform var(--transition-speed); +} + +.section:hover { + transform: translateY(-5px); +} + +/* Boutons */ +.btn { + display: inline-block; + padding: 0.75rem 1.5rem; + background-color: var(--primary-color); + color: white; + border-radius: 30px; + border: none; + cursor: pointer; + transition: all var(--transition-speed); + font-weight: 600; + text-transform: uppercase; + font-size: 0.9rem; + letter-spacing: 0.5px; + box-shadow: 0 4px 6px rgba(58, 134, 255, 0.2); +} + +.btn:hover { + background-color: var(--secondary-color); + color: white; + transform: translateY(-3px); + box-shadow: 0 6px 8px rgba(131, 56, 236, 0.3); +} + +/* Style des formulaires */ +input, textarea { + transition: border-color var(--transition-speed); +} + +input:focus, textarea:focus { + outline: none; + border-color: var(--primary-color) !important; + box-shadow: 0 0 0 2px rgba(58, 134, 255, 0.2); +} + +/* Mode sombre */ +.dark-theme { + --light-color: #1a1a2e; + --dark-color: #f8f9fa; + --gray-color: #e0e0e0; + background-color: #121212; +} + +.dark-theme .main-content { + background-color: #252525; +} + +.dark-theme .section, +.dark-theme [style*="background-color: var(--light-color)"] { + background-color: #2d2d2d !important; + color: var(--dark-color); +} + +.dark-theme p { + color: #e0e0e0; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: var(--primary-color); + border-radius: 10px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--secondary-color); +} + +/* ===== PROJECTS SECTION STYLES ===== */ +.projects-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + align-items: start; + gap: 2rem; + margin: 2rem 0; + padding: 0 1rem; +} + +.project-card { + display: flex; + flex-direction: column; + justify-content: space-between; + background: white; + border-radius: var(--border-radius); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); + overflow: hidden; + transition: all var(--transition-speed) ease; + border: 1px solid rgba(0, 0, 0, 0.05); + position: relative; +} + +.project-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: var(--gradient-header); + transform: scaleX(0); + transition: transform var(--transition-speed) ease; + transform-origin: left; +} + +.project-card:hover::before { + transform: scaleX(1); +} + +.project-card:hover { + transform: translateY(-8px); + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15); +} + +.project-card-header { + background: var(--gradient-header); + background-size: 200% 200%; + animation: gradientAnimation 15s ease infinite; + color: white; + padding: 1.5rem; + text-align: center; + min-height: 120px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; +} + +.project-card-header::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.1); + opacity: 0; + transition: opacity var(--transition-speed) ease; +} + +.project-card:hover .project-card-header::after { + opacity: 1; +} + +.project-card-header h3 { + font-size: 1.25rem; + font-weight: 600; + margin: 0; + z-index: 1; + position: relative; +} + +.project-card-content { + padding: 1.5rem; +} + +.project-card-content h4 { + color: var(--dark-color); + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 0.75rem; + line-height: 1.3; +} + +.project-card-content p { + color: var(--gray-color); + font-size: 0.9rem; + line-height: 1.6; + margin-bottom: 0.75rem; +} + +.project-card-content p:last-of-type { + margin-bottom: 1.25rem; +} + +.project-card-content strong { + color: var(--dark-color); + font-weight: 600; +} + +.project-card-content .btn { + background: var(--primary-color); + color: white; + padding: 0.75rem 1.5rem; + text-decoration: none; + border-radius: var(--border-radius); + font-weight: 500; + font-size: 0.9rem; + display: inline-block; + transition: all var(--transition-speed) ease; + border: none; + cursor: pointer; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.project-card-content .btn:hover { + background: var(--secondary-color); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(58, 134, 255, 0.3); +} + +.card-tags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 2rem; +} + +span.tag { + background: rgba(102, 126, 234, 0.1); + color: #667eea; + padding: 0.5rem 1rem; + border-radius: 12px; + font-size: 0.9rem; + font-weight: 600; + transition: background-color 0.3s ease, transform 0.3s ease; +} + +/* Dark theme support for project cards */ +.dark-theme .project-card { + background: #2d2d2d; + border-color: rgba(255, 255, 255, 0.1); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); +} + +.dark-theme .project-card:hover { + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4); +} + +.dark-theme .project-card-content h4 { + color: #e0e0e0; +} + +.dark-theme .project-card-content p { + color: #b0b0b0; +} + +.dark-theme .project-card-content strong { + color: #e0e0e0; +} + +/* Responsive design for project cards */ +@media (max-width: 768px) { + .projects-grid { + grid-template-columns: 1fr; + gap: 1.5rem; + margin: 1.5rem 0; + } + + .project-card-header { + padding: 1.25rem; + min-height: 100px; + } + + .project-card-header h3 { + font-size: 1.1rem; + } + + .project-card-content { + padding: 1.25rem; + } + + .project-card-content .btn { + width: 100%; + text-align: center; + } +} + +@media (max-width: 480px) { + .projects-grid { + padding: 0 0.5rem; + gap: 1rem; + } + + .project-card-content { + padding: 1rem; + } +} \ No newline at end of file diff --git a/Static/img/bg-login.jpg b/Static/img/bg-login.jpg new file mode 100644 index 0000000..af09156 Binary files /dev/null and b/Static/img/bg-login.jpg differ diff --git a/Static/js/admin.js b/Static/js/admin.js new file mode 100644 index 0000000..bf28554 --- /dev/null +++ b/Static/js/admin.js @@ -0,0 +1,135 @@ +import { readJson } from './readJson.js'; + +document.addEventListener('DOMContentLoaded', async function() { + const loadProjects = async () => { + try { + const data = await readJson('../data/projects.json'); + return data; + } catch (error) { + console.error('Erreur lors du chargement des projets :', error); + return []; + } + } + + // ===* ACTIONS SUR LA PAGE *=== + const section = document.querySelector('section'); + const divProjectsGrid = document.querySelector('.projects-grid'); + const createProjectButton = document.querySelector('[data-id="creation-project-btn"]'); + const divCreateProject = document.querySelector('.form-project'); + const sectionProjects = document.querySelector('[data-id="projects"]'); + + if (divProjectsGrid) { + const data = await loadProjects(); + + if (data) { + const projects = Object.values(data); + + console.log('Projets :', projects); + projects.forEach(project => { + let state = 'Actif'; + if (!project.active) { + state = 'Inactif'; + } else { + state = 'Actif'; + } + + const projectDiv = document.createElement('div'); + + projectDiv.className = 'card-projects'; + projectDiv.innerHTML = ` +
+

${project.type}

+
+
+ + +
+

Etat : ${state}

+

${project.name}

+

${project.description}

+ ${project.technologies && project.technologies.length > 0 ? ` +
+ ${project.technologies.map(tech => `${tech}`).join('')} +
` : ''} + ${project.link ? `
Voir le projet
` : ''} + `; + divProjectsGrid.appendChild(projectDiv); + + const deleteButton = projectDiv.querySelector('[data-id="delete-project"]'); + const editButton = projectDiv.querySelector('[data-id="edit-project"]'); + + deleteButton.addEventListener('click', async () => { + const confirmDelete = confirm(`Êtes-vous sûr de vouloir supprimer le projet "${project.name}" ?`); + if (confirmDelete) { + try { + window.location.href = `./?page=delete-project&id=${project.id}`; + } catch (error) { + console.error('Erreur lors de la suppression du projet :', error); + } + } + }); + + editButton.addEventListener('click', () => { + const projectId = editButton.getAttribute('data-project-id'); + const projectToEdit = projects.find(p => p.id === projectId); + + if (projectToEdit) { + // Remplir le formulaire avec les données du projet à modifier + document.getElementById('project-type').value = projectToEdit.type || ''; + document.getElementById('project-name').value = projectToEdit.name || ''; + document.getElementById('project-description').value = projectToEdit.description || ''; + document.getElementById('project-image').value = projectToEdit.image || ''; + document.getElementById('project-start-date').value = projectToEdit.start_date || ''; + document.getElementById('project-end-date').value = projectToEdit.end_date || ''; + + // Pré-sélectionner les technologies si disponibles + if (projectToEdit.technologies && window.technologiesManager) { + window.technologiesManager.preselectTechnologies(projectToEdit.technologies); + } + + // Afficher le formulaire et faire défiler vers celui-ci + divCreateProject.classList.remove('hidden'); + createProjectButton.textContent = 'Fermer le formulaire'; + createProjectButton.classList.remove('btn-success'); + createProjectButton.classList.add('btn-danger'); + + // Scroll vers le formulaire + divCreateProject.scrollIntoView({ behavior: 'smooth' }); + } + }); + }); + } + + + } + + if(createProjectButton) { + console.log("Create project button found"); + createProjectButton.addEventListener('click', () => { + divCreateProject.classList.toggle('hidden'); + if( divCreateProject.classList.contains('hidden')) { + createProjectButton.textContent = 'Ajouter projet'; + createProjectButton.classList.remove('btn-danger'); + createProjectButton.classList.add('btn-success'); + + // Réinitialiser le formulaire + const form = divCreateProject.querySelector('form'); + if (form) { + form.reset(); + // Réinitialiser aussi les technologies + if (window.technologiesManager) { + window.technologiesManager.clearAllSelections(); + } + } + } + else { + createProjectButton.textContent = 'Fermer le formulaire'; + createProjectButton.classList.remove('btn-success'); + createProjectButton.classList.add('btn-danger'); + + // Scroll vers le formulaire + divCreateProject.scrollIntoView({ behavior: 'smooth' }); + } + }) + } +}); \ No newline at end of file diff --git a/Static/js/main.js b/Static/js/main.js new file mode 100644 index 0000000..8d9e101 --- /dev/null +++ b/Static/js/main.js @@ -0,0 +1,301 @@ +import { readJson } from './readJson.js'; + +document.addEventListener('DOMContentLoaded', async function() { + const navbar = document.querySelector("nav"); + const header = document.querySelector("header"); + let pagePath = ""; + + const linkInterceptor = () => { + const a = document.querySelectorAll('a'); + + a.forEach(function(link) { + if( link.getAttribute('data-page') != "externe") { + link.addEventListener('click', function(event) { + event.preventDefault(); + const href = link.getAttribute('data-page'); + const id = link.getAttribute('id'); + if (!href) { + console.warn('Attribut data-page introuvable sur la balise:', link); + return; + } + if(id != "admin") { + loadPage(`./Views/${href}.html`); + } else { + window.location.href = href; + } + }); + } + }); + } + + const loadPage = async (page) => { + const mainContent = document.querySelector('.main-content'); + + const loadProjects = async () => { + try { + const data = await readJson('./data/projects.json'); + return data; + } catch (error) { + console.error('Erreur lors du chargement des projets :', error); + return []; + } + } + + const loadDataContacts = async () => { + try { + const data = await readJson('./data/contacts.json'); + return data; + } catch (error) { + console.error('Erreur lors du chargement des projets :', error); + return []; + } + } + + if (!mainContent) { + console.error('Élément .main-content introuvable dans le document.'); + return; + } + if(mainContent) { + fetch(page) + .then(response => { + if(!response.ok) { + throw new Error(`Erreur lors du chargement de la page : ${response.statusText}`); + } + pagePath = page; + return response.text(); + }) + .then(async (html) => { + mainContent.innerHTML = html; + linkInterceptor(); + + const divProjectsGrid = document.querySelector('.projects-grid'); + if (divProjectsGrid) { + console.log("Projects grid found, loading projects..."); + const data = await loadProjects(); + + if (data) { + const projects = Object.values(data); + + console.log('Projets :', projects); + projects.forEach(project => { + if (project.active) { + const projectDiv = document.createElement('div'); + projectDiv.className = 'project-card'; + projectDiv.innerHTML = ` +
+

${project.type}

+
+
+

${project.name}

+

${project.description}

+ ${project.link ? `
Voir le projet
` : ''} +

+
+ ${project.technologies && project.technologies.length > 0 ? + project.technologies.map(tech => `${tech}`).join('') : ''} +
+
+ `; + divProjectsGrid.appendChild(projectDiv); + } + }); + } + } + + const contactPage = document.querySelector('.contact-page'); + if (contactPage) { + console.log("Contact page found, loading contact data..."); + const data = await loadDataContacts(); + + if (data) { + const email = document.querySelector('[data-id="email"]'); + const phone = document.querySelector('[data-id="phone"]'); + const github = document.querySelector('[data-id="github"]'); + const linkedin = document.querySelector('[data-id="linkedin"]'); + const twitter = document.querySelector('[data-id="twitter"]'); + + email.textContent = data.email; + email.href = `mailto:${data.email}`; + + phone.textContent = data.gsm; + phone.href = `tel:${data.gsm}`; + github.href = data.github; + linkedin.href = data.linkedin; + twitter.href = data.twitter; + } + } + }); + } + } + + // Animation pour l'en-tête au défilement + window.addEventListener('scroll', () => { + if (window.scrollY > 50) { + navbar.classList.add('scrolled'); + } else { + navbar.classList.remove('scrolled'); + } + }); + + // Ajout de la détection du mode sombre + const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)"); + if (prefersDarkScheme.matches) { + document.body.classList.add("dark-theme"); + } + + // Gestion des formulaires + document.addEventListener('submit', (e) => { + const form = e.target.closest('form'); + if (form) { + e.preventDefault(); + // Simuler l'envoi du formulaire + const submitBtn = form.querySelector('button[type="submit"]'); + if (submitBtn) { + const originalText = submitBtn.textContent; + submitBtn.textContent = 'Envoi en cours...'; + submitBtn.disabled = true; + + setTimeout(() => { + form.innerHTML = `
+

Message envoyé avec succès!

+

Merci pour votre message. Je vous répondrai dans les plus brefs délais.

+
`; + }, 1500); + } + } + }); + + // Animation des particules dans le header + const createParticles = () => { + const particlesContainer = document.getElementById('particles'); + if (!particlesContainer) return; + + // Nombre de particules + const particleCount = 30; + + // Supprimer les particules existantes + particlesContainer.innerHTML = ''; + + // Créer de nouvelles particules + for (let i = 0; i < particleCount; i++) { + const particle = document.createElement('span'); + particle.classList.add('particle'); + + // Attributs aléatoires pour chaque particule + const size = Math.random() * 15 + 5; + const posX = Math.random() * 100; + const posY = Math.random() * 100; + const delay = Math.random() * 5; + const duration = Math.random() * 5 + 5; + + particle.style.width = `${size}px`; + particle.style.height = `${size}px`; + particle.style.left = `${posX}%`; + particle.style.top = `${posY}%`; + particle.style.animation = `float ${duration}s infinite ${delay}s`; + particle.style.opacity = Math.random() * 0.5 + 0.3; + + particlesContainer.appendChild(particle); + } + }; + + // Effet d'écriture au clavier + const initTypeWriter = () => { + const textElement = document.getElementById('typing-text'); + if (!textElement) return; + + const phrases = [ + "Développeur Web & Applications", + "Développeur Python", + "Développeur JavaScript", + "Développeur C# & Unity", + "Développeur EmberJS", + "Développeur Typescript", + "Développeur Angular", + ]; + + let phraseIndex = 0; + let charIndex = 0; + let isDeleting = false; + let typingSpeed = 100; + let isWaiting = false; + + function typeWriter() { + if (isWaiting) { + setTimeout(typeWriter, typingSpeed); + isWaiting = false; + return; + } + + const currentPhrase = phrases[phraseIndex]; + + if (isDeleting) { + // Effacer le texte + textElement.textContent = currentPhrase.substring(0, charIndex - 1); + charIndex--; + typingSpeed = 30; // Plus rapide pour effacer + } else { + // Écrire le texte + textElement.textContent = currentPhrase.substring(0, charIndex + 1); + charIndex++; + + // Variation aléatoire de la vitesse pour un effet plus naturel + typingSpeed = Math.random() * 50 + 80; + } + + // Si toute la phrase est écrite + if (!isDeleting && charIndex === currentPhrase.length) { + // Pause avant d'effacer + isDeleting = true; + typingSpeed = 2000; // Pause plus longue + isWaiting = true; + } + + // Si la phrase est effacée + if (isDeleting && charIndex === 0) { + isDeleting = false; + phraseIndex = (phraseIndex + 1) % phrases.length; + typingSpeed = 700; // Pause avant la prochaine phrase + isWaiting = true; + } + + setTimeout(typeWriter, typingSpeed); + } + + typeWriter(); + + // Effet d'apparition du curseur + const cursor = document.querySelector('.cursor'); + if (cursor) { + cursor.style.animation = 'blink 1s step-end infinite'; + } + }; + + // Effet de défilement doux lorsqu'on clique sur le header + if (header) { + header.addEventListener('click', () => { + // Défilement vers la section suivante + const nextSection = document.querySelector('nav'); + if (nextSection) { + window.scrollTo({ + top: nextSection.offsetTop, + behavior: 'smooth' + }); + } + }); + + // Effet visuel au survol pour indiquer que le header est cliquable + header.style.cursor = 'pointer'; + header.addEventListener('mouseenter', () => { + header.style.transform = 'scale(1.01)'; + }); + header.addEventListener('mouseleave', () => { + header.style.transform = 'scale(1)'; + }); + } + + // Initialiser les particules et l'effet d'écriture + createParticles(); + initTypeWriter(); + loadPage('Views/home.html'); +}); \ No newline at end of file diff --git a/Static/js/readJson.js b/Static/js/readJson.js new file mode 100644 index 0000000..c8d54b7 --- /dev/null +++ b/Static/js/readJson.js @@ -0,0 +1,11 @@ +const readJson = (filePath) => { + return fetch(filePath) + .then(response => { + if(!response.ok) { + throw new Error(`Erreur lors du chargement du fichier JSON : ${response.statusText}`); + } + return response.json(); + }) +}; + +export { readJson }; \ No newline at end of file diff --git a/Static/js/technologies.js b/Static/js/technologies.js new file mode 100644 index 0000000..3782129 --- /dev/null +++ b/Static/js/technologies.js @@ -0,0 +1,315 @@ +// Gestionnaire des technologies pour les projets +class TechnologiesManager { + constructor() { + this.technologies = { + 'frontend': { + label: 'Frontend Web', + items: [ + 'HTML', 'CSS', 'JavaScript', 'TypeScript', 'React', 'Vue.js', + 'Angular', 'Svelte', 'Next.js', 'Nuxt.js', 'Sass', 'Tailwind CSS', 'Bootstrap' + ] + }, + 'backend': { + label: 'Backend Web', + items: [ + 'PHP', 'Laravel', 'Symfony', 'Node.js', 'Express.js', 'Python', + 'Django', 'Flask', 'FastAPI', 'Ruby', 'Ruby on Rails', 'Go', 'Rust' + ] + }, + 'languages': { + label: 'Langages de Programmation', + items: [ + 'Java', 'Spring', 'C#', '.NET', 'C++', 'C', 'Kotlin', 'Swift', + 'Dart', 'Scala', 'R', 'MATLAB' + ] + }, + 'databases': { + label: 'Bases de Données', + items: [ + 'MySQL', 'PostgreSQL', 'MongoDB', 'Redis', 'SQLite', 'Elasticsearch', + 'MariaDB', 'Oracle', 'Firebase', 'Supabase' + ] + }, + 'mobile': { + label: 'Développement Mobile', + items: [ + 'React Native', 'Flutter', 'Ionic', 'Xamarin', 'Apache Cordova', 'Android', 'iOS' + ] + }, + 'games': { + label: 'Développement de Jeux', + items: [ + 'Unity', 'Unreal Engine', 'Godot', 'Phaser', 'Three.js', 'Babylon.js', + 'PixiJS', 'Construct 3', 'GameMaker Studio', 'RPG Maker' + ] + }, + 'devops': { + label: 'DevOps & Cloud', + items: [ + 'Docker', 'Kubernetes', 'AWS', 'Azure', 'Google Cloud', 'Heroku', + 'Vercel', 'Netlify', 'Jenkins', 'GitLab CI', 'GitHub Actions', 'Terraform' + ] + }, + 'tools': { + label: 'Outils & Frameworks', + items: [ + 'Git', 'GitHub', 'Webpack', 'Vite', 'Babel', 'ESLint', 'Prettier', + 'Jest', 'Cypress', 'Storybook' + ] + }, + 'design': { + label: 'Design & Création', + items: [ + 'Figma', 'Sketch', 'Adobe XD', 'Photoshop', 'Illustrator', 'Canva', + 'Blender', 'Maya', '3ds Max' + ] + } + }; + + this.selectedTechs = new Set(); + this.init(); + } + + init() { + this.render(); + this.attachEvents(); + this.addSearchFunctionality(); + this.addQuickActions(); + } + + generateId(tech) { + return 'tag-' + tech.toLowerCase().replace(/[^a-z0-9]/g, '-'); + } + + render() { + const container = document.querySelector('.technologies-grid'); + if (!container) return; + + let html = ` +
+
+ + 🔍 +
+
+ + + +
+
+ 0 technologie(s) sélectionnée(s) +
+
+
+ `; + + for (const [categoryKey, category] of Object.entries(this.technologies)) { + html += ` +
+
+

${category.label}

+ ▼ + (${category.items.length}) +
+
+ `; + + category.items.forEach(tech => { + const id = this.generateId(tech); + html += ` +
+ + +
+ `; + }); + + html += ` +
+
+ `; + } + + html += '
'; + container.innerHTML = html; + } + + attachEvents() { + // Événements pour les checkboxes + document.querySelectorAll('.tech-checkbox').forEach(checkbox => { + checkbox.addEventListener('change', (e) => { + if (e.target.checked) { + this.selectedTechs.add(e.target.value); + } else { + this.selectedTechs.delete(e.target.value); + } + this.updateSelectedCount(); + this.updateHiddenField(); + }); + }); + + // Événements pour replier/déplier les catégories + document.querySelectorAll('.category-header').forEach(header => { + header.addEventListener('click', (e) => { + const categoryKey = e.currentTarget.dataset.toggle; + const categoryItems = document.getElementById(`category-${categoryKey}`); + const toggle = e.currentTarget.querySelector('.category-toggle'); + + if (categoryItems.style.display === 'none') { + categoryItems.style.display = 'grid'; + toggle.textContent = '▼'; + } else { + categoryItems.style.display = 'none'; + toggle.textContent = '▶'; + } + }); + }); + } + + addSearchFunctionality() { + const searchInput = document.getElementById('tech-search'); + if (!searchInput) return; + + searchInput.addEventListener('input', (e) => { + const query = e.target.value.toLowerCase(); + const techItems = document.querySelectorAll('.tech-item'); + + techItems.forEach(item => { + const label = item.querySelector('.tech-label').textContent.toLowerCase(); + if (label.includes(query)) { + item.style.display = 'flex'; + } else { + item.style.display = 'none'; + } + }); + + // Masquer les catégories vides + document.querySelectorAll('.tech-category').forEach(category => { + const visibleItems = category.querySelectorAll('.tech-item[style*="flex"]'); + if (query && visibleItems.length === 0) { + category.style.display = 'none'; + } else { + category.style.display = 'block'; + } + }); + }); + } + + addQuickActions() { + const selectAllBtn = document.getElementById('select-all'); + const deselectAllBtn = document.getElementById('deselect-all'); + const toggleCategoriesBtn = document.getElementById('toggle-categories'); + + if (selectAllBtn) { + selectAllBtn.addEventListener('click', () => { + document.querySelectorAll('.tech-checkbox').forEach(checkbox => { + checkbox.checked = true; + this.selectedTechs.add(checkbox.value); + }); + this.updateSelectedCount(); + this.updateHiddenField(); + }); + } + + if (deselectAllBtn) { + deselectAllBtn.addEventListener('click', () => { + document.querySelectorAll('.tech-checkbox').forEach(checkbox => { + checkbox.checked = false; + }); + this.selectedTechs.clear(); + this.updateSelectedCount(); + this.updateHiddenField(); + }); + } + + if (toggleCategoriesBtn) { + toggleCategoriesBtn.addEventListener('click', () => { + const allCategories = document.querySelectorAll('.category-items'); + const allToggles = document.querySelectorAll('.category-toggle'); + const firstCategory = allCategories[0]; + const isCollapsed = firstCategory.style.display === 'none'; + + allCategories.forEach(category => { + category.style.display = isCollapsed ? 'grid' : 'none'; + }); + + allToggles.forEach(toggle => { + toggle.textContent = isCollapsed ? '▼' : '▶'; + }); + }); + } + } + + updateSelectedCount() { + const countDisplay = document.getElementById('count-display'); + if (countDisplay) { + const count = this.selectedTechs.size; + countDisplay.textContent = `${count} technologie(s) sélectionnée(s)`; + + // Changer la couleur selon le nombre + if (count === 0) { + countDisplay.className = 'count-empty'; + } else if (count <= 3) { + countDisplay.className = 'count-low'; + } else if (count <= 6) { + countDisplay.className = 'count-medium'; + } else { + countDisplay.className = 'count-high'; + } + } + } + + // Méthode pour pré-sélectionner des technologies (utile pour l'édition) + selectTechnologies(techArray) { + this.selectedTechs.clear(); + + techArray.forEach(tech => { + this.selectedTechs.add(tech); + const checkbox = document.querySelector(`input[value="${tech}"]`); + if (checkbox) { + checkbox.checked = true; + } + }); + + this.updateSelectedCount(); + this.updateHiddenField(); + } + + // Alias pour la compatibilité + preselectTechnologies(techArray) { + this.selectTechnologies(techArray); + } + + // Méthode pour effacer toutes les sélections + clearAllSelections() { + document.querySelectorAll('.tech-checkbox').forEach(checkbox => { + checkbox.checked = false; + }); + this.selectedTechs.clear(); + this.updateSelectedCount(); + this.updateHiddenField(); + } + + // Méthode pour mettre à jour le champ caché + updateHiddenField() { + const hiddenField = document.getElementById('selected-technologies'); + if (hiddenField) { + hiddenField.value = Array.from(this.selectedTechs).join(','); + } + } + + // Méthode pour obtenir les technologies sélectionnées + getSelectedTechnologies() { + return Array.from(this.selectedTechs); + } +} + +// Initialiser le gestionnaire au chargement de la page +document.addEventListener('DOMContentLoaded', () => { + window.techManager = new TechnologiesManager(); +}); + +// Export pour utilisation dans d'autres fichiers +if (typeof module !== 'undefined' && module.exports) { + module.exports = TechnologiesManager; +} diff --git a/Views/about.html b/Views/about.html new file mode 100644 index 0000000..391e517 --- /dev/null +++ b/Views/about.html @@ -0,0 +1,312 @@ +
+

À propos de moi

+
+
+

Je suis Anthony VIOLET, développeur web et designer freelance avec une passion pour la création d'expériences numériques innovantes.

+

Avec plusieurs années d'expérience dans le développement web, je combine compétences techniques et sensibilité esthétique pour créer des sites et applications qui se démarquent.

+

Mon approche du développement est centrée sur l'utilisateur, avec un focus sur la performance, l'accessibilité et l'esthétique.

+
+
+

Mon parcours

+

Diplômé en développement web et multimédia, j'ai travaillé sur divers projets allant de sites vitrines à des applications web complexes.

+

Ma curiosité et ma volonté d'apprentissage me poussent à me tenir constamment informé des dernières tendances et technologies du web.

+
+
+
+ +
+

Mes valeurs

+
+
+

Qualité

+

Je m'engage à livrer un travail soigné et de haute qualité, respectant les standards du web et les bonnes pratiques.

+
+
+

Innovation

+

Je cherche constamment à explorer de nouvelles solutions et approches pour résoudre les défis de conception et de développement.

+
+
+

Communication

+

Je privilégie une communication claire et transparente tout au long du processus de création.

+
+
+
+ +
+

Mes compétences

+
+ +

Python

+
+
+
+
NumPy
+
+
+
+
+
+
Pandas
+
+
+
+
+
+
SciKit-Learn
+
+
+
+
+
+
+ + +

JavaScript

+
+
+
+
Node.JS
+
+
+
+
+
+
React/JS
+
+
+
+
+
+
Angular
+
+
+
+
+
+
Laravel
+
+
+
+
+
+
+ + +

PHP

+
+
+
+
Symfony
+
+
+
+
+
+
Procédural & OO
+
+
+
+
+
+
+ + +

C#

+
+
+
+
Unity
+
+
+
+
+
+
Godot
+
+
+
+
+
+
Logiciels
+
+
+
+
+
+
+
+ + +
+

Langues

+
+
+

Français

+
+
+ 100% +
+
+

Natif

+
+
+

Anglais

+
+
+ 30% +
+
+

Basique

+
+
+

Espagnol

+
+
+ 70% +
+
+

Intermédiaire

+
+
+
+ + +

Technologies utilisées

+
+
HTML5/CSS3
+
JavaScript
+
Python
+
PHP
+
SQL
+
Node.js
+
React
+
Angular
+
Pandas
+
NumPy
+
Symfony
+
Laravel
+
SciKit-Learn
+
+
+ +
+

Contactez-moi

+

Vous souhaitez discuter d'un projet ou simplement en savoir plus sur mon travail?

+ Envoyez-moi un message +
+ +
+

Dernières expériences professionnelles

+
+ +
+
+

Stagiaire chez Tryptik

+

11/2024 - 12/2024

+

Développement application, maintien ambiance SCRUM, développement web.

+
+ SCRUM + Développement web + Application +
+
+ + +
+
+

Collecte vendanges

+

2023

+

Récolte, ramassage et transport de nature.

+
+ Travail d'équipe + Rigueur +
+
+ + +
+
+

Assistant de vie

+

2023

+

Fourni des assistances aux personnes âgées dans leurs activités quotidiennes, en assurant le service des repas et en proposant des activités.

+
+ Assistance + Service + Conseil +
+
+ + +
+
+

Divers métiers

+

2010 - 2017

+

Barman, boucher, préparateur de commandes, vendeur en magasin de matériaux.

+
+ Polyvalence + Adaptabilité + Service client +
+
+
+
+ +
+

Formation

+
+
+

Développeur Gaming

+

Technocité - 2025

+

Après avoir approfondi mes connaissances en web et applications, ma curiosité m'a poussé à découvrir le développement dans le gaming. J'ai pu apréhender et maitriser le C# et des outils comme Unity..

+

Formé en tant que développeur gaming, j'ai acquis à maîtriser le C# ainsi que le moteur Unity.

+
+
+

Développeur Web orientée data analyste

+

Technofutur TIC - 2024

+

Après environ 15 ans d'apprentissage en autodidacte, j'ai pu approfondir et expérimenter mes connaissances dans le développement web (PHP, POO, CSS, HTML, SQL...).

+

Formé en tant que développeur web en data analyse, j'ai acquis à maîtriser JS/TS, Python (Pandas, NumPy, Nest.JS, Express, Angular, et Python (Pandas, NumPy, MatPlotLib...)).

+
+
+

Préparateur de commande en entrepôt

+

AFPA Dijon - France - 2010

+
+
+
+ +
+

À propos

+
+

Après environ 15 ans d'apprentissage en autodidacte, j'ai pu approfondir et expérimenter mes connaissances dans le développement web (PHP, POO, CSS, HTML, SQL...).

+

Formé en tant que développeur web en data analyse, j'ai acquis à maîtriser JS/TS, Python (Pandas, NumPy, Nest.JS, Express, Angular, et Python (Pandas, NumPy, MatPlotLib...)).

+
+
+ +
+

Centres d'intérêt

+
+
+
+ 💻 +
+

Développement et Programmation

+
+
+
+ ✈️ +
+

Voyages

+
+
+
+ 🎮 +
+

Gaming

+
+
+
+ 📚 +
+

Lecture de romans

+
+
+
\ No newline at end of file diff --git a/Views/contact.html b/Views/contact.html new file mode 100644 index 0000000..3ee097b --- /dev/null +++ b/Views/contact.html @@ -0,0 +1,58 @@ +
+

Contactez-moi

+

Vous avez un projet en tête ou une question? N'hésitez pas à me contacter en utilisant le formulaire ci-dessous.

+
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+ +
+

Autres moyens de contact

+
+
+

Email

+

+
+
+

Téléphone

+

+33 6 XX XX XX XX

+
+
+

Réseaux sociaux

+
+ LinkedIn + Twitter + GitHub +
+
+
+
+ +
+

Disponibilité

+

Je suis actuellement disponible pour des projets freelance à temps partiel.

+

N'hésitez pas à me contacter pour discuter de votre projet et obtenir un devis personnalisé.

+
diff --git a/Views/home.html b/Views/home.html new file mode 100644 index 0000000..afb5e6e --- /dev/null +++ b/Views/home.html @@ -0,0 +1,69 @@ +
+

Bienvenue sur mon portfolio

+ +

Voir mes projets

+
+ +
+

Mes compétences

+
+
+

Python

+

NumPy, Pandas, SciKit-Learn

+
+
+
+
+
+

JavaScript

+

Node.js, React/JS, Angular

+
+
+
+
+
+

PHP

+

Symfony, Laravel, OOP

+
+
+
+
+
+
+ Voir toutes mes compétences +
+
+ +
+

Services proposés

+
+
+

+ 🌐 + Création de site web +

+

Des sites web modernes, responsives et optimisés pour tous les appareils.

+
+
+

+ 📊 + Analyse de données +

+

Traitement et visualisation de données avec Python (NumPy, Pandas, SciKit-Learn).

+
+
+

+ 💻 + Développement web +

+

Applications web performantes avec JavaScript (Node.js, React, Angular) et PHP (Symfony).

+
+
+
+ +
+

Contactez-moi

+

Vous avez un projet en tête ou une question? N'hésitez pas à me contacter!

+ Me contacter +
\ No newline at end of file diff --git a/Views/index.html b/Views/index.html new file mode 100644 index 0000000..e54bc08 --- /dev/null +++ b/Views/index.html @@ -0,0 +1,17 @@ +
+

Admin

+

Ravi de te revoir, !

+
+ Partir +
+
+ Espace d'administration protégé. +
+
+
    +
  • Réglages généraux
  • +
  • Les projets
  • +
  • Infos contact
  • +
+
+
\ No newline at end of file diff --git a/Views/projects.html b/Views/projects.html new file mode 100644 index 0000000..ec40a43 --- /dev/null +++ b/Views/projects.html @@ -0,0 +1,19 @@ +
+

Mes projets

+

Découvrez une sélection de mes réalisations récentes.

+
+ +
+ +
+ +

Vous avez un projet en tête? N'hésitez pas à me contacter pour en discuter!

+ Me contacter +
diff --git a/admin/.DS_Store b/admin/.DS_Store new file mode 100644 index 0000000..5cfda11 Binary files /dev/null and b/admin/.DS_Store differ diff --git a/admin/SECURITY_README.md b/admin/SECURITY_README.md new file mode 100644 index 0000000..0c6802a --- /dev/null +++ b/admin/SECURITY_README.md @@ -0,0 +1,83 @@ +# Améliorations de Sécurité - Administration + +## 🔒 Corrections Apportées + +### 1. Authentification Sécurisée +- ✅ **Hash des mots de passe** : Utilisation de `password_hash()` et `password_verify()` +- ✅ **Protection CSRF** : Tokens CSRF sur tous les formulaires +- ✅ **Limitation des tentatives** : Max 5 tentatives, blocage de 15 minutes +- ✅ **Sessions sécurisées** : Configuration stricte des cookies de session +- ✅ **Timeout de session** : Déconnexion automatique après 1 heure d'inactivité + +### 2. Validation et Sanitization +- ✅ **Échappement HTML** : Protection contre XSS +- ✅ **Validation des données** : Vérification des formats et longueurs +- ✅ **Sanitization** : Nettoyage des entrées utilisateur + +### 3. Protection des Fichiers +- ✅ **Fichiers sensibles cachés** : `.htaccess` pour protéger config.php +- ✅ **Headers de sécurité** : X-Frame-Options, CSP, etc. +- ✅ **Logs de tentatives** : Enregistrement des tentatives de connexion + +## 🚀 Installation + +### Étape 1 : Générer le hash du mot de passe +1. Accédez à `admin/generate_password_hash.php` +2. Entrez votre mot de passe actuel +3. Copiez le hash généré +4. **SUPPRIMEZ le fichier** `generate_password_hash.php` + +### Étape 2 : Configuration +1. Ouvrez `admin/config.php` +2. Remplacez `ADMIN_PASSWORD_HASH` par le hash généré +3. Ajustez les autres paramètres si nécessaire + +### Étape 3 : Test +1. Testez la connexion avec vos identifiants +2. Vérifiez que les tentatives incorrectes sont bloquées +3. Testez la déconnexion sécurisée + +## ⚙️ Configuration + +### Paramètres dans config.php +```php +define('ADMIN_PASSWORD_HASH', 'votre_hash_ici'); +define('SESSION_TIMEOUT', 3600); // 1 heure +define('MAX_LOGIN_ATTEMPTS', 5); // 5 tentatives +define('LOGIN_LOCKOUT_TIME', 900); // 15 minutes +``` + +## 🔐 Fonctionnalités de Sécurité + +### Protection contre les attaques par force brute +- Limitation du nombre de tentatives par IP +- Blocage temporaire après échec +- Délai artificiel sur les tentatives échouées + +### Protection CSRF +- Token unique par session +- Vérification sur tous les formulaires sensibles +- Régénération automatique des tokens + +### Gestion des sessions +- Configuration sécurisée des cookies +- Régénération périodique des IDs de session +- Nettoyage complet lors de la déconnexion + +## 📁 Fichiers Modifiés + +- `admin/config.php` - Configuration de sécurité +- `admin/login.php` - Authentification sécurisée +- `admin/logout.php` - Déconnexion sécurisée +- `admin/index.php` - Gestion des sessions +- `admin/projects.php` - Protection CSRF et validation +- `admin/includes/nav.php` - Déconnexion sécurisée +- `admin/.htaccess` - Protection des fichiers +- `admin/generate_password_hash.php` - Générateur de hash (à supprimer) + +## ⚠️ Important + +1. **Supprimez** `generate_password_hash.php` après utilisation +2. **Sauvegardez** vos données avant mise en production +3. **Testez** toutes les fonctionnalités après déploiement +4. **Surveillez** les logs de tentatives de connexion diff --git a/admin/config.php b/admin/config.php new file mode 100644 index 0000000..cb84669 --- /dev/null +++ b/admin/config.php @@ -0,0 +1,119 @@ + 300) { // 5 minutes + session_regenerate_id(true); + $_SESSION['last_regeneration'] = time(); + } +} + +// Fonction pour vérifier les tentatives de connexion +function checkLoginAttempts($ip) { + $attempts_file = __DIR__ . '/login_attempts.json'; + + if (!file_exists($attempts_file)) { + return true; + } + + $attempts_data = file_get_contents($attempts_file); + $attempts = $attempts_data ? json_decode($attempts_data, true) : []; + + if (isset($attempts[$ip])) { + $last_attempt = $attempts[$ip]['last_attempt']; + $attempt_count = $attempts[$ip]['count']; + + // Si le délai de blocage est écoulé, réinitialiser + if (time() - $last_attempt > LOGIN_LOCKOUT_TIME) { + unset($attempts[$ip]); + file_put_contents($attempts_file, json_encode($attempts)); + return true; + } + + // Si trop de tentatives + if ($attempt_count >= MAX_LOGIN_ATTEMPTS) { + return false; + } + } + + return true; +} + +// Fonction pour enregistrer une tentative de connexion +function recordLoginAttempt($ip, $success = false) { + $attempts_file = __DIR__ . '/login_attempts.json'; + $attempts_data = file_exists($attempts_file) ? file_get_contents($attempts_file) : '{}'; + $attempts = json_decode($attempts_data, true) ?: []; + + if ($success) { + // Supprimer les tentatives en cas de succès + if (isset($attempts[$ip])) { + unset($attempts[$ip]); + } + } else { + // Enregistrer la tentative échouée + $attempts[$ip] = [ + 'count' => ($attempts[$ip]['count'] ?? 0) + 1, + 'last_attempt' => time() + ]; + } + + file_put_contents($attempts_file, json_encode($attempts)); +} + +// Générer un token CSRF +function generateCSRFToken() { + if (empty($_SESSION['csrf_token'])) { + $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); + } + return $_SESSION['csrf_token']; +} + +// Vérifier le token CSRF +function verifyCSRFToken($token) { + return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token); +} + +// Vérifier si l'utilisateur est connecté et la session est valide +function isAuthenticated() { + if (!isset($_SESSION['connected']) || !$_SESSION['connected']) { + return false; + } + + // Vérifier le timeout de session + if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > SESSION_TIMEOUT)) { + session_destroy(); + return false; + } + + // Mettre à jour l'activité + $_SESSION['last_activity'] = time(); + + return true; +} + +// Nettoyer et valider les données d'entrée +function sanitizeInput($data) { + return htmlspecialchars(trim($data), ENT_QUOTES, 'UTF-8'); +} +?> diff --git a/admin/contact.php b/admin/contact.php new file mode 100644 index 0000000..8c21337 --- /dev/null +++ b/admin/contact.php @@ -0,0 +1,111 @@ +Token de sécurité invalide."; + } else { + $firstname = sanitizeInput($_POST['firstname']); + $lastname = sanitizeInput($_POST['lastname']); + $email = sanitizeInput($_POST['email']); + $gsm = sanitizeInput($_POST['gsm']); + $linkedin = sanitizeInput($_POST['linkedin']); + $twitter = sanitizeInput($_POST['twitter']); + $github = sanitizeInput($_POST['github']); + $message = '
Formulaire soumis
'; + + $jsonFile = '../data/contacts.json'; + if(file_exists($jsonFile)) { + $content = file_get_contents($jsonFile); + $contact = $content; + + if($contact) { + $contact = json_decode($content, true); + } + + $updatedContact = [ + 'firstname' => $firstname, + 'lastname' => $lastname, + 'email' => $email, + 'gsm' => $gsm, + 'linkedin' => $linkedin, + 'twitter' => $twitter, + 'github' => $github + ]; + + $contact = array_merge($contact, $updatedContact); + if (file_put_contents($jsonFile, json_encode($contact, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) { + $message = '
Données de contact mises à jour avec succès !
'; + } else { + $message = '
Erreur lors de la mise à jour des données de contact.
'; + } + } + } +} else { + // Charger les données de contact existantes + $jsonFile = '../data/contacts.json'; + if(file_exists($jsonFile)) { + $content = file_get_contents($jsonFile); + if($content) { + $contact = json_decode($content, true); + $firstname = $contact['firstname'] ?? ''; + $lastname = $contact['lastname'] ?? ''; + $email = $contact['email'] ?? ''; + $gsm = $contact['gsm'] ?? ''; + $linkedin = $contact['linkedin'] ?? ''; + $twitter = $contact['twitter'] ?? ''; + $github = $contact['github'] ?? ''; + } + } +} + +?> +
+
+

Données de contacts

+

Les données de contacts affichées ici sont reprise sur le site dans la rubrique contact

+
+
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
\ No newline at end of file diff --git a/admin/delete-project.php b/admin/delete-project.php new file mode 100644 index 0000000..e9bca4b --- /dev/null +++ b/admin/delete-project.php @@ -0,0 +1,34 @@ +ID de projet manquant."; + exit; +} + +$jsonFile = '../data/projects.json'; + +if(file_exists($jsonFile)) { + $content = file_get_contents($jsonFile); + $projects = $content ? json_decode($content, true) : []; +} else { + $projects = []; +} + +// Check if project exists +$projectKey = array_search($id, array_column($projects, 'id')); +if ($projectKey === false) { + echo "
Projet non trouvé.
"; + exit; +} +// On supprime le projet +unset($projects[$projectKey]); +// Reindex the array +$projects = array_values($projects); +// Save updated projects back to the JSON file +if (file_put_contents($jsonFile, json_encode($projects, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) { + echo "
Projet supprimé avec succès !
"; +} else { + echo "
Erreur lors de la suppression du projet.
"; +} +?> \ No newline at end of file diff --git a/admin/generate_password_hash.php b/admin/generate_password_hash.php new file mode 100644 index 0000000..db61b58 --- /dev/null +++ b/admin/generate_password_hash.php @@ -0,0 +1,60 @@ +Générateur de hash pour mot de passe"; + +if ($_POST && isset($_POST['password'])) { + $password = $_POST['password']; + $hash = password_hash($password, PASSWORD_DEFAULT); + + echo "
"; + echo "Hash généré :
"; + echo "" . htmlspecialchars($hash) . "

"; + echo "Copiez ce hash dans config.php à la place de ADMIN_PASSWORD_HASH"; + echo "
"; + + // Vérification + if (password_verify($password, $hash)) { + echo "

✓ Vérification réussie - Le hash fonctionne correctement

"; + } else { + echo "

✗ Erreur de vérification

"; + } +} +?> + + + + + Générateur de Hash + + + +
+ ⚠️ ATTENTION : Supprimez ce fichier après utilisation pour des raisons de sécurité ! +
+ +
+
+
+ +
+ +
+

Instructions :

+
    +
  1. Entrez votre mot de passe ci-dessus
  2. +
  3. Copiez le hash généré
  4. +
  5. Remplacez la valeur de ADMIN_PASSWORD_HASH dans config.php
  6. +
  7. Supprimez ce fichier (generate_password_hash.php)
  8. +
+
+ + diff --git a/admin/home.php b/admin/home.php new file mode 100644 index 0000000..947a8d1 --- /dev/null +++ b/admin/home.php @@ -0,0 +1,61 @@ + +
+
+

Tableau de bord

+

Bienvenue dans l'espace d'administration de votre site personnel.

+ +
+
+

🎨 Projets

+

Gérez vos projets et réalisations

+ Accéder +
+ +
+

📧 Contacts

+

Consultez les données de contact

+ Accéder +
+ +
+

📊 Statistiques

+
+
+ 5 + Projets actifs +
+
+ 12 + Messages reçus +
+
+
+ +
+

🚀 Actions rapides

+
+ Voir le site + Se déconnecter +
+
+
+ + +
+
\ No newline at end of file diff --git a/admin/htaccess b/admin/htaccess new file mode 100644 index 0000000..c597c3d --- /dev/null +++ b/admin/htaccess @@ -0,0 +1,37 @@ +# Sécurisation du dossier d'administration + +# Cacher les fichiers sensibles + + Require all denied + + + + Require all denied + + + + Require all denied + + +# Protection contre les attaques par force brute + + Require all granted + # Limiter les requêtes POST (optionnel, à configurer selon vos besoins) + + +# Headers de sécurité + + Header always set X-Content-Type-Options nosniff + Header always set X-Frame-Options DENY + Header always set X-XSS-Protection "1; mode=block" + Header always set Referrer-Policy "strict-origin-when-cross-origin" + Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'" + + +# Désactiver l'affichage des erreurs PHP en production +php_flag display_errors off +php_flag log_errors on + +# Limiter la taille des uploads +php_value upload_max_filesize 10M +php_value post_max_size 10M diff --git a/admin/includes/nav.php b/admin/includes/nav.php new file mode 100644 index 0000000..d4175c3 --- /dev/null +++ b/admin/includes/nav.php @@ -0,0 +1,15 @@ +
+
    +
  • Administration
  • +
  • Accueil
  • +
  • Les projets
  • +
  • Données de contacts
  • +
  • Retour au site
  • +
  • +
    + + +
    +
  • +
+
\ No newline at end of file diff --git a/admin/index.php b/admin/index.php new file mode 100644 index 0000000..e9237a4 --- /dev/null +++ b/admin/index.php @@ -0,0 +1,36 @@ + $_SESSION['username'] + ); + $connected = "true"; +} else { + $page = "login"; +} + +?> + + + + + + + + Administration + +> + + + \ No newline at end of file diff --git a/admin/init.php b/admin/init.php new file mode 100644 index 0000000..c5aad49 --- /dev/null +++ b/admin/init.php @@ -0,0 +1,22 @@ + diff --git a/admin/login.php b/admin/login.php new file mode 100644 index 0000000..43a5daa --- /dev/null +++ b/admin/login.php @@ -0,0 +1,61 @@ +Trop de tentatives de connexion. Réessayez dans ' . ceil($remaining_time / 60) . ' minutes.'; +} else { + // Traitement du formulaire de connexion + if ($_POST && isset($_POST['username']) && isset($_POST['userPassword']) && isset($_POST['csrf_token'])) { + + // Vérifier le token CSRF + if (!verifyCSRFToken($_POST['csrf_token'])) { + $msg = 'Token de sécurité invalide. Veuillez réessayer.'; + } else { + $username = sanitizeInput($_POST['username']); + $password = $_POST['userPassword']; + + // Vérifier les identifiants + if ($username === ADMIN_USERNAME && password_verify($password, ADMIN_PASSWORD_HASH)) { + // Connexion réussie + session_regenerate_id(true); + $_SESSION['connected'] = true; + $_SESSION['username'] = $username; + $_SESSION['last_activity'] = time(); + + // Supprimer les tentatives de connexion en cas de succès + recordLoginAttempt($client_ip, true); + + $msg = 'Connexion réussie !
Accéder à l\'administration'; + } else { + // Connexion échouée + recordLoginAttempt($client_ip, false); + $msg = 'Identifiants incorrects.'; + + // Ajouter un délai pour ralentir les attaques par force brute + sleep(2); + } + } + } +} +?> +
+

Connexion

+ +
+ +
+ + +
+
+ + +
+ +
+
\ No newline at end of file diff --git a/admin/login_attempts.json b/admin/login_attempts.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/admin/login_attempts.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/admin/logout.php b/admin/logout.php new file mode 100644 index 0000000..e9396db --- /dev/null +++ b/admin/logout.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/admin/projects.php b/admin/projects.php new file mode 100644 index 0000000..3a0ec2d --- /dev/null +++ b/admin/projects.php @@ -0,0 +1,126 @@ +Token de sécurité invalide."; + } else { + // Handle form submission for project creation + if(isset($_POST['name']) && isset($_POST['description']) && isset($_POST['start_date'])) { + $type = sanitizeInput($_POST['type']); + $name = sanitizeInput($_POST['name']); + $description = sanitizeInput($_POST['description']); + $start_date = sanitizeInput($_POST['start_date']); + $end_date = isset($_POST['end_date']) && !empty($_POST['end_date']) ? sanitizeInput($_POST['end_date']) : null; + $link = isset($_POST['link']) && !empty($_POST['link']) ? sanitizeInput($_POST['link']) : null; + $technologies = isset($_POST['technologies']) && !empty($_POST['technologies']) ? + array_map('trim', explode(',', sanitizeInput($_POST['technologies']))) : []; + $tags = isset($_POST['tags']) && !empty($_POST['tags']) ? + array_map('trim', explode(',', sanitizeInput($_POST['tags']))) : []; + + // Validation des données + if (empty($type) || empty($name) || empty($description) || empty($start_date)) { + echo "
Tous les champs obligatoires doivent être remplis.
"; + } elseif (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $start_date)) { + echo "
Format de date de début invalide.
"; + } elseif ($end_date && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $end_date)) { + echo "
Format de date de fin invalide.
"; + } else { + $jsonFile = '../data/projects.json'; + // Read existing projects + if(file_exists($jsonFile)) { + $content = file_get_contents($jsonFile); + $projects = $content ? json_decode($content, true) : []; + } else { + $projects = []; + } + + // Create new project entry + $newProject = [ + 'id' => uniqid(), + 'type' => $type, + 'name' => $name, + 'description' => $description, + 'link' => $link, + 'technologies' => $technologies, + 'start_date' => $start_date, + 'end_date' => $end_date, + 'created_at' => date('Y-m-d H:i:s'), + 'active' => true + ]; + + // Add new project to the list + $projects[] = $newProject; + + // Save updated projects back to the JSON file + if (file_put_contents($jsonFile, json_encode($projects, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) { + echo "
Projet '$name' créé avec succès !
"; + // Reset form fields + unset($_POST); + } else { + echo "
Erreur lors de la sauvegarde du projet.
"; + } + } + } + } +} +?> +
+
+
+

Projets

+ Ajouter projet +
+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+

Chargement des technologies...

+
+
+ +
+
+ + + Ajoutez des mots-clés pour décrire votre projet (séparés par des virgules) +
+
+ + +
+
+ + +
+ +
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/data/contacts.json b/data/contacts.json new file mode 100644 index 0000000..74ce6f1 --- /dev/null +++ b/data/contacts.json @@ -0,0 +1,14 @@ +{ + "firstname": "Anthony", + "lastname": "VIOLET", + "email": "violet.anthony90@gmail.com", + "gsm": "", + "social": { + "linkedin": "", + "twitter": "", + "github": "" + }, + "linkedin": "https:\/\/www.linkedin.com\/in\/anthony-violet\/", + "twitter": "", + "github": "https:\/\/github.com\/MrToine" +} \ No newline at end of file diff --git a/data/projects.json b/data/projects.json new file mode 100644 index 0000000..18c248b --- /dev/null +++ b/data/projects.json @@ -0,0 +1,53 @@ +[ + { + "id": "685711e89e84c", + "type": "Site vitrine", + "name": "Entreprise de construction", + "description": "Un exemple de site vitrine pour une entreprise de construction. Ce projet concept démontre mes compétences en création d'interfaces professionnelles pour les indépendants et TPE Il illustre ma capacité à concevoir une stratégie digitale complète : galerie de réalisations, formulaire de devis, optimisation SEO locale et zone de témoignages clients.", + "link": "https:\/\/violet-anthony.eu\/projects.examples\/batipro", + "technologies": [ + "HTML", + "CSS", + "JavaScript", + "PHP" + ], + "start_date": "2025-05-19", + "end_date": "2025-05-23", + "created_at": "2025-06-21 20:11:20", + "active": true + }, + { + "id": "6857146e58bd6", + "type": "CRM", + "name": "Gestionnaire de prospects et de clients", + "description": "J'ai réaliser à titre personnel, un CRM pour le solodev. Il permet de créer des prospect, suivre ces derniers, envoyer des emails, les transformer en client, créer des devis, contacter et associer les clients à des projets. Le CRM à était un réelle défi à mes yeux car c'est la première fois que je créer un logiciel interconnecté par des modules avec python.", + "link": null, + "technologies": [ + "HTML", + "CSS", + "Python", + "Flask" + ], + "start_date": "2025-04-28", + "end_date": "2025-05-12", + "created_at": "2025-06-21 20:22:06", + "active": true + }, + { + "id": "685714d667695", + "type": "CV interactif", + "name": "Site pro", + "description": "J'ai développer ce site web pour permettre de mettre en avant mes compétences en tant que développeur web & applications. Il me permet de mettre en avant des projets réaliser pour des clients ou des projets fictif pour démontrer mon savoir faire.", + "link": "https:\/\/violet-anthony.eu", + "technologies": [ + "PHP", + "JavaScript", + "HTML", + "CSS" + ], + "start_date": "2025-05-12", + "end_date": "2025-05-16", + "created_at": "2025-06-21 20:23:50", + "active": true + } +] \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..35db15c --- /dev/null +++ b/index.html @@ -0,0 +1,54 @@ + + + + + + + + + + Anthony VIOLET | Consultant en développement web & applications + + + + + + + + + + + + + + + + + +
+
+
+
+

Anthony VIOLET

+

Développeur Web & Applications|

+
+
+
+
    +
  • Home
  • +
  • A propos de moi
  • +
  • Projets
  • +
  • Contact
  • +
  • Admin
  • +
+
+
+
+
+
+

© 2024 - 2025 Anthony VIOLET. All rights reserved.

+
+ + \ No newline at end of file