sky: Midnight Sky's link is fitted to clear AA on the lightest Night sky
All checks were successful
Build pull request / Build pull request (pull_request) Successful in 3m16s
All checks were successful
Build pull request / Build pull request (pull_request) Successful in 3m16s
The dark theme's link was #609385 (luminance 0.2505), 2.28:1 on the lightest Night sky (0.081642). It is now brightened in its own hue until its luminance is at least AA * (0.081642 + 0.05) - 0.05 = 0.5424: #8ad1bd (0.5468, hue 163 degrees as before), 4.53:1 on the lightest Night sky, 5.43:1 on the darkest, 11.28:1 on the sign-in card (was 5.68). Only the link changes in baked.ts; the user colours are unchanged. NIGHT_SKY_LIGHTEST is the Night backdrop constant, and a test fails if the two drift. Dusk is untouched. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7bc28f9256
commit
2c271c6ded
5 changed files with 29 additions and 17 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { atStandardTime } from './standardTime.testkit';
|
||||
import { baked } from '../../../sky/baked';
|
||||
import { AA, contrast, luminance } from '../../../sky/skyTokens.js';
|
||||
import { AA, NIGHT_SKY_LIGHTEST, contrast, luminance } from '../../../sky/skyTokens.js';
|
||||
import { Editor } from '../../components/editor/Editor.css';
|
||||
import { PageContent, PageNavContent } from '../../components/page/style.css';
|
||||
import { CallStatus } from '../call-status/styles.css';
|
||||
|
|
@ -59,13 +59,14 @@ describe('skyThemeId', () => {
|
|||
describe('ink of the theme a state picks, on bare sky', () => {
|
||||
// Per state: the picked theme, its text ink and link, and their WCAG ratios on the sky the scenes
|
||||
// draw (skyBackdrop, the worst case over its band). Text clears 4.5 in every state. Link clears
|
||||
// on day and not on dusk or night: the links are derived in src/sky/skyTokens.js, outside the band, and these
|
||||
// numbers are recorded as they are measured (Night's #609385 would clear 4.5 at a luminance of about 0.53 or more).
|
||||
// on day and night and not on dusk: the links are derived in src/sky/skyTokens.js, outside the band, and these
|
||||
// numbers are recorded as they are measured. Midnight's link is brightened in its own hue until it clears the
|
||||
// lightest Night sky (NIGHT_SKY_LIGHTEST); Dusk's dark link would need a backdrop of about 0.455, and the dusk floor is lower.
|
||||
// 'auto' resolves from the clock to one of these three and has the same numbers.
|
||||
const cells: [FixedSkyState, string, string, string, string, string][] = [
|
||||
['day', 'noonday-sky', '#342d25', '6.60', '#2a4f27', '4.55'],
|
||||
['dusk', 'noonday-sky', '#342d25', '4.60', '#2a4f27', '3.17'],
|
||||
['night', 'midnight-sky', '#aeccff', '4.89', '#609385', '2.28'],
|
||||
['night', 'midnight-sky', '#aeccff', '4.89', '#8ad1bd', '4.53'],
|
||||
];
|
||||
|
||||
it.each(cells)(
|
||||
|
|
@ -83,11 +84,15 @@ describe('ink of the theme a state picks, on bare sky', () => {
|
|||
expect(ratio(ink, skyBackdrop(state))).toBeGreaterThanOrEqual(AA);
|
||||
});
|
||||
|
||||
it('link clears AA on day (the terrain camera sees only the lighter sky) and falls short on dusk and night', () => {
|
||||
it('link clears AA on day and night and falls short on dusk', () => {
|
||||
const link = (s: FixedSkyState, id: keyof typeof SKY) => ratio(SKY[id].link, skyBackdrop(s));
|
||||
expect(link('day', 'noonday-sky')).toBeGreaterThanOrEqual(AA);
|
||||
expect(link('dusk', 'noonday-sky')).toBeLessThan(AA);
|
||||
expect(link('night', 'midnight-sky')).toBeLessThan(AA);
|
||||
expect(link('night', 'midnight-sky')).toBeGreaterThanOrEqual(AA);
|
||||
});
|
||||
|
||||
it('keeps the lightest Night sky the link was fitted to equal to the Night backdrop (no drift)', () => {
|
||||
expect(Math.abs(NIGHT_SKY_LIGHTEST - skyBackdrop('night').lightest)).toBeLessThan(1e-6);
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
|
@ -109,7 +114,7 @@ describe('ink of the theme a state picks, on bare sky', () => {
|
|||
describe('the plate, where text cannot rest on bare sky', () => {
|
||||
// Each theme's plate: fill, ink, link and the WCAG ratios of ink and link on the fill.
|
||||
const plates = [
|
||||
['midnight-sky', baked.midnight, '12.17', '5.68'],
|
||||
['midnight-sky', baked.midnight, '12.17', '11.28'],
|
||||
['noonday-sky', baked.noonday, '6.70', '4.62'],
|
||||
] as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
// Day and Dusk take the dark ink of Noonday Sky, Night the light ink of Midnight Sky. Each state's
|
||||
// drawn sky is shaped into a luminance band (skyBand.ts) in which that ink's TEXT clears 4.5:1 on
|
||||
// bare sky: Day and Dusk at least 0.2983 (dark ink), Night at most 0.0931 (light ink). skyTheme.test.ts
|
||||
// pins the measured text and link ratios. Link ink is not in the band: the Night link (#609385,
|
||||
// 2.36:1) and the dark links on Day and Dusk fall short, because the themes' link colours are
|
||||
// derived outside this code (src/sky/skyTokens.js); lightening the Night link to a luminance of
|
||||
// about 0.53 or more would clear it. The plate (skyPlate.css.ts) is still in the shell, pending
|
||||
// pins the measured text and link ratios. Link ink is not in the band: the Day and Dusk links are dark
|
||||
// and fall short of 4.5 where the sky is below about 0.455 (Dusk: 3.17), because the themes' link colours are
|
||||
// derived outside this code (src/sky/skyTokens.js). Midnight Sky's link is derived there against the lightest Night sky
|
||||
// (4.53 on it, 5.43 on the darkest). The plate (skyPlate.css.ts) is still in the shell, pending
|
||||
// its removal.
|
||||
import type { FixedSkyState, SkyState } from './skyState';
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export const AuthFooter = style({
|
|||
// Noonday); the other themes (Light, Silver, Dark, Butter) take the fallback colours.
|
||||
// WCAG ratios of text / Register link on the card, per theme:
|
||||
// Light, Silver, Dark, Butter #b7c7d9 / #99c7ff on #0d1a26 10.21 / 10.03
|
||||
// Midnight Sky #aeccff / #609385 on #0a090e 12.17 / 5.68
|
||||
// Midnight Sky #aeccff / #8ad1bd on #0a090e 12.17 / 11.28
|
||||
// Noonday Sky #342d25 / #2a4f27 on #b9b6af 6.70 / 4.62
|
||||
// The fallback link is set here because those themes' own links (Light 2.49:1, Silver 2.49:1) are
|
||||
// made for their own grounds, not this card; Dark and Butter already use the same hsl(213 100% 80%).
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ export const baked = {
|
|||
'#677dff',
|
||||
'#6a926a',
|
||||
],
|
||||
link: '#609385',
|
||||
link: '#8ad1bd',
|
||||
},
|
||||
noonday: {
|
||||
moment: {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ const luma = (c) => 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
|
|||
export const GRADIENT_STOPS = 7;
|
||||
export const AA = 4.5; // WCAG 2.x AA, normal text
|
||||
|
||||
// The lightest the Night sky draws behind the page (src/app/features/scene/skyState.ts, `skyBackdrop('night')`; a test in
|
||||
// skyTheme.test.ts fails if the two drift). Midnight Sky's link also sits on bare sky, so it must clear AA against this.
|
||||
export const NIGHT_SKY_LIGHTEST = 0.081642;
|
||||
|
||||
// Where and when the theme is lit. Equinox, 45N on the prime meridian: noon sun at 45 degrees.
|
||||
export const MOMENTS = {
|
||||
noonday: { time: '2026-09-22T12:00:00Z', lat: 45, lon: 0 },
|
||||
|
|
@ -186,10 +190,10 @@ export function bakeSky(name) {
|
|||
bubbles.Own,
|
||||
];
|
||||
const readable = (h) => grounds.every((g) => contrast(h, g) >= AA);
|
||||
const fit = (albedo) => {
|
||||
const fit = (albedo, ok = () => true) => {
|
||||
let key = 0.5;
|
||||
let h = hex(albedo, key);
|
||||
for (let n = 0; n < 80 && !readable(h); n += 1) {
|
||||
for (let n = 0; n < 80 && !(readable(h) && ok(h)); n += 1) {
|
||||
key *= dark ? 1.05 : 0.95;
|
||||
h = hex(albedo, key);
|
||||
}
|
||||
|
|
@ -207,7 +211,10 @@ export function bakeSky(name) {
|
|||
gradient,
|
||||
tokens,
|
||||
bubbles,
|
||||
userColors: PIGMENTS.map(fit),
|
||||
link: fit(dark ? ALBEDO.leafPale : ALBEDO.leaf),
|
||||
userColors: PIGMENTS.map((p) => fit(p)),
|
||||
// The dark link also has to clear AA on the bare Night sky: brightened, in its own hue, until it does.
|
||||
link: dark
|
||||
? fit(ALBEDO.leafPale, (h) => luminance(h) >= AA * (NIGHT_SKY_LIGHTEST + 0.05) - 0.05)
|
||||
: fit(ALBEDO.leaf),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue