Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/shot_border.mjs

3.3 KiB, 1 run

created by r2519314175:147, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1// shot_border.mjs — what --border-strong actually looks like.
2//
3// The numbers say every control now clears 3:1 against the surface it sits on, and
4// that a focused field reads 4.19-6.75 against its own unfocused pixels. Neither
5// says whether the app still looks like one system: a boundary lifted to a floor is
6// a boundary that got darker on a light palette and lighter on a dark one, and
7// eleven of those could easily read as eleven different apps.
8//
9// Four palettes, one from each corner of the tone/ink grid, and for each: the chat
10// composer (the field a person looks at most), the settings dialog (the densest run
11// of fields in the app), and a field with the focus on it, shot at 3x so the 1px
12// boundary and the 2px ring are both legible.
13import { open } from './harness.mjs';
14import path from 'node:path';
15import fs from 'node:fs';
16
17const OUT = path.join(process.cwd(), 'dev', 'shots');
18fs.mkdirSync(OUT, { recursive: true });
19
20// light band / dark ink, mid band / light ink, dark band / light ink, and the
21// candy one, which is the palette a boundary is easiest to lose in.
22const PALETTES = ['light', 'dusk', 'dark', 'lollypop'];
23
24const s = await open({ name: 'border', connect: false });
25const p = s.page;
26await p.setViewportSize({ width: 1400, height: 900 });
27await p.waitForTimeout(1500);
28
29for (const pal of PALETTES) {
30 await p.evaluate((name) => window.DaimondTheme.set(name), pal);
31 await p.waitForTimeout(400);
32
33 // The settings dialog: the densest run of fields in the app, and the one place
34 // where a text input, a select and an outlined button sit within 20px of each
35 // other, so an inconsistency between them cannot hide.
36 // The Admin drawer, which is where this app keeps the densest run of controls
37 // that are not behind an account: rows, an outlined button, the model chip.
38 await p.evaluate(() => {
39 const u = document.querySelector('#user-row');
40 if (u) u.click();
41 });
42 await p.waitForTimeout(600);
43 await p.screenshot({ path: path.join(OUT, `border_${pal}_app.png`) });
44 await p.keyboard.press('Escape');
45 await p.waitForTimeout(400);
46
47 // A field with the focus on it. `:focus-visible` only arms under a real Tab --
48 // Chromium reports `outline: none` for a scripted focus() -- so the focus is
49 // moved with the keyboard from wherever the page starts.
50 await p.keyboard.press('Tab');
51 await p.waitForTimeout(300);
52 const focused = await p.evaluate(() => {
53 const el = document.activeElement;
54 if (!el || el === document.body) return null;
55 const r = el.getBoundingClientRect();
56 const cs = getComputedStyle(el);
57 return {
58 sel: el.tagName.toLowerCase() + (el.id ? '#' + el.id : '')
59 + (el.className && typeof el.className === 'string' ? '.' + el.className.trim().split(/\s+/)[0] : ''),
60 outline: cs.outlineWidth + ' ' + cs.outlineStyle + ' ' + cs.outlineColor,
61 border: cs.borderTopColor,
62 box: { x: r.x, y: r.y, w: r.width, h: r.height },
63 };
64 });
65 if (focused && focused.box.w) {
66 console.log(`${pal.padEnd(9)} focus on ${focused.sel} — outline ${focused.outline}, border ${focused.border}`);
67 await p.screenshot({
68 path: path.join(OUT, `border_${pal}_focus.png`),
69 clip: {
70 x: Math.max(0, focused.box.x - 12), y: Math.max(0, focused.box.y - 12),
71 width: focused.box.w + 24, height: focused.box.h + 24,
72 },
73 scale: 'device',
74 });
75 } else {
76 console.log(`${pal.padEnd(9)} focus: nothing took it`);
77 }
78}
79
80await s.close();