mirror of
https://github.com/hanzoai/logo.git
synced 2026-09-30 22:52:39 +00:00
feat(motion): a loading state, and one spelling of the animated mark
There was no way to say "still working". ANIMATED_SVG folds in ONCE and stops
(`forwards`); MOTION_CSS is an intro flip plus a breathe loop. Neither is an
indeterminate state, so surfaces invented their own — the only spinner in the
fleet is ten hand-drawn raster frames in studio/branding.
getLoadingSVG() / LOADING_CSS / getLoadingMarkup() are the same origami fold the
mark already does, staggered across the five blocks and looping: each turns in
on its edge, holds while the others arrive, then turns out. Motion rather than
progress, which is what indeterminate should say. Built from MARK_BLOCKS and
MARK_SHADE, currentColor, prefers-reduced-motion honoured.
The one-shot mark hardcoded fill:#fff and .s{fill:#ddd}, so the animated mark
was invisible on a light page — the ground the house asks for. Both are
currentColor now, the slivers at reduced opacity rather than a second hex.
And svg/hanzo-mark-animated.svg was a hand-maintained SECOND spelling of that
animation — 5 paths and #fff against the constant's 7 and currentColor, one
package, two versions, already drifted. build.ts generates it from the constant
now, plus dist/logo-animated.svg with an export entry.
MARK_SHADE is exported: only the pre-baked MARK_PATHS string was reachable, so a
consumer wanting a different sliver ink had to retype the geometry.
This commit is contained in:
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
<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. Canonical 7-path shaded H. -->
|
||||
|
||||
<style>.o{fill:currentColor;opacity:0;transform-box:fill-box;transform-origin:left center;animation:fold 0.42s cubic-bezier(.16,1,.3,1) forwards}
|
||||
.s{fill:currentColor;opacity:.55}
|
||||
@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 o1 s" d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z"/>
|
||||
<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 o3 s" d="M0 44.6369L22.21 46.8285V44.6369H0Z"/>
|
||||
<path class="o o4" d="M66.7198 67V44.6369H44.5098V67H66.7198Z"/>
|
||||
</g></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hanzo/logo",
|
||||
"version": "1.0.22",
|
||||
"version": "1.0.23",
|
||||
"packageManager": "[email protected]",
|
||||
"description": "Official Hanzo logo package with React components and utilities",
|
||||
"type": "module",
|
||||
@@ -41,7 +41,8 @@
|
||||
"./logo-mono.svg": "./dist/logo-mono.svg",
|
||||
"./logo-white.svg": "./dist/logo-white.svg",
|
||||
"./svg/*": "./svg/*",
|
||||
"./dist/*": "./dist/*"
|
||||
"./dist/*": "./dist/*",
|
||||
"./logo-animated.svg": "./dist/logo-animated.svg"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm clean && pnpm compile && pnpm build:cli && pnpm generate",
|
||||
@@ -93,7 +94,7 @@
|
||||
},
|
||||
"brand": {
|
||||
"name": "Hanzo",
|
||||
"tagline": "Official brand marks — SVG, PNG, ICO, favicons & app icons.",
|
||||
"tagline": "Official brand marks \u2014 SVG, PNG, ICO, favicons & app icons.",
|
||||
"github": "hanzoai",
|
||||
"domain": "hanzo.ai"
|
||||
}
|
||||
|
||||
+9
-1
@@ -2,4 +2,12 @@
|
||||
// 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>";
|
||||
//
|
||||
// currentColor, not #fff. This hardcoded white, so the animated mark was
|
||||
// invisible on a light page — which is the ground the house asks for. The shade
|
||||
// slivers ride the same ink at reduced opacity rather than a second hex, so one
|
||||
// `color` themes the whole thing.
|
||||
//
|
||||
// This is the ONE-SHOT mark: it folds in and stops. For an indeterminate
|
||||
// "still working" state use getLoadingSVG() / LOADING_CSS from ./motion.
|
||||
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:currentColor;opacity:0;transform-box:fill-box;transform-origin:left center;animation:fold 0.42s cubic-bezier(.16,1,.3,1) forwards}\n.s{fill:currentColor;opacity:.55}\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>";
|
||||
|
||||
@@ -13,6 +13,7 @@ import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import sharp from 'sharp';
|
||||
import { getColorSVG, getMonoSVG, getMenuBarSVG, getFaviconSVG, getFaviconRasterSVG, getWhiteSVG, getLightSVG } from './logos.js';
|
||||
import { ANIMATED_SVG } from './animated.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';
|
||||
@@ -227,6 +228,12 @@ async function buildAll(): Promise<void> {
|
||||
fs.writeFileSync('dist/logo-mono.svg', monoSVG);
|
||||
fs.writeFileSync('dist/logo-white.svg', whiteSVG);
|
||||
fs.writeFileSync('dist/logo-light.svg', getLightSVG());
|
||||
// The standalone animated mark is GENERATED from the constant. It was a
|
||||
// hand-maintained second spelling — 5 paths and #fff against the constant's
|
||||
// 7 and currentColor — i.e. two versions of one animation in one package,
|
||||
// already drifted.
|
||||
fs.writeFileSync('svg/hanzo-mark-animated.svg', ANIMATED_SVG);
|
||||
fs.writeFileSync('dist/logo-animated.svg', ANIMATED_SVG);
|
||||
fs.writeFileSync('dist/logo-menubar.svg', menuBarSVG);
|
||||
fs.writeFileSync('dist/favicon.svg', faviconSVG);
|
||||
console.log('✓ Generated 6 SVG sources\n');
|
||||
|
||||
+5
-2
@@ -29,8 +29,11 @@ export const MARK_BLOCKS = [
|
||||
'M66.7198 67V44.6369H44.5098V67H66.7198Z',
|
||||
];
|
||||
|
||||
/** The two accent slivers that give the H its shaded depth. */
|
||||
const MARK_SHADE = [
|
||||
/** The two accent slivers that give the H its shaded depth.
|
||||
* Exported alongside MARK_BLOCKS: a consumer that wants a different sliver ink
|
||||
* had to retype the paths, because only the pre-baked MARK_PATHS string was
|
||||
* reachable. */
|
||||
export const MARK_SHADE = [
|
||||
'M0 44.6369L22.21 46.8285V44.6369H0Z',
|
||||
'M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z',
|
||||
];
|
||||
|
||||
+53
-1
@@ -1,4 +1,4 @@
|
||||
import { getMenuBarSVG } from './logos.js';
|
||||
import { getMenuBarSVG, MARK_BLOCKS, MARK_SHADE } from './logos.js';
|
||||
|
||||
/**
|
||||
* Brand motion — the shared shell animation for the Hanzo mark, pure CSS:
|
||||
@@ -40,3 +40,55 @@ export function getMotionHTML(wordmark = 'Hanzo'): string {
|
||||
export function getMotionMarkup(wordmark = 'Hanzo'): string {
|
||||
return `<style>${MOTION_CSS}</style>${getMotionHTML(wordmark)}`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The loading state — the origami fold, made indeterminate.
|
||||
*
|
||||
* The package had brand motion and no way to say "still working": ANIMATED_SVG
|
||||
* folds in ONCE and stops (`forwards`), and MOTION_CSS is an intro flip plus a
|
||||
* breathe loop. Neither is a spinner, so surfaces invented their own — the only
|
||||
* one in the fleet is ten hand-drawn raster frames.
|
||||
*
|
||||
* This is the same fold the mark already does, staggered across the five blocks
|
||||
* and looping: each block turns in on its edge, holds while the others arrive,
|
||||
* then turns out. It reads as motion rather than progress, which is what an
|
||||
* indeterminate state should say.
|
||||
*
|
||||
* `currentColor`, so it is visible on any ground — the one-shot animation
|
||||
* hardcodes `fill:#fff` and is invisible on the white the house asks for.
|
||||
*/
|
||||
export const LOADING_CSS = `.hanzo-loading{display:inline-block;line-height:0;color:inherit;width:1.25em;height:1.25em}
|
||||
.hanzo-loading svg{display:block;width:100%;height:100%}
|
||||
.hanzo-loading .hz-fold{fill:currentColor;transform-box:fill-box;transform-origin:left center;animation:hanzo-loading-fold 1.8s cubic-bezier(.16,1,.3,1) infinite}
|
||||
.hanzo-loading .hz-fold-1{animation-delay:.09s}
|
||||
.hanzo-loading .hz-fold-2{animation-delay:.18s}
|
||||
.hanzo-loading .hz-fold-3{animation-delay:.27s}
|
||||
.hanzo-loading .hz-fold-4{animation-delay:.36s}
|
||||
@keyframes hanzo-loading-fold{0%{opacity:0;transform:perspective(320px) rotateY(90deg)}20%,60%{opacity:1;transform:none}86%,100%{opacity:0;transform:perspective(320px) rotateY(-90deg)}}
|
||||
@media (prefers-reduced-motion:reduce){.hanzo-loading .hz-fold{animation:none;opacity:1;transform:none}}`;
|
||||
|
||||
/**
|
||||
* The looping mark on its own. Pair with LOADING_CSS, or use
|
||||
* getLoadingMarkup() for both at once.
|
||||
*/
|
||||
export function getLoadingSVG(): string {
|
||||
const shadeAt: Record<number, string> = { 0: MARK_SHADE[0], 3: MARK_SHADE[1] };
|
||||
const parts = MARK_BLOCKS.map((d, i) => {
|
||||
const block = `<path class="hz-fold hz-fold-${i}" d="${d}"/>`;
|
||||
const sliver = shadeAt[i]
|
||||
? `<path class="hz-fold hz-fold-${i}" opacity=".55" d="${shadeAt[i]}"/>`
|
||||
: '';
|
||||
return block + sliver;
|
||||
});
|
||||
return (
|
||||
`<svg role="img" aria-label="Loading" viewBox="0 0 67 67" xmlns="http://www.w3.org/2000/svg">` +
|
||||
parts.join('') +
|
||||
`</svg>`
|
||||
);
|
||||
}
|
||||
|
||||
/** The loading mark and its CSS as one self-contained fragment. */
|
||||
export function getLoadingMarkup(): string {
|
||||
return `<style>${LOADING_CSS}</style><span class="hanzo-loading">${getLoadingSVG()}</span>`;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<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. -->
|
||||
<!-- Origami fold-in → hover: one 3-D turn → press: squash. Pure CSS, no JS. Canonical 7-path shaded H. -->
|
||||
|
||||
<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}
|
||||
<style>.o{fill:currentColor;opacity:0;transform-box:fill-box;transform-origin:left center;animation:fold 0.42s cubic-bezier(.16,1,.3,1) forwards}
|
||||
.s{fill:currentColor;opacity:.55}
|
||||
@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 */
|
||||
@@ -15,8 +16,10 @@ svg:active .hanzo-prs{transform:scale(.92)}
|
||||
<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 o1 s" d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z"/>
|
||||
<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 o3 s" d="M0 44.6369L22.21 46.8285V44.6369H0Z"/>
|
||||
<path class="o o4" d="M66.7198 67V44.6369H44.5098V67H66.7198Z"/>
|
||||
</g></g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.8 KiB |
Reference in New Issue
Block a user