# Rep Llama — Brand & Design System The spec for everything visual in the Rep Llama suite. Read this before writing UI. > **This file is published** at — unlisted (served > `noindex`) but public, so agent tooling can fetch it without a login. This repo is the > source; the served copy is generated. After editing, run `scripts/sync-brand-site.sh` > from the meta-repo root and commit the result in `wanaku-portal`, or the published page > silently goes stale. `scripts/sync-brand-site.sh --check` proves it hasn't. **The name is two words — `Rep Llama` — everywhere except the lockup**, where it is set solid as `RepLlama`. Running text, tab titles, legal copy, alt text, App Store text: two words. See §3. **In copy addressed to a user, the product is `Rep Llama` — not `Rep Llama Suite`.** Decided 2026-08-06 (EM-10601), after the invite and approval emails were reported for reading "Rep Llama Suite". Applies to anything a user reads: email bodies and subjects, sender display names, marketing pages, in-app copy, App Store text. `Rep Llama Suite` survives only as an *internal* collective noun — architecture comments, repo docs, the `wanaku-suite` meta-repo itself — where the point is specifically "the set of tools" rather than the product a user is being sold or sent mail from. Not yet propagated. At the time of writing the string still appears in roughly 120 files across 14 repos, including several that must not be changed casually — see `docs/NAMING-MIGRATION.md`. --- ## How to use this (for agents) 1. Use the tokens. Never hardcode a hex that already has a token. **Light and dark are both required** — a token without its dark value is an unfinished token. 2. **The code is the source of truth, not this file.** If they disagree, the repo wins and this doc gets updated. Canonical sources: - Web tokens — `wanaku-portal/app/globals.css` - Web brand components — `wanaku-orders/components/brand/`, `components/suite/` - iOS tokens/components — `RepLlamaKit/Sources/RepLlamaDesign/` 3. **Fixed, do not restyle without an explicit request:** the colour tokens, the per-tool accents, the type stack, and spacing/radius/elevation/motion. **The MARK is not on that list — it changed on 2026-08-04** (see §4); the 2×2 grid is retired. 4. The tool catalog is **runtime-editable** in `/admin/tools` and served from `public.tools`. Treat §5 here as the seeded fallback; the database wins. --- ## 1. Colour tokens Mirrored from `wanaku-portal/app/globals.css`. `repllama-tokens.css` in this folder is a copy for non-Next surfaces; `tokens.json` is the machine-readable form. | Token | Light | Dark | |---|---|---| | `--brand` | `#2c6ad4` | `#6fa6ff` | | `--brand-hover` | `#21539f` | `#8fbaff` | | `--brand-fg` | `#ffffff` | `#06223f` | | `--brand-subtle` | `#e6f0fe` | `#15294a` | | `--accent` (copper) | `#e8b338` | `#e8b338` | | `--accent-fg` | `#002952` | `#1a1410` | | `--background` | `#f6f8fb` | `#0b1622` | | `--surface` | `#ffffff` | `#122033` | | `--surface-2` | `#e9edf2` | `#1a2b40` | | `--foreground` | `#002952` | `#e8eef5` | | `--muted` | `#5c6b79` | `#9db0c2` | | `--muted-2` | `#8b97a2` | `#6f8298` | | `--border` | `#dee4ec` | `#26384f` | | `--border-strong` | `#c5cfda` | `#38506b` | | `--success` / `-subtle` | `#1b9a66` / `#e3f4ec` | `#34c58a` / `#0f3325` | | `--warning` / `-subtle` | `#b5840f` / `#fbf0d4` | `#e0a52e` / `#3a2d10` | | `--danger` / `-subtle` | `#d8493c` / `#fbe7e4` | `#f0625a` / `#3a1a18` | | `--ring` | `#508ff7` | `#6fa6ff` | There is **no `--info` token** on the web; iOS maps `info` to brand. **The bridge contract.** The canonical brand components use raw CSS-variable utilities only (`bg-[var(--surface)]`), never semantic classes — shadcn's `--muted`/`--accent` are *backgrounds*, the opposite of ours, so raw vars in the un-prefixed namespace never collide. A repo lacking these vars declares ONE block scoped to `.repllama-chrome`, the hook class every brand component root carries. Never remap a host's own tokens. Contract: `--surface, --surface-2, --brand, --brand-subtle, --brand-hover, --brand-fg, --foreground, --muted, --muted-2, --border, --ring, --accent, --warning, --font-hanken, --font-inter`. --- ## 2. Typography | Role | Face | Weights | |---|---|---| | Display / wordmark | Hanken Grotesk | **500**, 700, 800 | | UI / body | Inter | 400, 500, 600, 700 | | Code / numerals | JetBrains Mono | 400, 500 | **500 is load-bearing** — the lockup sets `Rep` at 500. Omit it and the browser synthesizes the weight or snaps to 700, flattening the hierarchy the brand is built on. Loaded via `next/font` in every app (self-hosted, preloaded). The two non-Next surfaces — `wanaku-prospect` and the marketing site — use a Google Fonts `` with the same weights. iOS bundles no custom font, so the lockups use the system face at matching weights (`.medium` / `.heavy`); the hierarchy is preserved, the face is not. --- ## 3. The wordmark and its three lockups Canonical: `wanaku-orders/components/brand/Wordmark.tsx` (web), `RepLlamaKit/Sources/RepLlamaDesign/Wordmark.swift` (iOS). ### Solid vs spaced — the rule - **In the lockup:** `RepLlama`, set solid, no space. - **Everywhere else:** `Rep Llama`, two words. Running text, page titles, legal copy, alt text, accessibility labels, App Store metadata. Two words need a hierarchy, so within the solid form: ``` Rep 500, letter-spacing 0.01em Llama 800 outer letter-spacing -0.02em ← tracks the pair back together ``` At small sizes (the endorsement line) `Llama` drops to **700** — 800 is too dense there. ### The three lockups | Lockup | Composition | Use | |---|---|---| | **Horizontal** (default) | mark + wordmark, gap `0.2 ×` the mark's height, optical centre | the everyday lockup | | **Stacked** | mark above; `REP` at 500/10px/`0.22em` tracked uppercase in `--muted`, `Llama` at 800 below | square containers: app icons, avatars, favicons ≥32px | | **Endorsement** | tool name at 800, `by RepLlama` at 500/700 in `--muted` beneath | where a single tool is named and the mark already appears once — this is what every tool's nav renders | **Horizontal proportions are fixed**, at a 50px reference: **mark 50 / wordmark 25 / tagline 10 / gap 10** — the mark is exactly `2 ×` the wordmark and `5 ×` the tagline. The gap is deliberately tight because the mark is a framed badge and its frame already carries padding; a generous gap double-counts it and the lockup drifts apart. The endorsement lockup takes the same gap for the same reason (that rationale is a property of the mark, not the variant), and keeps its own tool-name and `by RepLlama` ratios. **Clear space:** one quarter of the mark's height on every side. The frame is part of the mark, not padding. Exported as `clearSpaceFor(size)`. **Minimum mark size:** 20px (`MARK_MIN_SIZE`), enforced in the component. The one exception is the browser favicon, fixed at 16px by the OS — that surface uses the frameless master in `app/icon.svg`, never the component. --- ## 4. The family mark **A llama head in a rounded frame.** Canonical: `wanaku-orders/components/brand/RepLlamaMark.tsx` (web, byte-identical across the ten tool repos *and* the portal under `check-shell-parity.sh`) and `RepLlamaKit/Sources/RepLlamaDesign/RepLlamaMark.swift` (iOS). Geometry lives in ONE table per platform — `RepLlamaMarkGeometry` on iOS, exported path constants on web — transcribed mechanically from the delivered masters. If you are re-drawing the animal, that's the bug. > **The 2×2 rounded-tile grid is RETIRED** (2026-08-04). It said "a suite of tools" and > nothing else — no name recall, no warmth — and it collided with the tool tiles it sat > above. Anything describing the mark as a suite grid is stale documentation. ### Three crops — the filenames are not self-explanatory "Flat" means *no sheen*. It never means *no frame*. The crop is a separate axis from the master: | Crop | Asset | What it is | Where | |---|---|---|---| | **Framed** | `mark-primary`, `master-flat`, `master-sheen` | Field + gold frame + animal | The standalone mark, app tiles, anywhere it needs its own ground. This is what `RepLlamaMark.tsx` draws. | | **Unframed** | `mark-small` (shipped as `exports/icon.svg`) | Field + animal, **no gold frame** | Favicon and small UI. It keeps the navy field — never let it go transparent or it vanishes into dark browser chrome. | | **Glyph** | `mark-glyph-ink`, `mark-glyph-reverse` | Animal **only** — no field, no frame, transparent | The one case the mark sits directly beside type. Nothing else. | Every derivative except the two glyphs carries the frame. **Beside type a framed mark reads as a button**, which is why the glyph exists. `mark-glyph-ink` is for light grounds, `mark-glyph-reverse` for dark — unlike the framed mark, the glyph has no field of its own, so it is the one part of the mark that must follow colour scheme. ⚠️ The kit's own artwork draws the **endorsement lockup with no mark at all** — verified in its rendered DOM: the variants row contains exactly two images, for the horizontal and stacked lockups, and the endorsement card has none. Its caption is explicit: the endorsement "labels a single tool without repeating the mark twice." A glyph belongs there only when the lockup is a standalone tool header with no tool icon beside it, and then it is sized by type metrics — its height spans the cap of the tool name to the baseline of the `by RepLlama` line (32px at the 19/11 reference), anchored top and bottom rather than vertically centred. ### Two masters, and the split is a hard rule | Master | Use | Why | |---|---|---| | **Sheen** | **128px and up** | Gold sweep down the neck and back, delicate 16-unit frame. Reads as crafted at size. | | **Flat** | **below 128px** | Same silhouette, no sweep, heavier 54-unit frame that survives the smaller render. | ⚠️ **The delivered brand kit says 48px. We ship 128 deliberately — do not "fix" it back.** A stroke renders at `units / 2048 × size`, so the sheen master's 16-unit frame is sub-pixel until 128px: at 48 it computes to **0.38px** and smears into a dark olive edge, while the flat master one pixel smaller renders a clean **1.24px** copper ring. Switching at 128 uses each master only in the range it was drawn for and alters no geometry. Owner-reviewed. **128 is now consistent everywhere it is asserted** (2026-08-06). `MARK_SHEEN_THRESHOLD` (web) and `sheenThreshold` (iOS) were always 128; the `Wordmark` prop doc that still said 48 was corrected across all eleven byte-identical repos, and the **published** kit at `/brand/kit.html` is patched on copy — `scripts/sync-brand-site.sh` rewrites the five places the kit asserts 48, and fails loudly if that copy ever changes. The kit file *in this folder* is the untouched design deliverable and still says 48; that is the one remaining place, and it is deliberate. The cost is that the sweep is effectively reserved for login, covers and marketing — which is where the cloth-weight brand surfaces live anyway. ### Colour — fixed hexes, not tokens Field `#002952`, frame and sweep `#e8b338`, animal white. The field is **deliberately darker than `--brand`** so the mark reads as the parent above the twelve tool accents, and **must never be recoloured to `--brand`**. Because it carries its own field it is a solid badge that works on any surface — and, unlike the retired grid, it does **not** adapt to dark mode. **In a mark with no field of its own, the eye is a HOLE, not a colour** (corrected 2026-08-06). The rule is exactly one line, and it splits the family cleanly: > **Carries its own field → solid eye in that field's colour. No field → the eye is > punched out with `fill-rule="evenodd"` and takes whatever is behind the mark.** `mark-primary`, `master-sheen`, `master-flat`, `mark-small` and `mark-mono-gold` carry a field, so their eye is solid and matches it. The five frameless assets — `mark-glyph-ink`, `mark-glyph-reverse`, `mark-mono-ink`, `mark-mono-reverse`, `mark-mono-grey` — are cut-outs. A fixed eye colour on a frameless mark is only ever correct by accident, and it was wrong in two visible ways at once. `mark-glyph-reverse` had a **white** eye on a white animal, so the eye was simply *absent*. `mark-mono-reverse` — the asset §"Installable icons" and the kit both name for the **per-tool app icon, reverse mark on the tool's accent field** — had a navy eye that stayed navy on Email Blast amber, Prospect lime and every other accent. As cut-outs both are right on any ground for free. **Never re-solidify one**, and if you redraw the animal keep the muzzle subpath merged into the body path rather than stacking it on top. ### Installable icons `app/icon.svg` and `app/apple-icon.png` are the llama **alone on solid `#002952`, frame cropped off** — a rounded frame inside an OS-rounded icon reads as an icon within an icon. They ship flat and opaque; iOS masks the corners itself, so do not pre-round them. `app/apple-icon.tsx` was deleted — there is no `ImageResponse` for it. `opengraph-image.tsx` still generates, but the mark is inlined there as a PNG data URI, because satori only decodes raster images and cannot render the component. ### Misuse, in priority order The sheen master below 128px; recolouring the animal or the field; rotation; non-uniform scaling (the delivered masters ship `preserveAspectRatio="none"` — it is stripped in both implementations); drop shadows; placing the mark on a mid-tone ground where the navy field stops separating. --- ## 5. The weave — the brand device Canonical: `wanaku-orders/components/suite/WeaveBand.tsx`, `RepLlamaKit/Sources/RepLlamaDesign/WeaveBand.swift`. A woven band after the striped aguayo a llama wears. It is the one brand element that is **not the logo**, and it exists so a surface can feel like us without a mark on it. **One implementation per platform.** The motif lives in a single geometry table that the component, the email rasterizer and the PDF renderers all read. If you are hand-rolling an SVG ``, that is the bug — `grep -rn "` in **user-space units**, with **no `viewBox`** — a viewBox would map the tile onto the full width and stretch the motif. Pattern ids come from `useId()`; two bands on one page must not collide. ### Three weights | Height | Contents | |---|---| | `4` hairline | ground + one copper hairline. No diamonds. | | `10` band | ground + two copper hairlines + diamond row. **The default.** | | `28` cloth | + light bands top and bottom, copper diamond core, stepped selvedge. Tiles vertically as a field. | ### Per-tool variant The ground takes the tool accent, and the thread pair becomes **white at 88%** and **`#002952` at 55%**. Copper only reads on the indigo ground — it disappears on the warm and teal accents — so it must never be used there. This is enforced in the API: passing a tint switches the threads, and there is no way to request copper on a tinted ground. ### Rules - Full-bleed to **both** edges, at the **top** of a surface or the **foot** of a printed page. Never inset, never independently rounded, never floating mid-layout. - **One per surface.** - Never set type on it. Never place the mark on it. - Under **120px** long, drop to the 4px hairline. - Cloth (28px+) only for login, empty states, covers, marketing. **Never behind body copy.** - Inside a tool the ground is **that tool's accent**; the hub and anything cross-tool uses `--brand`. **Never two accents on one surface.** ### Where it ships Auth/login card (top), printed line sheets and order confirmations (foot), the signing certificate (foot), login split-screen cloth panel. **NOT transactional email.** This section used to list "transactional email headers", and it disagreed with what actually ships. The Resend-hosted templates — `stripe-connect-setup` and `status-update`, which are the reference for suite transactional mail — open on a centred `wordmark.png` above a `#dee4ec` rule, with no weave anywhere. Sign Docs matches them (EM-10601). Only the portal's own mailer still renders a band, and it is the outlier. Resolved 2026-08-06 in favour of the templates: the wordmark identifies the sender at a glance in an inbox, which is the job of an email masthead, and a 10px band does not. If you are building a transactional email, use the masthead in §"Email masthead" below and do not reach for the weave. If the weave is ever wanted in email again, the technique is a repeating `background-image` over a `background-color` ground, so a client that blocks images still gets a correct solid band. The PNG is generated from the shared geometry by `npm run brand:weave` in the portal — never hand-drawn. ### Email masthead The canonical transactional email header, as shipped by the hosted templates and Sign Docs: | | | |---|---| | Card | 560px max-width, `#ffffff`, `border-radius:6px`, page ground `#f6f8fb` | | Logo | `wordmark.png` at `width="240"`, centred, `alt="Rep Llama - Tools reps actually need"` | | Padding | 32px above the logo, 28px below, 36px gutters | | Divider | 1px `#dee4ec` rule between masthead and body | | Type | `Inter,Arial,Helvetica,sans-serif`; body 16px/26px in `#002952` | | CTA | `#2c6ad4`, white, 15px/600, 14px×34px padding, `border-radius:6px` | Art is fetched from `https://app.repllama.com/brand/` — **one origin, never vendored into a tool repo.** A local copy is how the retired 2×2 grid mark survived in Sign Docs email for two weeks after the rest of the suite had moved on. Mail clients strip inline SVG and block `data:` URIs, so a hosted absolute URL is the only form that renders. --- ## 6. Tool catalog — twelve tools Seeded fallback. `public.tools` is authoritative and `/admin/tools` edits it live. | Tool | slug | Accent | lucide | SF Symbol | Section | Subdomain | |---|---|---|---|---|---|---| | Orders | `orders` | `#3B82F6` | `clipboard-list` | `list.bullet.clipboard` | Sell | orders | | B2B Site | `b2b` | `#0EA5E9` | `store` | `storefront.fill` | Sell | b2b | | Catalog Flip | `flip` | `#A855F7` | `book-open` | `book.pages` | Catalog | flip | | Catalog Pricer | `pricer` | `#14B8A6` | `tag` | `tag.fill` | Catalog | pricer | | Product Info | `products` | `#06B6D4` | `package-search` | `shippingbox.fill` | Catalog | products | | Catalog Studio | `studio` | `#EC4899` | `book-marked` | `books.vertical.fill` | Catalog | studio | | Flow | `flow` | `#F97316` | `images` | `photo.stack` | Catalog | flow | | Sign Docs | `sign` | `#10B981` | `signature` | `signature` | Outreach | sign | | Email Blast | `mail` | `#F59E0B` | `send` | `paperplane.fill` | Outreach | mail | | Rally | `rally` | `#F43F5E` | `calendar-clock` | `calendar.badge.clock` | Outreach | rally | | Fancy Forms | `forms` | `#6366F1` | `list-checks` | `checklist` | Outreach | forms | | Prospect | `prospect` | `#84CC16` | `map-pin` | `mappin.and.ellipse` | Outreach | prospect | All subdomains are `.repllama.com`; the hub is `app.repllama.com`. **This table now matches `public.tools` row for row** — it previously carried four values the shipped catalog never had (b2b's accent and SF symbol, Studio's glyphs, Image Flow's and Prospect's SF symbols). Where the spec and the database disagree, **the database wins**; these were reconciled to it on 2026-08-04, and b2b's accent was moved to the kit's `#0EA5E9` by owner decision at the same time. **The launcher card renders the accent as a 4px hairline TOP rail**, not a left border — see `components/tools/ToolRail.tsx`. Accent ground, one 1px thread, no diamonds, full-bleed. The thread is white at 88% or ink `#002952` at 55%, **whichever contrasts better against that ground** — a fixed white thread vanishes on the light accents. The kit's "luminance > 0.55" rule is not implementable as written: no accent here exceeds 0.55 under WCAG, so a literal reading paints every rail white. Locked tiles take a flat `--border-strong` rail with no thread. Admin's tile is slate `#64748b`, deliberately **not** a tool accent. **Adding a tool** needs a migration (`/admin/tools` can only UPDATE), the lucide glyph in `wanaku-portal/components/tools/ToolIcon.tsx` **in the same change** (an unmapped icon silently renders a clipboard), and a waffle re-sync. See `docs/RepLlamaWebVault/Adding a Tool.md`. Accent hues are nearly exhausted. `b2b` is **catalog-only**: a row and a locked tile, no repo, no app. **CORRECTED 2026-08-04:** `studio` used to be listed alongside it. That was wrong. Catalog Studio is a real, deployed, in-use app — repo `wanaku-catalog-studio`, its own Vercel project, serving `studio.repllama.com` (the subdomain is mid-cutover; see meta-repo `CLAUDE.md` gotcha 0b), with the full suite wiring: auth gate, `lib/suite`, `components/suite`, waffle. It was missed by the rebrand sweep only because the repo had never been cloned to `~/GitHub`, and the parity scripts read the standalone clones — no clone, no scan, no complaint. --- ## 7. Page titles ```ts export const metadata: Metadata = { metadataBase: new URL("https://.repllama.com"), title: { default: " · Rep Llama", template: "%s · Rep Llama", }, description: "", }; ``` - Separator is `·` (U+00B7), **never** a hyphen. Always ends in `Rep Llama`. - `default` carries the suffix too — the template does not apply to it. - The portal hub is the lone exception: plain `"Rep Llama"`, no template. - Descriptions come from the catalog sizzle, so the tab, the launcher tile and the OG card all say the same thing. --- ## 8. Spacing, radius, elevation Unchanged by the rebrand. Spacing `4 / 8 / 12 / 16 / 24` — shared by both platforms. Tinted-surface wash **0.12**, icon chip **0.10** (matching the web's `1A` alpha suffix). **Radius is platform-split, and this section used to hide that.** It read `badge 8, card 12` with no platform qualifier — which is the *iOS* scale, quoted incompletely (it drops `surface 16`). 12px appears **nowhere** in the suite's web UI: `wanaku-orders` ships `rounded-md` ×419, `rounded-lg` ×131, `rounded-full` ×26 and zero `rounded-xl`. A web surface built "to spec" from the old line would have been wrong. | | Source of truth | Scale | |---|---|---| | **iOS** | `RepLlamaKit/Sources/RepLlamaDesign/Theme.swift` → `RepLlamaTheme.Radius` | `badge 8`, `card 12`, `surface 16` | | **Web** | Tailwind, used directly — **there are no web radius tokens, by design** | `rounded-md` 6px for controls, `rounded-lg` 8px for surfaces (cards, modals, popovers) | Do not add web radius tokens, and do not copy the iOS numbers onto a web surface. The generated `repllama-tokens.css` emits no radius on purpose, and `scripts/sync-tokens.sh` fails on any token the canon invents. The one non-Next surface that cannot use Tailwind classes (`wanaku-prospect`) names the two it mirrors — `--radius-md` / `--radius-lg`, after the *class* rather than a role, so porting a canonical component stays mechanical — in its **own** stylesheet, never in the generated token file. iOS values are quoted here for orientation; `Theme.swift` is authoritative. If the two disagree the code wins and this table is what gets edited (chain of truth, §0). --- ## 9. Voice Plain, specific, unglamorous. Sizzle lines are one sentence, lowercase-ish, and describe the job — "Price a vendor catalog, fast." Not "Revolutionize your catalog workflow." The tagline is **"Tools reps actually need"**, with *actually* in `--warning`. Hub only. --- ## 10. Known cross-platform gaps - iOS bundles no custom font, so lockups use the system face at matching weights. - The static waffle lists in each repo are an **offline fallback**; the live catalog comes from `/api/suite/access`. A stale name there is cosmetic. - Announcements are **dormant** — authorable in the admin, but nothing renders them.