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. |
| 15 | import { open } from './harness.mjs'; |
| 16 | import path from 'node:path'; |
| 17 | import fs from 'node:fs'; |
| 18 | |
| 19 | const OUT = path.join(process.cwd(), 'dev', 'shots'); |
| 20 | fs.mkdirSync(OUT, { recursive: true }); |
| 21 | |
| 22 | const s = await open({ name: 'badge', connect: false }); |
| 23 | const p = s.page; |
| 24 | await 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. |
| 29 | async 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. |
| 43 | for (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 | |
| 71 | await p.setViewportSize({ width: 1400, height: 900 }); |
| 72 | await p.waitForTimeout(400); |
| 73 | await 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. |
| 77 | const badge = await p.$('.about-card .made-by'); |
| 78 | if (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. |
| 89 | const 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 | }); |
| 111 | console.log(JSON.stringify(m, null, 1)); |
| 112 | |
| 113 | await s.close(); |