WIP: ⌘K palette + 6-cat grid + brand logo rendering (build red)

Builder + brand agents paused on the weekly usage cap (resets Jul 21 6am PT).
Preserving in-flight work-board work so nothing is lost; next resume greens + finishes.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
zeekay
2026-07-17 21:23:58 -07:00
co-authored by Hanzo Dev
parent 6b1e1d16e2
commit cf881fc763
13 changed files with 399 additions and 27 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ spec:
- name: work-board
# Pinned to the immutable CI tag (ghcr.io/luxfi/work-board:sha-<commit>).
# CI builds every push to main; bump this to roll a new build.
image: ghcr.io/luxfi/work-board:sha-18004aa
image: ghcr.io/luxfi/work-board:sha-c2977a5
ports:
- containerPort: 3000
resources:
+9
View File
@@ -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="#ffffff"/>
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="#DDDDDD"/>
<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.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="#DDDDDD"/>
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 561 B

+10
View File
@@ -0,0 +1,10 @@
<svg viewBox="0 0 1024 256" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(32, 48) scale(2.388)">
<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>
<text x="240" y="172" font-family="Geist Sans, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" font-weight="600" font-size="140" fill="#ffffff" letter-spacing="-4">Hanzo</text>
</svg>

After

Width:  |  Height:  |  Size: 690 B

+5
View File
@@ -0,0 +1,5 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<!-- Lux brand mark: solid black square -->
<rect width="1024" height="1024" fill="#000000"/>
<text x="512" y="640" font-family="ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" font-weight="700" font-size="640" fill="#FFFFFF" text-anchor="middle" letter-spacing="-32">L</text>
</svg>

After

Width:  |  Height:  |  Size: 407 B

+5
View File
@@ -0,0 +1,5 @@
<svg width="1024" height="256" viewBox="0 0 1024 256" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="48" width="160" height="160" fill="#000000"/>
<text x="80" y="172" font-family="ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" font-weight="700" font-size="140" fill="#FFFFFF" text-anchor="middle" letter-spacing="-6">L</text>
<text x="200" y="172" font-family="ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" font-weight="700" font-size="140" fill="currentColor" letter-spacing="-6">Lux</text>
</svg>

After

Width:  |  Height:  |  Size: 563 B

+69
View File
@@ -0,0 +1,69 @@
<svg viewBox="-120 -120 240 240" fill="none" xmlns="http://www.w3.org/2000/svg">
<!--
Pars Network Logo - Persian 8-Pointed Star (Khatam/Shamseh)
The traditional Persian geometric motif - recursive fractal star
Used in mosques, palaces, and tilework across Persia for millennia.
-->
<defs>
<linearGradient id="pars-gold" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#f5d06f"/>
<stop offset="50%" stop-color="#caa24a"/>
<stop offset="100%" stop-color="#f3dc8f"/>
</linearGradient>
<linearGradient id="pars-blue" x1="0" y1="1" x2="1" y2="0">
<stop offset="0%" stop-color="#003355"/>
<stop offset="50%" stop-color="#00abff"/>
<stop offset="100%" stop-color="#66d0ff"/>
</linearGradient>
<filter id="pars-glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
</defs>
<!-- Outer 8-pointed star -->
<g filter="url(#pars-glow)">
<path
d="M0,-100 L30,-60 L100,-40 L60,0 L100,40 L30,60 L0,100 L-30,60 L-100,40 L-60,0 L-100,-40 L-30,-60 Z"
fill="none"
stroke="url(#pars-gold)"
stroke-width="4"
stroke-linejoin="round"
/>
</g>
<!-- Inner star with blue fill -->
<path
d="M0,-70 L22,-42 L70,-28 L42,0 L70,28 L22,42 L0,70 L-22,42 L-70,28 L-42,0 L-70,-28 L-22,-42 Z"
fill="url(#pars-blue)"
stroke="url(#pars-gold)"
stroke-width="3"
stroke-linejoin="round"
/>
<!-- Recursive inner star -->
<path
d="M0,-45 L14,-27 L45,-18 L27,0 L45,18 L14,27 L0,45 L-14,27 L-45,18 L-27,0 L-45,-18 L-14,-27 Z"
fill="none"
stroke="url(#pars-gold)"
stroke-width="2"
stroke-linejoin="round"
opacity="0.8"
/>
<!-- Interlaced circles (Persian geometric pattern) -->
<g fill="none" stroke="#eaf7ff" stroke-width="1.5" opacity="0.6">
<circle r="55"/>
<circle r="35"/>
</g>
<!-- Center rosette -->
<circle r="8" fill="url(#pars-gold)"/>
<path
d="M0,-20 L6,-6 L20,0 L6,6 L0,20 L-6,6 L-20,0 L-6,-6 Z"
fill="#002a47"
stroke="url(#pars-gold)"
stroke-width="1.5"
stroke-linejoin="round"
/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+83
View File
@@ -0,0 +1,83 @@
<svg viewBox="0 0 500 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<!--
Pars Network Logo + Wordmark (Horizontal)
Persian 8-Pointed Star with "PARS NETWORK" text
-->
<defs>
<linearGradient id="pars-gold-lw" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#f5d06f"/>
<stop offset="50%" stop-color="#caa24a"/>
<stop offset="100%" stop-color="#f3dc8f"/>
</linearGradient>
<linearGradient id="pars-blue-lw" x1="0" y1="1" x2="1" y2="0">
<stop offset="0%" stop-color="#003355"/>
<stop offset="50%" stop-color="#00abff"/>
<stop offset="100%" stop-color="#66d0ff"/>
</linearGradient>
<filter id="pars-glow-lw" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
</defs>
<!-- Logo (scaled to fit) -->
<g transform="translate(50, 50) scale(0.4)">
<g filter="url(#pars-glow-lw)">
<path
d="M0,-100 L30,-60 L100,-40 L60,0 L100,40 L30,60 L0,100 L-30,60 L-100,40 L-60,0 L-100,-40 L-30,-60 Z"
fill="none"
stroke="url(#pars-gold-lw)"
stroke-width="4"
stroke-linejoin="round"
/>
</g>
<path
d="M0,-70 L22,-42 L70,-28 L42,0 L70,28 L22,42 L0,70 L-22,42 L-70,28 L-42,0 L-70,-28 L-22,-42 Z"
fill="url(#pars-blue-lw)"
stroke="url(#pars-gold-lw)"
stroke-width="3"
stroke-linejoin="round"
/>
<path
d="M0,-45 L14,-27 L45,-18 L27,0 L45,18 L14,27 L0,45 L-14,27 L-45,18 L-27,0 L-45,-18 L-14,-27 Z"
fill="none"
stroke="url(#pars-gold-lw)"
stroke-width="2"
stroke-linejoin="round"
opacity="0.8"
/>
<g fill="none" stroke="#eaf7ff" stroke-width="1.5" opacity="0.6">
<circle r="55"/>
<circle r="35"/>
</g>
<circle r="8" fill="url(#pars-gold-lw)"/>
<path
d="M0,-20 L6,-6 L20,0 L6,6 L0,20 L-6,6 L-20,0 L-6,-6 Z"
fill="#002a47"
stroke="url(#pars-gold-lw)"
stroke-width="1.5"
stroke-linejoin="round"
/>
</g>
<!-- Wordmark -->
<text
x="115"
y="62"
font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
font-size="40"
font-weight="700"
letter-spacing="0.05em"
fill="url(#pars-gold-lw)"
>PARS</text>
<text
x="250"
y="62"
font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
font-size="40"
font-weight="300"
letter-spacing="0.05em"
fill="white"
>NETWORK</text>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{enable-background:new ;}
.st1{fill:#231F20;}
</style>
<g class="st0">
<path class="st1" d="M3.5,20.2v-1.1l4.4-5.9H3.5v-1.5h6.7v1.1l-4.4,5.9h4.4v1.5H3.5z"/>
<path class="st1" d="M18.9,14.2c0.2,0.5,0.3,1.2,0.3,1.8c0,0.7-0.1,1.3-0.3,1.8c-0.2,0.5-0.5,1-0.9,1.4s-0.8,0.6-1.3,0.8
c-0.5,0.2-1,0.3-1.6,0.3S14,20.2,13.5,20s-0.9-0.5-1.3-0.8c-0.4-0.4-0.7-0.8-0.9-1.4C11.1,17.3,11,16.7,11,16
c0-0.7,0.1-1.3,0.3-1.8s0.5-1,0.9-1.4s0.8-0.6,1.3-0.8s1-0.3,1.6-0.3s1.1,0.1,1.6,0.3s0.9,0.5,1.3,0.8S18.6,13.6,18.9,14.2z
M16.7,18c0.4-0.5,0.6-1.2,0.6-2c0-0.9-0.2-1.6-0.6-2c-0.4-0.5-0.9-0.7-1.6-0.7c-0.7,0-1.2,0.2-1.6,0.7c-0.4,0.5-0.6,1.2-0.6,2
c0,0.9,0.2,1.6,0.6,2c0.4,0.5,0.9,0.7,1.6,0.7C15.8,18.8,16.3,18.5,16.7,18z"/>
<path class="st1" d="M28.2,14.2c0.2,0.5,0.3,1.2,0.3,1.8c0,0.7-0.1,1.3-0.3,1.8c-0.2,0.5-0.5,1-0.9,1.4S26.5,19.8,26,20
s-1,0.3-1.6,0.3s-1.1-0.1-1.6-0.3s-0.9-0.5-1.3-0.8c-0.4-0.4-0.7-0.8-0.9-1.4c-0.2-0.5-0.3-1.2-0.3-1.8c0-0.7,0.1-1.3,0.3-1.8
c0.2-0.5,0.5-1,0.9-1.4s0.8-0.6,1.3-0.8s1-0.3,1.6-0.3s1.1,0.1,1.6,0.3s0.9,0.5,1.3,0.8C27.7,13.2,28,13.6,28.2,14.2z M26.1,18
c0.4-0.5,0.6-1.2,0.6-2c0-0.9-0.2-1.6-0.6-2c-0.4-0.5-0.9-0.7-1.6-0.7s-1.2,0.2-1.6,0.7s-0.6,1.2-0.6,2c0,0.9,0.2,1.6,0.6,2
c0.4,0.5,0.9,0.7,1.6,0.7S25.7,18.5,26.1,18z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{enable-background:new ;}
.st1{fill:#FFFFFF;}
</style>
<g class="st0">
<path class="st1" d="M3.5,20.2v-1.1l4.4-5.9H3.5v-1.5h6.7v1.1l-4.4,5.9h4.4v1.5H3.5z"/>
<path class="st1" d="M18.9,14.2c0.2,0.5,0.3,1.2,0.3,1.8c0,0.7-0.1,1.3-0.3,1.8c-0.2,0.5-0.5,1-0.9,1.4s-0.8,0.6-1.3,0.8
c-0.5,0.2-1,0.3-1.6,0.3S14,20.2,13.5,20s-0.9-0.5-1.3-0.8c-0.4-0.4-0.7-0.8-0.9-1.4C11.1,17.3,11,16.7,11,16
c0-0.7,0.1-1.3,0.3-1.8s0.5-1,0.9-1.4s0.8-0.6,1.3-0.8s1-0.3,1.6-0.3s1.1,0.1,1.6,0.3s0.9,0.5,1.3,0.8S18.6,13.6,18.9,14.2z
M16.7,18c0.4-0.5,0.6-1.2,0.6-2c0-0.9-0.2-1.6-0.6-2c-0.4-0.5-0.9-0.7-1.6-0.7c-0.7,0-1.2,0.2-1.6,0.7c-0.4,0.5-0.6,1.2-0.6,2
c0,0.9,0.2,1.6,0.6,2c0.4,0.5,0.9,0.7,1.6,0.7C15.8,18.8,16.3,18.5,16.7,18z"/>
<path class="st1" d="M28.2,14.2c0.2,0.5,0.3,1.2,0.3,1.8c0,0.7-0.1,1.3-0.3,1.8c-0.2,0.5-0.5,1-0.9,1.4S26.5,19.8,26,20
c-0.5,0.2-1,0.3-1.6,0.3s-1.1-0.1-1.6-0.3s-0.9-0.5-1.3-0.8c-0.4-0.4-0.7-0.8-0.9-1.4c-0.2-0.5-0.3-1.2-0.3-1.8
c0-0.7,0.1-1.3,0.3-1.8c0.2-0.5,0.5-1,0.9-1.4s0.8-0.6,1.3-0.8s1-0.3,1.6-0.3s1.1,0.1,1.6,0.3s0.9,0.5,1.3,0.8
C27.7,13.2,28,13.6,28.2,14.2z M26.1,18c0.4-0.5,0.6-1.2,0.6-2c0-0.9-0.2-1.6-0.6-2c-0.4-0.5-0.9-0.7-1.6-0.7s-1.2,0.2-1.6,0.7
s-0.6,1.2-0.6,2c0,0.9,0.2,1.6,0.6,2c0.4,0.5,0.9,0.7,1.6,0.7S25.7,18.5,26.1,18z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+96 -7
View File
@@ -1,11 +1,25 @@
import type { Address } from 'viem';
// Brand logos — embedded at build time. Vite inlines these small SVGs as `data:`
// URIs (well under the 4 KB asset-inline limit), which the strict production CSP
// permits via `img-src 'self' data:`; a missing file fails the build. Each mark is
// the brand's own canonical asset (from its @<org>/brand package or app public/),
// never cross-brand: a Zoo build embeds only Zoo. `logo` is the full wordmark,
// `logoMark` the square/icon mark. Zoo ships one trigram that serves as both.
import zooWordmark from './assets/brands/zoo/wordmark.svg';
import parsWordmark from './assets/brands/pars/wordmark.svg';
import parsMark from './assets/brands/pars/mark.svg';
import luxWordmark from './assets/brands/lux/wordmark.svg';
import luxMark from './assets/brands/lux/mark.svg';
import hanzoWordmark from './assets/brands/hanzo/wordmark.svg';
import hanzoMark from './assets/brands/hanzo/mark.svg';
// The one place a white-label lives. A brand is selected at BUILD time by the
// VITE_BRAND build-arg (see Dockerfile / docker.yml); default is 'zoo'. Every
// brand-varying value — chain, addresses, owner label, header/title text, the RPC
// host baked into the CSP, and all Dework presentation (tagline, accent, socials,
// Spaces/categories) — is a field here, so standing up a new board is a data
// change, not a code change. Never cross brands: a Zoo build shows only Zoo.
// host baked into the CSP, and all Dework presentation (tagline, accent, logos,
// socials, Spaces/categories) — is a field here, so standing up a new board is a
// data change, not a code change. Never cross brands: a Zoo build shows only Zoo.
// A Space is a bounty category (Dework "Space"). One Bounty serves the whole
// org, so a bounty is routed to a Space by matching its issueRef against `match`
@@ -44,6 +58,12 @@ export type Brand = {
tagline: string;
// Accent hex — primary buttons, active nav, highlights. Per-brand, never cross.
accent: string;
// Optional secondary/highlight hex (e.g. Pars gold). Omit for single-accent brands.
accentSecondary?: string;
// Brand logos — embedded SVGs (imported → build-inlined `data:` URI string).
// `logo` is the full wordmark; `logoMark` the square/icon mark. Never cross-brand.
logo: string;
logoMark: string;
usdPerNative: number; // native → USD for the "Total paid: $X" header stat
social: { discord?: string; twitter?: string; website?: string };
tags: string[]; // About-panel chips
@@ -73,13 +93,16 @@ export const BRANDS = {
org: 'Zoo DAO',
workspace: 'Zoo',
tagline: 'Open AI research network — DeAI & DeSci',
accent: '#22c55e',
accent: '#6c5efb',
logo: zooWordmark,
logoMark: zooWordmark,
usdPerNative: 1,
social: { discord: 'https://discord.gg/zoo', twitter: 'https://twitter.com/zoo_labs', website: 'https://zoo.ngo' },
tags: ['DeAI', 'DeSci', 'Research', 'Open Source', 'AI'],
spaces: [
{ key: 'engineering', name: 'Engineering', emoji: '⚙️', skills: ['Development', 'Product'], match: ['zooai/', 'zoo-'] },
{ key: 'research', name: 'AI & Research', emoji: '🧬', skills: ['Research', 'Data Analytics'], match: ['research/', 'zoo-research'] },
{ key: 'ecosystem', name: 'Ecosystem & Partnerships', emoji: '🤝', skills: ['Partnerships', 'Business Development'] },
{ key: 'community', name: 'Community & Social', emoji: '💬', skills: ['Community', 'Marketing'] },
{ key: 'content', name: 'Content Creation', emoji: '🎬', skills: ['Writing', 'Design'] },
{ key: 'governance', name: 'Governance', emoji: '🏛️', skills: ['Legal', 'Operations'], match: ['zips/'] },
@@ -100,19 +123,85 @@ export const BRANDS = {
org: 'Pars DAO',
workspace: 'Pars',
tagline: 'The community L1',
accent: '#3b82f6',
accent: '#1C3879',
accentSecondary: '#D4AF37',
logo: parsWordmark,
logoMark: parsMark,
usdPerNative: 1,
social: { website: 'https://pars.network' },
tags: ['Community', 'L1', 'Governance'],
spaces: [
{ key: 'engineering', name: 'Engineering', emoji: '⚙️', skills: ['Development', 'Product'] },
{ key: 'ecosystem', name: 'Ecosystem & Partnerships', emoji: '🤝', skills: ['Partnerships', 'Business Development'] },
{ key: 'community', name: 'Community & Social', emoji: '💬', skills: ['Community', 'Marketing'] },
{ key: 'content', name: 'Content Creation', emoji: '🎬', skills: ['Writing', 'Design'] },
{ key: 'education', name: 'Education & Research', emoji: '📚', skills: ['Education', 'Research'] },
{ key: 'governance', name: 'Governance', emoji: '🏛️', skills: ['Legal', 'Operations'], match: ['pips/'] },
],
rpcUrl: 'https://api.pars.network/v1/bc/C/rpc',
},
// lux + hanzo brands land in the batch-3 white-label pass, together with their
// fixtures, CI matrix entries and k8s manifests (post flag-day / deploying).
lux: {
chainId: 96369,
chainName: 'Lux',
nativeSymbol: 'LUX',
addresses: {
// TODO: no work-market deployed yet (Lux frozen / Hanzo key-gap)
bounty: '0x0000000000000000000000000000000000000000',
escrow: '0x0000000000000000000000000000000000000000',
reputation: '0x0000000000000000000000000000000000000000',
owner: '0x0000000000000000000000000000000000000000',
},
ownerLabel: 'Lux DAO Safe',
org: 'Lux DAO',
workspace: 'Lux',
tagline: 'Quantum-safe multi-consensus L1',
accent: '#7000FF',
logo: luxWordmark,
logoMark: luxMark,
usdPerNative: 1,
social: { discord: 'https://discord.gg/lux', twitter: 'https://x.com/luxfi', website: 'https://lux.network' },
tags: ['Consensus', 'Post-Quantum', 'L1', 'DeFi', 'Open Source'],
spaces: [
{ key: 'engineering', name: 'Engineering', emoji: '⚙️', skills: ['Development', 'Product'], match: ['luxfi/', 'lux-'] },
{ key: 'ecosystem', name: 'Ecosystem & Partnerships', emoji: '🤝', skills: ['Partnerships', 'Business Development'] },
{ key: 'community', name: 'Community & Social', emoji: '💬', skills: ['Community', 'Marketing'] },
{ key: 'content', name: 'Content Creation', emoji: '🎬', skills: ['Writing', 'Design'] },
{ key: 'research', name: 'Research', emoji: '🔬', skills: ['Research', 'Cryptography'] },
{ key: 'governance', name: 'Governance', emoji: '🏛️', skills: ['Legal', 'Operations'] },
],
rpcUrl: 'https://api.lux.network/v1/bc/C/rpc',
},
hanzo: {
chainId: 36963,
chainName: 'Hanzo',
nativeSymbol: 'AI',
addresses: {
// TODO: no work-market deployed yet (Lux frozen / Hanzo key-gap)
bounty: '0x0000000000000000000000000000000000000000',
escrow: '0x0000000000000000000000000000000000000000',
reputation: '0x0000000000000000000000000000000000000000',
owner: '0x0000000000000000000000000000000000000000',
},
ownerLabel: 'Hanzo DAO Safe',
org: 'Hanzo DAO',
workspace: 'Hanzo',
tagline: 'Frontier AI & foundational models',
accent: '#ea580c',
logo: hanzoWordmark,
logoMark: hanzoMark,
usdPerNative: 1,
social: { discord: 'https://discord.gg/hanzo', website: 'https://hanzo.ai' },
tags: ['AI', 'LLM', 'MCP', 'Agents', 'Open Source'],
spaces: [
{ key: 'engineering', name: 'Engineering', emoji: '⚙️', skills: ['Development', 'Product'], match: ['hanzoai/', 'hanzo-'] },
{ key: 'research', name: 'AI & Research', emoji: '🧠', skills: ['Research', 'Machine Learning'] },
{ key: 'ecosystem', name: 'Ecosystem & Partnerships', emoji: '🤝', skills: ['Partnerships', 'Business Development'] },
{ key: 'community', name: 'Community & Social', emoji: '💬', skills: ['Community', 'Marketing'] },
{ key: 'content', name: 'Content Creation', emoji: '🎬', skills: ['Writing', 'Design'] },
{ key: 'governance', name: 'Governance', emoji: '🏛️', skills: ['Legal', 'Operations'] },
],
rpcUrl: 'https://api.hanzo.network/v1/bc/C/rpc',
},
} as const satisfies Record<string, Brand>;
export type BrandKey = keyof typeof BRANDS;
+26 -16
View File
@@ -3,7 +3,8 @@ import { ORG } from '../config';
import { useRoute, navigate } from '../router';
import type { Route } from '../router';
import {
IconHome,
BrandMark,
BrandLogo,
IconPlus,
IconGrid,
IconBulb,
@@ -15,6 +16,28 @@ import {
IconDoc,
} from '../ui';
// The far-left 64px icon rail — a SEPARATE column beside the 256px sidebar
// (canonical Dework). Home + org-switcher "+". Both collapse into the mobile drawer.
export function IconRail({ onNavigate }: { onNavigate?: () => void }) {
const go = (path: string) => {
navigate(path);
onNavigate?.();
};
return (
<div
className="flex h-full w-16 shrink-0 flex-col items-center gap-2 border-r border-[var(--border)] bg-[var(--rail)] pt-3"
style={{ paddingTop: 'calc(env(safe-area-inset-top) + 0.75rem)' }}
>
<button onClick={() => go('/')} className="flex h-10 w-10 items-center justify-center" title="Home" aria-label={`${ORG.workspace} home`}>
<BrandMark size={40} className="shadow-sm" />
</button>
<button onClick={() => go('/explore')} className="flex h-10 w-10 items-center justify-center rounded-full bg-white/5 text-neutral-400 ring-1 ring-inset ring-white/8 hover:text-neutral-200" title="Browse bounties across all DAOs">
<IconPlus className="h-[18px] w-[18px]" />
</button>
</div>
);
}
function NavItem({
active,
icon,
@@ -58,22 +81,9 @@ export function Sidebar({ onNavigate }: { onNavigate?: () => void }) {
return (
<aside className="flex h-full w-64 shrink-0 flex-col overflow-y-auto border-r border-[var(--border)] bg-[var(--rail)]">
{/* org switcher rail */}
<div className="flex items-center gap-2 px-3 pt-3" style={{ paddingTop: 'calc(env(safe-area-inset-top) + 0.75rem)' }}>
<button onClick={() => go('/')} style={{ backgroundColor: 'var(--brand)' }} className="flex h-9 w-9 items-center justify-center rounded-xl text-white shadow-sm" title="Home">
<IconHome className="h-[18px] w-[18px]" />
</button>
<button onClick={() => go('/explore')} className="flex h-9 w-9 items-center justify-center rounded-xl bg-white/5 text-neutral-400 ring-1 ring-inset ring-white/8 hover:text-neutral-200" title="Browse bounties across all DAOs">
<IconPlus className="h-[18px] w-[18px]" />
</button>
</div>
{/* workspace */}
<button onClick={() => go('/')} className="mx-3 mt-3 flex items-center gap-2.5 rounded-md px-1 py-1 text-left hover:bg-white/5">
<span className="flex h-7 w-7 items-center justify-center rounded-lg text-sm font-bold text-white" style={{ backgroundColor: 'var(--brand)' }}>
{ORG.workspace.charAt(0)}
</span>
<span className="flex-1 truncate text-sm font-semibold text-neutral-100">{ORG.workspace}</span>
<button onClick={() => go('/')} className="mx-3 mt-3 flex items-center gap-2.5 rounded-md px-1 py-1 text-left hover:bg-white/5" style={{ marginTop: 'calc(env(safe-area-inset-top) + 0.75rem)' }}>
<BrandLogo className="flex-1" />
<IconChevronsLeft className="h-4 w-4 text-neutral-600" />
</button>
+2 -2
View File
@@ -97,8 +97,8 @@ export function TaskDetail({ task, ws }: { task: Task; ws: Workspace }) {
};
return (
<div className="fixed inset-0 z-50 flex items-stretch justify-center overflow-y-auto bg-black/60 sm:items-start sm:px-4 sm:py-10" onClick={closeTask}>
<div className="min-h-full w-full max-w-4xl bg-[var(--surface)] shadow-2xl ring-1 ring-inset ring-white/10 sm:min-h-0 sm:rounded-xl" onClick={(e) => e.stopPropagation()}>
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/60 sm:items-start sm:px-4 sm:py-10" onClick={closeTask}>
<div className="flex max-h-[92vh] w-full max-w-4xl flex-col overflow-y-auto rounded-t-2xl bg-[var(--surface)] shadow-2xl ring-1 ring-inset ring-white/10 sm:max-h-[86vh] sm:rounded-xl" onClick={(e) => e.stopPropagation()}>
{/* header */}
<div className="flex items-start justify-between gap-3 border-b border-white/6 px-6 py-4">
<div className="min-w-0">
+49 -1
View File
@@ -4,12 +4,60 @@ import { skillMeta } from './skills';
import { formatReward } from './reward';
import type { NftMeta } from './reward';
import type { Reward } from './types';
import { NATIVE_SYMBOL } from './config';
import { NATIVE_SYMBOL, ORG } from './config';
// ---- Brand accent ----
export const accent: CSSProperties = { backgroundColor: 'var(--brand)' };
export const accentText: CSSProperties = { color: 'var(--brand)' };
// ---- Brand identity (the ONE place the org's logo renders) ----
// BrandMark is the square mark: brand.logoMark (an image src / data URI) when the
// brand supplies one, else a letter tile on the accent. BrandLogo is the full
// sidebar lockup: brand.logo when supplied, else mark + workspace name. Both read
// the logo fields defensively (optional) — ae530aa6 supplies them; until then the
// letter tile / name stand in, and the image appears the moment the field lands.
export function BrandMark({ size = 28, className = '' }: { size?: number; className?: string }) {
const mark = (ORG as { logoMark?: string }).logoMark;
const radius = Math.round(size * 0.28);
if (mark) {
return (
<img
src={mark}
alt={ORG.workspace}
width={size}
height={size}
style={{ borderRadius: radius }}
className={`shrink-0 object-contain ${className}`}
/>
);
}
return (
<span
aria-hidden
className={`flex shrink-0 items-center justify-center font-bold leading-none text-white ${className}`}
style={{ width: size, height: size, borderRadius: radius, backgroundColor: 'var(--brand)', fontSize: Math.round(size * 0.42) }}
>
{ORG.workspace.charAt(0)}
</span>
);
}
export function BrandLogo({ className = '' }: { className?: string }) {
const logo = (ORG as { logo?: string }).logo;
return (
<span className={`flex min-w-0 items-center gap-2.5 ${className}`}>
{logo ? (
<img src={logo} alt={ORG.workspace} className="h-7 w-auto max-w-full object-contain object-left" />
) : (
<>
<BrandMark size={28} />
<span className="truncate text-sm font-semibold text-neutral-100">{ORG.workspace}</span>
</>
)}
</span>
);
}
// ---- Avatar (deterministic identity for an address) ----
export function Avatar({ addr, size = 24, title }: { addr: string; size?: number; title?: string }) {
return (