import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { BENCHMARK_METHODS, BENCHMARK_MIX_WEIGHTS, BENCHMARK_SHADE_STEPS, TAILWIND_BENCHMARK_METRICS, TAILWIND_BENCHMARK_PROTOCOL, TAILWIND_BENCHMARK_RUNNER, TAILWIND_BENCHMARK_VERSION, runTailwindSeedBenchmark, summarizeMethodResults, type BenchmarkSeed, } from "../src/lib/tailwind-scale-benchmark"; type CpsColor = { code: string; displayCode: string; name: string; family: string; familyCode: string; hex: string; oklch: { l: number; c: number; h: number }; }; const root = process.cwd(); const sourceData = path.join(root, "src", "data"); const publicData = path.join(root, "public", "data"); fs.mkdirSync(publicData, { recursive: true }); const publicationDate = "2026-07-22"; const corpusVersion = "cps-stratified-seed-corpus-1.0.0"; const cpsColors = JSON.parse(fs.readFileSync(path.join(sourceData, "cps-colors.json"), "utf8")) as CpsColor[]; const chromaticFamilies = [...new Set(cpsColors.map((color) => color.family))].slice(0, 30); function sha256(value: string | Buffer) { return crypto.createHash("sha256").update(value).digest("hex"); } function canonicalJson(value: unknown) { return `${JSON.stringify(value, null, 2)}\n`; } function selectEvenly(items: T[], count: number): T[] { if (count >= items.length) return [...items]; if (count === 1) return [items[Math.floor((items.length - 1) / 2)]]; return Array.from({ length: count }, (_, index) => items[Math.round(index * (items.length - 1) / (count - 1))]); } const referenceSeeds: BenchmarkSeed[] = chromaticFamilies.flatMap((family, familyIndex) => { const count = familyIndex % 3 === 0 ? 4 : 3; const candidates = cpsColors .filter((color) => color.family === family && color.oklch.l >= 0.35 && color.oklch.l <= 0.78 && color.oklch.c >= 0.04) .sort((a, b) => a.oklch.l - b.oklch.l || a.oklch.c - b.oklch.c || a.code.localeCompare(b.code)); return selectEvenly(candidates, count).map((color) => ({ id: `reference-${color.code.toLowerCase()}`, hex: color.hex, name: color.name, family: color.family, sourceCode: color.displayCode, cohort: "reference" as const, })); }); if (referenceSeeds.length !== 100 || new Set(referenceSeeds.map((seed) => seed.hex)).size !== 100) { throw new Error(`Expected 100 unique reference seeds, received ${referenceSeeds.length} rows and ${new Set(referenceSeeds.map((seed) => seed.hex)).size} unique HEX values.`); } const referenceCodes = new Set(referenceSeeds.map((seed) => seed.sourceCode)); const holdoutCandidates = cpsColors .filter((color) => chromaticFamilies.includes(color.family) && !referenceCodes.has(color.displayCode) && color.oklch.l >= 0.32 && color.oklch.l <= 0.82 && color.oklch.c >= 0.035) .sort((a, b) => a.oklch.h - b.oklch.h || a.oklch.l - b.oklch.l || a.code.localeCompare(b.code)); const holdoutSeeds: BenchmarkSeed[] = selectEvenly(holdoutCandidates, 20).map((color) => ({ id: `holdout-${color.code.toLowerCase()}`, hex: color.hex, name: color.name, family: color.family, sourceCode: color.displayCode, cohort: "holdout", })); const controlSeeds: BenchmarkSeed[] = [ ["control-red", "#FF0000", "sRGB red"], ["control-green", "#00FF00", "sRGB green"], ["control-blue", "#0000FF", "sRGB blue"], ["control-cyan", "#00FFFF", "sRGB cyan"], ["control-magenta", "#FF00FF", "sRGB magenta"], ["control-yellow", "#FFFF00", "sRGB yellow"], ["control-orange", "#FF7A00", "high-chroma orange"], ["control-violet", "#7C00FF", "high-chroma violet"], ["control-low-chroma-warm", "#8B8178", "low-chroma warm"], ["control-low-chroma-cool", "#75818B", "low-chroma cool"], ["control-light-gray", "#D1D5DB", "light neutral"], ["control-dark-gray", "#374151", "dark neutral"], ].map(([id, hex, name]) => ({ id, hex, name, family: "Synthetic control", cohort: "control" as const })); const allSeeds = [...referenceSeeds, ...holdoutSeeds, ...controlSeeds]; const seedPayload = { name: "Color Pick Tailwind Scale Seed Corpus", version: corpusVersion, benchmarkVersion: TAILWIND_BENCHMARK_VERSION, published: publicationDate, license: "https://creativecommons.org/licenses/by/4.0/", source: "https://colorpick.site/benchmarks/datasets/", methodology: "https://colorpick.site/benchmarks/methodology/", selection: { reference: "100 deterministic CPS seeds: 3 per chromatic family plus a fourth seed for every third family, sampled evenly across eligible lightness-sorted candidates.", eligibility: "CPS chromatic families only; OKLCH L from 0.35 through 0.78 and chroma at least 0.04.", holdout: "20 eligible non-reference CPS colors sampled across hue-sorted candidates after the reference corpus was frozen.", controls: "12 synthetic sRGB boundary, vivid, low-chroma, and neutral controls.", excluded: "No proprietary spot-color library, scraped brand palette, or user-submitted color is included.", }, counts: { reference: referenceSeeds.length, holdout: holdoutSeeds.length, controls: controlSeeds.length, total: allSeeds.length }, seeds: allSeeds, }; const seedJsonWithoutChecksum = canonicalJson(seedPayload); const datasetChecksum = sha256(seedJsonWithoutChecksum); const seedDataset = { ...seedPayload, checksum: `sha256:${datasetChecksum}` }; const referenceResults = referenceSeeds.map((seed) => runTailwindSeedBenchmark(seed)); const holdoutResults = holdoutSeeds.map((seed) => runTailwindSeedBenchmark(seed)); const controlResults = controlSeeds.map((seed) => runTailwindSeedBenchmark(seed)); const methodSummary = summarizeMethodResults(referenceResults); const holdoutSummary = summarizeMethodResults(holdoutResults); const rankByVariation = (rows: ReturnType) => [...rows].sort((a, b) => a.meanStepVariation - b.meanStepVariation).map((row) => row.method); const referenceRank = rankByVariation(methodSummary); const holdoutRank = rankByVariation(holdoutSummary); const rankingAgreement = referenceRank.filter((method, index) => holdoutRank[index] === method).length; const variationWinner = methodSummary.reduce((best, row) => row.meanStepVariation < best.meanStepVariation ? row : best); const uniformityWinner = methodSummary.reduce((best, row) => row.uniformityWins > best.uniformityWins ? row : best); const hueWinner = methodSummary.reduce((best, row) => row.medianMaximumHueDrift < best.medianMaximumHueDrift ? row : best); const worstCases = BENCHMARK_METHODS.map((method) => { const result = [...referenceResults].sort((a, b) => { const aValue = a.methods.find((entry) => entry.method === method)?.metrics.coefficientOfVariation ?? 0; const bValue = b.methods.find((entry) => entry.method === method)?.metrics.coefficientOfVariation ?? 0; return bValue - aValue; })[0]; const methodResult = result.methods.find((entry) => entry.method === method)!; return { method, label: methodResult.label, seedId: result.seed.id, seedName: result.seed.name, seedHex: result.seed.hex, sourceCode: result.seed.sourceCode, family: result.seed.family, coefficientOfVariation: methodResult.metrics.coefficientOfVariation, maximumHueDrift: methodResult.metrics.maximumHueDrift, gamutMappedShades: methodResult.metrics.gamutMappedShades, }; }); const variationDistribution = BENCHMARK_METHODS.map((method) => { const values = referenceResults.map((result) => result.methods.find((entry) => entry.method === method)!.metrics.coefficientOfVariation); return { method, label: methodSummary.find((row) => row.method === method)!.label, buckets: { "under-0.25": values.filter((value) => value < 0.25).length, "0.25-0.40": values.filter((value) => value >= 0.25 && value < 0.40).length, "0.40-0.55": values.filter((value) => value >= 0.40 && value < 0.55).length, "0.55-0.70": values.filter((value) => value >= 0.55 && value < 0.70).length, "0.70-plus": values.filter((value) => value >= 0.70).length, }, }; }); const controlFailures = controlResults.flatMap((result) => result.methods .filter((method) => method.metrics.lightnessViolations > 0) .map((method) => ({ seedId: result.seed.id, seedName: result.seed.name, seedHex: result.seed.hex, method: method.method, label: method.label, lightnessViolations: method.metrics.lightnessViolations, coefficientOfVariation: method.metrics.coefficientOfVariation, }))); const protocol = { name: "Color Pick Scale Generation Protocol", version: TAILWIND_BENCHMARK_PROTOCOL, benchmarkVersion: TAILWIND_BENCHMARK_VERSION, published: publicationDate, scope: "Deterministic comparison of four interpolation spaces for an 11-step 50–950 design-token scale anchored at shade 500.", shadeLabels: BENCHMARK_SHADE_STEPS, anchor: { shade: 500, treatment: "The normalized six-digit sRGB seed is preserved exactly." }, weights: BENCHMARK_MIX_WEIGHTS, methods: { hsl: "Interpolate HSL saturation and lightness toward achromatic white or black while retaining the seed hue.", srgb: "Interpolate gamma-encoded sRGB channels toward white or black.", "linear-srgb": "Decode sRGB channels to linear light, interpolate, then encode to sRGB.", oklch: "Interpolate OKLCH lightness and chroma toward achromatic white or black while retaining seed hue; reduce chroma at fixed lightness and hue when mapping to sRGB.", }, output: { gamut: "sRGB", rounding: "Round only the final output to 8-bit uppercase six-digit HEX; calculate measurements from those emitted values." }, metrics: { adjacentUniformity: "Delta E OK between each adjacent emitted shade; report mean, population standard deviation, and coefficient of variation.", lightness: "Count any increase greater than 0.0005 OKLCH L while moving from shade 50 to 950.", hueDrift: "Circular OKLCH hue difference from the seed for shades with measured chroma at least 0.02.", contrast: "Unrounded WCAG 2.x relative-luminance contrast ratio against #FFFFFF and #000000; 3:1 and 4.5:1 are treated as thresholds.", gamut: "Count OKLCH outputs that required chroma reduction to enter sRGB.", }, boundaries: [ "The scale labels follow the common 50–950 design-token convention; this protocol does not reproduce Tailwind CSS palette-generation internals.", "A more even Delta E OK progression is not automatically more usable, accessible, or visually preferable.", "Contrast counts are pair evidence, not a complete accessibility audit or certification.", ], }; const resultPayload = { name: "Tailwind Scale Accessibility Benchmark v1.0", version: TAILWIND_BENCHMARK_VERSION, published: publicationDate, publisher: "Color Pick", url: "https://colorpick.site/benchmarks/tailwind-scale-accessibility/", license: "https://creativecommons.org/licenses/by/4.0/", versions: { corpus: corpusVersion, protocol: TAILWIND_BENCHMARK_PROTOCOL, metrics: TAILWIND_BENCHMARK_METRICS, runner: TAILWIND_BENCHMARK_RUNNER, }, checksums: { seedDataset: `sha256:${datasetChecksum}` }, sampleSize: { referenceSeeds: referenceSeeds.length, methods: BENCHMARK_METHODS.length, scales: referenceSeeds.length * BENCHMARK_METHODS.length, emittedShades: referenceSeeds.length * BENCHMARK_METHODS.length * BENCHMARK_SHADE_STEPS.length, holdoutSeeds: holdoutSeeds.length, controls: controlSeeds.length }, directAnswer: `${variationWinner.label} had the lowest mean adjacent-step coefficient of variation in the frozen 100-seed reference corpus under protocol v1.0. The result is specific to these weights, metrics, and sRGB output rules—not evidence that one method is universally best.`, keyFindings: { lowestMeanStepVariation: variationWinner.method, lowestMeanStepVariationValue: variationWinner.meanStepVariation, mostPerSeedUniformityWins: uniformityWinner.method, perSeedUniformityWins: uniformityWinner.uniformityWins, lowestMedianMaximumHueDrift: hueWinner.method, lowestMedianMaximumHueDriftDegrees: hueWinner.medianMaximumHueDrift, referenceHoldoutRankAgreement: `${rankingAgreement}/${BENCHMARK_METHODS.length}`, }, worstCases, variationDistribution, controlFailures, methodSummary, holdoutSummary, protocol, results: [...referenceResults, ...holdoutResults, ...controlResults], limitations: [ "The 100 reference seeds are a stratified Color Pick CPS corpus, not a random sample of all possible colors and not a list of common brands.", "Results change when endpoints, weights, output gamut, gamut mapping, precision, or metric definitions change.", "Delta E OK is used as a practical perceptual-distance indicator; equal numeric steps do not guarantee equal appearance in every viewing condition.", "Contrast is measured only against opaque sRGB white and black. Typography, opacity, images, states, component geometry, and user testing are outside this benchmark.", "HSL, sRGB, linear-sRGB, and OKLCH each optimize different properties. No universal overall score or winner is published.", "Tailwind is a trademark of Tailwind Labs. This independent benchmark is not affiliated with or endorsed by Tailwind Labs.", ], }; const resultJsonWithoutChecksum = canonicalJson(resultPayload); const resultChecksum = sha256(resultJsonWithoutChecksum); const resultDataset = { ...resultPayload, checksums: { ...resultPayload.checksums, results: `sha256:${resultChecksum}` } }; const csvQuote = (value: unknown) => `"${String(value).replaceAll('"', '""')}"`; const seedCsv = [ ["id", "cohort", "name", "family", "source_code", "hex"].join(","), ...allSeeds.map((seed) => [seed.id, seed.cohort, seed.name ?? "", seed.family ?? "", seed.sourceCode ?? "", seed.hex].map(csvQuote).join(",")), ].join("\n") + "\n"; const resultCsv = [ ["seed_id", "cohort", "seed_hex", "method", "mean_adjacent_delta_e_ok", "step_cv", "lightness_violations", "max_hue_drift_degrees", "mapped_shades", "body_text_on_white_shades", "body_text_on_black_shades", "white_text_action_candidates", "black_text_action_candidates"].join(","), ...resultDataset.results.flatMap((result) => result.methods.map((method) => [ result.seed.id, result.seed.cohort, result.seed.hex, method.method, method.metrics.meanAdjacentDeltaE, method.metrics.coefficientOfVariation, method.metrics.lightnessViolations, method.metrics.maximumHueDrift, method.metrics.gamutMappedShades, method.metrics.bodyTextOnWhiteShades, method.metrics.bodyTextOnBlackShades, method.metrics.whiteTextActionCandidates, method.metrics.blackTextActionCandidates, ].map(csvQuote).join(","))), ].join("\n") + "\n"; const summary = { name: resultDataset.name, version: resultDataset.version, published: publicationDate, directAnswer: resultDataset.directAnswer, sampleSize: resultDataset.sampleSize, versions: resultDataset.versions, checksums: resultDataset.checksums, keyFindings: resultDataset.keyFindings, worstCases, variationDistribution, controlFailures, methodSummary, holdoutSummary, featuredSeed: referenceResults.find((result) => result.seed.family === "Blue") ?? referenceResults[0], limitations: resultDataset.limitations, }; const files = new Map([ ["tailwind-scale-seed-corpus-v1.0.0.json", canonicalJson(seedDataset)], ["tailwind-scale-seed-corpus-v1.0.0.csv", seedCsv], ["tailwind-scale-accessibility-benchmark-v1.0.0.json", canonicalJson(resultDataset)], ["tailwind-scale-accessibility-benchmark-v1.0.0.csv", resultCsv], ["tailwind-scale-protocol-v1.0.0.json", canonicalJson(protocol)], ["tailwind-scale-benchmark-citation-v1.0.0.bib", `@techreport{colorpick_tailwind_scale_2026,\n title = {Tailwind Scale Accessibility Benchmark v1.0},\n author = {{Color Pick}},\n institution = {Color Pick},\n year = {2026},\n month = {July},\n url = {https://colorpick.site/benchmarks/tailwind-scale-accessibility/},\n version = {1.0.0}\n}\n`], ]); for (const [filename, content] of files) fs.writeFileSync(path.join(publicData, filename), content); fs.copyFileSync(path.join(root, "src", "lib", "tailwind-scale-benchmark.ts"), path.join(publicData, "tailwind-scale-benchmark-engine-v1.0.0.ts")); fs.copyFileSync(path.join(root, "scripts", "generate-tailwind-benchmark.ts"), path.join(publicData, "tailwind-scale-benchmark-generator-v1.0.0.ts")); const releaseChecksums = [...files.entries()].map(([filename, content]) => `${sha256(content)} ${filename}`); releaseChecksums.push(`${sha256(fs.readFileSync(path.join(root, "src", "lib", "tailwind-scale-benchmark.ts")))} tailwind-scale-benchmark-engine-v1.0.0.ts`); releaseChecksums.push(`${sha256(fs.readFileSync(path.join(root, "scripts", "generate-tailwind-benchmark.ts")))} tailwind-scale-benchmark-generator-v1.0.0.ts`); fs.writeFileSync(path.join(publicData, "tailwind-scale-benchmark-checksums-v1.0.0.txt"), `${releaseChecksums.join("\n")}\n`); fs.writeFileSync(path.join(sourceData, "tailwind-benchmark-summary.json"), canonicalJson(summary)); console.log(`Generated Tailwind Scale Accessibility Benchmark v${TAILWIND_BENCHMARK_VERSION}: ${referenceSeeds.length} reference seeds, ${holdoutSeeds.length} holdout seeds, ${controlSeeds.length} controls, ${resultDataset.sampleSize.scales} primary scales.`);