Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions scripts/check-contrast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
- heading (large text) ink vs mid-gradient: ≥ 4.5:1
- status/button ink vs computed chip background: ≥ 7:1
- top-of-page link ink (pickTopInk) vs --sky-top: ≥ 4.5:1
- card text ink (--ink) vs --card-bg (.sky-card): ≥ 4.5:1 — this
pairing bottoms out around dusk (~6.6:1), which is why card text
must never carry an opacity de-emphasis: any multiplier drags the
dusk floor under 4.5 and Lighthouse fails whenever CI happens to
run at sunset.
Run: node scripts/check-contrast.ts */
import {
skyColors,
Expand All @@ -17,6 +22,7 @@ import {
let worstHeading = Infinity;
let worstChip = Infinity;
let worstTopInk = Infinity;
let worstCard = Infinity;
let failures = 0;

for (let alt = -90; alt <= 90; alt += 0.1) {
Expand All @@ -40,21 +46,27 @@ for (let alt = -90; alt <= 90; alt += 0.1) {
const topInk = pickTopInk(top);
const topInkRatio = contrast(topInk, top);

// .sky-card pairs --ink (pickInk) with --card-bg (chipFor's bg) —
// the combination blog cards and article bodies actually render.
const cardRatio = contrast(ink, chip.bg);

worstHeading = Math.min(worstHeading, headingRatio);
worstChip = Math.min(worstChip, chipRatio);
worstTopInk = Math.min(worstTopInk, topInkRatio);
worstCard = Math.min(worstCard, cardRatio);

if (headingRatio < 4.5 || chipRatio < 7 || topInkRatio < 4.5) {
if (headingRatio < 4.5 || chipRatio < 7 || topInkRatio < 4.5 || cardRatio < 4.5) {
failures++;
console.error(
`FAIL alt=${alt.toFixed(1)}° heading=${headingRatio.toFixed(2)} chip=${chipRatio.toFixed(2)} topInk=${topInkRatio.toFixed(2)}`,
`FAIL alt=${alt.toFixed(1)}° heading=${headingRatio.toFixed(2)} chip=${chipRatio.toFixed(2)} topInk=${topInkRatio.toFixed(2)} card=${cardRatio.toFixed(2)}`,
);
}
}

console.log(`worst heading contrast (needs ≥ 4.5): ${worstHeading.toFixed(2)}`);
console.log(`worst chip contrast (needs ≥ 7.0): ${worstChip.toFixed(2)}`);
console.log(`worst top-link contrast (needs ≥ 4.5): ${worstTopInk.toFixed(2)}`);
console.log(`worst card-text contrast (needs ≥ 4.5): ${worstCard.toFixed(2)}`);
if (failures > 0) {
console.error(`${failures} altitude(s) failed AAA`);
process.exit(1);
Expand Down
3 changes: 2 additions & 1 deletion src/pages/blog/[...slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@
← blog
</a>

<article class="sky-card rounded-2xl px-6 py-8 sm:px-10 sm:py-10">

Check warning on line 29 in src/pages/blog/[...slug].astro

View workflow job for this annotation

GitHub Actions / Lint + format

The classes: "sm:px-10, sm:py-10" can be simplified to "sm:p-10"

Check warning on line 29 in src/pages/blog/[...slug].astro

View workflow job for this annotation

GitHub Actions / Lint + format

The classes: "sm:px-10, sm:py-10" can be simplified to "sm:p-10"
<p class="text-xs lowercase tracking-widest opacity-70">{dateFmt(post.data.publishDate)}</p>
<!-- Full opacity on purpose — see blog/index.astro's date line. -->
<p class="text-xs lowercase tracking-widest">{dateFmt(post.data.publishDate)}</p>
<h1 class="mt-2 text-3xl font-medium tracking-tight sm:text-4xl">{post.data.title}</h1>

{
Expand Down
11 changes: 7 additions & 4 deletions src/pages/blog/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,17 @@ const dateFmt = (d: Date) =>
href={`/blog/${post.id}/`}
class="sky-card group block rounded-2xl px-5 py-4 transition-transform duration-300 hover:-translate-y-0.5 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
>
<p class="text-xs lowercase tracking-widest opacity-70">
{/* No opacity de-emphasis on card text: the computed
ink/card pairing bottoms out at ~6.6:1 around dusk, and
any opacity multiplies that below the 4.5:1 floor small
text needs (caught by Lighthouse when CI ran at sunset).
Size and tracking carry the hierarchy instead. */}
<p class="text-xs lowercase tracking-widest">
{dateFmt(post.data.publishDate)}
{post.data.original && ` · originally posted on ${post.data.original.source}`}
</p>
<h2 class="mt-1 text-xl font-medium">{post.data.title}</h2>
{post.data.description && (
<p class="mt-1 text-sm opacity-80">{post.data.description}</p>
)}
{post.data.description && <p class="mt-1 text-sm">{post.data.description}</p>}
</a>
</li>
))
Expand Down
Loading