49 Commits
Author SHA1 Message Date
hanzo-dev 70382d4f10 ci: canonical pair — hanzo.yml + forge caller
Hanzo CI/CD / cicd (push) Successful in 36s
CI/CD / cicd (push) Successful in 37s
github.com has no runner for hanzo-build-linux-amd64, so a caller under
.github/workflows is a gate that can never be scheduled. This is the ~7-line
caller on the plane that can (git.hanzo.ai git-runner fleet), pinned @v1.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-08-06 02:34:31 -07:00
zeekayandClaude Fable 5 be8468add4 publish through the shared sitedeploy action
deploy / site (push) Successful in 11s
The enqueue -> upload -> complete calls were transcribed into this file and
twelve others before hanzoai/ci carried them. Four steps collapse to one
`uses:`, so the plane's contract is written once and a fix lands everywhere
rather than in whichever repo someone remembered to edit.

It also self-provisions. sitedeploy POSTs /v1/projects idempotently before the
enqueue, so a slug that has never deployed no longer 404s on its first run --
which is every site in this fleet except hanzo-ai, hips and hanzo-app.

Build steps are untouched, verified byte-identical against the previous
revision; only the Sites-plane plumbing moved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 19:46:34 -07:00
zeekayandClaude Fable 5 915403d9fa Track the reference: upload under a grant, not with bucket keys
deploy / site (push) Failing after 9s
The canonical flow in hanzoai/hips moved while this was being written. It no
longer syncs with SITES_S3_*: the 202 carries a presigned POST policy confined
to this site's prefix and expiring in 30 minutes, CI POSTs one object at a time
under it, and completion reports the file manifest as `keys` so cloud prunes
the prefix -- a write-only grant cannot delete, so `--delete` had to move
server-side.

Worth stating why, not just what. Those bucket keys were the hanzo-sites
bucket's own long-lived credentials, and that bucket holds EVERY org's site
separated only by key prefix. A copy in this repo was write access to everyone's
site, forever, in as many places as there were repos. Now this repo holds no
storage credential at all; the only secret is HANZO_DEPLOY_TOKEN, which
identifies us rather than granting anything, and it lives on the FORGE because
.hanzo/workflows is what the forge reads.

Verified by diffing the generated plane steps against the reference: every
executable line is byte-identical, including the ::add-mask:: of the policy and
signature. The deltas are comment wrapping, the dead `bucket` output dropped
(nothing consumes it once the grant carries the URL), and the enqueue error
naming the slug -- these projects are unverifiable from here, so a 4xx should
say which one it could not find.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 18:56:10 -07:00
zeekayandClaude Fable 5 84586e4ee9 Record that hz.js cannot authenticate yet
Measured, not assumed: POST /v1/event with an unattributed batch answers
401 ingest_key_required, and hz.js through 0.3.11 sends neither an
Authorization header nor ?ingest_key= — the npm client has ingestKey on both
the fetch and the beacon, the script form was never given the same. So this
page is wired to the right door and is being turned away at it.

Written down rather than worked around. The fix is one attribute in
hanzoai/ui pkgs/event/hz.js; an inline script here that happened to send the
key would be a second telemetry client, which is the thing hz.js exists to
prevent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 18:42:48 -07:00
zeekayandClaude Fable 5 5dd8fb3ddd Correct which side of the mirror is writable
LLM.md said "git.hanzo.ai is canonical; GitHub is a mirror". The forge refuses
`git push` with `Mirror Repository hanzoai/logo is read-only` — it is a PULL
mirror of GitHub, so the arrow points the other way. Code lands on GitHub; the
forge pulls it and runs the workflows GitHub cannot see.

Written down because acting on the old sentence means pushing at a remote that
rejects you, and because the real consequence is easy to miss: a push is not a
deploy, and the lag until one is the mirror's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 18:40:54 -07:00
zeekayandClaude Fable 5 4aba18574d Ship logo.hanzo.ai on the Sites plane
deploy / site (push) Failing after 9s
publish / publish (push) Successful in 9s
index.html is one self-contained page. Nothing here executes, so an image, a
CR, a tag and a pod are four moving parts standing between a static file and a
browser. deploy.yml now enqueues, syncs, and flips live -- the same three steps
every other Hanzo site uses.

out/ is ASSEMBLED rather than built: index.html plus hz.js, the script-tag form
of @hanzo/event, copied out of node_modules. That is why @hanzo/event is a
devDependency here. It is not vendored and not loaded from a public CDN: the
lockfile decides which version of the telemetry client ships, and the only
script this page loads stays same-origin. hz.js posts to the same
api.hanzo.ai/v1/event as every bundled surface, so this page counts on the one
stream instead of being the one surface nobody measures.

The Dockerfile stays until hanzoai/universe retires the App CR and DNS moves --
deleting the recipe for the thing currently serving would be a half-migration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 18:29:29 -07:00
Claude 33b5ef2b2e ci: name a pool the fleet actually advertises
deploy / image (push) Successful in 9s
The deploy job asked for `ubuntu-latest`. This file lives in .hanzo/workflows,
which GitHub never reads -- git.hanzo.ai is the only thing that can run it, and
the fleet advertises no generic ubuntu-* label. That is deliberate and recorded
in the runner ConfigMap: ~1400 mirrored upstream forks all ask for ubuntu-latest,
and the forge errors rather than skipping a job it cannot place, so one bad job
was retried ~520 times across 10 runners.

An unmatched label is not a failure here, it is silence -- the job queues until
the 24h timeout. So this deploy has never produced an image, and nothing ever
said so. Same class as the playground regression, found by checking every
.hanzo/workflows file against the labels the runner ConfigMap actually declares
rather than against the ones I assumed.

hanzo-build-linux-amd64 is registered (verified against cm/git-runner-config).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:55:28 -07:00
hanzo-dev 8cf57626fc ci: drop the pull job — this repo is a mirror, Gitea already syncs it
Added it on the theory that the forge was drifting. It is not: the forge copy
matched GitHub at every check, because this repo is a pull-mirror and Gitea's
own scheduler keeps it current. The job would have been a second mechanism
doing what the first already does, pushing refs into a repo the server owns.

sync-from-github belongs to the NATIVE forge repos — cloud, ci, mirrors — which
have no mirror behind them. The real reason nothing ran here was the Actions
unit, off since migrate; hanzoai/mirrors reconcile.py now switches it on for any
repo carrying .hanzo/workflows.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 21:57:26 -07:00
hanzo-dev d106e4395a ci: one publish trigger, one pull transport; drop the Zoo Makefile
publish fires on a version change in package.json, the same as every sibling —
the v* tag was a second place the version lived. Its test step no longer carries
continue-on-error, and it installs with pnpm, which the repo moved to.

sync-from-github pulls the mirror into the forge every 10m, fast-forward only.
Nothing carried refs here before, and the repo drifted 3 ahead and 8 behind.

The Makefile was Zoo's, verbatim: Zoo help text, @zooai/logo, docker targets
tagging zooai/logo from a Hanzo repo. It wrapped npm around what are now pnpm
scripts and offered a second way to publish. Deleted, with the stray zoo names
in .gitignore and the menu-bar preview.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 21:50:59 -07:00
hanzo-dev 5520e7208c fix: lockfile must agree that sharp is a devDependency
The reconcile merge took sharp -> devDependencies from the published line and
the lockfile from main, which still listed it under dependencies. CI installs
with --frozen-lockfile by default, so the two disagreeing meant every publish
would have failed at install.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 21:47:23 -07:00
hanzo-dev 2aa71b0610 merge: reconcile the published 1.0.13 line with the pnpm/TS7 main
npm shipped @hanzo/logo@1.0.13 with the motion layer, but main sat at 1.0.10
and never had src/motion.ts — the registry was ahead of the branch, so a
checkout of main could not rebuild what users were installing.

Both sides survive: the motion layer and the ./motion export from the published
line, the pnpm migration, TS7 tsconfig and .hanzo/workflows from main. pnpm wins
the lockfile, so package-lock.json goes.

1.0.14 — the next patch after what is actually on the registry.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 21:37:19 -07:00
zeekayandhanzo-dev 1a38d4e6e9 build: migrate tsconfig to TypeScript 7 (native compiler)
TypeScript 7 is the native Go compiler and removes `baseUrl` and
`moduleResolution: node|node10`. Both appear here, so `tsc` from TS7
refuses the config outright (TS5102 / TS5108) and cannot typecheck.

`paths` targets resolve relative to `baseUrl` when it is set and relative
to the tsconfig file otherwise. Every `baseUrl` folded here already
pointed at the config's own directory, so dropping it moves nothing and
the targets are left byte-identical. Where a baseUrl pointed elsewhere,
each affected target was rewritten as join(baseUrl, target).

`moduleResolution` was chosen from the declared `module`: commonjs ->
node16, esnext/preserve -> bundler. Configs whose `module` is unset or
exotic were left alone rather than guessed at.

The result is accepted by BOTH toolchains, so nothing has to upgrade
TypeScript in lockstep. Verified on hanzo/chat packages/api: tsc 5.9
779 -> 778 errors (no regression), and tsc 7.0.2 now runs the project
in 2s where it previously refused the config.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 14:18:20 -07:00
hanzo-dev 33d2945d8e ci: drop the Gitea mirror-sync nudge
Superseded: the Hanzo GitHub App pushes a webhook, so the forge tracks GitHub
without a per-repo workflow. This file called git.hanzo.ai/api/v1/.../mirror-sync
— a Gitea API for a system we no longer drive — and would sit inert in every repo.

One mechanism, in one place, instead of ~350 copies of a cron.
2026-07-27 10:36:47 -07:00
zeekay 1972f6f7bb Merge remote-tracking branch 'origin/main' into HEAD
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 09:00:56 -07:00
zeekayandhanzo-dev 24def2ea2a build: move to pnpm
One package manager, one lockfile. pnpm-lock.yaml is tracked, package-lock.json is
deleted and every other manager's is ignored, and `packageManager` pins the version
corepack installs so a laptop and CI resolve the same tree.

Where a pnpm-workspace.yaml appears, it names the packages allowed to run install
scripts — pnpm denies them by default, so anything not listed cannot execute code
at install time just by being pulled in.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 09:00:55 -07:00
zeekayandhanzo-dev 9243dc2f96 ci: restore the @hanzo/logo publisher under .hanzo/workflows
The tag publisher was deleted this morning without a replacement, so @hanzo/logo
had no producer anywhere: no workflow, no failing run, just a package that would
stop moving. It is back at .hanzo/workflows/publish.yml where the forge reads it,
with two changes and no others.

It reads NPM_TOKEN, the one secret name every other Hanzo repo uses, instead of a
second name for the same credential. And it no longer writes a GitHub Release: the
forge does not serve that API, and the step ran after a successful publish, so it
would have turned a shipped package red.

.github/workflows now holds the canonical sync.yml alone — refs to git.hanzo.ai
and nothing else.

The site follows the same shape as every other Hanzo surface: index.html is
self-contained, so the Dockerfile copies it into ghcr.io/hanzoai/static with no
build stage, and deploy.yml publishes ghcr.io/hanzoai/logo:<sha> without deploying
it. hanzoai/universe crs/logo.yaml names the tag that serves and is inert until a
human pins one. Replaces a gh-pages push for a Pages site that was never
configured — logo.hanzo.ai has answered 404 from our own ingress the whole time.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 08:45:54 -07:00
hanzo-dev be91e9d5af ci: nudge git.hanzo.ai to pull on push
git.hanzo.ai mirrors this repo by PULL on a ~10-minute interval, and arcd runs
CI/CD there — so every push waited out that interval before anything built.
This asks Gitea to pull HEAD immediately.

Latency only: the repo already mirrors via the App webhook, so a missing
HANZO_GIT_TOKEN or a failed curl is non-fatal and never fails the push.
Idempotent (mirror-sync just pulls HEAD) and concurrency-coalesced.
2026-07-27 08:23:09 -07:00
hanzo-dev 13f3ac48ca ci: remove GitHub CI — native git.hanzo.ai + cd.hanzo.ai only
This workflow ran on a self-hosted actions-runner-controller pool. ARC is
decommissioned: CI executes inside git.hanzo.ai (Gitea Actions, .hanzo/workflows)
and delivery is cd.hanzo.ai reconciling the reviewed image pin in
hanzoai/universe. GitHub is a mirror and runs nothing.

Removed rather than repointed — there is no GitHub-side pool to move to, and
leaving it would keep ARC load-bearing.
2026-07-27 08:17:46 -07:00
hanzo-dev ed8c4680d9 ci: remove GitHub CI — native git.hanzo.ai + cd.hanzo.ai only
This workflow ran on a self-hosted actions-runner-controller pool. ARC is
decommissioned: CI executes inside git.hanzo.ai (Gitea Actions, .hanzo/workflows)
and delivery is cd.hanzo.ai reconciling the reviewed image pin in
hanzoai/universe. GitHub is a mirror and runs nothing.

Removed rather than repointed — there is no GitHub-side pool to move to, and
leaving it would keep ARC load-bearing.
2026-07-27 08:17:44 -07:00
Hanzo AI 4a73203aaf deps: sharp is the asset-CLI's tool, not a consumer runtime dep — devDependencies; publish 1.0.13 2026-07-20 13:00:40 -07:00
Hanzo AI b5ae14a3fb brand motion: canonical 7-path shaded H everywhere + pure-CSS motion layer (flip + breathe + wordmark collapse); publish 1.0.12 2026-07-20 10:43:28 -07:00
hanzo-dev a5aee54c05 build: optional wordmark generation (SVG + aspect-preserving PNGs)
Brands whose logos.ts exports getWordmarkSVG/getWordmarkWhite/getWordmarkMono
now ship a wide logotype alongside the square mark. Uses a namespace import
with optional chaining so brands without a wordmark skip it entirely — one
brand-neutral build, no per-brand wiring. publish 1.0.11

Claude-Session: https://claude.ai/code/session_01XsNmNwzHUSKXfN7gYruUsM
2026-07-08 06:41:16 -07:00
hanzo-dev 4fe5b7af54 brand isolation: build.ts is now brand-neutral (no hardcoded brand names — all from package.json); drop dead generator.ts/generate-showcase.ts and cross-brand aliases; publish 1.0.10
Enforces one-brand-per-repo: the shared build.ts carries no brand name (byte-
identical everywhere, driven by package.json "brand"); removed the unused
generator.ts/generate-showcase.ts (leftover, carried foreign-brand output
paths); genericized the animated-mark provenance and README brand-package
examples. Each package now names only its own brand.
2026-07-04 13:36:35 -07:00
hanzo-dev 3c86024fe8 build: deterministic showcase (drop build timestamp)
The showcase embedded new Date() in two spots, so every build re-dirtied
dist/showcase.html and churned git. Replaced with a static subtitle — builds
are now reproducible (no dates in generated files).
2026-07-04 13:03:02 -07:00
hanzo-dev 87dfe5f99c chore: sync generated showcase.html 2026-07-04 13:01:19 -07:00
hanzo-dev b997fd75d7 brand: real hero (mark + name, not 'logo' placeholder) + neutral svg names; publish 1.0.9
Re-mints the README hero: the template shipped a generic card whose wordmark
literally read "logo" with a truncated subtitle. generateHero() now composes
the brand's own white mark + real name + full tagline + github/domain footer,
driven by a package.json "brand" block — one template, every org. Also renames
dist SVG sources brand-neutral (logo.svg, favicon.svg, …) so the shared build is
correct for every brand (they were hanzo-prefixed after the earlier port).
2026-07-04 12:58:44 -07:00
hanzo-dev e1340f33a5 chore(npm): drop the ${NPM_TOKEN} project .npmrc
It overrode the user/CI npm auth with an empty token (E401 locally + in the
publish-on-tag CI). Auth now comes from ~/.npmrc locally and the
NPM_AUTH_TOKEN secret (npm config set) in CI — matching zoo/zen/lux, which
carry no project .npmrc. One way.
2026-07-04 12:39:09 -07:00
hanzo-dev e766ad4029 assets: track rendered dist images; build generates them; publish 1.0.8
- build now runs generate, so `npm run build` (and prepublishOnly / the
  publish-on-tag CI) render every image before packing — the npm package
  actually ships the pngs/icos/svgs, not just compiled JS.
- Track the rendered image assets (png/ico/svg + showcase.html) in git so
  every size is browsable for easy reference; compiled JS stays out (CI
  regenerates). Full set: favicon (+ .ico), icons, apple, dock, menubar,
  slack (2000² square), og — 58 png, 3 ico, 5 svg.
- Bump 1.0.7 → 1.0.8.
2026-07-03 16:06:56 -07:00
hanzo-dev c10ca626a0 build: add square 2000x2000 Slack app icon (+ 512)
Slack app directory wants a SQUARE 2000×2000 PNG (Slack applies its own
rounded-corner mask, so the source stays square, cornerRadius 0). Mark
centered on the brand-black field. dist/slack/slack-icon-2000.png +
slack-icon-512.png.
2026-07-03 15:55:51 -07:00
hanzo-dev 7411857e2b build: emit multi-resolution .ico + web-standard named icons
The generator produced PNG + SVG for every surface (icons, favicon, apple,
dock, menubar, og) but no .ico — the format Windows favicons, taskbar, and
.desktop launchers need. Add a dependency-free ICO encoder: it packs the
already-generated favicon PNGs into a real multi-resolution .ico (modern
ICO embeds PNG bytes verbatim, so it's a pure container write, no re-encode,
sizes read from each PNG's IHDR). Emits dist/favicon.ico (+ favicon/ copy,
16/32/48/64/128/256) and dist/icons/logo.ico (app icon, 16→256).

Also emit the exact filenames sites/browsers look for so dist/favicon/ is a
drop-in web manifest set: android-chrome-192/512, apple-touch-icon.png,
mstile-150x150. Every asset verified rendering (non-blank pixels) across all
categories; `file` confirms the .ico containers are valid.
2026-07-03 15:40:08 -07:00
z 4bb3ad8124 chore(brand): dynamic hero banner 2026-06-28 20:05:03 -07:00
z 0c69ab4e2d chore(brand): dynamic hero banner 2026-06-28 20:01:23 -07:00
hanzo-dev f810c4c5cc fix(ci): sync package-lock to bumped version 2026-06-28 19:24:58 -07:00
hanzo-dev 45024af61e feat: interactive animated mark — pure-CSS load→hover→press + getAnimatedSVG()
Self-contained animated SVG (load assemble, hover flourish, press squash);
classes namespaced per brand so multiple marks coexist on one page; honors
prefers-reduced-motion. Adds getAnimatedSVG()/getAnimatedDataUrl(), an
'animated' LogoVariant, and <Logo variant="animated"> React embed —
single-sourced from src/animated.ts. Ships raw svg/ in the package + README docs.
2026-06-28 19:19:35 -07:00
46a6b6ee67 ci: run on self-hosted ARC pool (hanzo-build-linux-amd64/deploy), not GitHub-hosted (#1)
Co-authored-by: zeekay <z@hanzo.ai>
2026-06-19 20:36:33 -07:00
Zach Kelling 6bee863809 chore: update LLM.md, standardize CLAUDE.md symlink convention
- LLM.md is the canonical AI context file (tracked in git)
- CLAUDE.md is a local symlink to LLM.md (gitignored)
- Updated LLM.md content for accuracy and conciseness
2026-02-17 18:13:25 -08:00
Zach Kelling ff0071a661 chore: add MIT LICENSE 2026-02-14 07:23:10 -08:00
Zach Kelling e103ddd645 chore: sync uncommitted changes 2026-02-14 07:23:10 -08:00
Zach Kelling eb0d7739c8 Fix GitHub Pages deployment 2026-01-25 21:09:47 -08:00
Zach Kelling 06041d8e19 Add GitHub Pages site with deploy workflow 2026-01-25 20:53:21 -08:00
Zach Kelling 96a6a32200 Add initial README 2026-01-25 20:07:42 -08:00
hanzo-dev a15de7064d ci: Add automated publish workflow 2025-11-12 04:26:32 -08:00
hanzo-dev bd804cf291 1.0.4 2025-11-12 04:13:35 -08:00
hanzo-dev 1a3c9d3958 fix: Replace outline mono logo with filled version 2025-11-12 04:13:35 -08:00
hanzo-dev f542de6cf1 1.0.3 2025-11-06 01:41:50 -08:00
hanzo-dev f47bdadb6d Export React components from main entry point
This change makes HanzoLogo and other React components available from
the main '@hanzo/logo' import instead of requiring '@hanzo/logo/react'.

This provides a more intuitive import path:
- Before: import { HanzoLogo } from '@hanzo/logo/react'
- After: import { HanzoLogo } from '@hanzo/logo'
2025-11-06 01:41:46 -08:00
hanzo-dev c3de9bd7d8 1.0.2 2025-10-21 11:45:26 -07:00
hanzo-dev 00eaf1c326 Update white logo to use filled SVG with proper shading 2025-10-21 11:45:26 -07:00
zandGitHub f9c300af63 Update README to remove monochrome icon details 2025-09-22 01:52:41 -05:00
96 changed files with 2184 additions and 1093 deletions
+17
View File
@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="640" viewBox="0 0 1280 640" role="img" aria-label="Hanzo brand assets">
<rect width="1280" height="640" fill="#0A0A0A"/>
<svg x="120" y="200" width="240" height="240" 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>
<text x="440" y="316" font-family="Inter,system-ui,-apple-system,sans-serif" font-size="94" font-weight="800" letter-spacing="-3" fill="#ffffff">Hanzo</text>
<text x="443" y="366" font-family="Inter,system-ui,sans-serif" font-size="27" fill="#ffffff" opacity=".62">Official brand marks — SVG, PNG, ICO, favicons &amp; app icons.</text>
<rect x="443" y="390" width="710" height="2" rx="1" fill="#ffffff" opacity=".85"/>
<text x="443" y="440" font-family="Inter,system-ui,sans-serif" font-size="23" font-weight="600" fill="#ffffff" opacity=".45">github.com/hanzoai</text>
<text x="1160" y="440" text-anchor="end" font-family="Inter,system-ui,sans-serif" font-size="23" font-weight="600" fill="#ffffff" opacity=".45">hanzo.ai</text>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+33 -4
View File
@@ -6,6 +6,8 @@ node_modules/
# Build output
dist/
build/
# The site directory CI assembles (index.html + hz.js out of node_modules).
out/
*.tsbuildinfo
generated/
@@ -57,10 +59,37 @@ tmp/
# But keep important files for GitHub display
!menu-bar-preview.html
!dist/zoo-logo.svg
!dist/zoo-logo-mono.svg
!dist/zoo-logo-menubar.svg
# Keep showcase images for README
!docs/assets/*.png
!docs/assets/*.svg
!docs/assets/*.svg
AGENTS.md
CLAUDE.md
GEMINI.md
GROK.md
QWEN.md
# ── Logo assets ──────────────────────────────────────────────────────────
# Track the RENDERED image assets (png/ico/svg) in dist/ so every size is
# browsable in git for easy reference. The npm package ships the full dist
# (images + compiled JS) — CI regenerates it on publish (build runs generate).
# Compiled JS/maps stay OUT of git (regenerated, churny).
!dist/
!dist/**/
!dist/**/*.png
!dist/**/*.ico
!dist/**/*.svg
!dist/*.svg
dist/**/*.js
dist/**/*.d.ts
dist/**/*.map
dist/*.js
dist/*.d.ts
.npmrc
# pnpm only — pnpm-lock.yaml is the tracked lockfile.
package-lock.json
yarn.lock
bun.lockb
+15
View File
@@ -0,0 +1,15 @@
# Canonical caller — every knob lives in /hanzo.yml, none here.
# Runs on the git-runner fleet at git.hanzo.ai, the only pool serving these
# labels. github.com resolves only .github/workflows and has no runner for
# them, so a caller placed there is a gate that cannot be scheduled.
name: CI/CD
on:
push:
branches: [main]
tags: ['v*']
pull_request:
workflow_dispatch:
jobs:
cicd:
uses: hanzoai/ci/.hanzo/workflows/build.yml@v1
secrets: inherit
+75
View File
@@ -0,0 +1,75 @@
name: deploy
# logo.hanzo.ai is a SITE, not an App: a static export with no compute. It ships on the
# Sites plane. Not GitHub Pages, not Cloudflare Pages, and not an image — baking
# files that never execute into a container so a Go binary can serve them means an
# image, a push, a tag pinned in a CR and running pods, all to hand back bytes.
#
# build -> out
# -> sitedeploy
#
# The publish mechanics are NOT here. They are bin/sitedeploy in hanzoai/ci,
# reached through its action, and every static surface in the fleet calls that one
# script — ensure-project, enqueue, presigned per-file upload, complete. A
# contract transcribed per repo is a contract that drifts, and this file used to
# carry its own copy of every call. It also SELF-PROVISIONS: the project is
# created idempotently before the enqueue, so committing this file is enough to
# stand the site up.
#
# The one secret is HANZO_DEPLOY_TOKEN, which identifies us rather than granting
# storage. It is set ON THE FORGE: this file lives under .hanzo/workflows/, which
# only git.hanzo.ai reads, so GitHub's secret store is not in this path at all.
on:
push:
branches: [main]
paths: ['index.html', 'package.json', 'pnpm-lock.yaml', '.hanzo/workflows/deploy.yml']
workflow_dispatch:
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
jobs:
site:
# NOT ubuntu-latest. This file lives in .hanzo/workflows, which GitHub never
# reads — git.hanzo.ai is the only thing that can run it, and its fleet
# advertises hanzo-build-linux-amd64. An unmatched label is not an error here,
# it is silence: the job queues until the 24h timeout and nothing says so.
runs-on: hanzo-build-linux-amd64
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
# index.html is the whole page — one self-contained file, inline CSS, no
# local subresources — so there is nothing to compile. What IS assembled is
# the site directory: the page plus hz.js, the script-tag form of
# @hanzo/event. hz.js is COPIED OUT OF node_modules, never vendored and
# never loaded from a public CDN: the lockfile pins which version ships and
# the page keeps its only script same-origin.
- name: assemble the site
run: |
set -euo pipefail
corepack enable
pnpm install --frozen-lockfile
rm -rf out && mkdir -p out
cp index.html out/index.html
cp node_modules/@hanzo/event/hz.js out/hz.js
test -s out/hz.js || { echo "::error::@hanzo/event/hz.js missing from the install"; exit 1; }
echo "assembled $(find out -type f | wc -l) files"
# ONE credential. The 202 carries a prefix-scoped, 30-minute presigned POST
# grant, so nothing here holds a bucket key. Do NOT add SITES_S3_* — that is
# the standing shared-bucket credential the grant replaced, and it could
# overwrite every other org's site.
- name: Publish to the Sites plane
uses: hanzoai/ci/.github/actions/sitedeploy@v1
with:
slug: logo
dir: out
env:
HANZO_DEPLOY_TOKEN: ${{ secrets.HANZO_DEPLOY_TOKEN }}
+51
View File
@@ -0,0 +1,51 @@
name: publish
# Publishes @hanzo/logo when the version in package.json changes on main.
# A version already on the registry is skipped, so re-runs are safe.
on:
push:
branches: [main]
paths: ['package.json']
workflow_dispatch:
permissions:
contents: read
jobs:
publish:
runs-on: hanzo-build-linux-amd64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org/
- name: Skip if already published
id: check
run: |
NAME=$(jq -r .name package.json)
VER=$(jq -r .version package.json)
if curl -fsSL "https://registry.npmjs.org/$NAME/$VER" > /dev/null 2>&1; then
echo "$NAME@$VER already on registry; skipping"
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
# `test` typechecks and then builds — one command for both. The rendered
# images under dist/ are tracked but the compiled dist/*.js and dist/*.d.ts
# are gitignored, so publishing without this ships every icon and no entry
# point. corepack takes the pnpm version from packageManager.
- name: Test
if: steps.check.outputs.skip != 'true'
run: |
corepack enable
pnpm install --frozen-lockfile
pnpm test
- name: Publish
if: steps.check.outputs.skip != 'true'
# npm, not pnpm: pnpm publish adds git checks a CI checkout fails.
# --no-provenance: a token publish 422s without it.
run: npm publish --access public --no-provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+9
View File
@@ -0,0 +1,9 @@
# logo.hanzo.ai — the logo page, served by hanzoai/static (a Go binary on
# scratch) behind hanzoai/ingress. No nginx, no GitHub Pages, no CF Pages: the
# site is an image the operator runs like every other Hanzo surface.
#
# index.html is the whole site: one self-contained page, inline CSS, no scripts
# and no local subresources. There is nothing to build, so there is no build
# stage — a compile step here would exist only to look like the others.
FROM ghcr.io/hanzoai/static:v0.5.1
COPY index.html /public/index.html
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Hanzo AI Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+121
View File
@@ -0,0 +1,121 @@
# @hanzo/logo
Official Hanzo logo package. Provides SVG logos as inline strings, React components, a pure-CSS brand motion layer, and a build pipeline for generating favicons, Apple touch icons, dock icons, and menu bar assets. Published as `@hanzo/logo` on npm (bump `version` on `main` → CI publishes).
## Canon
The Hanzo mark is the **7-path shaded H** (67x67 viewBox): five body blocks + two
`class="shade"` accent slivers (#DDDDDD). `MARK_PATHS` in `src/logos.ts` is the one
source of the geometry — every variant (color/mono/white/menubar/animated/motion)
renders it. The flat 5-path H is only for tiny favicons (`getFaviconSVG`).
## How this ships
One way, and it runs on our own stack:
push -> github.com/hanzoai/logo where code lands
-> git.hanzo.ai/hanzoai/logo pull mirror; runs the CI
.hanzo/workflows/publish.yml publishes @hanzo/logo to npm
.hanzo/workflows/deploy.yml ships logo.hanzo.ai (Sites plane)
**Push to GitHub. The forge is a READ-ONLY pull mirror**`git push` to it is
refused with `Mirror Repository hanzoai/logo is read-only`, so the `native`
remote is for reading the mirror's state, never for writing. GitHub holds the
code; the forge holds the CI, because `.github/workflows/` is empty and every
build, check and publish lives under `.hanzo/workflows/`, which only the forge
reads. Both use GitHub Actions syntax, so a workflow moves between them by
changing directory and nothing else.
The consequence worth remembering: a push is not a deploy. Nothing runs until
the mirror pulls, and that lag is the forge's, not yours.
`publish.yml` is the SOLE publisher of `@hanzo/logo`. It fires when `version` in
`package.json` changes on `main`, skips a version already on the registry, and
runs `pnpm test` first — typecheck, then build. The rendered images under `dist/`
are tracked but the compiled `dist/*.js` and `dist/*.d.ts` are gitignored, so
publishing without that build ships every icon and no entry point. Needs
`NPM_TOKEN` as a forge secret.
### The site
`index.html` is the whole page: one self-contained file, inline CSS, no local
subresources. It is a SITE, not an App — nothing here executes — so it ships on
the Sites plane and there is no image, no CR and no pod:
assemble out/ -> POST /v1/projects/logo/deploy 202 + an upload grant
-> POST each file under the grant bytes skip the API
-> POST …/deployments/<id>/complete {status, keys}
This repo holds NO S3 credential. The grant is confined to this site's prefix and
expires in 30 minutes; deletion rides the `keys` manifest, because a write-only
grant cannot remove a file. The one secret is `HANZO_DEPLOY_TOKEN`, set ON THE
FORGE — GitHub's secret store is not in this path at all.
No GitHub Pages and no Cloudflare Pages: the repo used to push this page to a
`gh-pages` branch, Pages was never configured for it, and `logo.hanzo.ai` has
answered 404 from our own ingress ever since.
`out/` is ASSEMBLED, not built — `index.html` plus `hz.js`, the script-tag form of
`@hanzo/event`, copied out of `node_modules` at build time. That is the whole
reason `@hanzo/event` is a devDependency here: the lockfile decides which version
of the telemetry client ships, and the only script this page loads stays
same-origin instead of coming off a public CDN. `hz.js` posts to the same
`api.hanzo.ai/v1/event` as every bundled Hanzo surface.
**Known gap, upstream:** `hz.js` (through 0.3.11) cannot authenticate. It sends
no `Authorization` header and no `?ingest_key=` query, while `/v1/event` answers
`401 ingest_key_required` to an unattributed batch — so every event this page
sends is currently refused. The npm client supports `ingestKey` on both the fetch
and the beacon; the script form was never given the same. The fix belongs in
`hanzoai/ui` `pkgs/event/hz.js` (read `data-ingest-key`, ride it the way
`core.ts` already does), NOT in a bespoke script here — a second telemetry client
is exactly what `hz.js` exists to prevent.
## Stack
- TypeScript 5, ESM-only output
- React (optional peer dep >= 16.8)
- sharp (image generation for favicons/icons)
- Exports: `.` (main), `./logos` (raw SVG getters), `./motion` (CSS motion layer), `./react` (React components)
## Structure
```
src/
index.ts # Re-exports logos, motion, types, react
logos.ts # MARK_PATHS + SVG generators: getColorSVG, getMonoSVG, getWhiteSVG,
# getMenuBarSVG (currentColor), getFaviconSVG, getAnimatedSVG
motion.ts # MOTION_CSS + getMotionHTML/getMotionMarkup — intro flip + idle
# breathing + wordmark collapse, pure CSS, reduced-motion-safe.
# Framework-free strings for Svelte/Tamagui/plain HTML.
animated.ts # ANIMATED_SVG — self-contained interactive mark (fold-in → hover
# turn → press squash)
types.ts # LogoVariant (color|mono|white|favicon|animated), LogoFormat, LogoOptions
react.tsx # HanzoLogo (variant + `animated` motion-shell prop), HanzoFavicon
build.ts # CLI build script for generating all icon variants (brand-neutral,
# reads package.json `brand`)
dist/ # Rendered assets (tracked): svg/, favicon/, apple/, dock/, icons/,
# menubar/, slack/, showcase.html
```
## Commands
```bash
pnpm install # install dev deps
pnpm typecheck # tsc --noEmit
pnpm build # clean + compile + build CLI + generate assets
pnpm test # typecheck + build (the release gate)
```
## Usage
```tsx
import { HanzoLogo } from '@hanzo/logo/react'
<HanzoLogo size={64} />
<HanzoLogo animated /> // motion shell: flip + breathe + wordmark
<HanzoLogo animated wordmark="Lux" /> // white-label
import { getColorSVG, getAnimatedSVG } from '@hanzo/logo/logos'
import { MOTION_CSS, getMotionHTML } from '@hanzo/logo/motion' // Svelte/Tamagui
```
## Notes
- Variants: color (white fill + shade), mono (black fill + shade), white, menubar
(currentColor), favicon (simplified 5-path for 16-64px), animated (interactive)
- Release: bump the patch in `package.json`, merge to `main`. There is no tag.
- `CLAUDE.md`, `AGENTS.md`, `QWEN.md`, `GEMINI.md` are all symlinks to this file
-109
View File
@@ -1,109 +0,0 @@
# Zoo Logo Generator Makefile
# @zooai/logo - TypeScript logo generator for the Zoo ecosystem
.PHONY: help install build clean dev test publish generate-icons preview lint format typecheck all
# Default target
all: install build
# Help target - displays available commands
help:
@echo "Zoo Logo Generator - Make targets:"
@echo ""
@echo " make install - Install dependencies"
@echo " make build - Build TypeScript files"
@echo " make clean - Remove build artifacts and dependencies"
@echo " make dev - Run in development mode with watch"
@echo " make test - Run tests"
@echo " make lint - Run ESLint"
@echo " make format - Format code with Prettier"
@echo " make typecheck - Run TypeScript type checking"
@echo " make generate - Generate all logo variations and icons"
@echo " make preview - Open preview of generated logos"
@echo " make publish - Publish package to npm"
@echo " make all - Install and build (default)"
@echo ""
# Install dependencies
install:
npm install
# Build TypeScript files
build:
npm run build
# Clean build artifacts and dependencies
clean:
rm -rf dist node_modules package-lock.json
@echo "✓ Cleaned build artifacts and dependencies"
# Development mode with watch
dev:
npm run dev
# Run tests
test:
npm test
# Run linter
lint:
npm run lint
# Format code
format:
npm run format
# Type checking
typecheck:
npm run typecheck
# Generate all logo variations and icons
generate: build
npm run generate
# Preview generated logos (opens in default browser)
preview: generate
@if [ -f "menu-bar-preview.html" ]; then \
open menu-bar-preview.html; \
else \
echo "Generating preview..."; \
npm run preview; \
open menu-bar-preview.html; \
fi
# Publish to npm
publish: clean install build test
npm publish
# Watch for changes and rebuild
watch:
npm run watch
# Install global CLI
install-cli:
npm link
# Uninstall global CLI
uninstall-cli:
npm unlink
# Check for updates
check-updates:
npx npm-check-updates
# Update dependencies
update-deps:
npx npm-check-updates -u
npm install
# Generate documentation
docs:
npx typedoc src/index.ts
# Docker build (for CI/CD)
docker-build:
docker build -t zooai/logo .
# Run in Docker
docker-run:
docker run --rm -v $(PWD)/dist:/app/dist zooai/logo
+41 -109
View File
@@ -1,124 +1,56 @@
<div align="center">
<img src="docs/assets/logo-macos-dock.png" alt="Hanzo Logo" width="256" height="256">
<p align="center">
<img src=".github/hero.svg" alt="@hanzo/logo" width="100%" />
</p>
# @hanzo/logo
# Hanzo — Brand & Logo Assets
Official Hanzo logo package providing TypeScript/React components and utilities for consistent branding across the Hanzo ecosystem.
Official Hanzo brand marks in every format and size — SVG, PNG, multi-resolution ICO, favicons, app/dock/tray icons, and social cards. Installable via `@hanzo/logo`, browsable in `dist/`.
[![npm version](https://img.shields.io/npm/v/@hanzo/logo)](https://www.npmjs.com/package/@hanzo/logo)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
</div>
## Formats
## Logo Showcase
- `svg/` - Vector formats (preferred)
- `png/` - Raster formats (various sizes)
- `ico/` - Favicon formats
<div align="center">
<table>
<tr>
<th colspan="5">Hanzo Logo - Multiple Sizes</th>
</tr>
<tr>
<td align="center">
<img src="docs/assets/logo-32.png" width="32" height="32" alt="32px"><br>
<sub>32×32</sub>
</td>
<td align="center">
<img src="docs/assets/logo-64.png" width="64" height="64" alt="64px"><br>
<sub>64×64</sub>
</td>
<td align="center">
<img src="docs/assets/logo-128.png" width="128" height="128" alt="128px"><br>
<sub>128×128</sub>
</td>
<td align="center">
<img src="docs/assets/logo-256.png" width="256" height="256" alt="256px"><br>
<sub>256×256</sub>
</td>
<td align="center">
<img src="docs/assets/logo-512.png" width="512" height="512" alt="512px"><br>
<sub>512×512</sub>
</td>
</tr>
</table>
## Variants
<table>
<tr>
<th>macOS Dock Icon</th>
<th>Monochrome</th>
<th>Menu Bar Icon</th>
</tr>
<tr>
<td align="center">
<img src="docs/assets/logo-macos-dock.png" width="128" height="128" alt="macOS Dock"><br>
<sub>Rounded corners + black background</sub>
</td>
<td align="center">
<img src="docs/assets/logo-mono-128.png" width="128" height="128" alt="Monochrome"><br>
<sub>For single color displays</sub>
</td>
<td align="center">
<img src="docs/assets/logo-menubar-32.png" width="32" height="32" alt="Menu Bar"><br>
<sub>Solid filled for menu bars</sub>
</td>
</tr>
</table>
</div>
- Primary logo
- Wordmark
- Icon/Symbol
- Monochrome (light/dark)
## Features
## Usage
- 🎨 **SVG Logo Generation** - Programmatically generate Hanzo logos in multiple formats
- ⚛️ **React Components** - Ready-to-use React components with TypeScript support
- 🎯 **Multiple Variants** - Color, monochrome, and menu bar optimized versions
- 📦 **Icon Generation** - Generate icons in all required sizes for web and desktop apps
- 🖥️ **Platform Icons** - macOS dock icons, menu bar icons, and favicons
- 🔧 **TypeScript Native** - Full TypeScript support with proper type definitions
See [brand guidelines](../brand) for proper usage.
## Installation
## Download
```bash
npm install @hanzo/logo
# or
yarn add @hanzo/logo
# or
pnpm add @hanzo/logo
```
## Quick Start
### React Component
```tsx
import { HanzoLogo } from '@hanzo/logo/react';
<HanzoLogo size={128} />
<HanzoLogo variant="mono" size={64} />
<HanzoLogo variant="white" className="w-16 h-16" />
```
### SVG Generation
```ts
import { getColorSVG, getMonoSVG, getWhiteSVG } from '@hanzo/logo';
const colorLogo = getColorSVG();
const monoLogo = getMonoSVG();
const whiteLogo = getWhiteSVG();
```
### Icon Generation
```ts
import { generateIcon, getColorSVGCropped } from '@hanzo/logo';
// Generate macOS dock icon
const svg = getColorSVGCropped();
await generateIcon(svg, 'dock-icon.png', 512, true);
```
All assets available in this repository.
## License
MIT © Hanzo AI
All rights reserved. Usage requires permission.
## Support
## Animated & interactive
For issues, questions, or suggestions, visit [GitHub Issues](https://github.com/hanzoai/logo/issues)
The mark ships a self-contained, **pure-CSS animated SVG** — a load animation, a
hover flourish, and a press squash, with **zero JavaScript**. Inline it into the DOM
(not via `<img>`) for the hover/press interactions to fire. Honors `prefers-reduced-motion`.
```tsx
// React — one-line interactive embed
import { HanzoLogo } from '@hanzo/logo';
<HanzoLogo variant="animated" size={64} />
```
```ts
// Vanilla / any framework
import { getAnimatedSVG, getAnimatedDataUrl } from '@hanzo/logo';
el.innerHTML = getAnimatedSVG(); // interactive (inline in the DOM)
img.src = getAnimatedDataUrl(); // load animation only (fine in <img>)
```
```html
<!-- Raw SVG over CDN — load animation in <img>; inline the file for hover/press -->
<img src="https://unpkg.com/@hanzo/logo/svg/hanzo-mark-animated.svg" width="64" alt="" />
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="64" rx="8" fill="#000000"/>
<g transform="translate(8, 8) scale(0.716)">
<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>

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 995 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+17
View File
@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="640" viewBox="0 0 1280 640" role="img" aria-label="Hanzo brand assets">
<rect width="1280" height="640" fill="#0A0A0A"/>
<svg x="120" y="200" width="240" height="240" 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>
<text x="440" y="316" font-family="Inter,system-ui,-apple-system,sans-serif" font-size="94" font-weight="800" letter-spacing="-3" fill="#ffffff">Hanzo</text>
<text x="443" y="366" font-family="Inter,system-ui,sans-serif" font-size="27" fill="#ffffff" opacity=".62">Official brand marks — SVG, PNG, ICO, favicons &amp; app icons.</text>
<rect x="443" y="390" width="710" height="2" rx="1" fill="#ffffff" opacity=".85"/>
<text x="443" y="440" font-family="Inter,system-ui,sans-serif" font-size="23" font-weight="600" fill="#ffffff" opacity=".45">github.com/hanzoai</text>
<text x="1160" y="440" text-anchor="end" font-family="Inter,system-ui,sans-serif" font-size="23" font-weight="600" fill="#ffffff" opacity=".45">hanzo.ai</text>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

+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="currentColor"/>
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" 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.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="currentColor"/>
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 611 B

+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="#000000"/>
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="#222222"/>
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#000000"/>
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="#000000"/>
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#000000"/>
<path d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="#222222"/>
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 576 B

+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: 576 B

+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: 576 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+299
View File
@@ -0,0 +1,299 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hanzo Logo Assets Showcase</title>
<style>
:root {
--bg-dark: #111;
--bg-light: #f5f5f5;
--grid-bg: repeating-conic-gradient(#ccc 0% 25%, #fff 0% 50%) 50% / 20px 20px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1a1a1a;
color: #fff;
padding: 40px;
line-height: 1.6;
}
h1 { margin-bottom: 10px; font-size: 2rem; }
.subtitle { color: #888; margin-bottom: 40px; }
h2 {
margin: 40px 0 20px;
padding-bottom: 10px;
border-bottom: 1px solid #333;
color: #fff;
}
.section { margin-bottom: 60px; }
.grid {
display: flex;
flex-wrap: wrap;
gap: 20px;
align-items: flex-end;
}
.icon-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.icon-box {
display: flex;
align-items: center;
justify-content: center;
background: var(--grid-bg);
border-radius: 8px;
padding: 10px;
}
.icon-box.dark {
background: var(--bg-dark);
}
.icon-box.light {
background: var(--bg-light);
}
.icon-box img {
display: block;
max-width: 100%;
height: auto;
}
.label {
font-size: 12px;
color: #888;
text-align: center;
}
.og-preview {
max-width: 600px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
.og-preview img {
width: 100%;
height: auto;
display: block;
}
.menubar-demo {
background: linear-gradient(to bottom, #d4d4d4, #c8c8c8);
padding: 4px 12px;
border-radius: 6px;
display: inline-flex;
align-items: center;
gap: 8px;
}
.menubar-demo.dark {
background: linear-gradient(to bottom, #3a3a3a, #2a2a2a);
}
.dock-demo {
background: rgba(255,255,255,0.1);
backdrop-filter: blur(20px);
padding: 8px;
border-radius: 20px;
display: inline-flex;
gap: 8px;
align-items: flex-end;
}
.dock-demo img {
border-radius: 22%;
}
.timestamp {
position: fixed;
bottom: 20px;
right: 20px;
font-size: 12px;
color: #666;
}
</style>
</head>
<body>
<h1>🎨 Hanzo Logo Assets</h1>
<p class="subtitle">Every brand mark, format, and size.</p>
<section class="section">
<h2>📁 SVG Sources</h2>
<div class="grid">
<div class="icon-item">
<div class="icon-box dark" style="width:120px;height:120px;">
<img src="logo.svg" alt="Color Logo" style="width:100px;height:100px;">
</div>
<span class="label">logo.svg</span>
</div>
<div class="icon-item">
<div class="icon-box light" style="width:120px;height:120px;">
<img src="logo-mono.svg" alt="Mono Logo" style="width:100px;height:100px;">
</div>
<span class="label">logo-mono.svg</span>
</div>
<div class="icon-item">
<div class="icon-box dark" style="width:120px;height:120px;">
<img src="logo-white.svg" alt="White Logo" style="width:100px;height:100px;">
</div>
<span class="label">logo-white.svg</span>
</div>
<div class="icon-item">
<div class="icon-box" style="width:120px;height:120px;">
<img src="favicon.svg" alt="Favicon" style="width:100px;height:100px;">
</div>
<span class="label">favicon.svg</span>
</div>
</div>
</section>
<section class="section">
<h2>🖼️ Standard Icons (PNG)</h2>
<div class="grid">
<div class="icon-item">
<div class="icon-box dark"><img src="icons/logo-32.png" alt="32px"></div>
<span class="label">32px</span>
</div>
<div class="icon-item">
<div class="icon-box dark"><img src="icons/logo-64.png" alt="64px"></div>
<span class="label">64px</span>
</div>
<div class="icon-item">
<div class="icon-box dark"><img src="icons/logo-128.png" alt="128px"></div>
<span class="label">128px</span>
</div>
<div class="icon-item">
<div class="icon-box dark"><img src="icons/logo-256.png" alt="256px"></div>
<span class="label">256px</span>
</div>
</div>
</section>
<section class="section">
<h2>⭐ Favicons</h2>
<div class="grid">
<div class="icon-item">
<div class="icon-box"><img src="favicon/favicon-16.png" alt="16px"></div>
<span class="label">16px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="favicon/favicon-32.png" alt="32px"></div>
<span class="label">32px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="favicon/favicon-48.png" alt="48px"></div>
<span class="label">48px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="favicon/favicon-64.png" alt="64px"></div>
<span class="label">64px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="favicon/favicon-192.png" alt="192px"></div>
<span class="label">192px (Android)</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="favicon/favicon-512.png" alt="512px"></div>
<span class="label">512px (PWA)</span>
</div>
</div>
</section>
<section class="section">
<h2>🍎 Apple Touch Icons</h2>
<div class="grid">
<div class="icon-item">
<div class="icon-box"><img src="apple/apple-touch-icon-60.png" alt="60px"></div>
<span class="label">60px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="apple/apple-touch-icon-120.png" alt="120px"></div>
<span class="label">120px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="apple/apple-touch-icon-152.png" alt="152px"></div>
<span class="label">152px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="apple/apple-touch-icon-180.png" alt="180px"></div>
<span class="label">180px</span>
</div>
</div>
</section>
<section class="section">
<h2>🖥️ Dock Icons (macOS)</h2>
<div class="dock-demo">
<img src="dock/dock-64.png" alt="Dock 64" style="width:48px;height:48px;">
<img src="dock/dock-128.png" alt="Dock 128" style="width:64px;height:64px;">
</div>
<div class="grid" style="margin-top: 20px;">
<div class="icon-item">
<div class="icon-box"><img src="dock/dock-128.png" alt="128px"></div>
<span class="label">128px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="dock/dock-256.png" alt="256px"></div>
<span class="label">256px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="dock/dock-512.png" alt="512px"></div>
<span class="label">512px</span>
</div>
</div>
</section>
<section class="section">
<h2>📱 Menu Bar Icons</h2>
<p style="color:#888;margin-bottom:20px;">Template icons adapt to light/dark menu bar</p>
<div style="display:flex;gap:40px;flex-wrap:wrap;">
<div>
<p style="margin-bottom:10px;font-size:14px;">Light Menu Bar</p>
<div class="menubar-demo">
<img src="menubar/menubar-16.png" alt="Menu bar icon" style="width:16px;height:16px;">
<span style="font-size:13px;color:#333;">Hanzo</span>
</div>
</div>
<div>
<p style="margin-bottom:10px;font-size:14px;">Dark Menu Bar</p>
<div class="menubar-demo dark">
<img src="menubar/iconTemplate.png" alt="Menu bar icon" style="width:16px;height:16px;filter:invert(1);">
<span style="font-size:13px;color:#fff;">Hanzo</span>
</div>
</div>
</div>
<div class="grid" style="margin-top:20px;">
<div class="icon-item">
<div class="icon-box light"><img src="menubar/menubar-16.png" alt="16px"></div>
<span class="label">16px</span>
</div>
<div class="icon-item">
<div class="icon-box light"><img src="menubar/menubar-22.png" alt="22px"></div>
<span class="label">22px</span>
</div>
<div class="icon-item">
<div class="icon-box light"><img src="menubar/menubar-16@2x.png" alt="16@2x" style="width:32px;"></div>
<span class="label">16@2x (32px)</span>
</div>
</div>
</section>
<section class="section">
<h2>📣 Open Graph / Social Images</h2>
<div style="display:flex;flex-direction:column;gap:30px;">
<div>
<p style="margin-bottom:10px;font-size:14px;">OG Image (1200×630)</p>
<div class="og-preview">
<img src="og/og-image.png" alt="OG Image">
</div>
</div>
<div>
<p style="margin-bottom:10px;font-size:14px;">Twitter Card (1200×600)</p>
<div class="og-preview">
<img src="og/twitter-card.png" alt="Twitter Card">
</div>
</div>
<div>
<p style="margin-bottom:10px;font-size:14px;">Square (1200×1200)</p>
<div class="og-preview" style="max-width:400px;">
<img src="og/og-square.png" alt="OG Square">
</div>
</div>
</div>
</section>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

+19
View File
@@ -0,0 +1,19 @@
# Canonical CI/CD config for hanzoai/logo — the one file both the hanzoai/ci
# reusable (.hanzo/workflows/cicd.yml) and platform.hanzo.ai read.
#
# GATE ONLY: @hanzo/logo is an npm package, and the logo site has its own lanes
# already (.hanzo/workflows/publish.yml, deploy.yml). A second image build for
# the same Dockerfile on the same push buys nothing.
#
# `pnpm test` here is `pnpm typecheck && pnpm build` — and the build half is the
# real gate, because this package's output is GENERATED: `pnpm generate` runs
# the compiled dist/build.js, which bakes the tuner's animation states into the
# svg + animated.ts that every consumer imports. A break in the generator is a
# break in the artifact, and it only shows when the generator is actually run.
test:
- name: build
run: |
set -e
corepack enable
pnpm install --frozen-lockfile
pnpm test
+133
View File
@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hanzo AI Logo</title>
<meta name="description" content="Hanzo AI official logos and marks">
<style>
:root {
--bg: #0a0a0a;
--fg: #fafafa;
--muted: #71717a;
--accent: #6366f1;
--border: #27272a;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg);
color: var(--fg);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
}
.container { max-width: 800px; text-align: center; }
h1 {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
letter-spacing: -0.02em;
}
.tagline {
font-size: 1.25rem;
color: var(--muted);
margin-bottom: 3rem;
}
.logo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 2rem;
margin-bottom: 3rem;
}
.logo-card {
background: var(--border);
border-radius: 12px;
padding: 2rem;
display: flex;
align-items: center;
justify-content: center;
min-height: 150px;
}
.logo-card.light { background: #fafafa; }
.logo-placeholder {
font-size: 3rem;
font-weight: 700;
letter-spacing: -0.05em;
}
.formats {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 2rem;
}
.format {
padding: 0.5rem 1rem;
background: var(--border);
border-radius: 6px;
font-size: 0.875rem;
color: var(--muted);
}
.links { display: flex; gap: 1rem; justify-content: center; }
a {
color: var(--accent);
text-decoration: none;
padding: 0.75rem 1.5rem;
border: 1px solid var(--border);
border-radius: 8px;
transition: all 0.2s;
}
a:hover {
background: var(--accent);
color: var(--bg);
border-color: var(--accent);
}
.footer {
position: fixed;
bottom: 2rem;
color: var(--muted);
font-size: 0.875rem;
}
</style>
</head>
<body>
<div class="container">
<h1>Hanzo AI Logo</h1>
<p class="tagline">Official logos and marks</p>
<div class="logo-grid">
<div class="logo-card">
<span class="logo-placeholder" style="color: var(--fg);">H</span>
</div>
<div class="logo-card light">
<span class="logo-placeholder" style="color: #0a0a0a;">H</span>
</div>
</div>
<div class="formats">
<span class="format">SVG</span>
<span class="format">PNG</span>
<span class="format">ICO</span>
<span class="format">PDF</span>
</div>
<div class="links">
<a href="https://github.com/hanzoai/logo">Download All</a>
<a href="https://github.com/hanzoai/brand">Brand Guidelines</a>
</div>
</div>
<div class="footer">&copy; 2025 Hanzo AI. All rights reserved.</div>
<!-- hz.js is the script-tag form of @hanzo/event — the SAME client and the
SAME wire as every bundled Hanzo surface, POSTing to api.hanzo.ai/v1/event.
This page has no bundler, so it gets the script rather than the module.
It is served from /hz.js, not a public CDN: CI copies it out of
node_modules at build time, so the lockfile decides which version ships and
the only script this page loads is same-origin. -->
<script async src="/hz.js" data-product="logo" data-host="https://api.hanzo.ai"></script>
</body>
</html>
+5 -5
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Zoo Logo Menu Bar Preview</title>
<title>Hanzo Logo Menu Bar Preview</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
@@ -127,7 +127,7 @@
</style>
</head>
<body>
<h1>Zoo Logo Menu Bar Preview</h1>
<h1>Hanzo Logo Menu Bar Preview</h1>
<h2>See how the monochrome logo looks in the Mac menu bar</h2>
<div class="preview-section">
@@ -141,7 +141,7 @@
<svg class="menu-icon" viewBox="0 0 16 16" fill="currentColor">
<path d="M3 7h10v2H3z M6 3h4v10H6z"/>
</svg>
<!-- Our Zoo icon -->
<!-- Our Hanzo icon -->
<svg id="menuIconDark" class="menu-icon" viewBox="98 102 812 820"></svg>
<!-- More system icons -->
<svg class="menu-icon" viewBox="0 0 16 16" fill="currentColor">
@@ -161,7 +161,7 @@
<svg class="menu-icon" viewBox="0 0 16 16" fill="currentColor">
<path d="M3 7h10v2H3z M6 3h4v10H6z"/>
</svg>
<!-- Our Zoo icon -->
<!-- Our Hanzo icon -->
<svg id="menuIconLight" class="menu-icon" viewBox="98 102 812 820"></svg>
<!-- More system icons -->
<svg class="menu-icon" viewBox="0 0 16 16" fill="currentColor">
@@ -290,7 +290,7 @@
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'zoo-logo-mono.svg';
a.download = 'hanzo-logo-mono.svg';
a.click();
}
-634
View File
@@ -1,634 +0,0 @@
{
"name": "@hanzo/logo",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@hanzo/logo",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"sharp": "^0.34.4"
},
"devDependencies": {
"@types/minimatch": "^5.1.2",
"@types/node": "^20.19.17",
"@types/react": "^18.3.24",
"react": "^18.3.1",
"typescript": "^5.9.2"
},
"peerDependencies": {
"react": ">=16.8.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
}
}
},
"node_modules/@emnapi/runtime": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
"integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@img/colour": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz",
"integrity": "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.3"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz",
"integrity": "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.3"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz",
"integrity": "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz",
"integrity": "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz",
"integrity": "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz",
"integrity": "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz",
"integrity": "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==",
"cpu": [
"ppc64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz",
"integrity": "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==",
"cpu": [
"s390x"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz",
"integrity": "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz",
"integrity": "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz",
"integrity": "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz",
"integrity": "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.3"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz",
"integrity": "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.3"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz",
"integrity": "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==",
"cpu": [
"ppc64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.3"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz",
"integrity": "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==",
"cpu": [
"s390x"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.3"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz",
"integrity": "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.3"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz",
"integrity": "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.3"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz",
"integrity": "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.3"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz",
"integrity": "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.5.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz",
"integrity": "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz",
"integrity": "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz",
"integrity": "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@types/minimatch": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.19.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz",
"integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.24",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz",
"integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"license": "MIT"
},
"node_modules/detect-libc": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz",
"integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sharp": {
"version": "0.34.4",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz",
"integrity": "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@img/colour": "^1.0.0",
"detect-libc": "^2.1.0",
"semver": "^7.7.2"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.4",
"@img/sharp-darwin-x64": "0.34.4",
"@img/sharp-libvips-darwin-arm64": "1.2.3",
"@img/sharp-libvips-darwin-x64": "1.2.3",
"@img/sharp-libvips-linux-arm": "1.2.3",
"@img/sharp-libvips-linux-arm64": "1.2.3",
"@img/sharp-libvips-linux-ppc64": "1.2.3",
"@img/sharp-libvips-linux-s390x": "1.2.3",
"@img/sharp-libvips-linux-x64": "1.2.3",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.3",
"@img/sharp-libvips-linuxmusl-x64": "1.2.3",
"@img/sharp-linux-arm": "0.34.4",
"@img/sharp-linux-arm64": "0.34.4",
"@img/sharp-linux-ppc64": "0.34.4",
"@img/sharp-linux-s390x": "0.34.4",
"@img/sharp-linux-x64": "0.34.4",
"@img/sharp-linuxmusl-arm64": "0.34.4",
"@img/sharp-linuxmusl-x64": "0.34.4",
"@img/sharp-wasm32": "0.34.4",
"@img/sharp-win32-arm64": "0.34.4",
"@img/sharp-win32-ia32": "0.34.4",
"@img/sharp-win32-x64": "0.34.4"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD",
"optional": true
},
"node_modules/typescript": {
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
}
}
}
+26 -9
View File
@@ -1,6 +1,7 @@
{
"name": "@hanzo/logo",
"version": "1.0.1",
"version": "1.0.14",
"packageManager": "pnpm@11.17.0",
"description": "Official Hanzo logo package with React components and utilities",
"type": "module",
"main": "dist/index.js",
@@ -8,7 +9,8 @@
"types": "dist/index.d.ts",
"files": [
"dist",
"README.md"
"README.md",
"svg"
],
"exports": {
".": {
@@ -16,6 +18,16 @@
"import": "./dist/index.js",
"require": "./dist/index.js"
},
"./logos": {
"types": "./dist/logos.d.ts",
"import": "./dist/logos.js",
"require": "./dist/logos.js"
},
"./motion": {
"types": "./dist/motion.d.ts",
"import": "./dist/motion.js",
"require": "./dist/motion.js"
},
"./react": {
"types": "./dist/react.d.ts",
"import": "./dist/react.js",
@@ -23,19 +35,19 @@
}
},
"scripts": {
"build": "npm run clean && npm run compile && npm run build:cli",
"build": "pnpm clean && pnpm compile && pnpm build:cli && pnpm generate",
"build:cli": "tsc src/build.ts --outDir dist --module esnext --target es2020 --moduleResolution node --esModuleInterop --skipLibCheck",
"compile": "tsc",
"clean": "rm -rf dist",
"dev": "tsc --watch",
"generate": "node dist/build.js",
"preview": "npm run generate && open menu-bar-preview.html",
"preview": "pnpm generate && open menu-bar-preview.html",
"lint": "eslint src --ext .ts,.tsx",
"format": "prettier --write 'src/**/*.{ts,tsx,json}'",
"typecheck": "tsc --noEmit",
"test": "npm run typecheck && npm run build",
"test": "pnpm typecheck && pnpm build",
"watch": "tsc --watch",
"prepublishOnly": "npm run build"
"prepublishOnly": "pnpm build"
},
"keywords": [
"hanzo",
@@ -47,14 +59,13 @@
],
"author": "Hanzo AI",
"license": "MIT",
"dependencies": {
"sharp": "^0.34.4"
},
"devDependencies": {
"@hanzo/event": "^0.3.11",
"@types/minimatch": "^5.1.2",
"@types/node": "^20.19.17",
"@types/react": "^18.3.24",
"react": "^18.3.1",
"sharp": "^0.34.4",
"typescript": "^5.9.2"
},
"peerDependencies": {
@@ -71,5 +82,11 @@
},
"publishConfig": {
"access": "public"
},
"brand": {
"name": "Hanzo",
"tagline": "Official brand marks — SVG, PNG, ICO, favicons & app icons.",
"github": "hanzoai",
"domain": "hanzo.ai"
}
}
+421
View File
@@ -0,0 +1,421 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
'@hanzo/event':
specifier: ^0.3.11
version: 0.3.11(react@18.3.1)
'@types/minimatch':
specifier: ^5.1.2
version: 5.1.2
'@types/node':
specifier: ^20.19.17
version: 20.19.43
'@types/react':
specifier: ^18.3.24
version: 18.3.31
react:
specifier: ^18.3.1
version: 18.3.1
sharp:
specifier: ^0.34.4
version: 0.34.5
typescript:
specifier: ^5.9.2
version: 5.9.3
packages:
'@emnapi/runtime@1.11.3':
resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
'@hanzo/event@0.3.11':
resolution: {integrity: sha512-HLdDWZPPonel3HPgmDOQ+LqXPV8hPEMZFGnXlFT2gPQq+MxcHgAY482Y9jHCCKwLFo8v805FZ81ylTlBAl5RVQ==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
react:
optional: true
'@img/colour@1.1.0':
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
'@img/sharp-darwin-arm64@0.34.5':
resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.34.5':
resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-darwin-arm64@1.2.4':
resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.2.4':
resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.2.4':
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.2.4':
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.2.4':
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-riscv64@1.2.4':
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.2.4':
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.2.4':
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.34.5':
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.34.5':
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.34.5':
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-riscv64@0.34.5':
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.34.5':
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.34.5':
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.34.5':
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.34.5':
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.34.5':
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
'@img/sharp-win32-arm64@0.34.5':
resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [win32]
'@img/sharp-win32-ia32@0.34.5':
resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.34.5':
resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [win32]
'@types/minimatch@5.1.2':
resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
'@types/node@20.19.43':
resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==}
'@types/prop-types@15.7.15':
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
'@types/react@18.3.31':
resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==}
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
semver@7.8.5:
resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
engines: {node: '>=10'}
hasBin: true
sharp@0.34.5:
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
snapshots:
'@emnapi/runtime@1.11.3':
dependencies:
tslib: 2.8.1
optional: true
'@hanzo/event@0.3.11(react@18.3.1)':
optionalDependencies:
react: 18.3.1
'@img/colour@1.1.0': {}
'@img/sharp-darwin-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.2.4
optional: true
'@img/sharp-darwin-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.2.4
optional: true
'@img/sharp-libvips-darwin-arm64@1.2.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.2.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.2.4':
optional: true
'@img/sharp-libvips-linux-arm@1.2.4':
optional: true
'@img/sharp-libvips-linux-ppc64@1.2.4':
optional: true
'@img/sharp-libvips-linux-riscv64@1.2.4':
optional: true
'@img/sharp-libvips-linux-s390x@1.2.4':
optional: true
'@img/sharp-libvips-linux-x64@1.2.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
optional: true
'@img/sharp-linux-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.2.4
optional: true
'@img/sharp-linux-arm@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.2.4
optional: true
'@img/sharp-linux-ppc64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.2.4
optional: true
'@img/sharp-linux-riscv64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-riscv64': 1.2.4
optional: true
'@img/sharp-linux-s390x@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.2.4
optional: true
'@img/sharp-linux-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.2.4
optional: true
'@img/sharp-linuxmusl-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
optional: true
'@img/sharp-linuxmusl-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
optional: true
'@img/sharp-wasm32@0.34.5':
dependencies:
'@emnapi/runtime': 1.11.3
optional: true
'@img/sharp-win32-arm64@0.34.5':
optional: true
'@img/sharp-win32-ia32@0.34.5':
optional: true
'@img/sharp-win32-x64@0.34.5':
optional: true
'@types/minimatch@5.1.2': {}
'@types/node@20.19.43':
dependencies:
undici-types: 6.21.0
'@types/prop-types@15.7.15': {}
'@types/react@18.3.31':
dependencies:
'@types/prop-types': 15.7.15
csstype: 3.2.3
csstype@3.2.3: {}
detect-libc@2.1.2: {}
js-tokens@4.0.0: {}
loose-envify@1.4.0:
dependencies:
js-tokens: 4.0.0
react@18.3.1:
dependencies:
loose-envify: 1.4.0
semver@7.8.5: {}
sharp@0.34.5:
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
semver: 7.8.5
optionalDependencies:
'@img/sharp-darwin-arm64': 0.34.5
'@img/sharp-darwin-x64': 0.34.5
'@img/sharp-libvips-darwin-arm64': 1.2.4
'@img/sharp-libvips-darwin-x64': 1.2.4
'@img/sharp-libvips-linux-arm': 1.2.4
'@img/sharp-libvips-linux-arm64': 1.2.4
'@img/sharp-libvips-linux-ppc64': 1.2.4
'@img/sharp-libvips-linux-riscv64': 1.2.4
'@img/sharp-libvips-linux-s390x': 1.2.4
'@img/sharp-libvips-linux-x64': 1.2.4
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
'@img/sharp-linux-arm': 0.34.5
'@img/sharp-linux-arm64': 0.34.5
'@img/sharp-linux-ppc64': 0.34.5
'@img/sharp-linux-riscv64': 0.34.5
'@img/sharp-linux-s390x': 0.34.5
'@img/sharp-linux-x64': 0.34.5
'@img/sharp-linuxmusl-arm64': 0.34.5
'@img/sharp-linuxmusl-x64': 0.34.5
'@img/sharp-wasm32': 0.34.5
'@img/sharp-win32-arm64': 0.34.5
'@img/sharp-win32-ia32': 0.34.5
'@img/sharp-win32-x64': 0.34.5
tslib@2.8.1:
optional: true
typescript@5.9.3: {}
undici-types@6.21.0: {}
+9
View File
@@ -0,0 +1,9 @@
# Install scripts run only for packages listed here.
allowBuilds:
sharp: true
# Our own scopes skip pnpm's new-release quarantine. Scope glob, not a version
# pin: pnpm rewrites version entries on every bump and breaks --frozen-lockfile.
minimumReleaseAgeExclude:
- '@hanzo/*'
- '@hanzogui/*'
+5
View File
@@ -0,0 +1,5 @@
// GENERATED from the @hanzo logo tuner bake, upgraded to the canonical
// 7-path shaded H (5 body blocks + 2 shade slivers). Source of truth for the
// interaction layer: the tuner's GEN.hanzo() + the shared interaction layer.
// Origami fold-in → hover: one 3-D turn → press: squash. Pure CSS, no JS.
export const ANIMATED_SVG = "<svg role=\"img\" aria-label=\"Hanzo\" viewBox=\"0 0 67 67\" xmlns=\"http://www.w3.org/2000/svg\">\n<!-- Origami fold-in → hover: one 3-D turn → press: squash. Pure CSS, no JS. Canonical 7-path shaded H. -->\n\n<style>.o{fill:#fff;opacity:0;transform-box:fill-box;transform-origin:left center;animation:fold 0.42s cubic-bezier(.16,1,.3,1) forwards}\n.s{fill:#ddd}\n@keyframes fold{from{opacity:0;transform:perspective(320px) rotateY(90deg)}to{opacity:1;transform:none}}\n.o0{animation-delay:0.00s}.o1{animation-delay:0.06s}.o2{animation-delay:0.12s}.o3{animation-delay:0.18s}.o4{animation-delay:0.24s}\n/* interaction — pure CSS, no JS: hover = one graceful turn (returns to rest), press = squash */\n.hanzo-hov{transform-box:view-box;transform-origin:33.5px 33.5px}\n.hanzo-prs{transform-box:view-box;transform-origin:33.5px 33.5px;transition:transform .22s cubic-bezier(.22,1,.36,1)}\nsvg:hover .hanzo-hov{animation:hanzoTurn 0.85s cubic-bezier(.45,0,.2,1)}\nsvg:active .hanzo-prs{transform:scale(.92)}\n@keyframes hanzoTurn{0%{transform:perspective(300px) rotateY(0)}100%{transform:perspective(300px) rotateY(360deg)}}\n@media (prefers-reduced-motion:reduce){.hanzo-hov,.hanzo-prs{animation:none!important;transform:none!important}.o{opacity:1;animation:none;transform:none}}\n</style>\n<g class=\"hanzo-hov\"><g class=\"hanzo-prs\">\n<path class=\"o o0\" d=\"M22.21 0H0V22.3184H22.21V0Z\"/>\n<path class=\"o o1\" d=\"M66.7198 0H44.5098V22.3184H66.7198V0Z\"/>\n<path class=\"o o1 s\" d=\"M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z\"/>\n<path class=\"o o2\" d=\"M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z\"/>\n<path class=\"o o3\" d=\"M22.21 67V44.6369H0V67H22.21Z\"/>\n<path class=\"o o3 s\" d=\"M0 44.6369L22.21 46.8285V44.6369H0Z\"/>\n<path class=\"o o4\" d=\"M66.7198 67V44.6369H44.5098V67H66.7198Z\"/>\n</g></g>\n</svg>";
+624 -50
View File
@@ -1,45 +1,94 @@
#!/usr/bin/env node
/**
* Hanzo Logo Build Script
* Generates all required icons for Hanzo ecosystem
* Logo Build Script
* Generates every brand icon, favicon, and social asset from the canonical
* mark. Brand identity (name, tagline, github, domain) is read from
* package.json "brand" — this file names no brand, so it is byte-identical
* across every logo package.
*/
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import sharp from 'sharp';
import { getColorSVG, getMonoSVG, getMenuBarSVG } from './logos.js';
import { getColorSVG, getMonoSVG, getMenuBarSVG, getFaviconSVG, getWhiteSVG } from './logos.js';
// Namespace view of the same module — lets the build pick up OPTIONAL marks
// (e.g. a wordmark) that only some brands define, without a hard import.
import * as marks from './logos.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function generateIcon(svgString: string, outputPath: string, size: number, addBackground = false): Promise<void> {
interface IconConfig {
name: string;
size: number;
svg?: string;
addBackground?: boolean;
bgColor?: string;
cornerRadius?: number;
aspectRatio?: { width: number; height: number };
}
async function generateIcon(
svgString: string,
outputPath: string,
size: number,
options: {
addBackground?: boolean;
bgColor?: string;
cornerRadius?: number;
aspectRatio?: { width: number; height: number };
} = {}
): Promise<void> {
const dir = path.dirname(outputPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (addBackground) {
// For dock icons, add rounded square black background with more padding
const logoSize = Math.floor(size * 0.65); // Reduced from 0.8 for more space
const padding = Math.floor((size - logoSize) / 2);
const cornerRadius = Math.floor(size * 0.22); // macOS-style corner radius
const { addBackground = false, bgColor = 'black', cornerRadius, aspectRatio } = options;
const bgSvg = `<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="${size}" height="${size}" rx="${cornerRadius}" ry="${cornerRadius}" fill="black"/>
if (aspectRatio) {
// For non-square images like OG graph
const { width, height } = aspectRatio;
const logoSize = Math.min(width, height) * 0.4;
const logoX = Math.floor((width - logoSize) / 2);
const logoY = Math.floor((height - logoSize) / 2);
const radius = cornerRadius ?? 0;
const bgSvg = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="${width}" height="${height}" rx="${radius}" ry="${radius}" fill="${bgColor}"/>
</svg>`;
// Create background layer
const bg = await sharp(Buffer.from(bgSvg)).png().toBuffer();
const logo = await sharp(Buffer.from(svgString))
.resize(Math.floor(logoSize), Math.floor(logoSize))
.png()
.toBuffer();
// Create logo layer
await sharp(bg)
.composite([{
input: logo,
top: logoY,
left: logoX
}])
.toFile(outputPath);
} else if (addBackground) {
// For dock icons, add rounded square background with padding
const logoSize = Math.floor(size * 0.65);
const padding = Math.floor((size - logoSize) / 2);
const radius = cornerRadius ?? Math.floor(size * 0.22);
const bgSvg = `<svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="${size}" height="${size}" rx="${radius}" ry="${radius}" fill="${bgColor}"/>
</svg>`;
const bg = await sharp(Buffer.from(bgSvg)).png().toBuffer();
const logo = await sharp(Buffer.from(svgString))
.resize(logoSize, logoSize)
.png()
.toBuffer();
// Composite them together
await sharp(bg)
.composite([{
input: logo,
@@ -53,63 +102,588 @@ async function generateIcon(svgString: string, outputPath: string, size: number,
.png()
.toFile(outputPath);
}
console.log(`${path.relative(process.cwd(), outputPath)} (${size}×${size})`);
console.log(`${path.relative(process.cwd(), outputPath)} (${options.aspectRatio ? `${options.aspectRatio.width}×${options.aspectRatio.height}` : `${size}×${size}`})`);
}
interface IconConfig {
name: string;
size: number;
svg?: string;
// writeIco packs several PNGs into ONE multi-resolution .ico — the format
// Windows favicons, taskbar, and .desktop icons want. Modern ICO embeds the PNG
// bytes verbatim (no BMP re-encode), so this is a pure container writer over the
// PNGs generateIcon already produced: no new dependency, no image re-processing.
// Sizes are read from each source PNG so the directory entry is always correct.
async function writeIco(pngPaths: string[], outputPath: string): Promise<void> {
const images = await Promise.all(
pngPaths.map(async (p) => {
const data = fs.readFileSync(p);
// PNG IHDR width/height are big-endian uint32 at byte offsets 16/20.
const w = data.readUInt32BE(16);
const h = data.readUInt32BE(20);
return { data, w, h };
}),
);
const count = images.length;
const header = Buffer.alloc(6);
header.writeUInt16LE(0, 0); // reserved
header.writeUInt16LE(1, 2); // type 1 = icon
header.writeUInt16LE(count, 4);
const dir = Buffer.alloc(16 * count);
let offset = 6 + 16 * count; // image data starts after header + directory
images.forEach((img, i) => {
const e = 16 * i;
dir.writeUInt8(img.w >= 256 ? 0 : img.w, e + 0); // 0 encodes 256
dir.writeUInt8(img.h >= 256 ? 0 : img.h, e + 1);
dir.writeUInt8(0, e + 2); // palette count (0 = truecolor)
dir.writeUInt8(0, e + 3); // reserved
dir.writeUInt16LE(1, e + 4); // color planes
dir.writeUInt16LE(32, e + 6); // bits per pixel
dir.writeUInt32LE(img.data.length, e + 8); // image size
dir.writeUInt32LE(offset, e + 12); // image offset
offset += img.data.length;
});
const outDir = path.dirname(outputPath);
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
fs.writeFileSync(outputPath, Buffer.concat([header, dir, ...images.map((i) => i.data)]));
const dims = images.map((i) => `${i.w}`).join('/');
console.log(`${path.relative(process.cwd(), outputPath)} (${dims})`);
}
// Brand identity for the README hero — read from package.json "brand". Falls
// back sensibly so a repo without the block still renders (never the old
// "logo" placeholder).
interface Brand { name: string; tagline: string; github: string; domain: string; }
function readBrand(): Brand {
let pkg: any = {};
try { pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); } catch { /* defaults */ }
const b = pkg.brand || {};
const scope = (pkg.name || '').replace(/^@/, '').split('/')[0]; // package scope
return {
name: b.name || (scope ? scope.charAt(0).toUpperCase() + scope.slice(1) : 'Brand'),
tagline: b.tagline || 'Official brand marks — SVG, PNG, ICO, favicons & app icons.',
github: b.github || scope,
domain: b.domain || '',
};
}
// Read once at load — every display string below is driven by this, so the
// file carries no brand name of its own.
const BRAND = readBrand();
// generateHero composes the README hero card (1280×640): the brand's own white
// mark + the real brand NAME (not the "logo" placeholder the template shipped)
// + a full tagline + github/domain footer. Written to .github/hero.svg, which
// the README embeds. One template, every brand — driven by readBrand().
function generateHero(whiteMark: string): void {
const brand = readBrand();
const esc = (s: string) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
// Embed the brand mark as a nested, positioned SVG. Strip any existing
// width/height on the mark's root tag first (some brand marks set them,
// which collide with the x/y/width/height we inject and corrupt the SVG);
// the viewBox is preserved so scaling stays correct.
const cleaned = whiteMark.replace(/^<svg\b[^>]*>/, (tag) => tag.replace(/\s(?:width|height)="[^"]*"/g, ''));
const mark = cleaned.replace(/^<svg /, '<svg x="120" y="200" width="240" height="240" ');
const hero = `<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="640" viewBox="0 0 1280 640" role="img" aria-label="${esc(brand.name)} brand assets">
<rect width="1280" height="640" fill="#0A0A0A"/>
${mark}
<text x="440" y="316" font-family="Inter,system-ui,-apple-system,sans-serif" font-size="94" font-weight="800" letter-spacing="-3" fill="#ffffff">${esc(brand.name)}</text>
<text x="443" y="366" font-family="Inter,system-ui,sans-serif" font-size="27" fill="#ffffff" opacity=".62">${esc(brand.tagline)}</text>
<rect x="443" y="390" width="710" height="2" rx="1" fill="#ffffff" opacity=".85"/>
<text x="443" y="440" font-family="Inter,system-ui,sans-serif" font-size="23" font-weight="600" fill="#ffffff" opacity=".45">github.com/${esc(brand.github)}</text>
<text x="1160" y="440" text-anchor="end" font-family="Inter,system-ui,sans-serif" font-size="23" font-weight="600" fill="#ffffff" opacity=".45">${esc(brand.domain)}</text>
</svg>
`;
fs.mkdirSync('.github', { recursive: true });
fs.writeFileSync('.github/hero.svg', hero);
// Also ship the hero inside dist/ so it resolves on the npm page via a CDN
// (jsdelivr) URL even for packages that aren't backed by a public git repo.
fs.writeFileSync('dist/hero.svg', hero);
console.log(`✓ .github/hero.svg + dist/hero.svg (${brand.name})`);
}
async function buildAll(): Promise<void> {
console.log('🎨 Hanzo Logo Builder\n');
console.log(`🎨 ${BRAND.name} Logo Builder\n`);
const colorSVG = getColorSVG();
const monoSVG = getMonoSVG();
const menuBarSVG = getMenuBarSVG();
const faviconSVG = getFaviconSVG();
const whiteSVG = getWhiteSVG();
// Ensure dist directory exists
if (!fs.existsSync('dist')) {
fs.mkdirSync('dist');
// Ensure dist directories exist
const dirs = ['dist', 'dist/icons', 'dist/favicon', 'dist/og', 'dist/apple', 'dist/dock', 'dist/menubar'];
for (const dir of dirs) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
// Save SVG sources
fs.writeFileSync('dist/hanzo-logo.svg', colorSVG);
fs.writeFileSync('dist/hanzo-logo-mono.svg', monoSVG);
fs.writeFileSync('dist/hanzo-logo-menubar.svg', menuBarSVG);
console.log('✓ Generated SVG sources\n');
console.log('📁 SVG Sources:');
fs.writeFileSync('dist/logo.svg', colorSVG);
fs.writeFileSync('dist/logo-mono.svg', monoSVG);
fs.writeFileSync('dist/logo-white.svg', whiteSVG);
fs.writeFileSync('dist/logo-menubar.svg', menuBarSVG);
fs.writeFileSync('dist/favicon.svg', faviconSVG);
console.log('✓ Generated 5 SVG sources\n');
// Reference icons
if (!fs.existsSync('dist/icons')) {
fs.mkdirSync('dist/icons', { recursive: true });
// Optional wordmark — brands whose logos.ts exports getWordmarkSVG (a wide
// logotype, distinct from the square icon mark) ship it as SVG + aspect-
// preserving PNGs. Brands without one skip this entirely: one build, still
// brand-neutral, no per-brand wiring here.
const wordmark = (marks as any).getWordmarkSVG?.() as string | undefined;
const wordmarkWhite = (marks as any).getWordmarkWhite?.() as string | undefined;
const wordmarkMono = (marks as any).getWordmarkMono?.() as string | undefined;
if (wordmark) {
fs.writeFileSync('dist/wordmark.svg', wordmark);
if (wordmarkWhite) fs.writeFileSync('dist/wordmark-white.svg', wordmarkWhite);
if (wordmarkMono) fs.writeFileSync('dist/wordmark-mono.svg', wordmarkMono);
fs.mkdirSync('dist/wordmark', { recursive: true });
for (const h of [128, 256, 512]) {
// height-only resize preserves the wide aspect ratio (no distortion)
await sharp(Buffer.from(wordmark)).resize({ height: h }).png().toFile(`dist/wordmark/wordmark-${h}.png`);
console.log(`✓ dist/wordmark/wordmark-${h}.png`);
}
}
console.log('Generating reference icons in dist/:');
const distIcons: IconConfig[] = [
{ name: 'dist/icons/16.png', size: 16 },
{ name: 'dist/icons/32.png', size: 32 },
{ name: 'dist/icons/64.png', size: 64 },
{ name: 'dist/icons/128.png', size: 128 },
{ name: 'dist/icons/256.png', size: 256 },
{ name: 'dist/icons/512.png', size: 512 },
{ name: 'dist/icons/1024.png', size: 1024 },
{ name: 'dist/icons/mono-16.png', size: 16, svg: monoSVG },
{ name: 'dist/icons/mono-32.png', size: 32, svg: monoSVG },
{ name: 'dist/icons/mono-64.png', size: 64, svg: monoSVG },
{ name: 'dist/icons/menubar-16.png', size: 16, svg: menuBarSVG },
{ name: 'dist/icons/menubar-32.png', size: 32, svg: menuBarSVG },
];
// README hero card (brand mark + real brand name), written to .github/.
generateHero(whiteSVG);
for (const icon of distIcons) {
await generateIcon(icon.svg || colorSVG, icon.name, icon.size);
// === STANDARD ICONS ===
console.log('📁 Standard Icons (dist/icons/):');
const standardSizes = [16, 32, 64, 128, 256, 512, 1024];
for (const size of standardSizes) {
await generateIcon(colorSVG, `dist/icons/logo-${size}.png`, size);
}
for (const size of [16, 32, 64, 128]) {
await generateIcon(monoSVG, `dist/icons/logo-mono-${size}.png`, size);
}
// Generate macOS dock icon with background
await generateIcon(colorSVG, 'dist/icons/dock-512.png', 512, true);
// === FAVICONS ===
console.log('\n📁 Favicons (dist/favicon/):');
const faviconSizes = [16, 32, 48, 64, 96, 128, 192, 256, 512];
for (const size of faviconSizes) {
await generateIcon(faviconSVG, `dist/favicon/favicon-${size}.png`, size);
}
// Also generate with black background versions
for (const size of [16, 32, 48]) {
await generateIcon(whiteSVG, `dist/favicon/favicon-bg-${size}.png`, size, {
addBackground: true,
bgColor: '#000000',
cornerRadius: 0
});
}
// === ICO (Windows favicon / taskbar / .desktop) ===
// Pack the favicon PNGs into standard multi-resolution .ico files. The
// canonical dist/favicon.ico is what a site drops at its web root; the
// per-surface copies keep everything one `ls dist` away.
console.log('\n📁 ICO files:');
const icoSizes = [16, 32, 48, 64, 128, 256];
const faviconPngs = icoSizes.map((s) => `dist/favicon/favicon-${s}.png`);
await writeIco(faviconPngs, 'dist/favicon.ico');
await writeIco(faviconPngs, 'dist/favicon/favicon.ico');
// A color-logo .ico (app/exe icon) from the color icons that exist (the
// standard set has no 48px, so use its own sizes, not the favicon set's).
await writeIco([16, 32, 64, 128, 256].map((s) => `dist/icons/logo-${s}.png`), 'dist/icons/logo.ico');
// === WEB-STANDARD NAMED ICONS (drop-in for any site manifest) ===
// The exact filenames tools/browsers look for: android-chrome-*, the
// default apple-touch-icon.png, and the Windows tile. Same art, canonical
// names, so a project can copy dist/favicon/* verbatim.
console.log('\n📁 Web-standard named icons (dist/favicon/):');
await generateIcon(faviconSVG, 'dist/favicon/android-chrome-192x192.png', 192);
await generateIcon(faviconSVG, 'dist/favicon/android-chrome-512x512.png', 512);
await generateIcon(colorSVG, 'dist/favicon/apple-touch-icon.png', 180, {
addBackground: true, bgColor: '#000000', cornerRadius: 40,
});
await generateIcon(whiteSVG, 'dist/favicon/mstile-150x150.png', 150, {
addBackground: true, bgColor: '#000000',
});
// === APPLE TOUCH ICONS ===
console.log('\n📁 Apple Touch Icons (dist/apple/):');
const appleSizes = [57, 60, 72, 76, 114, 120, 144, 152, 167, 180];
for (const size of appleSizes) {
await generateIcon(colorSVG, `dist/apple/apple-touch-icon-${size}.png`, size, {
addBackground: true,
bgColor: '#000000',
cornerRadius: Math.floor(size * 0.156) // iOS corner radius
});
}
// === OG GRAPH IMAGES ===
console.log('\n📁 Open Graph Images (dist/og/):');
// Standard OG (1200x630)
await generateIcon(whiteSVG, 'dist/og/og-image.png', 1200, {
aspectRatio: { width: 1200, height: 630 },
bgColor: '#000000'
});
// Twitter card (1200x600)
await generateIcon(whiteSVG, 'dist/og/twitter-card.png', 1200, {
aspectRatio: { width: 1200, height: 600 },
bgColor: '#000000'
});
// Square OG (1200x1200)
await generateIcon(whiteSVG, 'dist/og/og-square.png', 1200, {
aspectRatio: { width: 1200, height: 1200 },
bgColor: '#000000'
});
// === DOCK ICONS (macOS) ===
console.log('\n📁 Dock Icons (dist/dock/):');
const dockSizes = [64, 128, 256, 512, 1024];
for (const size of dockSizes) {
await generateIcon(whiteSVG, `dist/dock/dock-${size}.png`, size, {
addBackground: true,
bgColor: '#000000'
});
}
// @2x versions for Retina
for (const base of [128, 256, 512]) {
await generateIcon(whiteSVG, `dist/dock/dock-${base}@2x.png`, base * 2, {
addBackground: true,
bgColor: '#000000'
});
}
// === MENU BAR ICONS ===
console.log('\n📁 Menu Bar Icons (dist/menubar/):');
// Black icons for light menu bar
await generateIcon(monoSVG, 'dist/menubar/menubar-16.png', 16);
await generateIcon(monoSVG, 'dist/menubar/menubar-16@2x.png', 32);
await generateIcon(monoSVG, 'dist/menubar/menubar-16@3x.png', 48);
await generateIcon(monoSVG, 'dist/menubar/menubar-22.png', 22);
await generateIcon(monoSVG, 'dist/menubar/menubar-22@2x.png', 44);
// Template icons (for macOS dark/light mode adaptation)
await generateIcon(menuBarSVG, 'dist/menubar/iconTemplate.png', 16);
await generateIcon(menuBarSVG, 'dist/menubar/iconTemplate@2x.png', 32);
await generateIcon(menuBarSVG, 'dist/menubar/iconTemplate@3x.png', 48);
// === SLACK APP ICON ===
// Slack wants a SQUARE 2000×2000 PNG (it applies its own rounded-corner
// mask, so the source stays square — cornerRadius 0). Mark centered on the
// brand-black field. A 512 copy for anywhere a smaller square is needed.
console.log('\n📁 Slack App Icon (dist/slack/):');
await generateIcon(colorSVG, 'dist/slack/slack-icon-2000.png', 2000, {
addBackground: true, bgColor: '#000000', cornerRadius: 0,
});
await generateIcon(colorSVG, 'dist/slack/slack-icon-512.png', 512, {
addBackground: true, bgColor: '#000000', cornerRadius: 0,
});
// === GENERATE SHOWCASE HTML ===
console.log('\n📄 Generating showcase...');
generateShowcase();
console.log('\n✅ Build complete!');
console.log(' Open dist/showcase.html in browser to verify all assets\n');
}
function generateShowcase(): void {
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${BRAND.name} Logo Assets Showcase</title>
<style>
:root {
--bg-dark: #111;
--bg-light: #f5f5f5;
--grid-bg: repeating-conic-gradient(#ccc 0% 25%, #fff 0% 50%) 50% / 20px 20px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1a1a1a;
color: #fff;
padding: 40px;
line-height: 1.6;
}
h1 { margin-bottom: 10px; font-size: 2rem; }
.subtitle { color: #888; margin-bottom: 40px; }
h2 {
margin: 40px 0 20px;
padding-bottom: 10px;
border-bottom: 1px solid #333;
color: #fff;
}
.section { margin-bottom: 60px; }
.grid {
display: flex;
flex-wrap: wrap;
gap: 20px;
align-items: flex-end;
}
.icon-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.icon-box {
display: flex;
align-items: center;
justify-content: center;
background: var(--grid-bg);
border-radius: 8px;
padding: 10px;
}
.icon-box.dark {
background: var(--bg-dark);
}
.icon-box.light {
background: var(--bg-light);
}
.icon-box img {
display: block;
max-width: 100%;
height: auto;
}
.label {
font-size: 12px;
color: #888;
text-align: center;
}
.og-preview {
max-width: 600px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
.og-preview img {
width: 100%;
height: auto;
display: block;
}
.menubar-demo {
background: linear-gradient(to bottom, #d4d4d4, #c8c8c8);
padding: 4px 12px;
border-radius: 6px;
display: inline-flex;
align-items: center;
gap: 8px;
}
.menubar-demo.dark {
background: linear-gradient(to bottom, #3a3a3a, #2a2a2a);
}
.dock-demo {
background: rgba(255,255,255,0.1);
backdrop-filter: blur(20px);
padding: 8px;
border-radius: 20px;
display: inline-flex;
gap: 8px;
align-items: flex-end;
}
.dock-demo img {
border-radius: 22%;
}
.timestamp {
position: fixed;
bottom: 20px;
right: 20px;
font-size: 12px;
color: #666;
}
</style>
</head>
<body>
<h1>🎨 ${BRAND.name} Logo Assets</h1>
<p class="subtitle">Every brand mark, format, and size.</p>
<section class="section">
<h2>📁 SVG Sources</h2>
<div class="grid">
<div class="icon-item">
<div class="icon-box dark" style="width:120px;height:120px;">
<img src="logo.svg" alt="Color Logo" style="width:100px;height:100px;">
</div>
<span class="label">logo.svg</span>
</div>
<div class="icon-item">
<div class="icon-box light" style="width:120px;height:120px;">
<img src="logo-mono.svg" alt="Mono Logo" style="width:100px;height:100px;">
</div>
<span class="label">logo-mono.svg</span>
</div>
<div class="icon-item">
<div class="icon-box dark" style="width:120px;height:120px;">
<img src="logo-white.svg" alt="White Logo" style="width:100px;height:100px;">
</div>
<span class="label">logo-white.svg</span>
</div>
<div class="icon-item">
<div class="icon-box" style="width:120px;height:120px;">
<img src="favicon.svg" alt="Favicon" style="width:100px;height:100px;">
</div>
<span class="label">favicon.svg</span>
</div>
</div>
</section>
<section class="section">
<h2>🖼️ Standard Icons (PNG)</h2>
<div class="grid">
<div class="icon-item">
<div class="icon-box dark"><img src="icons/logo-32.png" alt="32px"></div>
<span class="label">32px</span>
</div>
<div class="icon-item">
<div class="icon-box dark"><img src="icons/logo-64.png" alt="64px"></div>
<span class="label">64px</span>
</div>
<div class="icon-item">
<div class="icon-box dark"><img src="icons/logo-128.png" alt="128px"></div>
<span class="label">128px</span>
</div>
<div class="icon-item">
<div class="icon-box dark"><img src="icons/logo-256.png" alt="256px"></div>
<span class="label">256px</span>
</div>
</div>
</section>
<section class="section">
<h2>⭐ Favicons</h2>
<div class="grid">
<div class="icon-item">
<div class="icon-box"><img src="favicon/favicon-16.png" alt="16px"></div>
<span class="label">16px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="favicon/favicon-32.png" alt="32px"></div>
<span class="label">32px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="favicon/favicon-48.png" alt="48px"></div>
<span class="label">48px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="favicon/favicon-64.png" alt="64px"></div>
<span class="label">64px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="favicon/favicon-192.png" alt="192px"></div>
<span class="label">192px (Android)</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="favicon/favicon-512.png" alt="512px"></div>
<span class="label">512px (PWA)</span>
</div>
</div>
</section>
<section class="section">
<h2>🍎 Apple Touch Icons</h2>
<div class="grid">
<div class="icon-item">
<div class="icon-box"><img src="apple/apple-touch-icon-60.png" alt="60px"></div>
<span class="label">60px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="apple/apple-touch-icon-120.png" alt="120px"></div>
<span class="label">120px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="apple/apple-touch-icon-152.png" alt="152px"></div>
<span class="label">152px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="apple/apple-touch-icon-180.png" alt="180px"></div>
<span class="label">180px</span>
</div>
</div>
</section>
<section class="section">
<h2>🖥️ Dock Icons (macOS)</h2>
<div class="dock-demo">
<img src="dock/dock-64.png" alt="Dock 64" style="width:48px;height:48px;">
<img src="dock/dock-128.png" alt="Dock 128" style="width:64px;height:64px;">
</div>
<div class="grid" style="margin-top: 20px;">
<div class="icon-item">
<div class="icon-box"><img src="dock/dock-128.png" alt="128px"></div>
<span class="label">128px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="dock/dock-256.png" alt="256px"></div>
<span class="label">256px</span>
</div>
<div class="icon-item">
<div class="icon-box"><img src="dock/dock-512.png" alt="512px"></div>
<span class="label">512px</span>
</div>
</div>
</section>
<section class="section">
<h2>📱 Menu Bar Icons</h2>
<p style="color:#888;margin-bottom:20px;">Template icons adapt to light/dark menu bar</p>
<div style="display:flex;gap:40px;flex-wrap:wrap;">
<div>
<p style="margin-bottom:10px;font-size:14px;">Light Menu Bar</p>
<div class="menubar-demo">
<img src="menubar/menubar-16.png" alt="Menu bar icon" style="width:16px;height:16px;">
<span style="font-size:13px;color:#333;">${BRAND.name}</span>
</div>
</div>
<div>
<p style="margin-bottom:10px;font-size:14px;">Dark Menu Bar</p>
<div class="menubar-demo dark">
<img src="menubar/iconTemplate.png" alt="Menu bar icon" style="width:16px;height:16px;filter:invert(1);">
<span style="font-size:13px;color:#fff;">${BRAND.name}</span>
</div>
</div>
</div>
<div class="grid" style="margin-top:20px;">
<div class="icon-item">
<div class="icon-box light"><img src="menubar/menubar-16.png" alt="16px"></div>
<span class="label">16px</span>
</div>
<div class="icon-item">
<div class="icon-box light"><img src="menubar/menubar-22.png" alt="22px"></div>
<span class="label">22px</span>
</div>
<div class="icon-item">
<div class="icon-box light"><img src="menubar/menubar-16@2x.png" alt="16@2x" style="width:32px;"></div>
<span class="label">16@2x (32px)</span>
</div>
</div>
</section>
<section class="section">
<h2>📣 Open Graph / Social Images</h2>
<div style="display:flex;flex-direction:column;gap:30px;">
<div>
<p style="margin-bottom:10px;font-size:14px;">OG Image (1200×630)</p>
<div class="og-preview">
<img src="og/og-image.png" alt="OG Image">
</div>
</div>
<div>
<p style="margin-bottom:10px;font-size:14px;">Twitter Card (1200×600)</p>
<div class="og-preview">
<img src="og/twitter-card.png" alt="Twitter Card">
</div>
</div>
<div>
<p style="margin-bottom:10px;font-size:14px;">Square (1200×1200)</p>
<div class="og-preview" style="max-width:400px;">
<img src="og/og-square.png" alt="OG Square">
</div>
</div>
</div>
</section>
</body>
</html>`;
fs.writeFileSync('dist/showcase.html', html);
console.log('✓ dist/showcase.html');
}
// Run the build
await buildAll().catch(console.error);
await buildAll().catch(console.error);
-87
View File
@@ -1,87 +0,0 @@
#!/usr/bin/env node
/**
* Generate showcase images for README
*/
import * as fs from 'fs';
import * as path from 'path';
import sharp from 'sharp';
import { getColorSVGCropped, getMonoSVG, getMenuBarSVG } from './logos';
async function generateShowcaseImages() {
console.log('🎨 Generating showcase images for README...\n');
// Ensure docs/assets directory exists
const docsDir = path.join(process.cwd(), 'docs', 'assets');
if (!fs.existsSync(docsDir)) {
fs.mkdirSync(docsDir, { recursive: true });
}
// Generate cropped color logo
const colorSVG = getColorSVGCropped();
fs.writeFileSync(path.join(docsDir, 'zoo-logo-cropped.svg'), colorSVG);
console.log('✓ Generated cropped color SVG');
// Generate various sizes of the color logo
const sizes = [32, 64, 128, 256, 512];
for (const size of sizes) {
await sharp(Buffer.from(colorSVG))
.resize(size, size)
.png()
.toFile(path.join(docsDir, `logo-${size}.png`));
console.log(`✓ Generated ${size}×${size} color logo`);
}
// Generate monochrome version
const monoSVG = getMonoSVG();
fs.writeFileSync(path.join(docsDir, 'zoo-logo-mono.svg'), monoSVG);
await sharp(Buffer.from(monoSVG))
.resize(128, 128)
.png()
.toFile(path.join(docsDir, 'logo-mono-128.png'));
console.log('✓ Generated monochrome logo');
// Generate menu bar icon
const menuBarSVG = getMenuBarSVG();
fs.writeFileSync(path.join(docsDir, 'zoo-logo-menubar.svg'), menuBarSVG);
await sharp(Buffer.from(menuBarSVG))
.resize(32, 32)
.png()
.toFile(path.join(docsDir, 'logo-menubar-32.png'));
console.log('✓ Generated menu bar icon');
// Generate macOS dock icon with black rounded background
const dockSize = 512;
const logoSize = Math.floor(dockSize * 0.8);
const padding = Math.floor((dockSize - logoSize) / 2);
const cornerRadius = Math.floor(dockSize * 0.22);
const bgSvg = `<svg width="${dockSize}" height="${dockSize}" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="${dockSize}" height="${dockSize}" rx="${cornerRadius}" ry="${cornerRadius}" fill="black"/>
</svg>`;
const bg = await sharp(Buffer.from(bgSvg)).png().toBuffer();
const logo = await sharp(Buffer.from(colorSVG))
.resize(logoSize, logoSize)
.png()
.toBuffer();
await sharp(bg)
.composite([{
input: logo,
top: padding,
left: padding
}])
.toFile(path.join(docsDir, 'logo-macos-dock.png'));
console.log('✓ Generated macOS dock icon');
console.log('\n✅ All showcase images generated!');
}
// Run if called directly
if (require.main === module) {
generateShowcaseImages().catch(console.error);
}
export { generateShowcaseImages };
-65
View File
@@ -1,65 +0,0 @@
import fs from 'fs';
import path from 'path';
import sharp from 'sharp';
import { getColorSVG, getMonoSVG } from './logos';
/**
* Generate a PNG icon from SVG
*/
export async function generateIcon(
svgString: string,
outputPath: string,
size: number
): Promise<void> {
const dir = path.dirname(outputPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
await sharp(Buffer.from(svgString))
.resize(size, size)
.png()
.toFile(outputPath);
}
/**
* Generate all standard icons
*/
export async function generateAllIcons(outputDir: string = 'dist/icons'): Promise<void> {
const colorSVG = getColorSVG();
const monoSVG = getMonoSVG();
// Standard sizes
const sizes = [16, 32, 64, 128, 256, 512, 1024];
console.log('Generating icons...');
for (const size of sizes) {
await generateIcon(colorSVG, path.join(outputDir, `zoo-${size}.png`), size);
await generateIcon(monoSVG, path.join(outputDir, `zoo-mono-${size}.png`), size);
}
// Generate @2x versions for macOS
const macSizes = [
{ base: 16, scale: 2 },
{ base: 32, scale: 2 },
{ base: 128, scale: 2 },
{ base: 256, scale: 2 },
{ base: 512, scale: 2 }
];
for (const { base, scale } of macSizes) {
await generateIcon(
colorSVG,
path.join(outputDir, `zoo-${base}@${scale}x.png`),
base * scale
);
}
// Menu bar templates
await generateIcon(monoSVG, path.join(outputDir, 'iconTemplate.png'), 16);
await generateIcon(monoSVG, path.join(outputDir, 'iconTemplate@2x.png'), 32);
await generateIcon(monoSVG, path.join(outputDir, 'iconTemplate@3x.png'), 48);
console.log('✅ Icons generated successfully!');
}
+3 -1
View File
@@ -5,4 +5,6 @@
*/
export * from './logos.js';
export * from './types.js';
export * from './motion.js';
export * from './types.js';
export * from './react.js';
+89 -10
View File
@@ -1,4 +1,5 @@
import type { LogoSettings, LogoOptions } from './types.js';
import { ANIMATED_SVG } from './animated.js';
// Hanzo logo settings
export const LOGO_SETTINGS: LogoSettings = {
@@ -12,6 +13,26 @@ export const LOGO_SETTINGS: LogoSettings = {
}
};
/** Mark viewBox — the block-H's native coordinate space. */
export const MARK_VIEWBOX = '0 0 67 67';
/**
* Raw block-H path geometry — the single source of the Hanzo mark.
* The canonical mark is SEVEN paths: five fill-less body blocks (callers set
* their own `fill` — e.g. `@hanzo/brand` card generators, og images) plus the
* two `class="shade"` accent slivers that give the H its shaded depth
* (#DDDDDD in every shipped variant; restyle via `.shade` if needed).
* Use this instead of re-typing the path data anywhere.
*/
export const MARK_PATHS =
'<path d="M22.21 67V44.6369H0V67H22.21Z"/>' +
'<path class="shade" fill="#DDDDDD" d="M0 44.6369L22.21 46.8285V44.6369H0Z"/>' +
'<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z"/>' +
'<path d="M22.21 0H0V22.3184H22.21V0Z"/>' +
'<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z"/>' +
'<path class="shade" fill="#DDDDDD" d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z"/>' +
'<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z"/>';
/**
* Generate Hanzo color SVG logo
*/
@@ -28,17 +49,17 @@ export function getColorSVG(): string {
}
/**
* Generate monochrome SVG logo
* Generate monochrome SVG logo (filled, for dark backgrounds)
*/
export function getMonoSVG(): string {
return `<svg viewBox="0 0 67 67" xmlns="http://www.w3.org/2000/svg">
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="none" stroke="black" stroke-width="1"/>
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="none" stroke="black" stroke-width="1"/>
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="none" stroke="black" stroke-width="1"/>
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="none" stroke="black" stroke-width="1"/>
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="none" stroke="black" stroke-width="1"/>
<path d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="none" stroke="black" stroke-width="1"/>
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="none" stroke="black" stroke-width="1"/>
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="#000000"/>
<path d="M0 44.6369L22.21 46.8285V44.6369H0Z" fill="#222222"/>
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#000000"/>
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="#000000"/>
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#000000"/>
<path d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" fill="#222222"/>
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#000000"/>
</svg>`;
}
@@ -54,7 +75,15 @@ export function getColorSVGCropped(): string {
* Generate white SVG logo (for dark backgrounds)
*/
export function getWhiteSVG(): string {
return getMonoSVG().replace(/stroke="black"/g, 'stroke="white"');
return `<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>`;
}
/**
@@ -73,6 +102,25 @@ export function getMenuBarSVG(): string {
</svg>`;
}
/**
* Generate favicon SVG (simplified white H on black background)
* Optimized for small sizes (16px-64px) - no 3D accents
*/
export function getFaviconSVG(): string {
// Simplified version without 3D accents for better scaling at small sizes
// Uses the core Hanzo "H" shape with padding for favicon context
return `<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="64" rx="8" fill="#000000"/>
<g transform="translate(8, 8) scale(0.716)">
<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>`;
}
/**
* Get logo as data URL
*/
@@ -87,6 +135,9 @@ export function getLogoDataUrl(options: LogoOptions = {}): string {
case 'white':
svg = getWhiteSVG();
break;
case 'favicon':
svg = getFaviconSVG();
break;
default:
svg = getColorSVG();
}
@@ -109,6 +160,9 @@ export function getLogoBase64(options: LogoOptions = {}): string {
case 'white':
svg = getWhiteSVG();
break;
case 'favicon':
svg = getFaviconSVG();
break;
default:
svg = getColorSVG();
}
@@ -121,6 +175,11 @@ export function getLogoBase64(options: LogoOptions = {}): string {
*/
export function getLogo(options: LogoOptions = {}): string {
const { variant = 'color', format = 'svg' } = options;
if (variant === 'animated') {
if (format === 'dataUrl') return getAnimatedDataUrl();
if (format === 'base64') return btoa(unescape(encodeURIComponent(getAnimatedSVG())));
return getAnimatedSVG();
}
switch (format) {
case 'dataUrl':
@@ -133,6 +192,8 @@ export function getLogo(options: LogoOptions = {}): string {
return getMonoSVG();
case 'white':
return getWhiteSVG();
case 'favicon':
return getFaviconSVG();
default:
return getColorSVG();
}
@@ -143,6 +204,24 @@ export function getLogo(options: LogoOptions = {}): string {
export const hanzoLogo = getColorSVG();
export const hanzoLogoMono = getMonoSVG();
export const hanzoLogoWhite = getWhiteSVG();
export const hanzoFavicon = getFaviconSVG();
export const hanzoLogoDataUrl = getLogoDataUrl();
export const hanzoLogoMonoDataUrl = getLogoDataUrl({ variant: 'mono' });
export const hanzoLogoWhiteDataUrl = getLogoDataUrl({ variant: 'white' });
export const hanzoLogoWhiteDataUrl = getLogoDataUrl({ variant: 'white' });
export const hanzoFaviconDataUrl = getLogoDataUrl({ variant: 'favicon' });
/**
* Interactive animated mark — load (assemble) → hover (one graceful turn,
* returns to rest) → press (squash). Pure CSS, no JS. Inline this SVG into
* the DOM (not <img>) for the hover/press interactions to fire.
*/
export function getAnimatedSVG(): string {
return ANIMATED_SVG;
}
/** Animated mark as a data: URL (load animation runs even in <img>). */
export function getAnimatedDataUrl(): string {
return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(ANIMATED_SVG)));
}
export const hanzoLogoAnimated = ANIMATED_SVG;
export const hanzoLogoAnimatedDataUrl = getAnimatedDataUrl();
+42
View File
@@ -0,0 +1,42 @@
import { getMenuBarSVG } from './logos.js';
/**
* Brand motion — the shared shell animation for the Hanzo mark, pure CSS:
*
* 1. intro flip — mark flips in (rotateY 180° → 0, scale .6 → 1, fade)
* 2. idle breathe — a slow, subtle scale pulse once at rest
* 3. wordmark — slides in after the flip, holds, then collapses;
* returns on :hover of the shell
*
* All reduced-motion-safe. Framework-free: pair `MOTION_CSS` with
* `getMotionHTML()` from React, Svelte, Tamagui, or plain HTML — the mark
* and wordmark inherit `currentColor`, so theming is one `color` away.
*/
export const MOTION_CSS = `.hanzo-motion{display:inline-flex;align-items:center;color:inherit}
.hanzo-motion .hanzo-mark{display:block;line-height:0;width:1.25em;height:1.25em;animation:hanzo-flip .5s cubic-bezier(.16,1,.3,1) .2s backwards,hanzo-breathe 6s ease-in-out 3.2s infinite}
.hanzo-motion .hanzo-mark svg{display:block;width:100%;height:100%}
.hanzo-motion .hanzo-word{overflow:hidden;display:inline-block;margin-left:.5em}
.hanzo-motion .hanzo-word>span{display:inline-block;white-space:nowrap;font-weight:600;transform:translateX(-102%);transition:transform .3s ease-out;animation:hanzo-word 2.8s ease-out}
.hanzo-motion:hover .hanzo-word>span{transform:translateX(0)}
@keyframes hanzo-flip{from{opacity:0;transform:perspective(320px) rotateY(180deg) scale(.6)}to{opacity:1;transform:none}}
@keyframes hanzo-breathe{0%,100%{transform:scale(1)}50%{transform:scale(1.04)}}
@keyframes hanzo-word{0%,40%{transform:translateX(-102%)}55%,82%{transform:translateX(0)}100%{transform:translateX(-102%)}}
@media (prefers-reduced-motion:reduce){.hanzo-motion .hanzo-mark,.hanzo-motion .hanzo-word>span{animation:none!important}.hanzo-motion .hanzo-word>span{transition:none}}`;
/**
* The motion shell markup — mark + collapsing wordmark. Inline it (with
* `MOTION_CSS` in a <style>) anywhere HTML renders; not for <img>.
*/
export function getMotionHTML(wordmark = 'Hanzo'): string {
return (
`<span class="hanzo-motion">` +
`<span class="hanzo-mark">${getMenuBarSVG()}</span>` +
`<span class="hanzo-word"><span>${wordmark}</span></span>` +
`</span>`
);
}
/** The shell + its CSS as one self-contained HTML fragment. */
export function getMotionMarkup(wordmark = 'Hanzo'): string {
return `<style>${MOTION_CSS}</style>${getMotionHTML(wordmark)}`;
}
+39 -8
View File
@@ -1,5 +1,6 @@
import React from 'react';
import { getColorSVG, getMonoSVG, getWhiteSVG } from './logos.js';
import { getColorSVG, getMonoSVG, getWhiteSVG, getFaviconSVG, getAnimatedSVG, getMenuBarSVG } from './logos.js';
import { MOTION_CSS } from './motion.js';
import type { LogoVariant } from './types.js';
export interface HanzoLogoProps {
@@ -7,6 +8,14 @@ export interface HanzoLogoProps {
size?: number | string;
className?: string;
style?: React.CSSProperties;
/**
* Render the brand motion shell: intro flip + idle breathing + a wordmark
* that slides in, holds, then collapses (returns on hover). Pure CSS,
* reduced-motion-safe. The mark inherits `currentColor`.
*/
animated?: boolean;
/** Wordmark text for the animated shell (white-label). Default "Hanzo". */
wordmark?: string;
}
/**
@@ -25,8 +34,28 @@ export const HanzoLogo: React.FC<HanzoLogoProps> = ({
variant = 'color',
size = 64,
className,
style
style,
animated = false,
wordmark = 'Hanzo'
}) => {
if (animated) {
return (
<span className={className} style={style}>
<style dangerouslySetInnerHTML={{ __html: MOTION_CSS }} />
<span className="hanzo-motion">
<span
className="hanzo-mark"
style={{ width: size, height: size }}
dangerouslySetInnerHTML={{ __html: getMenuBarSVG() }}
/>
<span className="hanzo-word">
<span>{wordmark}</span>
</span>
</span>
</span>
);
}
let svg = '';
switch (variant) {
@@ -36,6 +65,12 @@ export const HanzoLogo: React.FC<HanzoLogoProps> = ({
case 'white':
svg = getWhiteSVG();
break;
case 'favicon':
svg = getFaviconSVG();
break;
case 'animated':
svg = getAnimatedSVG();
break;
default:
svg = getColorSVG();
}
@@ -65,7 +100,7 @@ export const HanzoLogo: React.FC<HanzoLogoProps> = ({
* ```
*/
export const HanzoFavicon: React.FC = () => {
const svg = getColorSVG();
const svg = getFaviconSVG();
const dataUrl = `data:image/svg+xml,${encodeURIComponent(svg)}`;
return (
@@ -74,8 +109,4 @@ export const HanzoFavicon: React.FC = () => {
<link rel="apple-touch-icon" href={dataUrl} />
</>
);
};
// Export with backwards compatible names
export { HanzoLogo as ZooLogo };
export { HanzoFavicon as ZooFavicon };
};
+1 -1
View File
@@ -9,7 +9,7 @@ export interface LogoSettings {
};
}
export type LogoVariant = 'color' | 'mono' | 'white';
export type LogoVariant = 'color' | 'mono' | 'white' | 'favicon' | 'animated';
export type LogoFormat = 'svg' | 'dataUrl' | 'base64';
export interface LogoOptions {
+22
View File
@@ -0,0 +1,22 @@
<svg role="img" aria-label="Hanzo" viewBox="0 0 67 67" xmlns="http://www.w3.org/2000/svg">
<!-- Origami fold-in → hover: one 3-D turn → press: squash. Pure CSS, no JS. Generated from the @hanzo logo tuner — edit there, not here. -->
<style>.o{fill:#fff;opacity:0;transform-box:fill-box;transform-origin:left center;animation:fold 0.42s cubic-bezier(.16,1,.3,1) forwards}
@keyframes fold{from{opacity:0;transform:perspective(320px) rotateY(90deg)}to{opacity:1;transform:none}}
.o0{animation-delay:0.00s}.o1{animation-delay:0.06s}.o2{animation-delay:0.12s}.o3{animation-delay:0.18s}.o4{animation-delay:0.24s}
/* interaction — pure CSS, no JS: hover = one graceful turn (returns to rest), press = squash */
.hanzo-hov{transform-box:view-box;transform-origin:33.5px 33.5px}
.hanzo-prs{transform-box:view-box;transform-origin:33.5px 33.5px;transition:transform .22s cubic-bezier(.22,1,.36,1)}
svg:hover .hanzo-hov{animation:hanzoTurn 0.85s cubic-bezier(.45,0,.2,1)}
svg:active .hanzo-prs{transform:scale(.92)}
@keyframes hanzoTurn{0%{transform:perspective(300px) rotateY(0)}100%{transform:perspective(300px) rotateY(360deg)}}
@media (prefers-reduced-motion:reduce){.hanzo-hov,.hanzo-prs{animation:none!important;transform:none!important}.o{opacity:1;animation:none;transform:none}}
</style>
<g class="hanzo-hov"><g class="hanzo-prs">
<path class="o o0" d="M22.21 0H0V22.3184H22.21V0Z"/>
<path class="o o1" d="M66.7198 0H44.5098V22.3184H66.7198V0Z"/>
<path class="o o2" d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z"/>
<path class="o o3" d="M22.21 67V44.6369H0V67H22.21Z"/>
<path class="o o4" d="M66.7198 67V44.6369H44.5098V67H66.7198Z"/>
</g></g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+1 -1
View File
@@ -12,7 +12,7 @@
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"types": ["node"]