@hrznlabs/brand
v0.1.0
Published
Horizon brand tokens, logo assets and the world shader
Readme
@hrznlabs/brand
Tokens, logo assets, the world shader and its WebGL2 runtime for every Horizon surface. Night (sunset) is the default. tokens.css follows prefers-color-scheme; tokens.night-default.css is night unless data-theme="light".
Spec: docs/superpowers/specs/2026-10-15-horizon-brand-identity-design.md in the horizon monorepo.
Web (CSS)
@import '@hrznlabs/brand/tokens.css';
.card {
background: var(--color-surface);
color: var(--color-fg);
border-radius: var(--radius-card);
}Sites that own the theme toggle import @hrznlabs/brand/tokens.night-default.css instead: night on :root, day only under :root[data-theme="light"], no prefers-color-scheme block.
Load fonts with next/font/google: Oxanium, Space Grotesk, JetBrains Mono.
Tailwind v3
// tailwind.config.js
module.exports = {
presets: [require('@hrznlabs/brand/tailwind-preset')],
content: ['./src/**/*.{ts,tsx}'],
};Also import @hrznlabs/brand/tokens.css once in the global stylesheet. Colors are CSS variables, so bg-surface follows the active mode.
React Native
import { theme, type HorizonMode } from '@hrznlabs/brand';
const c = theme[mode as HorizonMode].color;Load fonts with @expo-google-fonts/oxanium, @expo-google-fonts/space-grotesk, @expo-google-fonts/jetbrains-mono.
World runtime (WebGL2)
import { createWorld, DEFAULT_FX } from '@hrznlabs/brand/world';
const world = createWorld({ day: false, ticker }); // one per page; ticker is optional
const handle = world.mount(canvas, { h: -0.3, a: Math.min(0.19, (0.4 * w) / h), host: heroEl });
if (!handle) showStillSky(); // no WebGL2, or the shader failed
world.setDay(true); // eases over ~1 s; pulses light rays 400 ms later
world.pulseRays(); // rise 1.6 s, hold to 2.8 s, decay to 7 s
handle.destroy(); // loses the context and removes listenerscreateWorld({ reducedMotion?, day?, fx?, ticker? })returns{ mount, setDay, pulseRays, setFx, kick, setReducedMotion, destroy }.mount(canvas, { a, h?, hpxTop?, hpxBottom?, sa?, host? }):ais the arch radius in height units; the horizon ishin [-.5, .5], orhpxTop/hpxBottomCSS pixels re-read every frame;sascales sun and arch (0 = waves only);hostreceives pointer ripple and dwell rays.- With a
ticker({ add(fn) => unsubscribe },fn(now)returnsfalseto stop) the runtime never callsrequestAnimationFrameitself, so a site runs one loop for every canvas. - Reduced motion is read live from
matchMedia('(prefers-reduced-motion: reduce)'): time freezes at 9 s, no ripple, and the loop stops once day has settled.reducedMotionin the options overrides it for tests. - Shader programs compile without blocking when
KHR_parallel_shader_compileis available; the canvas keepsopacity: 0until they are ready. Context loss hides the canvas; restore rebuilds everything. - Adaptive resolution (DPR cap 1.5, steps down to 55% on slow frames), render-only-when-visible,
antialias: false,powerPreference: 'low-power'. DEFAULT_FXis the frozen look (19 values,FX[12] = 0,FX[18]= bloom gain).FX_INDEXnames each slot.setFx()exists for tuning tools only.
Reference renderer: website src/components/world/.
World shader (raw strings)
import { WORLD_VERT, WORLD_FRAG, WORLD_UNIFORMS } from '@hrznlabs/brand/shaders/world';Uniforms (10): R resolution, T time, D day 0–1, M pointer, E energy 0–1, H horizon y, A arch radius, G light rays 0–1, SA sun and arch amount 0–1, FX[18] effect toggles. Bloom passes: BLOOM_*.
Guard
npx horizon-brand-guard $(git ls-files 'src/**/*.tsx' 'src/**/*.css')Fails on raw hex outside the tokens, retired motion (glitch loops, scan line, flicker, typewriter, float), and keyframes animating anything but transform, opacity, clip-path or offset-distance. Suppress a line with brand-guard-ignore plus a reason.
License
Code: MIT. Logo, wordmark and assets/: see LICENSE-BRAND.md.
