/** * contrast.ts — WCAG contrast audit module for audit_contrast tool. * Pure WCAG math + Playwright-backed page audit. * No new npm dependencies required. */ import { CaptureUnavailableError } from "./capture.js"; export { CaptureUnavailableError }; export type ContrastRow = { selector: string; text: string; foreground: string; background: string; fontPx: number; bold: boolean; large: boolean; status: "pass" | "fail" | "indeterminate"; ratio: number | null; aa: boolean | null; aaa: boolean | null; required_aa: number; delta_to_aa: number | null; effective_bg: string; bg_indeterminate: boolean; indeterminate_reason?: string; ratio_min?: number; ratio_max?: number; }; export type ContrastResult = { url?: string; total_text_elements: number; rows: ContrastRow[]; aa_failures: ContrastRow[]; aa_fail_count: number; indeterminate_bg_rows: ContrastRow[]; indeterminate_bg_count: number; indeterminate_bg_note: string; mode_note: string; warnings: string[]; }; export type BackgroundLayer = { color: string | null; image: string | null; }; type Rgba = [number, number, number, number]; /** * One or more aa_failures rows that should be reported as a single finding. * `isShortTextGroup` is true when every row's trimmed text is 0-2 characters * long (icon glyphs, single letters left over from a motion-split animation, * etc.) — these are noisy to report per-letter and are usually not * independent contrast bugs, so callers should collapse them into one * likely-artifact finding instead of one confirmed error per fragment. */ export type ContrastFailureGroup = { rows: ContrastRow[]; isShortTextGroup: boolean; }; /** * Group `aa_failures` rows so short-text (glyph/motion-split) fragments that * share the same rendering context — (foreground, background, required_aa) — * collapse into a single group instead of firing one finding per fragment. * Longer-text failures are never grouped with anything else; each keeps its * own single-row group so it can still be reported as a confirmed error. */ export declare function collapseShortTextContrastFailures(rows: ContrastRow[]): ContrastFailureGroup[]; /** * Parse a CSS colour string into [r, g, b, a] where r/g/b ∈ 0-255, a ∈ 0-1. * Handles: #RGB, #RRGGBB, #RGBA, #RRGGBBAA, rgb(), rgba(), black, white, transparent. * Unknown/unparseable → [0, 0, 0, 1] for this legacy public helper only. * Audit paths use parseKnownColor and surface parse failures as indeterminate. */ export declare function parseKnownColor(css: string): Rgba | null; export declare function parseColor(css: string): Rgba; /** Parse the color stops of a deliberately simple linear/radial gradient. */ export declare function parseGradientStops(image: string): Rgba[] | null; /** * WCAG relative luminance for an [r,g,b] triple (0-255). */ export declare function relativeLuminance(rgb: [number, number, number]): number; /** * WCAG contrast ratio between two [r,g,b] triples (0-255). Range: 1..21. * contrastRatio([0,0,0], [255,255,255]) === 21 (±0.01). */ export declare function contrastRatio(fg: [number, number, number], bg: [number, number, number]): number; /** * Composite an ordered stack of CSS color strings onto an opaque white base. * * @param layers - CSS color strings ordered **nearest ancestor first → furthest last**. * Each is parsed with parseColor. Fully-transparent layers (a === 0) are skipped. * The stack is composited alpha-over from furthest→nearest onto white [255,255,255]. * @returns An opaque [r, g, b] triple. Empty or all-transparent input → [255,255,255]. */ export declare function compositeBackground(layers: string[]): [number, number, number]; /** * Score a pre-collected snapshot of elements. No browser required. */ export declare function auditContrastSnapshot(elements: Array<{ selector: string; color: string; bgColor: string; bgColors?: string[]; bgLayers?: BackgroundLayer[]; bgIndeterminateReason?: string; fontPx?: number; bold?: boolean; text?: string; }>): ContrastResult; /** * Render a URL in headless Chromium, extract all visible text elements with * their computed colours, then score them with auditContrastSnapshot. */ export declare function auditContrastUrl(url: string, opts?: { viewport?: { w: number; h: number; }; timeoutMs?: number; theme?: "light" | "dark"; }): Promise; export type ContrastFixDetail = { color: string; ratio: number; direction: "lighter" | "darker"; }; export type SuggestContrastFix = { fg: string; bg: string; currentRatio: number; targetRatio: number; passes: boolean; fgFix: ContrastFixDetail | null; bgFix: ContrastFixDetail | null; reachable: boolean; recommendation: string; }; /** Keep only determinate failures when routing audit rows into remediation. */ export declare function filterSuggestibleContrastPairs(rows: T[]): T[]; export declare function suggestContrastFix(fg: string, bg: string, opts?: { targetRatio?: number; level?: "AA" | "AAA"; fontPx?: number; bold?: boolean; }): SuggestContrastFix;