<!-- https://getkonjo.com/design/foundations/imagery · source: docs/design/foundations/imagery.md -->

# Imagery

Photos, avatars, thumbnails, the mascot — and what each looks like when it fails.

> Part of the [Konjo design language](../konjo-design-language.md). The laws in the spine
> apply here unless this chapter contradicts them.

**Chrome carries no imagery.** No photographic headers, no textured backgrounds, no image
behind a nav bar. This was a deliberate calibration decision and it has a practical reason:
the hero block has to work for a dojo that has never uploaded a picture, and most of them
never will. A design that only looks good with photography is a design that looks broken for
your first hundred customers.

Imagery appears in exactly four places: **user content**, **avatars**, **event and dojo
pages**, and **onboarding**.

## The failure case is the design

Every image in Konjo is remote, and remote means slow, missing, or 404. Decide what the
absence looks like *before* the presence, because the absence is what a new dojo sees.

| | Loading | Missing / failed |
|---|---|---|
| Avatar | The initials fallback — never a spinner | The initials fallback, permanently |
| Thumbnail | `surface.tag` block at the final aspect ratio | Same block with an `inline` glyph, no error text |
| Event / dojo photo | Reserved space at the final ratio | The section renders without it; nothing collapses |

**Reserve the space at the final aspect ratio before the image arrives.** Content that jumps
when a picture loads is the most common way a list feels cheap, and it is entirely avoidable.

Use [`CachedImage`](../../../src/components/CachedImage.tsx) rather than a bare `Image`, and
[`MediaThumbnail`](../../../src/components/ui/MediaThumbnail.tsx) for anything in a grid or
feed — it already carries the radius, the ratio and the play overlay.

## Avatars

[`Avatar`](../../../src/components/ui/Avatar.tsx) is the only way a person is pictured.

- **Sizes are fixed**: 32 in a row and in the header, 88 on a profile. The header's carries a
  1px hairline ring so it separates from whatever it sits on.
- **Circular, always.** A squared avatar reads as a logo.
- **The fallback is initials on a flat fill**, not a silhouette glyph. A generic person icon
  makes eleven students look like the same student; "AT" does not. The fill is
  **`surface.muted`** with a 1px **`border.subtle`** ring and **`text.secondary`** initials —
  named here because "a flat fill" was the whole specification and the next person to build
  an avatar somewhere new would have picked one.
- **Never tint an avatar by belt.** Rank is carried by the [belt dot](rank.md) beside it. Two
  encodings of the same fact is one too many, and a coloured ring around a photo fights the
  photo.

## User photos and video

- **Posts and coaching clips keep their own aspect ratio**, capped so one tall image cannot
  own the screen.
- **Video routes to Mux**, never to Storage — see the
  [video pipeline](../../pipelines/video-mux.md). A video thumbnail is a `MediaThumbnail` with
  the play overlay; it never autoplays in a feed.
- **All uploads go through the [upload queue](../../../src/media/uploadQueue.ts)**, never
  Storage directly from a screen.

## Text on a photograph

The short version: don't. If you must —

- A **scrim** between the photo and the text, dark enough that the text clears 4.5:1 against
  the *lightest* pixel it covers, not the average.
- The text must still be legible if the image fails to load, which means the scrim is a
  solid-enough fill on its own.
- Never `accent.red` or any red on a photograph. Red is a role; on top of an unpredictable
  background it is just a colour that vanishes.

This is why the hero block is **a solid surface carrying type, never a photograph**.

## The mascot

The Konjo ninja is the brand's mark and its mascot: one character, silent, always a black
belt ([Kata interview](../2026-09-29-kata-founder-interview.md)). Canon, construction and the
pose list are in [the ninja prompt pack](../brand/ninja-prompt-pack.md); the vector masters are
in [`assets/brand/ninja/`](../../../assets/brand/ninja/), and the app renders the PNGs that
[`scripts/brand/icons.mjs`](../../../scripts/brand/icons.mjs) generates from them.

**Where the ninja may appear:**

- Onboarding and the auth screens, inline with the wordmark in
  [`KonjoWordmark`](../../../src/components/auth/KonjoWordmark.tsx).
- Marketing, emails and social.
- Empty states and error states, in the pose that matches the moment: *point* for "start here",
  *seiza* for nothing today, *oops* for something that failed.
- Celebrations and milestones: *promotion* for a belt, *jump* for a streak or badge.

**Where it never appears:**

- Chrome: tab bars, headers, navigation. The screen title does that job.
- Billing, waivers, safety flags and anything with legal or money consequences. Those moments
  are serious; a cartoon undercuts them.
- As a speaker. **The ninja is silent**: no speech bubbles, no first-person lines, no copy
  written "in its voice". The words on the screen stay in the app's plain voice.
- Recoloured, redrawn by hand, or holding a weapon.

In an empty or error state the ninja **accompanies** the message and the one way in; it never
replaces them. A screen that shows the ninja and no action is still an empty state with no way in.

## Never

- A stock photo of martial arts. Konjo shows *this* dojo or it shows type.
- A gradient standing in for an image.
- An image that is load-bearing for comprehension with no text alternative.
- A decorative image without `accessibilityElementsHidden` — see
  [accessibility](accessibility.md).
- Text baked into an image. It cannot scale, cannot be read aloud, and cannot be translated.
