Files
PasswordGeneratorHtml/forge.html

559 lines
19 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Forge - Générateur de Mots de Passe</title>
<style>
/* Variables de Thème */
:root {
--bg-color: #121212;
--primary-color: #1e1e1e;
--text-color: #f0f0f0;
--accent-color: #00ff7f; /* Vert "Forge" */
--accent-dark: #00aa55;
--border-color: #333;
--code-bg: #2a2a2a;
--strength-weak: #d9534f;
--strength-medium: #f0ad4e;
--strength-strong: #5cb85c;
--strength-vstrong: #00ff7f;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
display: grid;
place-items: center;
min-height: 100vh;
padding: 1rem;
}
/* Conteneur principal du générateur */
.generator-container {
width: 100%;
max-width: 500px;
background-color: var(--primary-color);
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
border: 1px solid var(--border-color);
overflow: hidden;
animation: fadeIn 0.8s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
header {
padding: 1.5rem 2rem;
text-align: center;
border-bottom: 1px solid var(--border-color);
}
header h1 {
color: var(--accent-color);
font-size: 2rem;
text-shadow: 0 0 10px rgba(0, 255, 127, 0.7);
}
main {
padding: 2rem;
}
/* Conteneur du résultat (mot de passe + copie) */
.result-container {
display: flex;
margin-bottom: 1.5rem;
}
#password-display {
flex-grow: 1;
font-family: "Courier New", Courier, monospace;
font-size: 1.25rem;
padding: 0.75rem 1rem;
background-color: var(--code-bg);
border: 1px solid var(--border-color);
color: var(--text-color);
border-radius: 8px 0 0 8px;
outline: none;
}
#copy-btn {
padding: 0 1rem;
background-color: var(--accent-color);
color: var(--bg-color);
border: none;
cursor: pointer;
font-size: 1.5rem; /* Emoji size */
border-radius: 0 8px 8px 0;
transition: background-color 0.3s ease;
}
#copy-btn:hover {
background-color: var(--accent-dark);
}
#copy-btn.copied {
background-color: var(--strength-strong);
}
/* Indicateur de force */
.strength-container {
margin-bottom: 1.5rem;
}
#strength-bar {
width: 100%;
height: 10px;
background-color: var(--code-bg);
border-radius: 5px;
overflow: hidden;
margin-bottom: 0.5rem;
}
#strength-bar-inner {
height: 100%;
width: 0%;
background-color: var(--strength-weak);
border-radius: 5px;
transition: width 0.4s ease, background-color 0.4s ease;
}
#strength-text {
font-size: 0.9rem;
text-align: right;
display: block;
color: #aaa;
}
/* Conteneur des options */
.options-container {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.option {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
}
.option-checkbox {
display: flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
}
/* Slider de longueur */
label[for="length"] {
font-size: 1.1rem;
font-weight: 500;
}
#length-value {
font-size: 1.1rem;
font-weight: 500;
color: var(--accent-color);
font-family: "Courier New", Courier, monospace;
}
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 8px;
background: var(--code-bg);
border-radius: 5px;
outline: none;
margin-top: 0.5rem;
grid-column: 1 / -1; /* S'étend sur toute la largeur */
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: var(--accent-color);
border-radius: 50%;
cursor: pointer;
transition: background-color 0.3s ease;
}
input[type="range"]::-webkit-slider-thumb:hover {
background: var(--accent-dark);
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
background: var(--accent-color);
border-radius: 50%;
cursor: pointer;
}
/* Cases à cocher personnalisées */
input[type="checkbox"] {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background-color: var(--code-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: pointer;
position: relative;
transition: background-color 0.3s ease;
}
input[type="checkbox"]:checked {
background-color: var(--accent-color);
border-color: var(--accent-color);
}
input[type="checkbox"]:checked::before {
content: '✔';
color: var(--bg-color);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 14px;
font-weight: bold;
}
.option-checkbox label {
cursor: pointer;
user-select: none;
}
/* Bouton de génération */
.cta-button {
width: 100%;
padding: 1rem;
font-size: 1.1rem;
font-weight: bold;
background-color: var(--accent-color);
color: var(--bg-color);
border: none;
border-radius: 8px;
cursor: pointer;
margin-top: 1rem;
transition: background-color 0.3s ease, transform 0.1s ease;
}
.cta-button:hover {
background-color: var(--accent-dark);
}
.cta-button:active {
transform: scale(0.98);
}
footer {
text-align: center;
font-size: 0.8rem;
color: #555;
padding-top: 1.5rem;
border-top: 1px solid var(--border-color);
}
</style>
</head>
<body>
<div class="generator-container">
<header>
<h1><span id="forge-icon">⛓️</span> Forge</h1>
</header>
<main>
<div class="result-container">
<input type="text" id="password-display" readonly placeholder="Générez un mot de passe...">
<button id="copy-btn" title="Copier dans le presse-papiers">📋</button>
</div>
<div class="strength-container">
<div id="strength-bar">
<div id="strength-bar-inner"></div>
</div>
<span id="strength-text"></span>
</div>
<div class="options-container">
<div class="option">
<label for="length">Longueur du mot de passe</label>
<span id="length-value">16</span>
<input type="range" id="length" min="8" max="64" value="16">
</div>
<div class="option option-checkbox">
<input type="checkbox" id="uppercase" checked>
<label for="uppercase">Inclure des Majuscules (A-Z)</label>
</div>
<div class="option option-checkbox">
<input type="checkbox" id="lowercase" checked>
<label for="lowercase">Inclure des Minuscules (a-z)</label>
</div>
<div class="option option-checkbox">
<input type="checkbox" id="numbers" checked>
<label for="numbers">Inclure des Nombres (0-9)</label>
</div>
<div class="option option-checkbox">
<input type="checkbox" id="symbols" checked>
<label for="symbols">Inclure des Symboles (!@#$)</label>
</div>
<div class="option option-checkbox">
<input type="checkbox" id="exclude-ambiguous">
<label for="exclude-ambiguous">Exclure les caractères ambigus</label>
</div>
</div>
<button id="generate-btn" class="cta-button">Générer le Mot de Passe</button>
</main>
<footer>
<p>Forge v1.0 - La sécurité avant tout.</p>
</footer>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Sélection des éléments DOM
const lengthSlider = document.getElementById('length');
const lengthValue = document.getElementById('length-value');
const passwordDisplay = document.getElementById('password-display');
const generateBtn = document.getElementById('generate-btn');
const copyBtn = document.getElementById('copy-btn');
const options = {
uppercase: document.getElementById('uppercase'),
lowercase: document.getElementById('lowercase'),
numbers: document.getElementById('numbers'),
symbols: document.getElementById('symbols'),
excludeAmbiguous: document.getElementById('exclude-ambiguous')
};
const strengthBar = document.getElementById('strength-bar-inner');
const strengthText = document.getElementById('strength-text');
// Jeux de caractères
const charsets = {
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
lowercase: 'abcdefghijklmnopqrstuvwxyz',
numbers: '0123456789',
symbols: '!@#$%^&*()_+-=[]{}|;:,.<>?'
};
const ambiguous = 'oO0l1I|';
// Mettre à jour la valeur du slider
lengthSlider.addEventListener('input', (e) => {
lengthValue.textContent = e.target.value;
updateStrength();
});
// Écouteurs pour les options
Object.values(options).forEach(option => {
option.addEventListener('change', updateStrength);
});
generateBtn.addEventListener('click', generatePassword);
copyBtn.addEventListener('click', copyToClipboard);
/**
* Génère un nombre aléatoire sécurisé (si possible)
*/
function getRandomInt(max) {
// Utilise crypto.getRandomValues pour une meilleure entropie
const randomBuffer = new Uint32Array(1);
window.crypto.getRandomValues(randomBuffer);
let randomNumber = randomBuffer[0] / (0xFFFFFFFF + 1);
return Math.floor(randomNumber * max);
}
/**
* Mélange un tableau (algorithme Fisher-Yates)
*/
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = getRandomInt(i + 1);
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
/**
* Fonction principale de génération
*/
function generatePassword() {
const length = parseInt(lengthSlider.value, 10);
let charPool = '';
let guaranteedChars = [];
let password = [];
// 1. Construire le pool de caractères et garantir un de chaque type coché
if (options.uppercase.checked) {
charPool += charsets.uppercase;
guaranteedChars.push(charsets.uppercase[getRandomInt(charsets.uppercase.length)]);
}
if (options.lowercase.checked) {
charPool += charsets.lowercase;
guaranteedChars.push(charsets.lowercase[getRandomInt(charsets.lowercase.length)]);
}
if (options.numbers.checked) {
charPool += charsets.numbers;
guaranteedChars.push(charsets.numbers[getRandomInt(charsets.numbers.length)]);
}
if (options.symbols.checked) {
charPool += charsets.symbols;
guaranteedChars.push(charsets.symbols[getRandomInt(charsets.symbols.length)]);
}
// 2. Gérer le cas où rien n'est coché
if (charPool === '') {
alert('Veuillez sélectionner au moins un jeu de caractères.');
passwordDisplay.value = '';
updateStrength(); // Réinitialise la barre
return;
}
// 3. Exclure les caractères ambigus
if (options.excludeAmbiguous.checked) {
charPool = charPool.split('').filter(char => !ambiguous.includes(char)).join('');
guaranteedChars = guaranteedChars.filter(char => !ambiguous.includes(char));
// S'assurer que le pool n'est pas vide après filtrage
if (charPool === '') {
alert('Impossible de générer un mot de passe, tous les caractères disponibles sont ambigus.');
return;
}
}
// 4. Remplir le reste du mot de passe
const remainingLength = length - guaranteedChars.length;
for (let i = 0; i < remainingLength; i++) {
password.push(charPool[getRandomInt(charPool.length)]);
}
// 5. Combiner les caractères garantis et le reste, puis mélanger
password = password.concat(guaranteedChars);
password = shuffleArray(password);
// 6. Afficher
passwordDisplay.value = password.slice(0, length).join(''); // Assure la bonne longueur
updateStrength(); // Mettre à jour la force après la génération
// Réinitialiser le bouton "Copier"
copyBtn.innerHTML = '📋';
copyBtn.classList.remove('copied');
}
/**
* Met à jour l'indicateur de force
*/
function updateStrength() {
const length = parseInt(lengthSlider.value, 10);
let score = 0;
let text = '';
let color = 'var(--strength-weak)';
let width = '0%';
const categories =
(options.uppercase.checked ? 1 : 0) +
(options.lowercase.checked ? 1 : 0) +
(options.numbers.checked ? 1 : 0) +
(options.symbols.checked ? 1 : 0);
if (categories === 0 || passwordDisplay.value === '') {
strengthText.textContent = '';
strengthBar.style.width = '0%';
return;
}
// Calcul basique du score (entropie simplifiée)
score = length * categories * (options.symbols.checked ? 1.5 : 1);
if (length < 8) {
score = 0; // Trop court
}
if (score < 25) {
text = 'Très Faible';
color = 'var(--strength-weak)';
width = '25%';
} else if (score < 40) {
text = 'Moyen';
color = 'var(--strength-medium)';
width = '50%';
} else if (score < 60) {
text = 'Fort';
color = 'var(--strength-strong)';
width = '75%';
} else {
text = 'Très Fort';
color = 'var(--strength-vstrong)';
width = '100%';
}
strengthText.textContent = text;
strengthBar.style.width = width;
strengthBar.style.backgroundColor = color;
}
/**
* Copie le mot de passe dans le presse-papiers
*/
function copyToClipboard() {
const password = passwordDisplay.value;
if (!password) return;
navigator.clipboard.writeText(password).then(() => {
// Succès
copyBtn.innerHTML = '✅';
copyBtn.classList.add('copied');
// Revenir à l'icône de base après 2 secondes
setTimeout(() => {
copyBtn.innerHTML = '📋';
copyBtn.classList.remove('copied');
}, 2000);
}).catch(err => {
// Erreur (rare, mais possible)
alert("Erreur lors de la copie : " + err);
});
}
// Générer un premier mot de passe au chargement
generatePassword();
});
</script>
</body>
</html>