@@ -65,6 +70,10 @@ const team = [
}).join('');
+ // Add list role to grid for screen readers
+ grid.setAttribute('role', 'list');
+ grid.setAttribute('aria-label', 'Team members');
+
})();
let teamOpen = false;
@@ -186,8 +195,12 @@ function formatAndValidateUrl(input) {
function fillExample(url) {
- document.getElementById('urlInput').value = url;
- document.getElementById('urlInput').focus();
+ const input = document.getElementById('urlInput');
+ input.value = url;
+ input.focus();
+
+ // Update aria-label to reflect filled value for screen readers
+ input.setAttribute('aria-label', `URL input, filled with ${url}. Press Enter or click Scan URL to scan.`);
}
@@ -270,8 +283,8 @@ function calculateRiskScore(url, isThreat) {
score = Math.min(100, score);
- let confidence = isThreat ? 99 : 88;
- if (!isThreat && score > 20) confidence -= 12;
+ let confidence = isThreat ? 99 : 88;
+ if (!isThreat && score > 20) confidence -= 12;
return { score, confidence, breakdown };
}
@@ -285,44 +298,60 @@ function showResult(type, title, desc, url, threats) {
error: '!'
};
+ // Map result type to human-readable label for screen readers
+ const ariaLabels = {
+ safe: 'Safe',
+ danger: 'Danger',
+ error: 'Error',
+ loading: 'Loading'
+ };
+
const isThreat = type === 'danger';
let riskSectionHtml = '';
let riskData = null;
if ((type === 'safe' || type === 'danger') && url) {
riskData = calculateRiskScore(url, isThreat);
-
+
let meterColor = 'var(--accent-1)';
if (riskData.score > 30) meterColor = '#fbbf24';
if (riskData.score > 60) meterColor = '#f87171';
let breakdownHtml = riskData.breakdown.map(item => {
let icon = '';
- if (item.type === 'safe') icon = `
`;
- else if (item.type === 'warning') icon = `
`;
- else if (item.type === 'danger') icon = `
`;
+ let iconLabel = '';
+ if (item.type === 'safe') {
+ icon = `
`;
+ iconLabel = 'Safe';
+ } else if (item.type === 'warning') {
+ icon = `
`;
+ iconLabel = 'Warning';
+ } else if (item.type === 'danger') {
+ icon = `
`;
+ iconLabel = 'Danger';
+ }
- return `
${icon} ${item.text}
`;
+ return `
${iconLabel}: ${icon} ${item.text}
`;
}).join('');
riskSectionHtml = `
-
+
-
-
+
+
-
@@ -331,9 +360,9 @@ function showResult(type, title, desc, url, threats) {
document.getElementById('result').innerHTML = `
-
+
-
+
${
type === 'loading'
@@ -348,20 +377,28 @@ function showResult(type, title, desc, url, threats) {
${title}
${desc}
- ${url ? `
${url}
` : ''}
+ ${url ? `
${url}
` : ''}
${threats && threats.length
- ? `
${threats.map(t =>
- `${t} `).join('')}
`
+ ? `
${threats.map(t =>
+ `${t} `).join('')}
`
: ''}
${(type === 'safe' || type === 'danger') ? `
- ⬇ Download PDF
- ⬇ Download Image
+ ⬇ Download PDF
+ ⬇ Download Image
` : ''}
`;
+ // Move focus to result div so screen readers announce the outcome
+ const resultEl = document.getElementById('result');
+ resultEl.setAttribute('tabindex', '-1');
+ resultEl.focus();
+
if (riskSectionHtml) {
+ // Append risk section after result card
+ resultEl.querySelector('.result-card').insertAdjacentHTML('beforeend', riskSectionHtml);
+
setTimeout(() => {
const bar = document.querySelector('.risk-meter-bar');
if (bar) {
@@ -438,27 +475,29 @@ async function checkSecurity() {
document.getElementById('scanBtn');
btn.disabled = true;
+ btn.setAttribute('aria-busy', 'true');
+ btn.setAttribute('aria-label', 'Scanning URL, please wait...');
// Loading State — enhanced scan animation
document.getElementById('result').innerHTML = `
-
+
-
+
Scanning URL...
-
${url}
+
${url}
-
+
@@ -514,21 +553,31 @@ async function checkSecurity() {
updateStats('danger');
showResult('danger', 'Threat Detected!',
`This URL is flagged as dangerous. Do not visit it.
-
-
- ${isHttps ? '✅' : '⚠️'}
HTTPS: ${isHttps ? 'Secure connection' : 'Not secure'}
+
+
+ ${isHttps ? '✅' : '⚠️'}
+ ${isHttps ? 'Secure' : 'Insecure'}:
+ HTTPS: ${isHttps ? 'Secure connection' : 'Not secure'}
-
- ${hasMalware ? '🔴' : '✅'}
Malware: ${hasMalware ? 'Detected!' : 'No malware detected'}
+
+ ${hasMalware ? '🔴' : '✅'}
+ ${hasMalware ? 'Detected' : 'Clear'}:
+ Malware: ${hasMalware ? 'Detected!' : 'No malware detected'}
-
- ${hasPhishing ? '🔴' : '✅'}
Phishing: ${hasPhishing ? 'Phishing detected!' : 'No phishing detected'}
+
+ ${hasPhishing ? '🔴' : '✅'}
+ ${hasPhishing ? 'Detected' : 'Clear'}:
+ Phishing: ${hasPhishing ? 'Phishing detected!' : 'No phishing detected'}
-
- ${hasUnwanted ? '🔴' : '✅'}
Unwanted Software: ${hasUnwanted ? 'Detected!' : 'None detected'}
+
+ ${hasUnwanted ? '🔴' : '✅'}
+ ${hasUnwanted ? 'Detected' : 'Clear'}:
+ Unwanted Software: ${hasUnwanted ? 'Detected!' : 'None detected'}
-
- ${hasHarmful ? '🔴' : '✅'}
Harmful App: ${hasHarmful ? 'Detected!' : 'None detected'}
+
+ ${hasHarmful ? '🔴' : '✅'}
+ ${hasHarmful ? 'Detected' : 'Clear'}:
+ Harmful App: ${hasHarmful ? 'Detected!' : 'None detected'}
`, url, threats);
} else {
@@ -543,21 +592,31 @@ async function checkSecurity() {
showResult('safe', 'URL is Safe',
`No threats detected. Google Safe Browsing found no issues.
-
-
- ${isHttps ? '✅' : '⚠️'}
HTTPS: ${isHttps ? 'Secure connection' : 'Not secure — use with caution'}
+
+
+ ${isHttps ? '✅' : '⚠️'}
+ ${isHttps ? 'Secure' : 'Warning'}:
+ HTTPS: ${isHttps ? 'Secure connection' : 'Not secure — use with caution'}
-
- ${hasMalware ? '🔴' : '✅'}
Malware: ${hasMalware ? 'Detected!' : 'No malware detected'}
+
+ ${hasMalware ? '🔴' : '✅'}
+ ${hasMalware ? 'Detected' : 'Clear'}:
+ Malware: ${hasMalware ? 'Detected!' : 'No malware detected'}
-
- ${hasPhishing ? '🔴' : '✅'}
Phishing: ${hasPhishing ? 'Phishing detected!' : 'No phishing detected'}
+
+ ${hasPhishing ? '🔴' : '✅'}
+ ${hasPhishing ? 'Detected' : 'Clear'}:
+ Phishing: ${hasPhishing ? 'Phishing detected!' : 'No phishing detected'}
-
- ${hasUnwanted ? '🔴' : '✅'}
Unwanted Software: ${hasUnwanted ? 'Detected!' : 'None detected'}
+
+ ${hasUnwanted ? '🔴' : '✅'}
+ ${hasUnwanted ? 'Detected' : 'Clear'}:
+ Unwanted Software: ${hasUnwanted ? 'Detected!' : 'None detected'}
-
- ${hasHarmful ? '🔴' : '✅'}
Harmful App: ${hasHarmful ? 'Detected!' : 'None detected'}
+
+ ${hasHarmful ? '🔴' : '✅'}
+ ${hasHarmful ? 'Detected' : 'Clear'}:
+ Harmful App: ${hasHarmful ? 'Detected!' : 'None detected'}
`, url, []);
}
@@ -569,7 +628,8 @@ async function checkSecurity() {
'', []);
} finally {
btn.disabled = false;
-
+ btn.removeAttribute('aria-busy');
+ btn.setAttribute('aria-label', 'Scan the entered URL for security threats');
}
}
@@ -577,6 +637,7 @@ async function checkSecurity() {
document.getElementById('urlInput').addEventListener('keydown', e => {
if (e.key === 'Enter') checkSecurity();
});
+
async function captureReport() {
const card = document.querySelector('#result .result-card');
if (!card) return null;
@@ -588,6 +649,7 @@ async function captureReport() {
background: #1e293b; border-radius: 12px;
font-family: sans-serif; color: #f1f5f9;
`;
+ reportDiv.setAttribute('aria-hidden', 'true');
reportDiv.innerHTML = `
🛡️
@@ -631,6 +693,7 @@ async function downloadPDF() {
pdf.addImage(imgData, 'PNG', 0, 20, pageWidth, imgHeight);
pdf.save('cybershield-report.pdf');
}
+
// ─────────────────────────────
// THEME TOGGLE
// ─────────────────────────────
@@ -652,12 +715,20 @@ async function downloadPDF() {
function applyTheme(theme) {
if (theme === 'light') {
document.documentElement.classList.add('light-mode');
- if (btn) btn.textContent = '☀️';
+ if (btn) {
+ btn.textContent = '☀️';
+ btn.setAttribute('aria-label', 'Switch to dark mode');
+ btn.setAttribute('aria-pressed', 'false');
+ }
} else {
document.documentElement.classList.remove('light-mode');
- if (btn) btn.textContent = '🌙';
+ if (btn) {
+ btn.textContent = '🌙';
+ btn.setAttribute('aria-label', 'Switch to light mode');
+ btn.setAttribute('aria-pressed', 'true');
+ }
}
localStorage.setItem('theme', theme);
}
-})();
+})();
\ No newline at end of file
diff --git a/style.css b/style.css
index bf46d56..16cd835 100644
--- a/style.css
+++ b/style.css
@@ -1285,4 +1285,29 @@ input[type="text"].input-error {
@media (max-width: 380px) {
.team-grid { grid-template-columns: 1fr; }
+}
+
+/* ── Accessibility: Focus styles ── */
+:focus-visible {
+ outline: 2px solid #00ffb4;
+ outline-offset: 3px;
+ border-radius: 4px;
+}
+
+/* Remove default focus for mouse users only */
+:focus:not(:focus-visible) {
+ outline: none;
+}
+
+/* Screen reader only utility */
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
}
\ No newline at end of file