base wears the mark of the host that served it, not a generic glyph
base.hanzo.ai showed a rounded-square "B" — a placeholder, not Hanzo's logo — in the tab and in the app, which renders it from icon.ts in __root and login. The real marks already exist and are already served per brand at /brands/<brand>/logo.svg. They are carried here rather than linked cross-origin, and they draw in currentColor, so they take the surface's own colour instead of shipping a second palette. ONE map from host to brand, in index.html, resolved before the bundle loads and left on window for the app to read. The alternative was that map plus a copy in the bundle, and two answers to "which brand is this host" is exactly how a page ends up titled Hanzo over somebody else's logo. Title, favicon and in-app mark now come from the same answer and cannot disagree. Checked from the built output across every host the fleet serves: base.hanzo.ai / superbase / recipes.base Hanzo Base + hanzo mark lux.id, base.lux.network Lux Base + lux mark zoolabs.id, base.zoo.ngo Zoo Base + zoo mark localhost Base + generic glyph An unknown host keeps the unbranded glyph, and the patterns anchor to the registrable domain: evil-hanzo.ai.attacker.com and zoolabs.id.attacker.com are handed no mark of ours at all. Co-authored-by: Hanzo Dev <[email protected]>
@@ -0,0 +1,18 @@
|
||||
<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.bg { fill: #000; }
|
||||
.mark { fill: #fff; }
|
||||
@media (prefers-color-scheme: light) {
|
||||
.bg { fill: #fff; }
|
||||
.mark { fill: #000; }
|
||||
}
|
||||
</style>
|
||||
<rect class="bg" width="64" height="64" rx="8"/>
|
||||
<g transform="translate(8, 8) scale(0.716)">
|
||||
<path class="mark" d="M22.21 67V44.6369H0V67H22.21Z"/>
|
||||
<path class="mark" d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z"/>
|
||||
<path class="mark" d="M22.21 0H0V22.3184H22.21V0Z"/>
|
||||
<path class="mark" d="M66.7198 0H44.5098V22.3184H66.7198V0Z"/>
|
||||
<path class="mark" d="M66.7198 67V44.6369H44.5098V67H66.7198Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 696 B |
@@ -0,0 +1,9 @@
|
||||
<svg viewBox="0 0 67 67" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="currentColor"/>
|
||||
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="currentColor" opacity="0.8"/>
|
||||
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="currentColor"/>
|
||||
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="currentColor"/>
|
||||
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="currentColor"/>
|
||||
<path d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="currentColor" opacity="0.8"/>
|
||||
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 638 B |
@@ -0,0 +1,12 @@
|
||||
<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.bg { fill: #000; }
|
||||
.mark { fill: #fff; }
|
||||
@media (prefers-color-scheme: light) {
|
||||
.bg { fill: #fff; }
|
||||
.mark { fill: #000; }
|
||||
}
|
||||
</style>
|
||||
<rect class="bg" width="64" height="64" rx="8"/>
|
||||
<path class="mark" d="M32 52 L12 18 L52 18 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 343 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M50 85 L15 25 L85 25 Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 129 B |
@@ -0,0 +1,12 @@
|
||||
<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.bg { fill: #000; }
|
||||
@media (prefers-color-scheme: light) {
|
||||
.bg { fill: #fff; }
|
||||
}
|
||||
</style>
|
||||
<rect class="bg" width="64" height="64" rx="8"/>
|
||||
<circle cx="32" cy="22" r="12" fill="#00A652"/>
|
||||
<circle cx="21" cy="40" r="12" fill="#ED1C24"/>
|
||||
<circle cx="43" cy="40" r="12" fill="#2E3192"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 389 B |
@@ -0,0 +1,13 @@
|
||||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<clipPath id="outerCircleMono">
|
||||
<circle cx="508" cy="510" r="283"></circle>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#outerCircleMono)">
|
||||
<circle cx="513" cy="369" r="234" fill="none" stroke="currentColor" stroke-width="33"></circle>
|
||||
<circle cx="365" cy="595" r="234" fill="none" stroke="currentColor" stroke-width="33"></circle>
|
||||
<circle cx="643" cy="595" r="234" fill="none" stroke="currentColor" stroke-width="33"></circle>
|
||||
<circle cx="508" cy="510" r="265" fill="none" stroke="currentColor" stroke-width="36"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 691 B |
@@ -2,26 +2,39 @@
|
||||
<html lang="en" class="dark" style="background:#000;color-scheme:dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link id="favicon" rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<title>Base</title>
|
||||
<!-- The page carries the brand of the host it was served from. Base is shared
|
||||
infrastructure: the same binary answers on hanzo.ai, lux.network and
|
||||
zoo.ngo, so a name compiled into the page is right on one of them and
|
||||
wrong on the rest. Inline and before the bundle, because the tab is
|
||||
titled while React is still downloading. -->
|
||||
<!-- The page carries the mark and the name of the host it was served from.
|
||||
Base is shared infrastructure: the same binary answers on hanzo.ai,
|
||||
lux.network and zoo.ngo, so anything compiled in is right on one of them
|
||||
and wrong on the rest.
|
||||
|
||||
THE BRAND IS RESOLVED HERE, ONCE, and left on window for the app to
|
||||
read. The alternative was this map plus a second copy in the bundle, and
|
||||
two copies of "which brand is this host" is how a page ends up titled
|
||||
Hanzo over somebody else's mark.
|
||||
|
||||
Inline and ahead of the bundle, because the tab is titled and the icon
|
||||
swapped while React is still downloading. -->
|
||||
<script>
|
||||
(function () {
|
||||
var h = location.hostname
|
||||
var brand = /(^|\.)(lux\.(network|finance|id))$/.test(h) ? 'Lux'
|
||||
: /(^|\.)(zoo\.(ngo|network)|zoolabs\.id)$/.test(h) ? 'Zoo'
|
||||
: /(^|\.)hanzo\.(ai|id)$/.test(h) ? 'Hanzo'
|
||||
var slug = /(^|\.)(lux\.(network|finance|id))$/.test(h) ? 'lux'
|
||||
: /(^|\.)(zoo\.(ngo|network)|zoolabs\.id)$/.test(h) ? 'zoo'
|
||||
: /(^|\.)hanzo\.(ai|id)$/.test(h) ? 'hanzo'
|
||||
: ''
|
||||
if (brand) document.title = brand + ' Base'
|
||||
// Unknown host keeps the unbranded mark. A lookalike domain must not be
|
||||
// handed our logo, so the patterns anchor to the registrable domain.
|
||||
window.__brand = slug
|
||||
if (!slug) return
|
||||
document.title = { lux: 'Lux', zoo: 'Zoo', hanzo: 'Hanzo' }[slug] + ' Base'
|
||||
var link = document.getElementById('favicon')
|
||||
if (link) link.href = '/brands/' + slug + '/favicon.svg'
|
||||
})()
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-DPUOfdJ-.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BQLoHopS.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-7Zn13fNS.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2,23 +2,36 @@
|
||||
<html lang="en" class="dark" style="background:#000;color-scheme:dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link id="favicon" rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<title>Base</title>
|
||||
<!-- The page carries the brand of the host it was served from. Base is shared
|
||||
infrastructure: the same binary answers on hanzo.ai, lux.network and
|
||||
zoo.ngo, so a name compiled into the page is right on one of them and
|
||||
wrong on the rest. Inline and before the bundle, because the tab is
|
||||
titled while React is still downloading. -->
|
||||
<!-- The page carries the mark and the name of the host it was served from.
|
||||
Base is shared infrastructure: the same binary answers on hanzo.ai,
|
||||
lux.network and zoo.ngo, so anything compiled in is right on one of them
|
||||
and wrong on the rest.
|
||||
|
||||
THE BRAND IS RESOLVED HERE, ONCE, and left on window for the app to
|
||||
read. The alternative was this map plus a second copy in the bundle, and
|
||||
two copies of "which brand is this host" is how a page ends up titled
|
||||
Hanzo over somebody else's mark.
|
||||
|
||||
Inline and ahead of the bundle, because the tab is titled and the icon
|
||||
swapped while React is still downloading. -->
|
||||
<script>
|
||||
(function () {
|
||||
var h = location.hostname
|
||||
var brand = /(^|\.)(lux\.(network|finance|id))$/.test(h) ? 'Lux'
|
||||
: /(^|\.)(zoo\.(ngo|network)|zoolabs\.id)$/.test(h) ? 'Zoo'
|
||||
: /(^|\.)hanzo\.(ai|id)$/.test(h) ? 'Hanzo'
|
||||
var slug = /(^|\.)(lux\.(network|finance|id))$/.test(h) ? 'lux'
|
||||
: /(^|\.)(zoo\.(ngo|network)|zoolabs\.id)$/.test(h) ? 'zoo'
|
||||
: /(^|\.)hanzo\.(ai|id)$/.test(h) ? 'hanzo'
|
||||
: ''
|
||||
if (brand) document.title = brand + ' Base'
|
||||
// Unknown host keeps the unbranded mark. A lookalike domain must not be
|
||||
// handed our logo, so the patterns anchor to the registrable domain.
|
||||
window.__brand = slug
|
||||
if (!slug) return
|
||||
document.title = { lux: 'Lux', zoo: 'Zoo', hanzo: 'Hanzo' }[slug] + ' Base'
|
||||
var link = document.getElementById('favicon')
|
||||
if (link) link.href = '/brands/' + slug + '/favicon.svg'
|
||||
})()
|
||||
</script>
|
||||
</head>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.bg { fill: #000; }
|
||||
.mark { fill: #fff; }
|
||||
@media (prefers-color-scheme: light) {
|
||||
.bg { fill: #fff; }
|
||||
.mark { fill: #000; }
|
||||
}
|
||||
</style>
|
||||
<rect class="bg" width="64" height="64" rx="8"/>
|
||||
<g transform="translate(8, 8) scale(0.716)">
|
||||
<path class="mark" d="M22.21 67V44.6369H0V67H22.21Z"/>
|
||||
<path class="mark" d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z"/>
|
||||
<path class="mark" d="M22.21 0H0V22.3184H22.21V0Z"/>
|
||||
<path class="mark" d="M66.7198 0H44.5098V22.3184H66.7198V0Z"/>
|
||||
<path class="mark" d="M66.7198 67V44.6369H44.5098V67H66.7198Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 696 B |
@@ -0,0 +1,9 @@
|
||||
<svg viewBox="0 0 67 67" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="currentColor"/>
|
||||
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="currentColor" opacity="0.8"/>
|
||||
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="currentColor"/>
|
||||
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="currentColor"/>
|
||||
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="currentColor"/>
|
||||
<path d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="currentColor" opacity="0.8"/>
|
||||
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 638 B |
@@ -0,0 +1,12 @@
|
||||
<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.bg { fill: #000; }
|
||||
.mark { fill: #fff; }
|
||||
@media (prefers-color-scheme: light) {
|
||||
.bg { fill: #fff; }
|
||||
.mark { fill: #000; }
|
||||
}
|
||||
</style>
|
||||
<rect class="bg" width="64" height="64" rx="8"/>
|
||||
<path class="mark" d="M32 52 L12 18 L52 18 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 343 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M50 85 L15 25 L85 25 Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 129 B |
@@ -0,0 +1,12 @@
|
||||
<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.bg { fill: #000; }
|
||||
@media (prefers-color-scheme: light) {
|
||||
.bg { fill: #fff; }
|
||||
}
|
||||
</style>
|
||||
<rect class="bg" width="64" height="64" rx="8"/>
|
||||
<circle cx="32" cy="22" r="12" fill="#00A652"/>
|
||||
<circle cx="21" cy="40" r="12" fill="#ED1C24"/>
|
||||
<circle cx="43" cy="40" r="12" fill="#2E3192"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 389 B |
@@ -0,0 +1,13 @@
|
||||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<clipPath id="outerCircleMono">
|
||||
<circle cx="508" cy="510" r="283"></circle>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#outerCircleMono)">
|
||||
<circle cx="513" cy="369" r="234" fill="none" stroke="currentColor" stroke-width="33"></circle>
|
||||
<circle cx="365" cy="595" r="234" fill="none" stroke="currentColor" stroke-width="33"></circle>
|
||||
<circle cx="643" cy="595" r="234" fill="none" stroke="currentColor" stroke-width="33"></circle>
|
||||
<circle cx="508" cy="510" r="265" fill="none" stroke="currentColor" stroke-width="36"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 691 B |
@@ -6,4 +6,16 @@
|
||||
// Vite rewrites the references it can see — the ones in index.html and anything
|
||||
// imported — but a path written as a string in JSX is opaque to it and ships
|
||||
// verbatim. So the base belongs in one value, here, rather than at each usage.
|
||||
export const icon = `${import.meta.env.BASE_URL}icon.svg`
|
||||
// The mark is the SERVING HOST'S, not a compiled-in one, for the same reason the
|
||||
// title is: one binary answers for Hanzo, Lux and Zoo. index.html resolves which
|
||||
// brand this host is before the bundle loads and leaves the answer on
|
||||
// window.__brand — read here rather than re-derived, so there is exactly one map
|
||||
// from host to brand and no way for the name and the logo to disagree.
|
||||
//
|
||||
// An unknown host falls back to the unbranded mark. That is deliberate: lending
|
||||
// our logo to a lookalike domain is worse than showing a generic glyph.
|
||||
const slug = typeof window === 'undefined' ? '' : ((window as { __brand?: string }).__brand ?? '')
|
||||
|
||||
export const icon = slug
|
||||
? `${import.meta.env.BASE_URL}brands/${slug}/logo.svg`
|
||||
: `${import.meta.env.BASE_URL}icon.svg`
|
||||
|
||||