Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/shot_badge.mjs

5.0 KiB, 1 run

created by r2519314175:143, 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_badge.mjs — the maker's badge, which is now in the About dialog.
2//
3// It used to sit in the top bar at twice its original height, and this script
4// photographed the bar and measured whether anything above it sliced the plate.
5// The badge has moved: the bar holds an About button in that slot and both of
6// the badge's claims are in the dialog it opens, where each is a link with room
7// to be read.
8//
9// The same two questions still matter, in the new place. Does the foot of the
10// dialog read as one quiet signature row rather than as another control? And is
11// anything clipping the plate -- this app's recurring defect is ink drawn
12// outside a border box and sliced by an ancestor's overflow, and the card the
13// badge now sits in is `overflow-y: auto`, which is exactly such an ancestor.
14// So the measurement is taken as well as the picture.
15import { open } from './harness.mjs';
16import path from 'node:path';
17import fs from 'node:fs';
18
19const OUT = path.join(process.cwd(), 'dev', 'shots');
20fs.mkdirSync(OUT, { recursive: true });
21
22const s = await open({ name: 'badge', connect: false });
23const p = s.page;
24await p.waitForTimeout(1500);
25
26/// Open About, and wait for the artwork rather than for the card: the splash is
27/// the tallest thing in it and the foot is not where it will finally be until
28/// the picture has laid out.
29async function openAbout() {
30 for (let i = 0; i < 3; i++) { await p.keyboard.press('Escape'); await p.waitForTimeout(90); }
31 await p.evaluate(() => { const b = document.getElementById('about-btn'); if (b) b.click(); });
32 await p.waitForSelector('.about-card .made-by img', { timeout: 8000 });
33 await p.waitForFunction(() => {
34 const i = document.querySelector('.about-card .about-splash');
35 return i && i.complete && i.naturalWidth > 0;
36 }, null, { timeout: 8000 });
37 await p.waitForTimeout(300);
38}
39
40// The card at four widths: two desktops, and the two either side of the phone
41// breakpoint. The badge is drawn at every one of them now -- on a phone it used
42// to be dropped from the bar entirely, which is the loss this move undid.
43for (const w of [1400, 900, 780, 390]) {
44 await p.setViewportSize({ width: w, height: 900 });
45 await p.waitForTimeout(400);
46 await openAbout();
47 const card = await p.$('.about-card');
48 if (card) await card.screenshot({ path: path.join(OUT, `badge_about_${w}.png`) });
49 const fit = await p.evaluate(() => {
50 const card = document.querySelector('.about-card');
51 const row = card.querySelector('.about-maker');
52 const mb = card.querySelector('.made-by');
53 const r = mb.getBoundingClientRect(), rr = row.getBoundingClientRect();
54 return {
55 badgeDrawn: mb.getClientRects().length > 0,
56 badge: { w: +r.width.toFixed(1), h: +r.height.toFixed(1) },
57 // Is the signature row still a row, or has it wrapped?
58 rowH: +rr.height.toFixed(1),
59 // Does the card have to scroll to show it?
60 cardScroll: card.scrollHeight - card.clientHeight,
61 // The two hit areas, which are the point of the artwork being here.
62 hits: [...card.querySelectorAll('.mb-hit')].map(a => {
63 const b = a.getBoundingClientRect();
64 return { name: a.getAttribute('aria-label'), w: Math.round(b.width), x: Math.round(b.x) };
65 }),
66 };
67 });
68 console.log(`${String(w).padStart(5)}px ${JSON.stringify(fit)}`);
69}
70
71await p.setViewportSize({ width: 1400, height: 900 });
72await p.waitForTimeout(400);
73await openAbout();
74
75// An 8x crop of the badge itself, for the eye: the plate's border is 1px and a
76// clipped edge at 1x is a rumour.
77const badge = await p.$('.about-card .made-by');
78if (badge) {
79 const b = await badge.boundingBox();
80 await p.screenshot({
81 path: path.join(OUT, 'badge_8x.png'),
82 clip: { x: b.x - 8, y: Math.max(0, b.y - 8), width: b.width + 16, height: b.height + 16 },
83 scale: 'device',
84 });
85}
86
87// Whether anything above it cuts the badge off, measured the way the sweep does:
88// the painted rectangle against every ancestor that could clip it.
89const m = await p.evaluate(() => {
90 const img = document.querySelector('.about-card .made-by img');
91 const r = img.getBoundingClientRect();
92 const out = { badge: { w: r.width, h: r.height }, clippers: [] };
93 const word = document.querySelector('.about-card .about-word:not([aria-hidden])');
94 out.wordmark = word ? word.getBoundingClientRect().height : null;
95 out.card = document.querySelector('.about-card').getBoundingClientRect().height;
96 for (let el = img.parentElement; el && el !== document.documentElement; el = el.parentElement) {
97 const cs = getComputedStyle(el);
98 if (cs.overflow === 'visible' && cs.overflowX === 'visible' && cs.overflowY === 'visible') continue;
99 const a = el.getBoundingClientRect();
100 out.clippers.push({
101 sel: el.tagName.toLowerCase() + (el.className ? '.' + String(el.className).split(' ')[0] : ''),
102 overflow: cs.overflow + '/' + cs.overflowX + '/' + cs.overflowY,
103 headroom: +(r.top - a.top).toFixed(2),
104 footroom: +(a.bottom - r.bottom).toFixed(2),
105 leftroom: +(r.left - a.left).toFixed(2),
106 rightroom: +(a.right - r.right).toFixed(2),
107 });
108 }
109 return out;
110});
111console.log(JSON.stringify(m, null, 1));
112
113await s.close();