Brand tile colors as generated stylesheet (CSP blocks inline styles), footer credit link
This commit is contained in:
@@ -4,3 +4,4 @@ node_modules/
|
||||
pnpm-lock.yaml
|
||||
index.html
|
||||
src/icons.generated.ts
|
||||
src/styles/brands.generated.css
|
||||
|
||||
@@ -40,3 +40,33 @@ writeFileSync(new URL('../src/icons.generated.ts', import.meta.url), out)
|
||||
console.log(
|
||||
`icons: ${found.length} embedded, ${missing.length} missing${missing.length ? ` (${missing.join(', ')})` : ''}`,
|
||||
)
|
||||
|
||||
// Brand tile colors as a GENERATED STYLESHEET (not inline style attributes:
|
||||
// the production CSP is style-src 'self', which blocks style= attributes).
|
||||
// Tile ink is picked by WCAG contrast against the brand color — white ink
|
||||
// fails on several light brands.
|
||||
function luminance(hex) {
|
||||
const n = hex.replace('#', '')
|
||||
const [r, g, b] = [0, 2, 4].map((i) => {
|
||||
const c = parseInt(n.slice(i, i + 2), 16) / 255
|
||||
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4
|
||||
})
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||
}
|
||||
function inkFor(hex) {
|
||||
const L = luminance(hex)
|
||||
return 1.05 / (L + 0.05) >= (L + 0.05) / 0.05 ? '#faf7ee' : '#14161a'
|
||||
}
|
||||
|
||||
const tools = JSON.parse(readFileSync(new URL('../src/data/tools.json', import.meta.url), 'utf8'))
|
||||
const rules = tools
|
||||
.filter((t) => t.color)
|
||||
.map((t) => `#${t.id} .tool-logo {\n background: ${t.color};\n color: ${inkFor(t.color)};\n}`)
|
||||
const css = `/* AUTO-GENERATED by scripts/gen-icons.mjs — do not edit.
|
||||
Brand tile background + WCAG-picked ink per tool (from tools.json).
|
||||
A stylesheet, not style= attributes: the CSP (style-src 'self')
|
||||
blocks inline styles in production. */
|
||||
${rules.join('\n')}
|
||||
`
|
||||
writeFileSync(new URL('../src/styles/brands.generated.css', import.meta.url), css)
|
||||
console.log(`brands: ${rules.length} tile rules → src/styles/brands.generated.css`)
|
||||
|
||||
@@ -114,5 +114,8 @@ const localeRoot = `${localePath(locale)}/`.replace('//', '/')
|
||||
{t('footer.factsUpdated')}
|
||||
<a href={localeRoot} class="text-dim">exitchatcontrol.org</a>
|
||||
</small>
|
||||
<p class="mt-3 mb-0 text-center font-mono text-xs text-dim">
|
||||
by <Ext href="https://x.com/AureaLibe">Aurealibe</Ext>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -26,23 +26,9 @@ if (!tool) {
|
||||
throw new Error(`ToolCard: unknown tool id "${toolId}" (not in src/data/tools.json)`)
|
||||
}
|
||||
|
||||
// Tile ink picked by WCAG contrast against the brand color — white ink fails
|
||||
// on several light brands (the axe lesson from the reviewed PR).
|
||||
function luminance(hex: string): number {
|
||||
const n = hex.replace('#', '')
|
||||
const [r, g, b] = [0, 2, 4].map((i) => {
|
||||
const c = parseInt(n.slice(i, i + 2), 16) / 255
|
||||
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4
|
||||
})
|
||||
return 0.2126 * r! + 0.7152 * g! + 0.0722 * b!
|
||||
}
|
||||
const brand = tool.color ?? '#7d5a10'
|
||||
const ink = (() => {
|
||||
const L = luminance(brand)
|
||||
const white = 1.05 / (L + 0.05)
|
||||
const dark = (L + 0.05) / 0.05
|
||||
return white >= dark ? '#faf7ee' : '#14161a'
|
||||
})()
|
||||
// Tile colors live in src/styles/brands.generated.css (generated from
|
||||
// tools.json by scripts/gen-icons.mjs) — a stylesheet, because the
|
||||
// production CSP (style-src 'self') blocks style= attributes.
|
||||
const iconPath = tool.iconSlug ? BRAND_ICONS[tool.iconSlug] : undefined
|
||||
|
||||
const locale = (Astro.currentLocale ?? defaultLocale) as Locale
|
||||
@@ -72,7 +58,7 @@ const labels = LABELS[locale]
|
||||
|
||||
<article class="tool-card" id={tool.id} data-level={tool.level ?? undefined}>
|
||||
<header class="tool-head">
|
||||
<span class="tool-logo" aria-hidden="true" style={`--b:${brand};--ink-on-brand:${ink}`}>
|
||||
<span class="tool-logo" aria-hidden="true">
|
||||
{
|
||||
iconPath ? (
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
"www.wired.com",
|
||||
"www.wireguard.com",
|
||||
"www.yubico.com",
|
||||
"x.com",
|
||||
"yunohost.org"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
/* AUTO-GENERATED by scripts/gen-icons.mjs — do not edit.
|
||||
Brand tile background + WCAG-picked ink per tool (from tools.json).
|
||||
A stylesheet, not style= attributes: the CSP (style-src 'self')
|
||||
blocks inline styles in production. */
|
||||
#t-signal .tool-logo {
|
||||
background: #3a76f0;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-simplex .tool-logo {
|
||||
background: #1e63d0;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-session .tool-logo {
|
||||
background: #178a5a;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-briar .tool-logo {
|
||||
background: #c1272d;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-bitchat .tool-logo {
|
||||
background: #e8871e;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-molly .tool-logo {
|
||||
background: #6d4bb3;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-protonmail .tool-logo {
|
||||
background: #6d4aff;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-tuta .tool-logo {
|
||||
background: #b01e2e;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-mailbox .tool-logo {
|
||||
background: #2470d4;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-mullvadbrowser .tool-logo {
|
||||
background: #2b4b8f;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-firefox .tool-logo {
|
||||
background: #e66000;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-brave .tool-logo {
|
||||
background: #fb542b;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-ublock .tool-logo {
|
||||
background: #7a0d0d;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-search .tool-logo {
|
||||
background: #de5833;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-quad9 .tool-logo {
|
||||
background: #1a5fb4;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-mullvaddns .tool-logo {
|
||||
background: #2b4b8f;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-cloudflare .tool-logo {
|
||||
background: #f38020;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-pihole .tool-logo {
|
||||
background: #96060c;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-mullvad .tool-logo {
|
||||
background: #2b4b8f;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-protonvpn .tool-logo {
|
||||
background: #6d4aff;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-ivpn .tool-logo {
|
||||
background: #3b5bdb;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-protondrive .tool-logo {
|
||||
background: #6d4aff;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-cryptomator .tool-logo {
|
||||
background: #26a69a;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-nc-storage .tool-logo {
|
||||
background: #0082c9;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-bitwarden .tool-logo {
|
||||
background: #175ddc;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-keepassxc .tool-logo {
|
||||
background: #2c6b9e;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-protonpass .tool-logo {
|
||||
background: #6d4aff;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-yubikey .tool-logo {
|
||||
background: #84bd00;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-aegis .tool-logo {
|
||||
background: #2c5c9c;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-mastodon .tool-logo {
|
||||
background: #6364ff;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-nostr .tool-logo {
|
||||
background: #8e30eb;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-element .tool-logo {
|
||||
background: #0dbd8b;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-fediverse .tool-logo {
|
||||
background: #ea580c;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-bitcoin .tool-logo {
|
||||
background: #f7931a;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-monero .tool-logo {
|
||||
background: #ff6600;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-localai .tool-logo {
|
||||
background: #111111;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-cloudai .tool-logo {
|
||||
background: #de5833;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-maps .tool-logo {
|
||||
background: #0a7d33;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-notes .tool-logo {
|
||||
background: #2e7d6b;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-visio .tool-logo {
|
||||
background: #1d6fb8;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-photos .tool-logo {
|
||||
background: #7b3fbf;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-alias .tool-logo {
|
||||
background: #c0392b;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-backups .tool-logo {
|
||||
background: #5a6570;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-databrokers .tool-logo {
|
||||
background: #b8860b;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-yunohost .tool-logo {
|
||||
background: #e29100;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-nextcloud .tool-logo {
|
||||
background: #0082c9;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-synapse .tool-logo {
|
||||
background: #0dbd8b;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-tailscale .tool-logo {
|
||||
background: #242424;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-website .tool-logo {
|
||||
background: #7a4ddb;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-tor .tool-logo {
|
||||
background: #7d4698;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-grapheneos .tool-logo {
|
||||
background: #1f8a70;
|
||||
color: #14161a;
|
||||
}
|
||||
#t-linux .tool-logo {
|
||||
background: #2b2b2b;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-tails .tool-logo {
|
||||
background: #56347c;
|
||||
color: #faf7ee;
|
||||
}
|
||||
#t-qubes .tool-logo {
|
||||
background: #3874d8;
|
||||
color: #14161a;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
@import 'tailwindcss';
|
||||
@import './print.css';
|
||||
@import './brands.generated.css';
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
Exit Chat Control · design tokens ("stamped dossier" identity)
|
||||
|
||||
Reference in New Issue
Block a user