Merge pull request 'auth: test the emitted footer rule; wrap comment; correct the terrain luminance figure' (#62) from void/footer-lows into dev
Some checks failed
Rat dev preview / Build dev preview (push) Has been cancelled
Some checks failed
Rat dev preview / Build dev preview (push) Has been cancelled
This commit is contained in:
commit
7494591624
5 changed files with 84 additions and 6 deletions
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -86,6 +86,7 @@
|
|||
"@types/ua-parser-js": "0.7.36",
|
||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||
"@typescript-eslint/parser": "5.46.1",
|
||||
"@vanilla-extract/integration": "6.5.0",
|
||||
"@vitejs/plugin-react": "4.2.0",
|
||||
"buffer": "6.0.3",
|
||||
"cz-conventional-changelog": "3.3.0",
|
||||
|
|
@ -5095,6 +5096,7 @@
|
|||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@vanilla-extract/integration/-/integration-6.5.0.tgz",
|
||||
"integrity": "sha512-E2YcfO8vA+vs+ua+gpvy1HRqvgWbI+MTlUpxA8FvatOvybuNcWAY0CKwQ/Gpj7rswYKtC6C7+xw33emM6/ImdQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.7",
|
||||
"@babel/plugin-syntax-typescript": "^7.20.0",
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@
|
|||
"@types/ua-parser-js": "0.7.36",
|
||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||
"@typescript-eslint/parser": "5.46.1",
|
||||
"@vanilla-extract/integration": "6.5.0",
|
||||
"@vitejs/plugin-react": "4.2.0",
|
||||
"buffer": "6.0.3",
|
||||
"cz-conventional-changelog": "3.3.0",
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@ export const SKY_PLATE_FALLBACK = {
|
|||
/**
|
||||
* The sign-in footer's tint: how much of the plate's fill (--sky-bubble) is mixed over the blurred scene behind the
|
||||
* footer line. The footer is a blurred, tinted band with soft edges, not a plate: the scene shows through it. The
|
||||
* footer ink is the plate's, so the tint has to bring the scene behind it to where that ink clears 4.5:1
|
||||
* (skyTheme.test.ts). The browser mixes in sRGB, so the test does too (skyTheme.test.ts): over pure black 0.80 gives 4.36 and 0.82 gives 4.55, and
|
||||
* the darkest terrain drawn is 0.0045, not black; measured on the drawn pixels the footer clears 6.2.
|
||||
* footer ink is the plate's, so the tint has to bring the scene behind it to where that ink clears 4.5:1. The browser
|
||||
* mixes in sRGB, so the test does too (skyTheme.test.ts): over pure black 0.80 gives 4.36 and 0.82 gives 4.55. The
|
||||
* terrain is not black: in the shots at 390x844 (terrain scene, tier low) the darkest pixel in the footer's region is
|
||||
* 0.0203 at Dusk, 0.0936 at Day (dark ink) and 0.0052 at Night (light ink). Measured on the drawn pixels with the band
|
||||
* on, the worst link contrast is 5.0 at Dusk, 5.7 at Day and 11.2 at Night.
|
||||
*/
|
||||
export const FOOTER_TINT = 0.82;
|
||||
|
|
|
|||
72
src/app/pages/auth/footerBand.test.ts
Normal file
72
src/app/pages/auth/footerBand.test.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import zlib from 'zlib';
|
||||
import { compile, processVanillaFile } from '@vanilla-extract/integration';
|
||||
import { AuthFooter } from './styles.css';
|
||||
|
||||
// The CSS styles.css.ts emits for the sign-in footer, read back from vanilla-extract's own compile.
|
||||
const emitted = async () => {
|
||||
const filePath = `${process.cwd()}/src/app/pages/auth/styles.css.ts`;
|
||||
const { source } = await compile({ filePath, cwd: process.cwd(), identOption: 'debug' });
|
||||
const code = await processVanillaFile({ source, filePath, identOption: 'debug' });
|
||||
const encoded = (code.match(/source=#([A-Za-z0-9+/=]+)/) as RegExpMatchArray)[1];
|
||||
return zlib.gunzipSync(Buffer.from(encoded, 'base64')).toString();
|
||||
};
|
||||
const declarations = (css: string, selector: string) =>
|
||||
[...css.matchAll(/^([^{}\n]+) \{\n([^}]*)\}/gm)]
|
||||
.filter((m) => m[1] === selector)
|
||||
.map((m) =>
|
||||
m[2]
|
||||
.split('\n')
|
||||
.map((l) => l.trim().replace(/;$/, ''))
|
||||
.filter(Boolean)
|
||||
);
|
||||
|
||||
describe('the sign-in footer band, as emitted', () => {
|
||||
const over = `:root[data-scene] .${AuthFooter}`;
|
||||
|
||||
it('is one rule over the scene, so no later rule can override its mask', async () => {
|
||||
expect(declarations(await emitted(), over)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('puts the plate fill before the color-mix tint, so a browser without color-mix keeps the fill', async () => {
|
||||
const [rule] = declarations(await emitted(), over);
|
||||
const fills = rule.filter((d) => d.startsWith('background-color:'));
|
||||
expect(fills).toHaveLength(2);
|
||||
expect(fills[0]).toBe('background-color: var(--sky-bubble, #0d1a26)');
|
||||
expect(fills[1]).toContain('color-mix(');
|
||||
});
|
||||
|
||||
it('blurs what is behind it by a real radius', async () => {
|
||||
const [rule] = declarations(await emitted(), over);
|
||||
const blur = rule.find((d) => d.startsWith('backdrop-filter:')) ?? '';
|
||||
const radius = Number(blur.match(/^backdrop-filter: blur\((\d+(?:\.\d+)?)px\)$/)?.[1]);
|
||||
expect(radius).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('fades along both axes over a soft ramp, not a hard edge', async () => {
|
||||
const [rule] = declarations(await emitted(), over);
|
||||
const mask = rule.find((d) => d.startsWith('mask-image:')) ?? '';
|
||||
expect(rule).toContain('mask-composite: intersect');
|
||||
['bottom', 'right'].forEach((axis) => {
|
||||
const ramp = mask.match(
|
||||
new RegExp(
|
||||
`linear-gradient\\(to ${axis}, transparent, #000 (\\d+)px, #000 calc\\(100% - (\\d+)px\\), transparent\\)`
|
||||
)
|
||||
);
|
||||
expect(ramp).not.toBeNull();
|
||||
// Each end ramps from clear to opaque over at least 8px.
|
||||
expect(Number(ramp?.[1])).toBeGreaterThanOrEqual(8);
|
||||
expect(Number(ramp?.[2])).toBeGreaterThanOrEqual(8);
|
||||
});
|
||||
});
|
||||
|
||||
it('has no border or radius, in the scene rule or in the base footer rule', async () => {
|
||||
const css = await emitted();
|
||||
const [rule] = declarations(css, over);
|
||||
const base = declarations(css, `.${AuthFooter}`);
|
||||
expect(base).toHaveLength(1);
|
||||
[rule, base[0]].forEach((r) => {
|
||||
expect(r.filter((d) => /^(border|outline)/.test(d))).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -90,10 +90,11 @@ globalStyle(`${scene} ${AuthCardContent}`, {
|
|||
gap: config.space.S500,
|
||||
});
|
||||
|
||||
// The footer line sits on the terrain, which is dark at its foot in every sky (luminance 0.02 to 0.04 at dusk), where
|
||||
// the sky's dark ink is unreadable. Over the scene it gets a blurred band tinted with the plate's fill and the plate's
|
||||
// The footer line sits on the terrain, which is dark at its foot in every sky (luminance 0.02 at dusk), where the
|
||||
// sky's dark ink is unreadable. Over the scene it gets a blurred band tinted with the plate's fill and the plate's
|
||||
// ink, faded out at its edges so it reads as haze and not as a bar: the scene shows through and round it. A browser
|
||||
// without color-mix gets the plate's opaque fill, as the chat bubbles do (message/layout/layout.css.ts). The tint (FOOTER_TINT) is checked against the ink in skyTheme.test.ts.
|
||||
// without color-mix gets the plate's opaque fill, as the chat bubbles do (message/layout/layout.css.ts). The tint
|
||||
// (FOOTER_TINT) is checked against the ink in skyTheme.test.ts, the emitted rule in footerBand.test.ts.
|
||||
const FADE_Y =
|
||||
'linear-gradient(to bottom, transparent, #000 16px, #000 calc(100% - 16px), transparent)';
|
||||
const FADE_X =
|
||||
|
|
|
|||
Loading…
Reference in a new issue