Jul 2nd - Optimized code

This commit is contained in:
2026-07-02 14:36:06 -04:00
parent d27ccb4ddd
commit c633960e3d
8 changed files with 478 additions and 554 deletions
+42 -194
View File
@@ -6,6 +6,7 @@
<style>:root { --accent: {{ branding.primary_color }}; --accent-h: {{ branding.primary_color }}; }</style>
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:opsz,wght@9..40,400;9..40,500;9..40,600&display=swap" rel="stylesheet"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet"/>
<link rel="stylesheet" href="{{ url_for('static', filename='css/password-strength.css') }}"/>
<style>
:root{
--bg:#f0f4f8;--surface:#ffffff;--border:#e2e8f0;--border2:#cbd5e1;
@@ -31,61 +32,10 @@
input.valid{border-color:var(--success);box-shadow:0 0 0 3px rgba(5,150,105,.08);}
input.invalid{border-color:var(--danger);box-shadow:0 0 0 3px rgba(220,38,38,.08);}
/* ── Password input wrapper (for show/hide toggle) ── */
.pw-wrap{position:relative;}
.pw-wrap input{padding-right:42px;}
.pw-toggle{
position:absolute;right:12px;top:50%;transform:translateY(-50%);
background:none;border:none;color:var(--muted);cursor:pointer;
font-size:16px;padding:2px;line-height:1;
transition:color .15s;
}
.pw-toggle:hover{color:var(--accent);}
/* ── Strength bar ── */
.strength-wrap{margin-top:8px;}
.strength-bar-track{
height:4px;background:var(--border);border-radius:2px;
overflow:hidden;margin-bottom:6px;
}
.strength-bar-fill{
height:100%;border-radius:2px;width:0%;
transition:width .3s ease,background .3s ease;
}
.strength-label{
font-size:11px;font-weight:600;letter-spacing:.3px;
font-family:'Space Mono',monospace;
transition:color .3s;
display:flex;align-items:center;gap:5px;
}
/* ── Rule checklist ── */
.rules{
margin-top:8px;display:grid;grid-template-columns:1fr 1fr;gap:3px 12px;
background:var(--bg);border:1px solid var(--border);border-radius:8px;
padding:10px 12px;
}
.rule{
display:flex;align-items:center;gap:5px;
font-size:11.5px;color:var(--muted);
transition:color .2s;
}
.rule .ri{
font-size:13px;width:14px;text-align:center;
transition:color .2s, transform .15s;
}
.rule.met{color:var(--success);}
.rule.met .ri{color:var(--success);transform:scale(1.1);}
.rule.unmet .ri{color:var(--border2);}
/* ── Confirm match indicator ── */
.match-msg{
font-size:11.5px;margin-top:6px;display:flex;align-items:center;gap:5px;
font-family:'Space Mono',monospace;font-weight:600;letter-spacing:.2px;
min-height:18px;transition:color .2s;
}
.match-msg.ok{color:var(--success);}
.match-msg.fail{color:var(--danger);}
/* Register page renders the strength label/match message in the
monospace brand font; the shared stylesheet leaves font-family
unset so it inherits from the page. */
.pw-strength-label, .pw-match{font-family:'Space Mono',monospace;}
/* ── Submit ── */
.btn{width:100%;background:var(--accent);border:none;color:#fff;border-radius:9px;padding:12px;font-size:14px;font-weight:600;cursor:pointer;transition:background .15s;font-family:inherit;margin-top:4px;box-shadow:0 2px 6px rgba(37,99,235,.3);}
@@ -152,11 +102,10 @@
<label>Password *</label>
<div class="pw-wrap">
<input type="password" name="password" id="pw" required
maxlength="128"
placeholder="Min. 8 characters"
autocomplete="new-password"
oninput="onPasswordInput()"/>
autocomplete="new-password"/>
<button type="button" class="pw-toggle" id="pw-toggle"
onclick="toggleVis('pw','pw-toggle')"
title="Show / hide password"
tabindex="-1">
<i class="bi bi-eye"></i>
@@ -164,28 +113,28 @@
</div>
<!-- Strength bar -->
<div class="strength-wrap" id="strength-wrap" style="display:none;">
<div class="strength-bar-track">
<div class="strength-bar-fill" id="strength-bar"></div>
<div class="pw-strength-wrap" id="strength-wrap" style="display:none;">
<div class="pw-strength-track">
<div class="pw-strength-fill" id="strength-bar"></div>
</div>
<div class="strength-label" id="strength-label"></div>
<div class="pw-strength-label" id="strength-label"></div>
</div>
<!-- Rule checklist — mirrors server-side validate_password() rules -->
<div class="rules" id="rules-box" style="display:none;">
<div class="rule unmet" id="rule-len">
<div class="pw-rules" id="rules-box" style="display:none;">
<div class="pw-rule unmet" id="rule-len">
<span class="ri"><i class="bi bi-circle"></i></span>
<span>8+ characters</span>
</div>
<div class="rule unmet" id="rule-upper">
<div class="pw-rule unmet" id="rule-upper">
<span class="ri"><i class="bi bi-circle"></i></span>
<span>Uppercase letter</span>
</div>
<div class="rule unmet" id="rule-digit">
<div class="pw-rule unmet" id="rule-digit">
<span class="ri"><i class="bi bi-circle"></i></span>
<span>Number</span>
</div>
<div class="rule unmet" id="rule-special">
<div class="pw-rule unmet" id="rule-special">
<span class="ri"><i class="bi bi-circle"></i></span>
<span>Special character</span>
</div>
@@ -197,17 +146,16 @@
<label>Confirm Password *</label>
<div class="pw-wrap">
<input type="password" name="confirm_password" id="pw2" required
maxlength="128"
placeholder="Repeat password"
autocomplete="new-password"
oninput="onConfirmInput()"/>
autocomplete="new-password"/>
<button type="button" class="pw-toggle" id="pw2-toggle"
onclick="toggleVis('pw2','pw2-toggle')"
title="Show / hide password"
tabindex="-1">
<i class="bi bi-eye"></i>
</button>
</div>
<div class="match-msg" id="match-msg"></div>
<div class="pw-match" id="match-msg"></div>
</div>
<button type="submit" class="btn" id="submit-btn">
@@ -217,133 +165,33 @@
<div class="links">Already have an account? <a href="{{ url_for('auth.login') }}">Sign in</a></div>
</div>
<script src="{{ url_for('static', filename='js/password-strength.js') }}"></script>
<script>
// ── Mirrors server-side validate_password() rules in validation_service.py ──
// Any change to the server rules must be reflected here too.
const RULES = {
len : pw => pw.length >= 8,
upper : pw => /[A-Z]/.test(pw),
digit : pw => /\d/.test(pw),
special: pw => /[!@#$%^&*()\-_=+\[\]{};:'",.<>?/\\|`~]/.test(pw),
};
// Strength scoring: each rule = 1 point; bonus point for length >= 16
function scorePassword(pw) {
let score = 0;
if (RULES.len(pw)) score++;
if (RULES.upper(pw)) score++;
if (RULES.digit(pw)) score++;
if (RULES.special(pw)) score++;
if (pw.length >= 16) score++; // bonus for extra length
return score; // 05
}
const LEVELS = [
{ label: 'Very Weak', color: '#ef4444', pct: 12 },
{ label: 'Weak', color: '#f97316', pct: 30 },
{ label: 'Fair', color: '#eab308', pct: 52 },
{ label: 'Good', color: '#22c55e', pct: 76 },
{ label: 'Strong', color: '#059669', pct: 100 },
];
function setRule(id, met) {
const el = document.getElementById('rule-' + id);
if (!el) return;
el.className = 'rule ' + (met ? 'met' : 'unmet');
el.querySelector('.ri').innerHTML = met
? '<i class="bi bi-check-circle-fill"></i>'
: '<i class="bi bi-circle"></i>';
}
function onPasswordInput() {
const pw = document.getElementById('pw').value;
const wrap = document.getElementById('strength-wrap');
const rules = document.getElementById('rules-box');
const bar = document.getElementById('strength-bar');
const lbl = document.getElementById('strength-label');
if (pw.length === 0) {
wrap.style.display = 'none';
rules.style.display = 'none';
document.getElementById('pw').classList.remove('valid','invalid');
updateSubmitState();
onConfirmInput();
return;
}
wrap.style.display = 'block';
rules.style.display = 'grid';
// Update rule checklist
setRule('len', RULES.len(pw));
setRule('upper', RULES.upper(pw));
setRule('digit', RULES.digit(pw));
setRule('special', RULES.special(pw));
// Update strength bar
const score = scorePassword(pw);
const lvl = LEVELS[Math.max(0, score - 1)] || LEVELS[0];
bar.style.width = lvl.pct + '%';
bar.style.background = lvl.color;
lbl.style.color = lvl.color;
lbl.innerHTML = `<i class="bi bi-shield${score >= 4 ? '-fill' : score >= 2 ? '-half' : ''}"></i> ${lvl.label}`;
// Border feedback on password field
const allMet = RULES.len(pw) && RULES.upper(pw) && RULES.digit(pw) && RULES.special(pw);
document.getElementById('pw').classList.toggle('valid', allMet);
document.getElementById('pw').classList.toggle('invalid', !allMet);
onConfirmInput();
updateSubmitState();
}
function onConfirmInput() {
const pw = document.getElementById('pw').value;
const pw2 = document.getElementById('pw2').value;
const msg = document.getElementById('match-msg');
const el2 = document.getElementById('pw2');
if (pw2.length === 0) {
msg.textContent = '';
msg.className = 'match-msg';
el2.classList.remove('valid','invalid');
updateSubmitState();
return;
}
if (pw === pw2) {
msg.innerHTML = '<i class="bi bi-check-circle-fill"></i> Passwords match';
msg.className = 'match-msg ok';
el2.classList.add('valid');
el2.classList.remove('invalid');
} else {
msg.innerHTML = '<i class="bi bi-x-circle-fill"></i> Passwords do not match';
msg.className = 'match-msg fail';
el2.classList.add('invalid');
el2.classList.remove('valid');
}
updateSubmitState();
}
const pwStrength = PasswordStrength.init({
password : 'pw',
confirm : 'pw2',
strengthBar : 'strength-bar',
strengthLabel: 'strength-label',
strengthWrap : 'strength-wrap',
rulesWrap : 'rules-box',
rules: { len: 'rule-len', upper: 'rule-upper', digit: 'rule-digit', special: 'rule-special' },
matchMsg : 'match-msg',
toggles: [
{ input: 'pw', button: 'pw-toggle' },
{ input: 'pw2', button: 'pw2-toggle' },
],
onChange: updateSubmitState,
});
function updateSubmitState() {
const pw = document.getElementById('pw').value;
const pw2 = document.getElementById('pw2').value;
const btn = document.getElementById('submit-btn');
const allMet = RULES.len(pw) && RULES.upper(pw) && RULES.digit(pw) && RULES.special(pw);
const match = pw === pw2 && pw2.length > 0;
// Disable only when user has started typing in either field and criteria
// are not met — never disable before they've interacted with the fields.
const pw = document.getElementById('pw').value;
const pw2 = document.getElementById('pw2').value;
const btn = document.getElementById('submit-btn');
const match = pw === pw2 && pw2.length > 0;
// Disable only when the user has started typing in either field and
// criteria are not met — never disable before they've interacted.
const hasStarted = pw.length > 0 || pw2.length > 0;
btn.disabled = hasStarted && !(allMet && match);
}
function toggleVis(inputId, btnId) {
const inp = document.getElementById(inputId);
const btn = document.getElementById(btnId);
const isPassword = inp.type === 'password';
inp.type = isPassword ? 'text' : 'password';
btn.querySelector('i').className = isPassword ? 'bi bi-eye-slash' : 'bi bi-eye';
btn.title = isPassword ? 'Hide password' : 'Show password';
btn.disabled = hasStarted && !(PasswordStrength.allRulesMet(pw) && match);
}
</script>
</body>