A single, binary system.
Every visual decision in this product flows from one conceit: a black-and-white world split by a 15° diagonal. Good is on one side, Bad on the other. There are no greys, no gradients, no decorative colour. What looks like depth is opacity over the contrast colour. What looks like motion is a continuous stream of value.
This manual covers the building blocks — type, logo, the angle, characters, components, colour overrides, stream visualisations — and the rules for assembling them. If you are designing for Moral Hazard, building a collaboration, or running a co-branded campaign, everything you need is here, and the materials are in the brand-kit.
Direct. Binary. Slightly provocative.
Moral Hazard is an experimental on-chain game, never a financial product. The voice matches: matter-of-fact, mechanical, and unembarrassed about the risks. There is no excitement to manufacture — the game is interesting on its own.
The Question · sacred
Always written exactly. The colon before "Good or Bad?" is intentional.
Good or Bad?
Claim phrase
A short tagline that sits beside the logo or as a hero line on social/print. It distils the whole mechanic into one line:
Words we don't use
Legal posture matters. These swaps are not stylistic preferences.
| Avoid | Say instead |
|---|---|
| invest / investment | participate / participation |
| returns / gains / profit / earnings | distribution share / break-even progress |
| guaranteed | (never imply certainty) |
| gambling / betting | game-theoretic experiment |
| paying / payment | transferring / transfer |
| price | asked amount |
| buy / purchase | claim |
| financial product / instrument | (never use) |
| cost (for streaming) | streaming outflow |
| owner / buyer (for Good Spot) | Good Spot holder |
| investor / participant (for Bad) | Bad streamer |
Always written out
- 1.337% is always 1.337%. Never rounded. Pair with “permanently locked” or “immutable” when discussing trust.
- "Not financial advice." verbatim on game pages.
- Lowercase sentences, sentence case in body. Headings and labels are UPPERCASE.
- No cursive / italic text anywhere in product or marketing. The one exception is a direct quote. (The non-public admin page may use italic in rare cases.)
- Second person, no exclamation enthusiasm. The closest the product gets to a “yay” is the REACHED pill on the break-even bar — and note that its colour is
--mh-active, a user-overridable accent. The green is a chosen accent for legibility, not a signal of profit; it could just as well be blue.
One line. Everywhere.
The 15° diagonal is the brand's central conceit. It separates good from bad on every page, anchors the logo, frames the NFT artwork, becomes the chip on a button corner, rotates into a horizontal cut on mobile. Always 15°. Never approximate.
The math
tan(15°) ≈ 0.2679. For a viewport --mh-angle ramp at full height h, the horizontal offset between the top and bottom edges of the cut is h × 0.2679. The canonical clip-path used in production is:
Page splits
Three split positions ship today. Same angle, different centre.
Landing & invite. Both characters visible.
Good Spot, All Spots, Voting, Admin. Content on white.
Bad Streams, Profile, FAQ. Content on black.
On phone & tablet
When the viewport is too narrow for a vertical diagonal to make sense, the cut rotates to a horizontal cut at the bottom edge of a header band. Header height: 140 px (phone) / 200 px (tablet). The math doesn't change — the band's horizontal traverse is still headerHeight × 0.2679.
As a styling element
Beyond the page split, the 15° angle also appears as the chipped top-left corner on cards, buttons, and panels. Same tangent, smaller magnitude.
The mark is a tiny game state.
The logo encodes the mechanic. An outer black ring contains a white circle. Inside, a left half-circle (tilted 15° right) represents the Good Spot. A small white cut-circle is overlaid by a right half-circle. Three dots radiate out — they are bad streamers. Solid arcs connect them in (distributions from claims); dashed arcs return out (streams back). The logo is, in miniature, the game.
Variants
Hazard
Rare · stacked variant. A vertical lockup with the wordmark below the symbol (as used on the NFT media) exists for constrained contexts only — a logo wall, a square avatar, a watermark where a horizontal lockup won't fit. Use it only when neither horizontal arrangement is possible.
Wordmark typography
"Moral " in Encode Sans Light · "Hazard" in Encode Sans Bold. Tracking -0.01em. Single space between the words, no kerning adjustments. "Moral Hazard" is always set on a single line. The wordmark may sit to either side of the symbol — left or right — whichever balances better with the surrounding layout. Never stack the words vertically, never break onto two lines, never wrap inside the symbol.
Wordmark + claim variant
An extended lockup pairs the wordmark with the project's short claim — “Stream. Claim. Observe.”. The claim sits as a second line directly underneath "Moral Hazard", in Encode Sans Regular, fully black, no italic, at roughly 0.4–0.5× the wordmark size. Alignment matches the wordmark: right-aligned when the lockup sits left of the symbol, left-aligned when it sits right of the symbol.
Clear space
Reserve a clear margin equal to 0.5 × the symbol height on every side. No other element — including text, image, or background pattern — should enter this zone.
Minimum size
- Digital: 32 px symbol height (favicon excepted — 16 px). Wordmark + symbol lockup minimum 120 px wide.
- Print: 8 mm symbol height. Lockup minimum 35 mm wide.
Colour
The logo is intrinsically black and white — a single asset works on both light and dark backgrounds. There is no separate "inverted" version; the white half of the mark provides its own contrast against a dark page. Never recolour to a third hue. Under a UI override or a branded campaign the logo's two tones follow the overridden base colours (see §13) — but by default, and wherever possible, it stays black and white. The animated variant keeps the same constraints — only opacity is animated, never hue.
Hazard
The mark in motion
The mark animates as a tiny game in motion: the streamer dots pulse and the three dashed return-arcs flow inward. It loops indefinitely and lives peripheral, never attention-grabbing - always subtile. The general animation principles — plus the swipe, next-step pulse and stream-line animations — live in §11 Animation.
Black on white. White on black.
Every shade you see in production is opacity on the contrast colour. There are four base colors that define the main UI coloring. Users can override colors to individualize their interface and store their preferences permanently in their Moral Hazard Profile NFT. Everything else is alpha, computed against whichever contrast is in play.
The four base colors · default
Alpha ladder · the only allowed shades
When you need depth, hover, or hierarchy, reach into this ladder. New alpha values are not introduced ad-hoc.
UI override · user-selectable palettes
Users can override the colors. The system is unchanged — only the colour values swap. Everything continues to compose because every other colour is alpha on whichever contrast is in play. See §13 · UI override & campaigns for the full set of override and branded-campaign examples.
Default black & white is the canonical look and what every external material should use wherever possible. Overrides are primarily a personal preference inside the app — but they may also appear in branded marketing. If a token launches with a partner campaign (e.g. OPx on Optimism), the announcement can swap the black for the Optimism red while keeping the system, the angle, and the characters intact. By default, though: black and white. See §13.
Semantic accents · sparingly
Only two colours sit outside the black/white system by default. They mark things that need an accent to be distinguishable from the rest of the content — never decoration, never filler.
Two further tokens exist for specific, rare signals and should be treated as exceptions, not part of the everyday palette: --mh-hot (the "HOT" heat label on a Spots card) and --mh-info (active-token dot in the sidebar). Reach for them only where that exact meaning fires.
Encode Sans. Four weights. No exceptions.
One typeface across the whole system. Light for body, semibold for labels and toggles, bold for headings and numerals that need to anchor. No serif companion, no monospace face, no decorative caps. The mono in event logs and addresses uses the platform's ui-monospace stack — never bundled. Get the family from Encode Sans on Google Fonts ↗.
Weights
Type scale
| Token | Size | Used for |
|---|---|---|
--fs-4xl | 88 px | "Do you think humans are…" hero |
--fs-3xl | 64 px | Landing hero |
--fs-2xl | 44 px | Page H1 |
--fs-xl | 32 px | Page subtitles, hero stats |
--fs-lg | 22 px | Small headings, big numbers |
--fs-md | 18 px | Lead paragraphs, FAQ answers |
--fs-base | 16 px | Body |
--fs-sm | 14 px | Body small · default reading floor |
--fs-xs | 12 px | Captions, breadcrumbs |
--fs-2xs | 11 px | Uppercase labels, chip text |
Smallest body size is 14 px. Labels and toggles may still use 11 px intentionally — they are short, all-caps, tracked at 0.12em.
Headings
Every stream
flowing in.
- 700 weight, UPPERCASE. Tight letter-spacing (
-0.015emat large sizes). - Line height 0.95–1.05 for hero, 1.05–1.2 for sub-heads.
- Never use the regular or light weights for headings.
Labels
- Semibold (600), UPPERCASE, 11–12 px, tracking 0.12em. The most-used type style in the app.
- Opacity defaults to 0.5 on the contrast colour. Increase to 0.8 when the label is the main signal.
Body
Claim the spot by transferring the asked amount. Every active stream redirects into your wallet until someone takes it from you. The asked amount ticks up 1% per claim and decays after inactivity.
- Light (300), sentence case, line-height 1.45–1.6, max width 720 px.
- Slight tracking loosening (
0.01em) for readability at 16 px and below.
Numeric
font-variant-numeric: tabular-nums lining-numson every number, especially streaming balances. Eliminates jitter as digits change.- Group thousands with a thin space (
U+202F), not a comma or a period. Decimal is always a period. - Bold weight for emphasis numbers (asked amount, big totals). Light weight for in-paragraph numerals.
Two sides. Two genders. Four files.
The characters are not mascots and they are not flat silhouettes either. They are black-and-white illustrative portraits with real facial detail, hair, clothing, accessories — designed to read clearly even when reduced to a single contrast colour. The good character is open and confident; the bad character is cool, deadpan, sunglasses on and thumbs-up. Always positioned on the side opposite to the content.
The four variants
Placement & direction rules
- Always on the side opposite the content. Good content on the white side → good character lives on the black strip. Bad content on the black side → bad character lives on the white strip.
- Facing inward, toward each other. Good characters look toward the right (right shoulder forward); bad characters look toward the left. Eyes look straight ahead, not at the viewer — so a good/bad pair almost meets eyes across the diagonal.
- Direction-aware cropping. Good characters must never be cut off on the right edge; bad characters must never be cut off on the left edge — that's the side they face. Let them bleed off the opposite/bottom edge instead, integrating into the background rather than ending on a hard straight line.
- Rendered as
mask-imageon a coloured div so the figure inherits the contrast colour for its side. Never embed the SVG as a literal black or white shape — it must theme-adapt when the user overrides the base palette. - Gender is a per-user preference stored on the ProfileNFT. The same character pair appears across all sessions for that user.
- One colour, full illustration. The figures contain shape detail (hair, glasses, lapels, fingers), but they are monotone — every line and fill is the same colour, achieved via the alpha mask. No second tint. No greys.
Character sets & variants
The four-character grid above is the default set. Additional sets exist (and more can be commissioned) — a robot/android set, a couch set, a fighter set, animal sets, and so on. Every set keeps the same rules: two sides, two genders, shared posture for the good pair, a shared “thing” for the bad pair, two colours only.
- Robot / android set — soft-line friendly good robots vs. hard-line aggressive evil androids.
- Animal sets — red panda, fox, raccoon, otter, squirrel … each a good vs. bad pair.
- Couch & fighter sets — alternative poses that keep the good/bad posture and the shared-"thing" rule.
Each set ships as four files (good-guy, good-gal, bad-guy, bad-gal) plus a switch icon — all two-colour and following the rules below.
Commissioning a new set · the ruleset
New character sets need to follow these non-negotiable rules:
- Two colours only. Good = light figure on dark; bad = dark figure on light. Square source tile.
- Shared posture for the good pair — looking toward the right, right shoulder forward, left shoulder back.
- Shared posture for the bad pair — looking toward the left, same shoulder geometry.
- A shared “thing” for the bad pair unique to each set (the default set uses sunglasses + thumbs-up; never reuse that for a new set).
- Detail level matched to the reference set. Eyes look straight ahead, not at the camera, so a good/bad pair almost meets eyes.
- Bleed, don't cut. Characters integrate into the background — never a straight horizontal/vertical crop. Keep the figure within an inner ~500×900 px area of the 1024×1024 px canvas.
- No symbols, patches, logos or text on the figures.
Sizes
| Context | Symbol height |
|---|---|
| Desktop full-page | 650 px (default) |
| Tablet header band (200 px) | 190 px |
| Phone header band (140 px) | 133 px |
| Inline avatar (e.g. profile card) | 36–48 px |
Bespoke. Binary. No emoji.
The icon set is part of the brand. No icon font, no Lucide, no Heroicons. Every icon is drawn in the same vocabulary as the rest of the system: hard corners, no strokes thinner than 1.5 px, theme-adaptive via mask-image.
Emoji & decorative glyphs
Forbidden. No emoji in product copy, marketing copy, or social posts. No unicode glyphs as decoration. The only sanctioned glyphs in body text are the directional arrows → ← ↑ ↓ and the status dot ● (used for active-stream / connection indicators).
Invite-code critters
Invite codes are represented by a sequence of animal icons (e.g. bird · cat · dog · duck) — easier to remember and share than an alphanumeric code. These are UICONS by Flaticon (flaticon.com/uicons), not bespoke art — a curated subset chosen to sit alongside the rest of the icon language: hard, simple, legible at small sizes. Because they are a third-party set, they fall under Flaticon's licence terms and are not redistributed in the brand-kit; link to the source instead.
Roster: a curated set of UICONS animals (bird, cat, dog, duck, frog, mouse, snail, squirrel, and more). The exact subset is defined in code; it can grow as the invite space needs it.
Drawing new icons
- Pure black or pure white fills. No strokes thinner than 1.5 px. No rounded line caps.
- Hard corners. Match the geometric language of the existing set.
- Square viewBox. 16, 24, or 32 — pick one per icon and stick with it.
- Theme-adaptive. Render as
mask-imageon a coloured div so the icon picks up the contrast colour.
Subtle. Peripheral. Continuous.
The streaming feeling is a defining characteristic of the product — money is in motion at all times. The visualisation has to convince of that without becoming attention-grabbing. Three subtle indicators do all the work.
The three indicators
1 · Flow shimmer
A 200%-wide gradient passes across an actively-streaming value once every 2 seconds. Opacity stays at 0.06–0.08 — barely visible, but unmistakable when the eye catches it.
2 · Active dot
A 6 px emerald dot beside the value, slow-ticking between 1.0 and 0.35 opacity every 1.8 seconds. At most one per cluster of streaming values.
3 · Animated balance ticking
For values that actually grow (a streamer's accumulated balance), interpolate the digits between server polls. RAF-driven at ~100 ms cadence, polling every 5 s. The number counts up smoothly. Tabular figures keep the layout from jittering.
When to use them
| Value | Shimmer | Dot | Ticking |
|---|---|---|---|
| Total flow rate (per game) | ✓ | ✓ (one for the cluster) | — |
| Your stream's flow rate | ✓ | — | — |
| Your accumulated received | — | — | ✓ |
| Total streamers count | — | ✓ | — |
| Asked amount (changes only on claim) | — | — | — |
Flow & distribution lines
The inbound flow lines and outbound distribution lines that literally connect each streamer to the good spot are animated stream visualisations — their encoding rules and a live example now live in §11 Animation.
Hairlines, not shadows.
Every container in the system is bounded by a 1 px hairline at 10–20% opacity. No drop shadows. No glow. The two exceptions: the emerald pulse on a successful action, and the flow shimmer on a streaming number.
Buttons
- Primary — solid contrast, 2 px border. The main action.
- Secondary — outline 2 px. First step in a two-step flow, or "alternative path".
- Ghost — 1 px hairline border at 0.3 alpha. Tertiary action.
- Disabled — translucent fill, no interaction,
cursor: not-allowed.
Pills
- Pills carry meaning. Don't use a hot-red pill to label "popular" or a warn pill to label "new". The colour is reserved for the listed states.
border-radius: 2px— almost a hard rectangle. Never round.
Stats
Cards
1.0426
Holder reached break-even on day 4. Claim now and every active stream redirects to your wallet.
- 1 px hairline border at 10% alpha. No drop shadow. No glow.
- Corner radius:
0for default,2 pxfor chips,4 pxfor cards/panels,8 pxfor inputs and the sidebar group chevron.999 pxonly for round status dots and the round logo. - The 15° chip-corner is the only "decorative" container treatment allowed.
Labels & toggles
Break-even bar
Two states. Below 100%: a neutral grey fill on the alpha-ladder track, the percent shown as a label — no pill. At or above 100%: the bar fills with --mh-active and a single REACHED +N% pill sits on the right, carrying the overshoot percent inside it. There is never a "reached" pill while the bar is still filling.
Linear. Or cubic-bezier(0.4, 0, 0.2, 1).
No bounce. No spring. No overshoot. Motion is functional — page transitions, hover fades, stream shimmers. Anything that draws the eye without communicating state change is forbidden.
Durations
| Token | ms | Used for |
|---|---|---|
--dur-fast | 150 | Hover opacity, simple state swaps |
--dur-base | 300 | Color, transform |
--dur-page | 700 | Layout-shell diagonal slide |
--dur-shimmer | 2000 | Flow-shimmer cycle |
Easing
cubic-bezier(0.4, 0, 0.2, 1) for everything UI. Linear for the shimmer and the dot tick. No ease-out-back, ease-out-cubic, or spring-based curves.
Hover / press
- Hover: opacity drops to 0.7 over 150 ms. No colour shift. No scale. The thing under the cursor fades, it doesn't change colour.
- Press: no shrink, no scale. A sub-300 ms opacity drop, or a fill swap on a toggle button.
Slow reveal · headlines
Large page headlines fade and rise in on first paint — a slow, single reveal (opacity 0→1, a few pixels of upward travel), gated so it plays once when the content becomes active. Never loop it, never apply it to body text or numbers. On print, PDF export, and prefers-reduced-motion the end-state shows immediately. Easing cubic-bezier(0.4, 0, 0.2, 1), ~500–700 ms.
Page transitions
The 700 ms diagonal slide between /good, /bad, and the landing is the brand. Don't replace it with a fade or a slide. Don't shorten it.
Loops, swipes, pulses — never decoration.
Every animation earns its place by communicating something: a continuous stream of value, a state change carried along the 15° angle, or the one next step a user should take. Animations are generally infinite loops — streams read as continuous moving strokes, distributions as lines that build up fully and disappear again. Opacity and stroke-dash are the only animated properties — never hue, never scale-bounce.
- Loop, don't perform. Continuous animations (streams, the idle mark) stay peripheral — never attention-grabbing.
- No bouncing, no spring easing. Linear for continuous loops,
cubic-bezier(0.4, 0, 0.2, 1)for transitions — neverease-out-backor a spring. - Reduced motion. Everything here collapses to its end-state under
prefers-reduced-motion.
The logo's own idle loop lives with the mark in §03; the functional UI-motion tokens (durations, easing, hover/press, page transitions) are in §10.
Content swipe · the 15° angle
Swapping the content of a panel in place — like the /bad story ↔ dashboard toggle — never cross-fades. The outgoing content is wiped away along the brand's 15° diagonal and the incoming content is revealed behind it, so the signature angle carries every state change. Component: <AngledSwap>, ~700 ms, cubic-bezier(0.4, 0, 0.2, 1), keyed on the content id so it fires on an actual swap — not on every re-render.
The wipe edge is parallel to the page split — 15° off vertical. Same direction every time; never animate hue or scale. Reference: <AngledSwap> on /good & /bad.
Next-step pulse
When a flow has one obvious next action — Take it after an Approve confirms, Connect to Pool after a stream opens, Claim Deposit once it unlocks — that single control pulses to pull the eye to it. A faint ring pings outward while the button's opacity breathes between 1 and 0.7. Only ever one element pulses at a time; it stops the moment it's clicked or is no longer the next step. Never pulse a destructive action.
- One at a time. The pulse means "do this next" — two pulsing things means neither reads as the next step.
- Opacity + ring only. 1.25 s loop, no scale, no colour. Stops on click or when the step is done.
- Never destructive. Close-stream / burn / forfeit actions never pulse — the pulse invites, and those need a deliberate choice.
Flow & distribution lines
On the game pages the streams are drawn literally: lines connecting each bad streamer to the good spot. Two directions, encoded differently:
- Flow lines (inbound) — an animated dash flowing continuously toward the good spot. One per active bad stream.
- Distribution lines (outbound) — a solid path most of the time, fainter than the flow; in a loop it clears toward the streamers and then redraws toward them — a claim distributing the pool back out.
- Thickness encodes flow rate. A bigger streaming outflow draws a thicker line; tiny streams are hairlines. This lets the eye rank streamers without reading numbers.
- Speed separates the streams. Each line's dash animates at a slightly different speed, also representing its flowrate. Side-effect: neighbouring lines don't visually merge into one moving field. Always slow enough to stay peripheral.
- The user's own stream uses
--mh-active(and a touch more weight) so they can immediately pick their line out of the crowd. Every other line is the contrast colour at reduced alpha.
Reference implementation: the /good and /bad page stream visualisations. Mirror their thickness mapping, per-line speed jitter, and the --mh-active treatment for the viewer's own stream. See also §09.
Three NFTs. One system.
Every NFT is a server-rendered SVG that queries the live contract for current stats. They share one system: the same diagonal background, the same logo and "powered by" marks, the same character treatment. The forms stay put; the colours flip by side. The holder's chosen characters, gender, side and colour overrides carry through — good/bad spot NFTs use the holder's characters, the ProfileNFT reflects the user's own settings.
The three NFTs
A short list of forbidden moves.
Each of these has appeared in concept work or pitches. None of them ship.
Same system. Swapped paint.
The whole product is built so the four base colours and the character set can be swapped without anything breaking. That powers two things: a personal UI override (a user theming their own app), and a branded campaign (a partner integration that temporarily dresses the UI in their colours and characters). The rules below keep both on-system.
What can be overridden
- The four base tokens —
--mh-good,--mh-bad,--mh-good-contrast,--mh-bad-contrast. Everything else is alpha on whichever contrast is in play, so it all re-composes automatically. - The character set — any of the available sets (default, robot/android, animals, …) and the gender per side.
- Nothing structural. The 15° angle, the layouts, the type, the logo geometry, the component rules — all fixed. Only paint and characters change.
Override examples
Branded campaign · live demo
A campaign keeps the diagonal, the characters, and every layout rule — it just swaps the bad-side colour for the partner's. Here the same "you are one of N" hero, rendered default vs. an Optimism-red campaign:
Note the accent: when the bad side becomes red, an accent that would clash (like a red "HOT") gets re-picked; --mh-active stays distinct. Always sanity-check accent legibility against the overridden base before shipping a campaign.
Rules for collaborators
- Override the colours, not the system. Keep the diagonal, the type, the logo, the component rules.
- Keep contrast honest. The good/bad backgrounds and their contrasts must stay legible (WCAG AA for body text). Don't pick two mid-tones.
- Characters may be swapped to a partner set but must follow the character rules (two sides, two genders, shared posture / shared "thing", two colours, facing inward).
- The logo stays the logo. Under a campaign its two tones follow the base colours, but its geometry, spacing, and minimum sizes don't change.
- Campaigns are temporary and scoped. Default black & white is the resting state and what un-branded materials use.
What ships in the .zip
A deliberately public-safe set of files for other projects, partners, and press — only material we own and can legally redistribute. It does not ship third-party assets (token icons, chain logos, the UICONS animals, the font binaries); those are linked to their sources instead. One folder and this design manual at the root.
Download the MoralHazard-BrandKit.zip →
brand-kit/
Deliberately excluded (for licensing / legal reasons): token icons, logos from other companies/projects, the UICONS invite animals, and the font binaries.
Encode Sans font via Google Fonts → Superfluid Brand kit → UICONS by Flaticon→
Something to
discuss in person?
Planning a collaboration, a co-branded campaign, or have a question this manual doesn't answer?
Get in touch →