這段要貼在哪裡:先在自己的工作資料夾建立同名相對位置,再複製內容。
const status = document.querySelector("#status");
const action = document.querySelector("#action");
function parseRgb(value) {
const match = value.match(/rgba?\((\d+)[, ]+(\d+)[, ]+(\d+)/);
return match ? match.slice(1, 4).map(Number) : null;
}
function relativeLuminance([red, green, blue]) {
const channels = [red, green, blue].map((channel) => channel / 255).map((channel) => (
channel <= 0.03928 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4
));
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
}
function contrastRatio(foreground, background) {
if (!foreground || !background) return 0;
const lighter = Math.max(relativeLuminance(foreground), relativeLuminance(background));
const darker = Math.min(relativeLuminance(foreground), relativeLuminance(background));
return (lighter + 0.05) / (darker + 0.05);
}
function readStylesheetText() {
return Array.from(document.styleSheets).flatMap((sheet) => {
try { return Array.from(sheet.cssRules).map((rule) => rule.cssText); } catch { return []; }
}).join("\n");
}
function auditCss() {
const card = document.querySelector(".service-card");
const heading = document.querySelector(".service-card h2");
const description = document.querySelector(".service-description");
const button = document.querySelector("#action");
if (!status || !card || !heading || !description || !button) {
if (status) status.textContent = "CSS 輔助檢查無法開始:先確認指定元素存在。";
return;
}
const headingStyle = getComputedStyle(heading);
const descriptionStyle = getComputedStyle(description);
const cardStyle = getComputedStyle(card);
const buttonStyle = getComputedStyle(button);
const stylesheetText = readStylesheetText();
const textContrast = contrastRatio(parseRgb(descriptionStyle.color), parseRgb(cardStyle.backgroundColor));
const buttonContrast = contrastRatio(parseRgb(buttonStyle.color), parseRgb(buttonStyle.backgroundColor));
const report = {
fontFamily: headingStyle.fontFamily,
fontSize: headingStyle.fontSize,
fontWeight: headingStyle.fontWeight,
lineHeight: headingStyle.lineHeight,
textColor: descriptionStyle.color,
cardBackground: cardStyle.backgroundColor,
textContrast: Number(textContrast.toFixed(2)),
buttonContrast: Number(buttonContrast.toFixed(2)),
usesBrandToken: stylesheetText.includes("var(--brand)"),
usesMutedToken: stylesheetText.includes("var(--muted)"),
hasFocusRule: stylesheetText.includes(":focus-visible") && stylesheetText.includes("var(--focus)") && !stylesheetText.includes("outline: none"),
javascriptExecuted: true,
};
const failures = [];
if (headingStyle.lineHeight === "normal" || !stylesheetText.includes(".service-card h2")) failures.push("文字階層");
if (!report.usesBrandToken || !report.usesMutedToken) failures.push("token/var() 使用");
if (textContrast < 4.5) failures.push("一般文字對比");
if (buttonContrast < 4.5) failures.push("按鈕文字對比");
if (!report.hasFocusRule) failures.push("鍵盤 focus");
const passed = failures.length === 0;
status.textContent = passed
? "JavaScript 已執行;CSS 條件通過,請再用畫面、DevTools 與 390px viewport 交叉確認。"
: `JavaScript 已執行;CSS 已讀取,但仍有未通過項目:${failures.join("、")}。`;
console.log("LESSON-06 CSS audit", { passed, report, failures });
}
if (action) action.addEventListener("click", auditCss);