mirror of
https://github.com/hanzoai/agency.git
synced 2026-11-02 19:13:28 +00:00
84 lines
4.8 KiB
Plaintext
84 lines
4.8 KiB
Plaintext
import { getMenuBarSVG, MARK_BLOCKS, MARK_SHADE } 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') {
|
|
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') {
|
|
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() {
|
|
const shadeAt = { 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() {
|
|
return `<style>${LOADING_CSS}</style><span class="hanzo-loading">${getLoadingSVG()}</span>`;
|
|
}
|