fix(wallet-web): visible brand marks, centered auth card, spaced CTAs, no cross-brand chain leak

- brands/{lux,zoo,hanzo}.svg: real marks (violet Lux prism, colorful Zoo venn,
  Hanzo block-H). The prior rect fill="currentColor" + black glyph collapsed to a
  solid black square in the light-theme header (currentColor is the dark text color).
- gui-stub tokenize(): numeric Tamagui space/size tokens ($3/$6/$10) now resolve to
  px on a 4px scale. They were mapped to undefined CSS vars (var(--3)), so every
  gap/padding/fontSize was silently dropped — the source of the gap-less "button
  blob" (colliding radii) and the top-anchored auth card.
- Welcome: plain inline-styled, viewport-centered card with a max-width panel, the
  brand mark, and 44px CTAs separated by real gaps (matches AppShell/Callback pattern,
  independent of the unresolved Tamagui token contract).
- overlays/{lux,zoo,hanzo}/brand.json: drop sibling first-party L1s from the chain
  selector. Zoo no longer lists "Lux C-Chain"/"Hanzo"; each brand keeps its own
  networks + neutral public EVMs (Ethereum/Arbitrum/Base/Polygon/Avalanche).

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
zeekay
2026-07-19 01:30:36 -07:00
co-authored by Hanzo Dev
parent 6dc2c4e9b4
commit 75c529deb8
8 changed files with 143 additions and 51 deletions
+2 -2
View File
@@ -27,7 +27,7 @@
"faviconUrl": "/brands/hanzo.svg",
"primaryColor": "#FFFFFF",
"defaultChainId": 36963,
"supportedChainIds": [36963, 36911, 96369, 200200, 1, 42161, 8453, 137],
"supportedChainIds": [36963, 36911, 1, 42161, 8453, 137],
"walletConnectProjectId": "",
"insightsHost": "",
"insightsApiKey": "",
@@ -46,7 +46,7 @@
},
"chains": {
"defaultChainId": 36963,
"supported": [36963, 36911, 96369, 200200, 1, 42161, 8453, 137]
"supported": [36963, 36911, 1, 42161, 8453, 137]
},
"rpc": {
"36963": "https://api.hanzo.ai/mainnet/ext/bc/C/rpc",
+2 -2
View File
@@ -27,7 +27,7 @@
"faviconUrl": "/brands/lux.svg",
"primaryColor": "#FFFFFF",
"defaultChainId": 96369,
"supportedChainIds": [96369, 96368, 200200, 36963, 36911, 494949, 1, 42161, 8453, 137, 43114],
"supportedChainIds": [96369, 96368, 1, 42161, 8453, 137, 43114],
"walletConnectProjectId": "",
"insightsHost": "",
"insightsApiKey": "",
@@ -46,7 +46,7 @@
},
"chains": {
"defaultChainId": 96369,
"supported": [96369, 96368, 200200, 36963, 36911, 494949, 1, 42161, 8453, 137, 43114]
"supported": [96369, 96368, 1, 42161, 8453, 137, 43114]
},
"rpc": {
"96369": "https://api.lux.network/mainnet/ext/bc/C/rpc",
+2 -2
View File
@@ -27,7 +27,7 @@
"faviconUrl": "/brands/zoo.svg",
"primaryColor": "#FFFFFF",
"defaultChainId": 200200,
"supportedChainIds": [200200, 200201, 96369, 36963, 1, 42161, 8453, 137],
"supportedChainIds": [200200, 200201, 1, 42161, 8453, 137],
"walletConnectProjectId": "",
"insightsHost": "",
"insightsApiKey": "",
@@ -46,7 +46,7 @@
},
"chains": {
"defaultChainId": 200200,
"supported": [200200, 200201, 96369, 36963, 1, 42161, 8453, 137]
"supported": [200200, 200201, 1, 42161, 8453, 137]
},
"rpc": {
"200200": "https://api.zoo.network/rpc",
+8 -2
View File
@@ -1,4 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" role="img" aria-label="Hanzo">
<rect width="64" height="64" rx="14" fill="currentColor" />
<path d="M20 18 L20 46 M44 18 L44 46 M20 32 L44 32" stroke="#000" stroke-width="6" stroke-linecap="round" />
<rect width="64" height="64" rx="14" fill="#111111" />
<g transform="translate(12 12) scale(0.6)">
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="#FFFFFF" />
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#FFFFFF" />
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="#FFFFFF" />
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#FFFFFF" />
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#FFFFFF" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 283 B

After

Width:  |  Height:  |  Size: 584 B

+3 -2
View File
@@ -1,4 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" role="img" aria-label="Lux">
<rect width="64" height="64" rx="14" fill="currentColor" />
<path d="M16 22 L32 48 L48 22 Z" fill="#000" />
<rect width="64" height="64" rx="14" fill="#8B5CF6" />
<path d="M32 13 L51 32 L32 51 L13 32 Z" fill="#FFFFFF" />
<path d="M32 24 L40 32 L32 40 L24 32 Z" fill="#8B5CF6" />
</svg>

Before

Width:  |  Height:  |  Size: 220 B

After

Width:  |  Height:  |  Size: 285 B

+15 -3
View File
@@ -1,4 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" role="img" aria-label="Zoo">
<rect width="64" height="64" rx="14" fill="currentColor" />
<path d="M20 20 L44 20 L20 44 L44 44" stroke="#000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" fill="none" />
<svg xmlns="http://www.w3.org/2000/svg" viewBox="242 241 540 540" fill="none" role="img" aria-label="Zoo">
<defs>
<clipPath id="zooOuter"><circle cx="512" cy="511" r="270"/></clipPath>
<clipPath id="zooGreen"><circle cx="513" cy="369" r="234"/></clipPath>
<clipPath id="zooRed"><circle cx="365" cy="595" r="234"/></clipPath>
</defs>
<g clip-path="url(#zooOuter)">
<circle cx="513" cy="369" r="234" fill="#00A652"/>
<circle cx="365" cy="595" r="234" fill="#ED1C24"/>
<circle cx="643" cy="595" r="234" fill="#2E3192"/>
<g clip-path="url(#zooGreen)"><circle cx="365" cy="595" r="234" fill="#FCF006"/></g>
<g clip-path="url(#zooGreen)"><circle cx="643" cy="595" r="234" fill="#01ACF1"/></g>
<g clip-path="url(#zooRed)"><circle cx="643" cy="595" r="234" fill="#EA018E"/></g>
<g clip-path="url(#zooGreen)"><g clip-path="url(#zooRed)"><circle cx="643" cy="595" r="234" fill="#FFFFFF"/></g></g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 303 B

After

Width:  |  Height:  |  Size: 947 B

+11 -1
View File
@@ -20,7 +20,17 @@ type CSSish = React.CSSProperties & { [k: string]: any }
function tokenize(v: any): string | undefined {
if (v === undefined || v === null) return undefined
if (typeof v === "number") return `${v}px`
if (typeof v === "string" && v.startsWith("$")) return `var(--${v.slice(1)})`
if (typeof v === "string" && v.startsWith("$")) {
const t = v.slice(1)
// Numeric Tamagui space/size token ($3, $6, $10, $0.5) → px on a 4px
// scale. These are NOT CSS vars (loadBrandConfig only sets named color
// vars like --accent1), so mapping them to `var(--3)` silently dropped
// every gap/padding/fontSize — the source of the "button blob" (no gap)
// and top-anchored card. Resolve them to real pixels instead.
if (/^-?\d*\.?\d+$/.test(t)) return `${parseFloat(t) * 4}px`
// Named theme token ($neutral2, $accent1) → CSS var set by loadBrandConfig.
return `var(--${t})`
}
return String(v)
}
+91 -28
View File
@@ -1,13 +1,70 @@
/**
* First-launch splash. Two paths: create new wallet, or import existing.
* Foundation router mounts this at /auth.
*
* Styled with plain inline styles + brand CSS vars (the same pattern as
* AppShell / Callback / Download) rather than @hanzo/gui primitives: the
* gui-stub's Tamagui token props do not carry a working flex/centering
* contract, which left the card top-anchored and the CTAs gap-less. This
* screen owns its layout so it renders correctly on every brand + breakpoint.
*/
import { Link } from "react-router-dom"
import { Button, Card, Text, YStack } from "@hanzo/gui"
import { useNavigate } from "react-router-dom"
import { brand, getIamConfig } from "@luxfi/wallet-brand"
import { useSession } from "../../store/session"
const wrap: React.CSSProperties = {
// Fill the AppShell content row (viewport minus header + main padding) and
// center the card so there is no empty void beneath it on desktop.
minHeight: "calc(100vh - 140px)",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 20,
padding: 16,
boxSizing: "border-box",
}
const card: React.CSSProperties = {
width: "100%",
maxWidth: 380,
display: "flex",
flexDirection: "column",
gap: 12,
padding: 24,
borderRadius: 14,
background: "var(--surface2, #f9f9f9)",
border: "1px solid var(--surface3, #e5e5e5)",
boxSizing: "border-box",
}
const buttonBase: React.CSSProperties = {
width: "100%",
minHeight: 44,
padding: "12px 16px",
borderRadius: 10,
fontSize: 15,
fontWeight: 600,
cursor: "pointer",
fontFamily: "inherit",
}
const primaryButton: React.CSSProperties = {
...buttonBase,
background: "var(--accent1, #000)",
color: "var(--surface1, #fff)",
border: "1px solid var(--accent1, #000)",
}
const outlinedButton: React.CSSProperties = {
...buttonBase,
background: "transparent",
color: "var(--neutral1, #111)",
border: "1px solid var(--surface3, #d4d4d4)",
}
export default function Welcome() {
const navigate = useNavigate()
const login = useSession((s) => s.login)
const status = useSession((s) => s.status)
// Account login is additive — only offered when the brand has an IdP wired.
@@ -16,42 +73,48 @@ export default function Welcome() {
const idpName = brand.shortName || brand.name || "Lux"
return (
<YStack flex={1} ai="center" jc="center" gap="$6" p="$6">
<Text fontSize="$10" fontWeight="700">
{brand.walletName || "▼"}
</Text>
<Text fontSize="$4" col="$neutral2" ta="center" maxWidth={360}>
<div style={wrap}>
{brand.logoUrl ? (
<img src={brand.logoUrl} alt="" width={56} height={56} aria-hidden="true" />
) : null}
<h1 style={{ margin: 0, fontSize: 28, fontWeight: 700, color: "var(--neutral1, #111)" }}>
{brand.walletName || "Wallet"}
</h1>
<p
style={{
margin: 0,
maxWidth: 340,
textAlign: "center",
fontSize: 15,
lineHeight: 1.5,
color: "var(--neutral2, #666)",
}}
>
{brand.description || "Self-custodial wallet for the Lux ecosystem."}
</Text>
</p>
<Card p="$5" maxWidth={360} width="100%">
<YStack gap="$3">
<Link to="/auth/create" style={{ textDecoration: "none" }}>
<Button width="100%" theme="active">
<div style={card}>
<button type="button" style={primaryButton} onClick={() => navigate("/auth/create")}>
Create new wallet
</Button>
</Link>
<Link to="/auth/import" style={{ textDecoration: "none" }}>
<Button width="100%" variant="outlined">
</button>
<button type="button" style={outlinedButton} onClick={() => navigate("/auth/import")}>
Import existing
</Button>
</Link>
</button>
{accountLoginEnabled ? (
<Button
width="100%"
variant="outlined"
<button
type="button"
style={{ ...outlinedButton, opacity: status === "loading" ? 0.6 : 1 }}
disabled={status === "loading"}
// Self-custody keys stay local; this adds a cloud account
// (lux.id) for sync + MPC custody. Navigates to the IdP.
onPress={() => {
// Self-custody keys stay local; this adds a cloud account (lux.id)
// for sync + MPC custody. Navigates to the IdP.
onClick={() => {
void login()
}}
>
{status === "loading" ? "Connecting…" : `Sign in with ${idpName}`}
</Button>
</button>
) : null}
</YStack>
</Card>
</YStack>
</div>
</div>
)
}