mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-28 07:32:52 +00:00
IAM's sign-in mail loads its mark from the org's site at /email/mark.png, so the export serves one: @hanzo/brand's five paths, white, on an opaque black tile, 132px for a 44px place. An email client shows no SVG, and an opaque tile reads on a light page, a dark one, and in a client that inverts the page but never an image. It is derived by scripts/email-mark.mjs, never drawn. e2e/gates/link.spec.ts drives the link the mail carries, the way a person meets it: opened beside the waiting tab, which signs in and the link's tab follows; opened in the browser that asked with no tab waiting, which signs in where it opened; opened in another browser, which shows the code and spends nothing, and the code typed where the sign-in began, in a field the browser fills (one-time-code, numeric). The mark is held served, byte for byte.
47 lines
2.0 KiB
JavaScript
47 lines
2.0 KiB
JavaScript
/**
|
|
* The mark Hanzo's emails show, at https://hanzo.ai/email/mark.png.
|
|
*
|
|
* IAM's sign-in mail (hanzoai/iam `internal/otp/signin.go`, `MarkPath`) loads
|
|
* the mark from the org's site at /email/mark.png. An email client shows no SVG
|
|
* (Gmail drops it), so this is a PNG: @hanzo/brand's five paths, white, on an
|
|
* opaque black tile with its corners cut, 132px square for a 44px place. The
|
|
* tile is opaque so the mark reads on a light page, on a dark one, and in a
|
|
* client that inverts the page's colours but never an image's.
|
|
*
|
|
* Derived, never drawn: run `node scripts/email-mark.mjs` when @hanzo/brand's
|
|
* mark changes, and commit the PNG it writes.
|
|
*/
|
|
import { readFileSync } from 'node:fs'
|
|
import { createRequire } from 'node:module'
|
|
import { dirname, join } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { chromium } from '@playwright/test'
|
|
|
|
const SIZE = 132
|
|
const RADIUS = 30
|
|
const GLYPH = 72
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
const OUT = join(here, '..', 'public', 'email', 'mark.png')
|
|
const FROM = createRequire(import.meta.url).resolve('@hanzo/brand/assets/logo/favicon.svg')
|
|
|
|
const svg = readFileSync(FROM, 'utf8')
|
|
const paths = svg.match(/<path [^>]*\/>/g) ?? []
|
|
if (paths.length !== 5) throw new Error(`the mark should be five paths, found ${paths.length}`)
|
|
if (/stroke/.test(svg)) throw new Error('the mark carries a stroke, which bridges its gaps')
|
|
|
|
const page = `<!doctype html><html><body style="margin:0;background:transparent">
|
|
<div id="mark" style="width:${SIZE}px;height:${SIZE}px;border-radius:${RADIUS}px;background:#000;display:grid;place-items:center">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 67 67" width="${GLYPH}" height="${GLYPH}" fill="#fff">${paths.join('')}</svg>
|
|
</div></body></html>`
|
|
|
|
const browser = await chromium.launch()
|
|
try {
|
|
const tab = await browser.newPage({ deviceScaleFactor: 1 })
|
|
await tab.setContent(page)
|
|
await tab.locator('#mark').screenshot({ path: OUT, omitBackground: true })
|
|
} finally {
|
|
await browser.close()
|
|
}
|
|
console.log(`wrote ${OUT}`)
|