PageHare logo — the pagehole system (binding)
Status: v1.1 — refined marks and four approved motion states. These brand rules apply to agents and people. If a request conflicts with a
rule, stop and explain the conflict before making the change.
Informative history lives in docs/design/brand-history.md (read-only).
The idea
One shape: a hole (an ellipse) with a hare's ears rising from it. The hare lives in the page. Everything in the system is a state of that one hole — emerging, spiraling, missed, or diving. The hole ellipse is the same shape in every mark; only what comes out of it changes.
Where the assets live
| What | Where |
|---|---|
Canonical currentColor SVG sources | packages/app/src/lib/brand/*.svg |
| Mark component (inlines the sources) | packages/app/src/lib/brand/BrandMark.svelte |
| Header lockup (responsive) | packages/app/src/lib/brand/BrandLockup.svelte |
| Mode-1 tenant attribution | packages/app/src/lib/brand/PoweredBy.svelte |
| Motion family (CSS-only SVG) | packages/app/src/lib/brand/BrandMotion.svelte |
| Accessible loading wrapper | packages/app/src/lib/components/PageholeLoader.svelte |
| Loader consumer rules | packages/app/src/lib/components/useDelayedLoading.svelte.ts |
| Favicons / PWA / apple-touch | packages/app/static/ (wired in app.html via %ph.icons%) |
| Empty-state art | packages/app/src/lib/brand/empty-state.svg (mark E) |
| Non-served exports (avatars, banners, motion) | brand/exports/ |
| Email lockups | packages/app/src/lib/email/assets/ |
| Retired previous brand | brand/retired/ |
Usage, asset regeneration, and verification are documented in
docs/design/brand-assets.md. The /styleguide
brand section renders the actual components, including replayable one-shot motion.
Marks use currentColor. Include their SVG directly in Svelte components at
build time. Do not import them as base64 or send SVG strings inside JavaScript.
They use the surrounding text color, so they follow the active theme.
Variant table
| Variant | Name | What it is | Use for |
|---|---|---|---|
| A | master | Ears rising from the hole, inside the page frame | App icon ≥ 32px, splash, hero |
| B | bare | Ears + hole, no frame | Small sizes (< 32px), lockup fallback, powered-by, dense UI |
| D | spiral | Concentric ripples around the hole | Occasional decoration only; never favicon, app icon, or status |
| E | missed | Hole with faint miss-arcs, ear half-sunk | 404 and empty states — "you fell too far" |
| — | lockup | Mark + "PageHare" wordmark, one SVG | Header/nav when there is room |
There is no static dive mark. The dive (ears sink, ripple, pop back) exists
only as motion — BrandMotion, its PageholeLoader wrapper, and the
brand/exports/motion/ files.
Never render a frozen dive pose.
Handoff rules
-
Lockup ↔ mark B at 120px. The full lockup shows when it has ≥ 120px of inline space; below that it collapses to the bare mark B. Implemented as a container query in
BrandLockup.svelte— it reacts to the space it is given, not the viewport. -
Mark A ↔ mark B at 32px. The framed master mark loses legibility below 32px; hand off to the bare mark B at small sizes (
Logo.svelte). -
Favicons use B; app icons use A. Browser tabs must remain legible at their smallest size, so the scalable favicon and 16px raster use B; 32px and 48px raster favicons use A. The ICO contains those same raster sizes. PWA and apple-touch icons use A with safe padding. Never use the spiral D on either surface.
-
Keep the recognition cues. The refined A uses a lighter page frame and a modestly larger hare-and-hole group; canonical SVG sources own the geometry. Keep the bare B silhouette, serif wordmark, and existing theme palette.
-
Social introductions use the recognizable logo and name. Banners show the PageHare lockup and a short product promise, placed clear of common profile-image crops. E is supporting state art, not the brand introduction.
-
Dive is motion-only. See above.
-
Mode 1 (tenant custom domains): tenant surfaces get mark B "powered by" only. PageHare favicons and OG marks NEVER render on a tenant domain — the
hooks.server.tstenant branch serves the tenant's favicon or none (renderBrandHeadinserver/theme.ts).
Motion family
BrandMotion animates the existing ears around a fixed hole. Its SVG and CSS
ship without a player, animation library, raster frames, or animation JavaScript.
The hole's proportions stay canonical: the ears, ripple, and brief accent strokes
carry the gesture; the hole itself does not stretch or bounce.
| Variant | Name | Use | Playback and resting pose |
|---|---|---|---|
dive | Quiet dive | Default loading | 1.8s loop; sinks, makes one ripple, returns to B |
ears | Curious ears | Smaller or quieter loading | 1.8s loop; ear flicks while the silhouette remains visible |
missed | Wrong burrow | 404 and empty states | 2.8s once, then rests as E |
success | A small hooray | Successful publishing | 1.4s once, then rests as B |
-
One-shot means once per displayed state. Do not loop 404, empty-state, or success motion. The styleguide has an explicit replay control for review.
-
Success follows the completed publish. Never celebrate autosave, a pending request, or a failed publish. Preserve the ordinary text confirmation.
-
Motion carries no essential information. Use visible state text and appropriate status semantics alongside it.
BrandMotionis always decorative;PageholeLoadersupplies the accessible loading announcement. -
Reduced motion is a deliberate resting pose. Disable all animation and show B for loading/success or E for missed. Do not freeze a diving frame or require JavaScript to reach the resting state.
-
Small brand gestures are the scoped exception to DS §7. The approved dive return and success stretch may overshoot modestly. Ordinary UI motion remains restrained; this is not permission to bounce controls or page content.
-
Reader output remains zero presentation JS. A CSS-only missed state may accompany an empty archive or reader error; it does not enable hydration.
Loader UX (consumer rules)
PageholeLoader accepts dive (default) or ears. The loader is a dumb visual;
the timing rules live in useDelayedLoading:
-
Don't show for loads shorter than 300ms — a flash of the dive is worse than no loader.
-
Once shown, hold for at least one full cycle (~1.8s) — a half-swallowed hare reads as a glitch.
-
prefers-reduced-motionis honored insideBrandMotion: static B, no dive, ear flick, or ripple.
Themes
The brand ships three themes (repo packages/app/src/lib/styles/): Warren
(default — near-white canvas, forest-green accent, baked into tokens.css),
Burrow (terracotta) and Twilight (indigo) as [data-theme] files.
Themes reassign Tier-1 tokens only; because marks are currentColor, they
recolor for free. AA is enforced in both modes by
scripts/check-theme-contrast.mjs.
The cold-read ritual (before shipping any new or edited mark)
One person, zero context, three checks: squint (does it still read as a hare in a hole?), upside-down (does the silhouette hold?), and at 24px (is it legible as a favicon?). If any fails, it is not ready.