Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"site:build": "node site/build-site.mjs",
"site:dev": "npm run site:build && node scripts/site-local-server.mjs",
"site:serve": "node scripts/site-local-server.mjs",
"test:site": "node tests/system-map.test.mjs && node tests/site.test.mjs && node tests/site-brand-assets.test.mjs && node tests/site-local-server.test.mjs && node tests/site-header-browser.test.mjs && node tests/site-first-use-browser.test.mjs && node tests/patterns-browser.test.mjs",
"test:site": "node tests/system-map.test.mjs && node tests/site.test.mjs && node tests/site-brand-assets.test.mjs && node tests/site-favicon-browser.test.mjs && node tests/site-local-server.test.mjs && node tests/site-header-browser.test.mjs && node tests/site-first-use-browser.test.mjs && node tests/patterns-browser.test.mjs",
"test:components:inventory": "node tests/components/component-reference-inventory.test.mjs",
"test:components:contracts": "node tests/components/component-contract-registry.test.mjs",
"test:components:unit": "node tests/components/component-state-model.test.mjs && node tests/components/component-specimen-runtime.test.mjs && node tests/components/native-components.test.mjs && node tests/components/interactive-components.test.mjs",
Expand Down
17 changes: 10 additions & 7 deletions scripts/build-site-brand-assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ def mark(parts, foreground, height, identifier='', monochrome=False):
return f'<g{identifier_attribute} transform="translate({x:g} {y:g}) scale({scale:g})">{polygons}</g>'


def icon_svg(theme='light', height=None, radius=None, optical=False, responsive=False, monochrome=False):
def icon_svg(theme='light', height=None, radius=None, optical=False, responsive=False, monochrome=False, transparent=False):
height = height if height is not None else ICONS['visible_mark_height']
radius = radius if radius is not None else ICONS['browser_radius']
background, foreground = (CONFIG['ivory'], CONFIG['ink']) if theme == 'light' else (CONFIG['ink'], CONFIG['ivory'])
body = '' if monochrome else f'<rect width="64" height="64" rx="{radius:g}" fill="{background}"/>'
body = '' if monochrome or transparent else f'<rect width="64" height="64" rx="{radius:g}" fill="{background}"/>'
if responsive:
body += f'<style>#optical{{display:none}}@media(max-width:{ICONS["optical_max_size"]}px){{#canonical{{display:none}}#optical{{display:inline}}}}</style>'
body += mark(GEOMETRY['canonical'], foreground, height, 'canonical') + mark(GEOMETRY['optical_16'], foreground, height, 'optical')
Expand Down Expand Up @@ -92,14 +92,17 @@ def icon_svg(theme='light', height=None, radius=None, optical=False, responsive=
with tempfile.TemporaryDirectory(prefix='judgmentkit-web-icons-') as temporary:
temporary = Path(temporary)
for theme in ['light', 'dark']:
write(f'{ICONS["output_directory"]}/favicon-{theme}.svg', icon_svg(theme, responsive=True))
write(f'{ICONS["output_directory"]}/favicon-{theme}.svg', icon_svg(theme, responsive=True, transparent=ICONS['browser_background'] == 'transparent'))
for size in [16, 32, 48]:
source = temporary / f'{theme}-{size}.svg'
source.write_text(icon_svg(theme, optical=size == 16))
source.write_text(icon_svg(theme, optical=size == 16, transparent=ICONS['browser_background'] == 'transparent'))
render(source, icons_dir / f'favicon-{theme}-{size}.png', (size, size))
frames = [Image.open(icons_dir / f'favicon-{ICONS["fallback_theme"]}-{size}.png').convert('RGBA') for size in [16, 32, 48]]
frames = [Image.open(icons_dir / f'favicon-{theme}-{size}.png').convert('RGBA') for size in [16, 32, 48]]
ico = icons_dir / f'favicon-{theme}.ico'
frames[-1].save(ico, format='ICO', sizes=[(16, 16), (32, 32), (48, 48)], append_images=frames[:-1])
OUTPUTS.append(ico)
ico = icons_dir / 'favicon.ico'
frames[-1].save(ico, format='ICO', sizes=[(16, 16), (32, 32), (48, 48)], append_images=frames[:-1])
ico.write_bytes((icons_dir / f'favicon-{ICONS["fallback_theme"]}.ico').read_bytes())
OUTPUTS.append(ico)
write(f'{ICONS["output_directory"]}/safari-pinned-tab.svg', icon_svg(monochrome=True))
for name, height in [('app-icon', ICONS['visible_mark_height']), ('maskable', ICONS['maskable_visible_mark_height'])]:
Expand All @@ -115,7 +118,7 @@ def icon_svg(theme='light', height=None, radius=None, optical=False, responsive=
'generator': 'scripts/build-site-brand-assets.py',
'canonical_lockup': SOCIAL['lockup_source'],
'canonical_lockup_sha256': digest(lockup_path),
'sources': {p: digest(ROOT / p) for p in [ICONS['geometry_source'], ICONS['master_source'], 'site/brand-assets.json']},
'sources': {p: digest(ROOT / p) for p in [ICONS['geometry_source'], ICONS['master_source'], 'site/brand-assets.json', 'site/assets/brand/favicon-theme.js']},
'canvas': [1200, 630],
'social_placement': {'scale': scale, 'translation': [x, y], 'source_viewBox': [0, 0, social_width, social_height]},
'web_icons': ICONS,
Expand Down
18 changes: 16 additions & 2 deletions scripts/verify-public-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1180,7 +1180,7 @@ async function verifyPublicRoutes(baseUrl, options = {}) {
"See a screen repaired",
"Explore examples",
'<link rel="canonical" href="https://judgmentkit.ai/">',
`<link rel="icon" href="${SITE_BRAND_ICON_BUNDLE.routes["/assets/brand/favicon-light.svg"]}"`,
`<script src="${SITE_BRAND_ICON_BUNDLE.routes["/assets/brand/favicon-theme.js"]}" data-brand-favicon-controller`,
'<meta property="og:site_name" content="JudgmentKit">',
],
"homepage",
Expand Down Expand Up @@ -1227,6 +1227,18 @@ async function verifyPublicRoutes(baseUrl, options = {}) {
const docs = await fetchText(baseUrl, "/docs/");
assert.equal(getAnalyticsScriptSrc(docs.text, "docs"), analyticsScriptSrc);
assertReleaseFooter(docs.text, expectedPackageVersion, "docs");
for (const [label, html] of [["home", home.text], ["value", value.text], ["docs", docs.text]]) {
const head = html.match(/<head\b[^>]*>([\s\S]*?)<\/head>/i)?.[1];
assert.ok(head, `${label}: document head`);
const controllers = [...head.matchAll(/<script\b[^>]*\bdata-brand-favicon-controller[^>]*>/gi)];
assert.equal(controllers.length, 1, `${label}: one synchronous favicon controller`);
assert.equal(/\s(?:async|defer|type)(?:\s|=|>)/i.test(controllers[0][0]), false, `${label}: classic parser-blocking script`);
const active = head.replace(/<!--[\s\S]*?-->|<noscript\b[^>]*>[\s\S]*?<\/noscript>|<(script|style)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, "");
assert.equal(/<link\b[^>]*\brel=["'](?:shortcut )?icon["']/i.test(active), false, `${label}: no competing static favicon`);
for (const fragment of [`src="${SITE_BRAND_ICON_BUNDLE.routes["/assets/brand/favicon-theme.js"]}"`, `data-light-icon="${SITE_BRAND_ICON_BUNDLE.routes["/assets/brand/favicon-light.ico"]}"`, `data-dark-icon="${SITE_BRAND_ICON_BUNDLE.routes["/assets/brand/favicon-dark.ico"]}"`]) assert.ok(controllers[0][0].includes(fragment), `${label}: current controller and themed ICO URLs`);
assert.ok(head.includes(`<noscript data-brand-favicon-fallback><link rel="icon" href="${SITE_BRAND_ICON_BUNDLE.routes["/assets/brand/favicon-light.ico"]}" type="image/x-icon" sizes="16x16 32x32 48x48"></noscript>`), `${label}: light no-script fallback`);
}

assertIncludes(
docs.text,
[
Expand Down Expand Up @@ -1342,7 +1354,9 @@ async function verifyPublicRoutes(baseUrl, options = {}) {
}
assert.deepEqual(webManifest.icons.map(icon => [icon.sizes, icon.purpose]), [["192x192", "any"], ["512x512", "any"], ["192x192", "maskable"], ["512x512", "maskable"]]);
for (const fragment of [
`rel="icon" href="${SITE_BRAND_ICON_BUNDLE.routes["/favicon.ico"]}"`,
`data-light-icon="${SITE_BRAND_ICON_BUNDLE.routes["/assets/brand/favicon-light.ico"]}"`,
`data-dark-icon="${SITE_BRAND_ICON_BUNDLE.routes["/assets/brand/favicon-dark.ico"]}"`,
`<noscript data-brand-favicon-fallback><link rel="icon" href="${SITE_BRAND_ICON_BUNDLE.routes["/assets/brand/favicon-light.ico"]}"`,
`rel="apple-touch-icon" href="${SITE_BRAND_ICON_BUNDLE.routes["/apple-touch-icon.png"]}"`,
`rel="mask-icon" href="${SITE_BRAND_ICON_BUNDLE.routes["/safari-pinned-tab.svg"]}"`,
`rel="manifest" href="${SITE_BRAND_ICON_BUNDLE.manifestRoute}"`,
Expand Down
28 changes: 16 additions & 12 deletions site/assets/brand-generation.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"sources": {
"brand/slab-j/geometry.json": "26e03b985e09c029e627fdf7a4bb347906dea0fc8cea8ba437e7333a782003d0",
"brand/slab-j/judgmentkit.svg": "bd33c93b9493a8e2e19a3f78d8ff30c34a5a53bfb48b524a5e9a9acd1d9443dd",
"site/brand-assets.json": "c5fd87e74129effce4a330d035d88b808f2335f2839fcf6d41785eea6ffaa336"
"site/brand-assets.json": "a67e11455014cc22a452799900aafe52a5ba015e4b8b5d9c63e558e9063b0f55",
"site/assets/brand/favicon-theme.js": "438e2c44cada8c2a0ae77ff4000d87f5d943b00afda39a786850f5fe3871140e"
},
"canvas": [
1200,
Expand All @@ -28,7 +29,7 @@
"geometry_source": "brand/slab-j/geometry.json",
"master_source": "brand/slab-j/judgmentkit.svg",
"canvas": 64,
"browser_radius": 8,
"browser_radius": 0,
"visible_mark_height": 48,
"center": [
32,
Expand All @@ -39,7 +40,8 @@
"maskable_safe_radius": 25.6,
"fallback_theme": "dark",
"output_directory": "site/assets/brand",
"raster_density": 384
"raster_density": 384,
"browser_background": "transparent"
},
"maskable_placement": {
"visible_mark_height": 36,
Expand All @@ -54,15 +56,17 @@
"outputs": {
"site/assets/judgmentkit-social-thumbnail.svg": "767c41ede48bb426ca3c8df288618c1fb0a79ddc40415130fac603f5ad241dbb",
"site/assets/judgmentkit-social-thumbnail.png": "c67b13e33c69cbc6782363b7b349176b5680e3aa39ec6724c36b6fb5e9783f08",
"site/assets/brand/favicon-light.svg": "1c4c2a6022d2e50c341e9522620470f79174898632eda9fe01c32f3f352325ae",
"site/assets/brand/favicon-light-16.png": "359b1e88b0201d55d64d0360b730f5dc6c0317960173ac12ba4f75c7b8402e84",
"site/assets/brand/favicon-light-32.png": "a552d29c0c088f003ed9fe542a4d6e7fa62d3c02252e3c87fbe0c0afd6896710",
"site/assets/brand/favicon-light-48.png": "0c1c18c67187439a32845d5e3ff80a2aed85a134077961c9ea6ab8a85ba65baf",
"site/assets/brand/favicon-dark.svg": "53f9a529ec4752bbd30dfccc866b664d81ce659cc53487501362e32bc5f17808",
"site/assets/brand/favicon-dark-16.png": "e917ba316d3a368ea4dc6d13998acb25411f61b8b4a45562d1621935afaf1ada",
"site/assets/brand/favicon-dark-32.png": "7baa4434d5370411737a564a7c759679c501bd6548e0bddca445f5685b9e462d",
"site/assets/brand/favicon-dark-48.png": "79217a48024d4e5da5a5a284fa397c6fe8fb4a40e794a4120c5fadd119c440b4",
"site/assets/brand/favicon.ico": "26ef197cc6939ba27079c5e7abde5221f59b5a81cf9f2a4fce270a70a52f9169",
"site/assets/brand/favicon-light.svg": "df03d49d63c285d414ec74d5c6e230365a8466c6580c4178445db1401c392c2b",
"site/assets/brand/favicon-light-16.png": "479534679f88fa1d40bd223e2320f2a952a621340ed6ad46269b2f38cd81e6ea",
"site/assets/brand/favicon-light-32.png": "7a7ae3a8e6f45c07b85b3c56275ec7e47bef3b340468143778783617aa66922a",
"site/assets/brand/favicon-light-48.png": "366bae7d8abaed9d3b2da2e7842ba52a16c1c050d79a189a8fa0cafcef15e6a6",
"site/assets/brand/favicon-light.ico": "4612b20cadd4c0f3c2a2320ece628544dc1b38900a743f5345b690a9ee6af27f",
"site/assets/brand/favicon-dark.svg": "90267474375f5953b5cad24698e06f8e1a4a1e68b7993260e70ae32639cb4852",
"site/assets/brand/favicon-dark-16.png": "f895ed537c628d25feeb14daa962510611588fae0a0e6552010fd32dd1150634",
"site/assets/brand/favicon-dark-32.png": "309bb4263e5bb781d06020a209dbeb6a36bec6a79ca2d206fa42d9c39490f51c",
"site/assets/brand/favicon-dark-48.png": "1a136fe5053cbaeb03fa7572a9365e1631ebe1180eb4fb081fe26fe08cdf0ccd",
"site/assets/brand/favicon-dark.ico": "d6e5567d5beee7e7e1b789fd560b9c3725eaa18fafd47c53d20f199da5776824",
"site/assets/brand/favicon.ico": "d6e5567d5beee7e7e1b789fd560b9c3725eaa18fafd47c53d20f199da5776824",
"site/assets/brand/safari-pinned-tab.svg": "87e3cec474f28a2bc320d72c7baf2f577c60d7e5e2becbcb39730d20bf56fa82",
"site/assets/brand/app-icon.svg": "be2f013ffc8129208a1ce3e98b843662ea931c9421bb0ac99d7e9c94333689ba",
"site/assets/brand/apple-touch-icon.png": "6a0750bb93a2e2d9a6d69d19193dfaa8de42e99916bba77a697c821c5825b628",
Expand Down
Binary file modified site/assets/brand/favicon-dark-16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/assets/brand/favicon-dark-32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/assets/brand/favicon-dark-48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added site/assets/brand/favicon-dark.ico
Binary file not shown.
2 changes: 1 addition & 1 deletion site/assets/brand/favicon-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/assets/brand/favicon-light-16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/assets/brand/favicon-light-32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified site/assets/brand/favicon-light-48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added site/assets/brand/favicon-light.ico
Binary file not shown.
2 changes: 1 addition & 1 deletion site/assets/brand/favicon-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions site/assets/brand/favicon-theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
(() => {
const script = document.currentScript;
const appearance = window.matchMedia("(prefers-color-scheme: dark)");
const icon = document.createElement("link");
icon.rel = "icon";
icon.type = "image/x-icon";
icon.sizes = "16x16 32x32 48x48";
icon.dataset.brandFavicon = "";
const update = () => {
icon.href = appearance.matches ? script.dataset.darkIcon : script.dataset.lightIcon;
};
update();
document.head.append(icon);
appearance.addEventListener("change", update);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fall back to the legacy MediaQueryList listener

On older Safari/WebKit versions that support prefers-color-scheme but expose only MediaQueryList.addListener, this unconditional call throws after installing the initial icon, so later appearance changes never update it. The repository already handles this compatibility case in site/build-site.mjs:6305-6308; use the same addEventListener/addListener fallback here and cover the legacy listener shape in the controller test.

Useful? React with 👍 / 👎.

})();
Binary file modified site/assets/brand/favicon.ico
Binary file not shown.
76 changes: 74 additions & 2 deletions site/brand-assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,21 @@
{
"source": "site/assets/judgmentkit-social-thumbnail.svg",
"route": "/assets/judgmentkit-social-thumbnail.svg"
},
{
"source": "site/assets/brand/favicon-light.ico",
"route": "/assets/brand/favicon-light.ico",
"content_versioned": true
},
{
"source": "site/assets/brand/favicon-dark.ico",
"route": "/assets/brand/favicon-dark.ico",
"content_versioned": true
},
{
"source": "site/assets/brand/favicon-theme.js",
"route": "/assets/brand/favicon-theme.js",
"content_versioned": true
}
],
"head_exceptions": [
Expand Down Expand Up @@ -141,7 +156,7 @@
"geometry_source": "brand/slab-j/geometry.json",
"master_source": "brand/slab-j/judgmentkit.svg",
"canvas": 64,
"browser_radius": 8,
"browser_radius": 0,
"visible_mark_height": 48,
"center": [
32,
Expand All @@ -152,7 +167,8 @@
"maskable_safe_radius": 25.6,
"fallback_theme": "dark",
"output_directory": "site/assets/brand",
"raster_density": 384
"raster_density": 384,
"browser_background": "transparent"
},
"legacy_icon_aliases": [
{
Expand Down Expand Up @@ -254,6 +270,62 @@
{
"route": "/assets/brand/site-86b6a01d9795231a.webmanifest",
"target": "/site.webmanifest"
},
{
"route": "/assets/brand/favicon-light-cfee0c66322f931c.svg",
"target": "/assets/brand/favicon-light.svg"
},
{
"route": "/assets/brand/favicon-light-16-cfee0c66322f931c.png",
"target": "/assets/brand/favicon-light-16.png"
},
{
"route": "/assets/brand/favicon-light-32-cfee0c66322f931c.png",
"target": "/assets/brand/favicon-light-32.png"
},
{
"route": "/assets/brand/favicon-dark-cfee0c66322f931c.svg",
"target": "/assets/brand/favicon-dark.svg"
},
{
"route": "/assets/brand/favicon-dark-16-cfee0c66322f931c.png",
"target": "/assets/brand/favicon-dark-16.png"
},
{
"route": "/assets/brand/favicon-dark-32-cfee0c66322f931c.png",
"target": "/assets/brand/favicon-dark-32.png"
},
{
"route": "/assets/brand/favicon-cfee0c66322f931c.ico",
"target": "/favicon.ico"
},
{
"route": "/assets/brand/safari-pinned-tab-cfee0c66322f931c.svg",
"target": "/safari-pinned-tab.svg"
},
{
"route": "/assets/brand/apple-touch-icon-cfee0c66322f931c.png",
"target": "/apple-touch-icon.png"
},
{
"route": "/assets/brand/app-icon-192-cfee0c66322f931c.png",
"target": "/assets/brand/app-icon-192.png"
},
{
"route": "/assets/brand/app-icon-512-cfee0c66322f931c.png",
"target": "/assets/brand/app-icon-512.png"
},
{
"route": "/assets/brand/maskable-192-cfee0c66322f931c.png",
"target": "/assets/brand/maskable-192.png"
},
{
"route": "/assets/brand/maskable-512-cfee0c66322f931c.png",
"target": "/assets/brand/maskable-512.png"
},
{
"route": "/assets/brand/site-cfee0c66322f931c.webmanifest",
"target": "/site.webmanifest"
}
]
}
Loading
Loading