merge: adopt @hanzogui/shell in hanzo.chat (Meet Hanzo launcher + brand tokens + landing header/footer)

This commit is contained in:
zeekay
2026-07-23 21:12:53 -07:00
8 changed files with 103 additions and 108 deletions
+2
View File
@@ -35,12 +35,14 @@
"@dicebear/collection": "^9.2.2",
"@dicebear/core": "^9.2.2",
"@hanzo/ai": "^0.2.1",
"@hanzo/brand": "^1.4.1",
"@hanzo/event": "^0.2.0",
"@hanzo/iam": "^0.13.1",
"@hanzo/logo": "^1.0.13",
"@hanzo/ui": "npm:@hanzo/ui-shadcn@^5.7.4",
"@hanzo/usage": "^0.1.6",
"@hanzochat/client": "workspace:*",
"@hanzogui/shell": "^7.4.2",
"@hanzochat/data-provider": "workspace:*",
"@headlessui/react": "^2.1.2",
"@marsidev/react-turnstile": "^1.1.0",
+13 -8
View File
@@ -11,6 +11,7 @@ import ExportAndShareMenu from './ExportAndShareMenu';
import BookmarkMenu from './Menus/BookmarkMenu';
import { TemporaryChat } from './TemporaryChat';
import AddMultiConvo from './AddMultiConvo';
import MeetHanzoLauncher from '~/components/Nav/MeetHanzoLauncher';
import { useHasAccess } from '~/hooks';
import { cn } from '~/utils';
@@ -80,14 +81,18 @@ export default function Header() {
)}
</div>
{!isSmallScreen && (
<div className="flex items-center gap-2">
<ExportAndShareMenu
isSharedButtonEnabled={startupConfig?.sharedLinksEnabled ?? false}
/>
<TemporaryChat />
</div>
)}
<div className="flex items-center gap-2">
{!isSmallScreen && (
<>
<ExportAndShareMenu
isSharedButtonEnabled={startupConfig?.sharedLinksEnabled ?? false}
/>
<TemporaryChat />
</>
)}
{/* Cross-app "Meet Hanzo" launcher — unified ecosystem nav, far right. */}
<MeetHanzoLauncher />
</div>
</div>
{/* Empty div for spacing */}
<div />
+6 -100
View File
@@ -1,4 +1,5 @@
import React, { useCallback } from 'react';
import { HanzoHeader, HanzoPreFooterCTA, HanzoFooter } from '@hanzogui/shell';
import { useGetStartupConfig } from '~/data-provider';
import { getHanzoIamSdk } from '~/utils/iam';
@@ -65,24 +66,6 @@ const IconArrowRight = ({ className = 'ml-2 size-4' }: { className?: string }) =
</svg>
);
/* Hanzo geometric H logo */
const HanzoLogo = ({ className = 'size-5', style }: { className?: string; style?: React.CSSProperties }) => (
<svg viewBox="0 0 67 67" className={className} style={style} xmlns="http://www.w3.org/2000/svg">
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="currentColor" />
<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.7198 67V44.6369H44.5098V67H66.7198Z" fill="currentColor" />
</svg>
);
/* GitHub icon */
const IconGitHub = ({ className = 'size-5' }: { className?: string }) => (
<svg className={className} role="img" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</svg>
);
/* ------------------------------------------------------------------ */
/* Data */
/* ------------------------------------------------------------------ */
@@ -157,55 +140,8 @@ export default function LandingPage() {
fontFamily: "'Inter', 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
}}
>
{/* ---- Navbar (matches fd HomeLayout nav) ---- */}
<nav
className="sticky top-0 z-50 backdrop-blur-md"
style={{
backgroundColor: 'rgba(5, 5, 5, 0.8)',
borderBottom: `1px solid ${colors.border}`,
}}
>
<div className="mx-auto flex max-w-[1400px] items-center justify-between px-6 py-3">
<div className="flex items-center gap-2.5">
<HanzoLogo className="size-5 text-white" />
<span className="text-sm font-bold tracking-tight">Hanzo Chat</span>
</div>
<div className="flex items-center gap-3">
<a
href="https://docs.hanzo.ai/chat"
target="_blank"
rel="noopener noreferrer"
className="hidden text-sm transition-colors sm:inline"
style={{ color: colors.mutedFg }}
onMouseOver={(e) => (e.currentTarget.style.color = colors.fg)}
onMouseOut={(e) => (e.currentTarget.style.color = colors.mutedFg)}
>
Docs
</a>
<a
href="https://github.com/hanzoai/chat"
target="_blank"
rel="noopener noreferrer"
className="hidden transition-colors sm:inline"
style={{ color: colors.mutedFg }}
onMouseOver={(e) => (e.currentTarget.style.color = colors.fg)}
onMouseOut={(e) => (e.currentTarget.style.color = colors.mutedFg)}
>
<IconGitHub className="size-[18px]" />
</a>
<a
href={loginHref}
onClick={handleLoginClick}
className="rounded-full px-5 py-2 text-sm font-medium tracking-tight transition-colors"
style={{ backgroundColor: colors.brand, color: '#000' }}
onMouseOver={(e) => (e.currentTarget.style.filter = 'brightness(1.15)')}
onMouseOut={(e) => (e.currentTarget.style.filter = 'none')}
>
Log in
</a>
</div>
</div>
</nav>
{/* ---- Unified Hanzo marketing header (shared @hanzogui/shell) ---- */}
<HanzoHeader surface="hanzo.chat" />
{/* ---- Hero (bordered card like dev.hanzo.ai) ---- */}
<div className="mx-auto w-full max-w-[1400px] px-4 pt-4">
@@ -557,39 +493,9 @@ zen5-coder: I'll help you refactor the auth module.
</div>
</div>
{/* ---- Footer ---- */}
<footer
className="mt-0 py-8"
style={{ borderTop: `1px solid ${colors.border}` }}
>
<div className="mx-auto flex max-w-[1400px] flex-col items-center justify-between gap-6 px-6 sm:flex-row md:px-12">
<div className="flex items-center gap-2 text-sm" style={{ color: 'hsl(0, 0%, 40%)' }}>
<HanzoLogo className="size-4" style={{ color: 'hsl(0, 0%, 40%)' }} />
Powered by Hanzo AI
</div>
<div className="flex flex-wrap justify-center gap-6 text-sm" style={{ color: 'hsl(0, 0%, 40%)' }}>
{[
{ label: 'hanzo.ai', href: 'https://hanzo.ai' },
{ label: 'Documentation', href: 'https://docs.hanzo.ai/chat' },
{ label: 'Console', href: 'https://console.hanzo.ai' },
{ label: 'Privacy', href: 'https://hanzo.ai/privacy' },
{ label: 'Terms', href: 'https://hanzo.ai/terms' },
].map(({ label, href }) => (
<a
key={label}
href={href}
target="_blank"
rel="noopener noreferrer"
className="transition-colors"
onMouseOver={(e) => (e.currentTarget.style.color = colors.fg)}
onMouseOut={(e) => (e.currentTarget.style.color = 'hsl(0, 0%, 40%)')}
>
{label}
</a>
))}
</div>
</div>
</footer>
{/* ---- Unified Hanzo pre-footer CTA + ecosystem footer (shared shell) ---- */}
<HanzoPreFooterCTA surface="hanzo.chat" />
<HanzoFooter currentProductId="chat" />
</div>
);
}
@@ -0,0 +1,21 @@
/**
* Meet Hanzo — the cross-app "9-dot grid" launcher from the shared
* @hanzogui/shell. Added ALONGSIDE LibreChat's conversation nav so Hanzo Chat
* joins the unified Hanzo ecosystem nav.
*
* Click-only: the launcher's global ⌘/Ctrl-K quick-switch listener is disabled
* (`quickSwitchKey={false}`) so adopting the shell never claims an app-wide
* keyboard shortcut on the chat surface.
*/
import { HanzoAppLauncher } from '@hanzogui/shell';
export default function MeetHanzoLauncher() {
return (
<HanzoAppLauncher
currentApp="chat"
align="right"
quickSwitchKey={false}
label="Meet Hanzo"
/>
);
}
+5
View File
@@ -1,3 +1,8 @@
/* Shared Hanzo brand tokens (monochrome --hanzo-* scale). Imported first so the
chat :root below overrides any collision — chat keeps its own grey ramp while
the --hanzo-* palette becomes the single shared source across every surface. */
@import '@hanzo/brand/styles/variables.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
+18
View File
@@ -96,6 +96,24 @@ module.exports = {
850: '#171717',
900: '#0d0d0d',
},
// Shared @hanzo/brand monochrome scale (from variables.css). Additive —
// chat's own `gray`/`mono` ramp is untouched; new shared chrome can use
// `hanzo-*` so the palette resolves to the ONE brand source of truth.
hanzo: {
black: 'var(--hanzo-black)',
white: 'var(--hanzo-white)',
50: 'var(--hanzo-mono-50)',
100: 'var(--hanzo-mono-100)',
200: 'var(--hanzo-mono-200)',
300: 'var(--hanzo-mono-300)',
400: 'var(--hanzo-mono-400)',
500: 'var(--hanzo-mono-500)',
600: 'var(--hanzo-mono-600)',
700: 'var(--hanzo-mono-700)',
800: 'var(--hanzo-mono-800)',
900: 'var(--hanzo-mono-900)',
950: 'var(--hanzo-mono-950)',
},
green: mono,
red: mono,
blue: mono,
+4
View File
@@ -289,6 +289,10 @@ export default defineConfig(({ command }) => ({
'micromark-extension-math': 'micromark-extension-llm-math',
},
},
// Pre-bundle the compiled-ESM shell so the dev server resolves it cleanly.
optimizeDeps: {
include: ['@hanzogui/shell'],
},
}));
interface SourcemapExclude {
+34
View File
@@ -379,6 +379,9 @@ importers:
'@hanzo/ai':
specifier: ^0.2.1
version: 0.2.1
'@hanzo/brand':
specifier: ^1.4.1
version: 1.4.1(react@18.3.1)
'@hanzo/event':
specifier: ^0.2.0
version: 0.2.0(react@18.3.1)
@@ -400,6 +403,9 @@ importers:
'@hanzochat/data-provider':
specifier: workspace:*
version: link:../packages/data-provider
'@hanzogui/shell':
specifier: ^7.4.2
version: 7.4.2(@hanzo/iam@0.13.1(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@headlessui/react':
specifier: ^2.1.2
version: 2.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -3797,6 +3803,15 @@ packages:
resolution: {integrity: sha512-m0k6V6Im8m5RXWHUy/E7DvpZ9tFBYUw21NTYFxNxUiPI1l+yEvHEVytqEbEtHZOCa9Ytb8nfuNzJIsfCeF/zZw==}
engines: {node: '>=18'}
'@hanzo/brand@1.4.1':
resolution: {integrity: sha512-tApXct8gE/HOYa61h6TWQfDp07l1pmB+vqmxlJtU+MhocbwEkW4VPJrQdPuwF/5GPZYQauNT2uaRpnZdCK8ikw==}
hasBin: true
peerDependencies:
react: '>=18'
peerDependenciesMeta:
react:
optional: true
'@hanzo/event@0.2.0':
resolution: {integrity: sha512-jx+xlT5no88PYg/k465/pQ7a+oQcfImB5X0dJ4/ZSSF5bwqPCItvN/9Znt4SQpq18EQeMuR5GiLmxo1wSP5uqw==}
peerDependencies:
@@ -3925,6 +3940,13 @@ packages:
'@anthropic-ai/sandbox-runtime':
optional: true
'@hanzogui/shell@7.4.2':
resolution: {integrity: sha512-Haig4FLJUAgNbHvYxgsX/Y4D+VZW9fzvYoYi6Z19ZKkYCXnFG6S0wQmwZhqFW5KCmlnwUV7jUSJw9vOdvqj7nw==}
peerDependencies:
'@hanzo/iam': ^0.13.1
react: '*'
react-dom: '*'
'@headlessui/react@2.2.9':
resolution: {integrity: sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==}
engines: {node: '>=10'}
@@ -17704,6 +17726,12 @@ snapshots:
'@hanzo/ai@0.2.1': {}
'@hanzo/brand@1.4.1(react@18.3.1)':
dependencies:
'@hanzo/logo': 1.0.13(react@18.3.1)
optionalDependencies:
react: 18.3.1
'@hanzo/event@0.2.0(react@18.3.1)':
optionalDependencies:
react: 18.3.1
@@ -17857,6 +17885,12 @@ snapshots:
- ws
- zod
'@hanzogui/shell@7.4.2(@hanzo/iam@0.13.1(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@hanzo/iam': 0.13.1(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@headlessui/react@2.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@floating-ui/react': 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)