English governs the contract statements on this page. Contract names, addresses and numbers are shown as they are on the chain.
KAY9 Design System
KAY9 — The On-Chain Watchdog. This document is the design source of truth for kay9.io and every KAY9 interface. It was produced in the Fable design phase and is what the implementation reproduces. If the implementation and this document disagree, fix the implementation.
The brand promise is simple: KAY9 watches so you do not have to trust. Everything on the site should feel like standing in a quiet, well-lit control room with a very alert dog.
1. Brand foundation
1.1 Canonical asset
Kay9.png (1254×1254, black ground, brass ring, spaniel portrait) is the only mascot. It is never redrawn, recolored, flipped, cropped through the face, or replaced by a generic dog. Derived assets live in apps/web/public/brand/ and are produced from the original by script:
| File | Use |
|---|---|
kay9-original.png | Untouched source, served for press / OG fallback |
kay9-mark.png, kay9-mark-1024.png, kay9-mark-64.png | Circular cut with transparent corners; navigation, hero, loading |
favicon.ico, favicon-16.png, favicon-32.png, apple-touch-icon.png | Browser |
icon-192.png, icon-512.png, icon-maskable-*.png | PWA |
token-icon-256.png | Token lists, wallet asset icon |
social-avatar.png | X / Telegram / Discord |
og-image.png, og-image.jpg | OpenGraph 1200×630 |
kay9-watermark.png | Desaturated 18 % alpha for background treatments only |
The ring in the logo is the brand's structural motif. Every ring on the site (score rings, scanning ring, progress rings, timelock rings) is the same ring: brass, 6 px stroke at 240 px, rounded caps, sweeping clockwise from 12 o'clock.
1.2 Personality
Watchdog. Serious. Protective. Premium. Intelligent. Crypto-native. Trustworthy. Powerful. Slightly edgy.
What this means in practice:
- Quiet confidence over hype. No rocket emojis, no "100x", no countdown-to-moon. The site states facts a contract can prove and links to the proof.
- The dog is a guard, not a pet. The mascot is shown large, still, and lit from the ring. It is never bouncing, winking, or wearing hats. Motion around it (scanning ring, status ticks) is what moves.
- Meme identity without cartoon styling. The meme lives in copy that is dry and a little edgy ("Bark first. Rug never."), not in cute illustration.
- Premium means restraint. One accent, two type families, generous space, hairline structure only where it carries meaning.
1.3 Voice
- Sentence case everywhere, including buttons and navigation. No tracked-out uppercase labels.
- Plain verbs. "Request deep audit", "Read the contract", "Claim tokens". A button's label is the past-tense of its toast: "Request deep audit" → "Deep audit requested".
- Say what is proven and where: "Fixed supply — enforced by
KAY9Token, no mint function exists." - Never say "verified", "safe", or "approved" about an audited token. Say "scored 25/100 — high risk" and show the signals.
- Never imply a creator receives 1 % of every trade. Say "Official pool fee: 1 %" and separately explain the LP / creator split that is actually deployed.
- Simulated or demo data is always labelled, in the component itself: a small "Demo data" chip in the corner, brass outline, never hidden in a tooltip.
Sample lines (usable as copy):
- Hero: KAY9 — The On-Chain Watchdog. "A fixed-supply token, a fair launch, permanent liquidity, and an audit protocol that writes its verdicts to the chain. The website is a window. The contracts are the truth."
- Audit page: "Paying for an audit buys analysis, not a score. A token can pay and still get 25/100."
- Transparency page: "Every line below is read from Robinhood Chain when you load this page. Nothing here is a promise."
- Empty registry: "No reports on this chain yet. The watchdog is awake — request the first deep audit."
- Error: "Robinhood Chain RPC did not answer. Showing the last values we read (block 56,063,858, 40 s ago). Retry."
2. Design tokens
All tokens are CSS custom properties on :root; Tailwind v4 @theme maps them. The site commits to a single dark look (the logo is black-ground; a light theme would fight the asset). color-scheme: dark is set explicitly and all surfaces are painted, never transparent.
2.1 Color
Base palette (6 named values, all sampled from or derived from Kay9.png):
| Token | Hex | Role |
|---|---|---|
--ground | #000000 | Page ground behind the hero and mascot. True black because the asset is true black. |
--surface | #15120E | Default section and card surface (warm charcoal, from the ring's shadow). |
--surface-2 | #221D16 | Raised surfaces: modals, table headers, inputs. |
--ink | #F3EBDD | Primary text (warm ivory, from the ring highlight #FDF497 desaturated). |
--brass | #F2B62A | The accent. Ring, primary buttons, live indicators, focus. From ring sample #F7B928. |
--copper | #8A4A1E | Deep accent from the fur shadow #88441A: gradients behind the mascot, pressed states, secondary rings. |
Supporting text and line tokens:
| Token | Hex | Role |
|---|---|---|
--ink-2 | #BEB4A2 | Secondary text |
--ink-3 | #958D7F | Muted text, timestamps, disabled. 5.7:1 on --surface, 5.1:1 on --surface-2; the previous #7F776A was 4.2:1 and 3.8:1, under the 4.5:1 normal-text minimum for the micro copy that uses it. |
--line | #2C261E | Hairlines, table rules |
--line-strong | #4A4032 | Input borders, active rules |
--brass-soft | rgba(242,182,42,0.14) | Brass tint for chips and selected rows |
--brass-deep | #B98418 | Brass at chart-safe lightness (validated) |
Status palette (reserved; never reused for series). Validated with the dataviz palette script against #15120E: all checks pass.
| Token | Hex | Meaning | Always paired with |
|---|---|---|---|
--ok | #2FA377 | Low risk / healthy / confirmed | shield-check icon + label |
--warn | #C77D1A | Elevated risk / stale / pending | triangle icon + label |
--danger | #D8434A | High risk / failed / impossible | octagon icon + label |
Trust score bands (0 = worst reading, 100 = most trustworthy; scores are trust, higher is better):
| Band | Range | Color | Label |
|---|---|---|---|
| Green | 76–100 | --ok | Low risk |
| Amber | 41–75 | --warn | Elevated risk |
| Red | 0–40 | --danger | High risk |
Why the number runs this way now, and what it does not change. The scale used to run the opposite direction — 0 safest, 100 worst — for reasons that were sound at the time (the on-chain field the site was reading was itself named and computed as risk, and inverting only the display would have meant the site and the chain disagreed about what a number meant). That is no longer the constraint: overallTrust is now the field's real name on the on-chain AuditResult, computed by the engine as 100 - risk before it is ever signed, hashed or displayed (toTrustScores in services/watchdog/src/engine/scoring.ts), so the site and the chain say the same thing under the same name. What did not change is everything the old three reasons were actually protecting:
- This project still may never call a token safe. A 100 is "nothing risky was found by this engine at this block", never a safety certificate, and the permitted phrasings in §19 and the band labels above (
Low risk, neverSafeorVerified) still hold exactly as before. The highest published number is not an endorsement any more than the lowest one was an accusation. - Missing evidence still never looks clean. It used to raise the published risk; it now caps the published trust instead, so a category or an overall score the engine could not fully measure can never read as fully trustworthy — see
UNCERTAINTY_FLOOR/CRITICAL_FLOOR/HIGH_FLOORinscoring.ts, which still operate as risk-space floors internally and become trust-space ceilings only at the one conversion boundary. - A ring or a bar still fills in proportion to its value — and now that fill direction is no longer backwards: a trustworthy asset draws a nearly full ring, which is what every progress meter a reader has ever seen already trained them to expect. The rule that no surface may render a score without naming the axis (
charts.trustAxis, "Trust") stays in force regardless, because a reader should never have to infer which axis a number is on — it is just no longer the load-bearing defence against a misreading that it used to be. This sits alongside the rule in §19 that no surface may render a score without its date: a bare number is not a result.
Chart categorical palette (fixed order, validated on #15120E, all six checks pass):
| Slot | Hex | Assigned to |
|---|---|---|
| 1 | #B98418 (brass-deep) | Public fair auction / KAY9 / "protocol" series |
| 2 | #5E8FD6 | Permanent liquidity reserve / ETH-side series |
| 3 | #C0602A | Team |
| 4 | #8B7BE8 | Burn / other |
Sequential ramp (single hue brass, light→dark) for magnitude: #F6DFA3 → #E9C25C → #C99A2A → #A0761B → #6F5010. Diverging (rarely needed): #5E8FD6 (cool) → #6E675C (neutral) → #C0602A (warm).
2.2 Typography
Two families, clearly distinct, both from Google Fonts with local fallbacks:
- Bricolage Grotesque (display). Variable font; use
wght 700–800,wdth 75–87,opszauto. Its tightened width at large sizes gives KAY9 a bark that Inter-alikes cannot. Used for the wordmark, page titles, big numbers, section headings. - IBM Plex Sans (text) and IBM Plex Mono (data). Plex is engineered and slightly severe, which is the watchdog register. Numbers use
font-variant-numeric: tabular-numseverywhere a column of numbers appears. Mono is only for hashes, addresses, calldata, and Q96 values, never for labels.
Fallback stacks: "Bricolage Grotesque", "Arial Narrow", "Helvetica Neue", Arial, sans-serif and "IBM Plex Sans", system-ui, "Segoe UI", sans-serif, "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace.
Type scale (Elements of Typographic Style, 1.25 ratio at text sizes, larger jumps for display). Base 17 px on desktop, 16 px on mobile.
| Token | Size / line | Family / weight | Use |
|---|---|---|---|
display-1 | 88 / 0.95, wdth 75 | Bricolage 800 | Hero title only |
display-2 | 56 / 1.0, wdth 80 | Bricolage 800 | Page titles |
h1 | 40 / 1.1, wdth 87 | Bricolage 700 | Section titles |
h2 | 28 / 1.2 | Bricolage 700 | Card and subsection titles |
h3 | 20 / 1.3 | Plex Sans 600 | Row titles |
lead | 21 / 1.5 | Plex Sans 400 | Intro paragraphs |
body | 17 / 1.6 | Plex Sans 400 | Body |
small | 14 / 1.5 | Plex Sans 400 | Meta, table cells |
micro | 12 / 1.4 | Plex Sans 500 | Chips, axis ticks |
num-xl | 64 / 1.0 | Bricolage 800 tabular | Score rings, hero stats |
mono | 14 / 1.5 | Plex Mono 400 | Addresses, hashes |
Rules: line length ≤ 72 characters for body (max-width: 62ch). Headlines are never one-word-accented. No eyebrow labels above headings unless the eyebrow carries data (e.g. a live block number). Mobile display sizes: display-1 48, display-2 36, h1 30.
2.3 Spacing, radius, elevation
- Spacing scale: 4, 8, 12, 16, 24, 32, 48, 64, 96, 128. Sections are separated by 96 (desktop) / 64 (mobile), never by cards.
- Radius has meaning:
r-ring9999 for rings, chips, avatars;r-28 px for inputs and buttons;r-314 px for cards that are interactive (link cards, audit job cards). Static content blocks have no radius and no border; they are separated by space or a single hairline. - Elevation: there are no drop shadows. Raised surfaces use
--surface-2and a 1 px--linetop edge. Modals add a 40 % black scrim. The only glow on the site is the brass halo behind the mascot (radial-gradientof--copperat 35 % → transparent), and the score ring's 1 px outer glow when a score finalizes.
2.4 Layout
- Container
max-width: 1200px, 12-column grid, 24 px gutters; 16 px on mobile. - Left-aligned, never centered, including the hero. Centered layouts read as templates; a watchdog reads a ledger from the left.
- Asymmetry is the default: text 7 columns, evidence 5 columns. On mobile everything stacks in reading order: title, fact, proof.
- The right column is where evidence lives: contract addresses, live values, rings, charts. The left column is where claims live. This claim/evidence split repeats on every page so the eye learns it.
Desktop (12 col)
┌──────────────────────────────┬────────────────────┐
│ claim (7) │ evidence (5) │
│ headline, prose, CTA │ ring / live values │
└──────────────────────────────┴────────────────────┘
Mobile
┌────────────────┐
│ claim │
│ evidence │
└────────────────┘
3. Structural devices
Structure encodes information; it never decorates.
- The Ledger. A table with hairline rules, no zebra striping, no card wrapper. Used whenever content is claims verified against chain: transparency, contract permissions, allocation totals. Columns are always
Claim | Value | Enforced by | Proof. "Proof" is a link to the explorer or a function name, never the word "verified". - Numbered sequence. Used only for genuine sequences: How it works (request → detect → analyze → sign → commit), fair-launch phases (auction → clearing → migration → trading), team unlock timeline. Never on feature grids.
- The Ring. Score, progress, and time. Always brass on
--linetrack, always with the number inside innum-xland the band label under it. Rings do not animate on scroll; they animate once when their value arrives from chain. - Live dot. A 8 px brass dot with a slow 2 s pulse (opacity 1 → 0.4) means "read from chain in the last 60 s". If the read is older than 60 s the dot goes
--ink-3and stops pulsing; if the RPC failed it becomes a--warntriangle with "stale" text. Nothing else pulses on the site. - Chips. Radius full, 1 px border, micro type. Used for chain names, status, tiers, and the "Demo data" flag. Never for decoration.
- Address block. Mono,
0x1234…abcdmiddle-truncated with a copy affordance and an explorer link, plus a "verified source" check when Blockscout reports the source as verified.
4. Motion
Motion budget: one orchestrated moment per page, plus responses to user actions. prefers-reduced-motion disables all non-essential motion and replaces it with instant state changes; rings then render at final value.
- Page load (home only): the brass ring around the mascot draws itself once (1.2 s, ease-out), then a thin radar sweep (a 30° conic gradient at 12 % brass) rotates once (2 s) and stops at 12 o'clock. Four status ticks light up around the ring in sequence as each chain read resolves (block, token, pool, audit hub). That is the whole page-load sequence. No section fade-ins, no card hover lifts.
- Score reveal (audit result): ring fills from 0 to score (900 ms, cubic-bezier(.2,.8,.2,1)); number counts up; band label fades in at the end. Plays once per result, on arrival.
- Auction state: the clearing price line on the auction chart moves with a 300 ms ease when a new checkpoint arrives; the "your bid" marker slides with it.
- Transaction states: button → "Confirm in wallet" (spinner, brass outline) → "Pending" (ring rotates) → "Confirmed" (ring fills, check) → resolves back to a link "View transaction". Errors replace the button label with the reason and offer "Try again".
- Unlock countdown: team vesting rings tick once per second; digits do not flip or roll.
- Hover: links underline (1 px,
--brass); interactive cards get a--line-strongborder. That is all.
5. Information architecture
Primary navigation (7 items; mobile collapses to a sheet):
KAY9 | Token Fair launch Audit Registry Transparency Docs | [Connect wallet]
Routes:
| Route | Purpose |
|---|---|
/ | Home |
/token | Live token facts (address, supply, pool, fee, liquidity, team lock) |
/tokenomics | Allocation and vesting explained and visualized |
/launch | Fair launch: state machine, live auction, bid, claim |
/audit | Request a basic scan or deep audit; job tracking |
/audit/[jobId] | Job detail and result |
/registry | Browse all committed reports across chains |
/registry/[chain]/[asset] | Asset report history |
/transparency | The Ledger |
/how-it-works | Sequence explanation of the protocol |
/contracts | Every deployed contract explained |
/docs | Rendered protocol documentation (from /docs/*.md) |
"Tokenomics", "How it works" and "Contracts" are reachable from the footer, the Token page, and in-page links; the primary nav stays at seven.
Wallet is only requested on /launch (bid, claim) and /audit (approve, request). Every other page never calls eth_requestAccounts.
Footer: contract addresses (mono, copy), chain (Robinhood Chain 4663), explorer, GitHub, docs, "Built for kay9.io — the contracts survive without it."
6. Pages
6.1 Home
The hero opens on the most characteristic thing in KAY9's world: the dog inside the ring, and the ring reporting live chain status.
┌─────────────────────────────────────────────┬───────────────────────────┐
│ KAY9 │ ╭──────────╮ │
│ The On-Chain Watchdog (display-1)│ ╭─┤ mascot ├─╮ │
│ │ block ● 56,063,858 │
│ Fixed supply. Fair launch. Permanent │ token ● deployed │
│ liquidity. 0 % token tax. An audit protocol │ pool ● 1 % fee, live │
│ whose verdicts live on Robinhood Chain. │ audit ● 2-of-3 online │
│ │ ╰──────────╯ │
│ [Request a deep audit] Read the contracts │ │
└─────────────────────────────────────────────┴───────────────────────────┘
Below the hero, in order (each a full-width section separated by 96 px, left-aligned, claim/evidence split):
- What is proven — six short claims with the enforcing contract named under each: fixed supply, fair launch, permanent liquidity, 0 % token tax, official pool fee 1 %, team locked on-chain. Not cards: a two-column list with hairlines between rows.
- The audit protocol — the sequence (request → detect → analyze → sign → commit) as a horizontal numbered rail with the on-chain steps drawn in brass and the off-chain step drawn in
--ink-3outline, so the trust boundary is visible in the diagram itself. - Live protocol activity — a compact ledger of the last 8 events (audit requested, result committed, team release, fee collected) read from chain with block links. Empty state: "No protocol events yet on this chain."
- Tokenomics preview — one stacked bar (45.5 / 45.5 / 9) with direct labels and a link to
/tokenomics. - Cross-chain vision — a small tree diagram (KAY9 core → EVM adapter → Robinhood, BNB, later chains; → Solana adapter). Chains not yet live are shown in outline with "planned" chips. No fake logos of partners.
6.2 Token
Right column: the mascot small (120 px) above a ledger of live values. Left column: explanation.
Ledger rows (all read from chain; each has an explorer proof link): Network, Token address, Symbol, Decimals, Total supply, Circulating (total − vesting-locked − launch-vault-held), Official pool (key, fee 1 %, tick spacing 200, hook), Pool liquidity (ETH and KAY9 sides), Liquidity lock (FeeSplitter address, "positions irrecoverable"), Team allocation, Vesting status (released / locked / next unlock), Owner/Timelock addresses.
Pre-deployment state: rows show "Not deployed yet" in --ink-3 with the predicted address when available and a chip "Testnet" or "Not launched".
6.3 Tokenomics
This page explains everything, with the numbers held constant across three visualizations that answer three questions.
- Where does supply go? A single horizontal stacked bar, 1,000,000,000 KAY9, three segments with 2 px gaps and direct labels: Public fair auction 455,000,000 (45.5 %), Permanent liquidity reserve 455,000,000 (45.5 %), Team 90,000,000 (9 %). Below, in prose: "The 91 % launch allocation is split evenly: half is sold in the auction, half is paired with the auction's ETH as permanent liquidity."
- When can the team sell? A step chart (time on x, cumulative unlocked on y, 0 → 9 %) with three steps annotated with exact UTC timestamps read from
KAY9TeamVesting: TGE 1 %, +6 calendar months 5 %, +12 calendar months 9 %. Below it, three rings (one per tranche) showing time remaining. The step chart is a single brass line on a recessive grid; the current time is a vertical--ink-3rule. - Where does the auction ETH go? A Sankey-like flow drawn as three horizontal bands: auction ETH → v4 pool (100 %), 455 M reserve KAY9 → v4 pool, LP NFT → FeeSplitter (locked). Fee split box beside it showing the actual deployed arrangement (native ETH fees 40 % creator / 60 % compounding; token fees 100 % compounding).
Every number is followed by "enforced by Contract.function" in small --ink-2. Nothing on this page is a promise.
6.4 Fair launch
A state machine page. The header shows the current phase as a numbered rail:
1 Not launched → 2 Auction live → 3 Clearing / migration → 4 Trading on Uniswap v4
Phase panels:
- Not launched: the parameters that will be used (floor FDV, graduation FDV, duration 4 h, tick spacing, LP allocation 100 %) read from the launch vault's pending configuration, with implied prices computed client-side and labelled "computed from on-chain parameters and the Chainlink ETH/USD feed". "Launch requires the owner's signature" is stated plainly.
- Auction live: left, the clearing price chart (price vs time, brass line, checkpoints as 8 px dots, floor as a hairline, graduation threshold as a labelled
--ink-3rule). Right, the bid panel: budget input in ETH, max price (as FDV in USD with the raw Q96 shown in mono beneath), "Your bid clears if the final price ≤ your max". Below, "Your bids" ledger with exit/claim actions. A large ring shows time remaining. A second ring shows "raised / required to graduate". Link out: "Also visible on the Uniswap app auctions tab". - Clearing / migration: shows the final clearing price, tokens sold, ETH raised, and the migrate call anyone can send ("Anyone can trigger migration after block N").
- Trading: pool key, position IDs held by the FeeSplitter, "Liquidity principal cannot be withdrawn by anyone", fee accounting, "Unsold KAY9: N — placed as permanent single-sided liquidity" (or the real outcome).
Fairness statement block (static, on every phase): no presale, no whitelist, no team pre-buy, no validation hook (validationHook = 0x0), team holds only the declared 9 %.
6.5 Audit
Two-step form on the left, the price and status evidence on the right.
┌───────────────────────────────┬──────────────────────────────┐
│ 1 Choose the target │ Deep audit price │
│ [Robinhood ▾] [0x… / mint] │ $10.00 target │
│ 2 Choose the depth │ 824.37 KAY9 now │
│ ○ Basic scan free │ KAY9/ETH TWAP 30 m ● ok │
│ ● Deep audit $10 in KAY9 │ ETH/USD Chainlink ● fresh │
│ │ Pool liquidity ● ok │
│ [Connect wallet to request] │ Operators online 2 of 3 │
└───────────────────────────────┴──────────────────────────────┘
- Basic scan runs immediately in the browser/against the public engine endpoint and renders a result card labelled "Basic scan — not committed on-chain".
- Deep audit: Connect → Approve KAY9 (or sign permit) → Request. After the transaction confirms, the page routes to
/audit/[jobId]. - If pricing is unavailable (stale oracle, thin liquidity, no observations), the price panel shows the exact reason in
--warnand the request button is disabled with "Deep audits are paused until pricing is reliable". Nothing falls back to spot.
6.6 Audit job
Top: the score ring (or a rotating ring while pending) with the band label. Left: job facts ledger (job ID, target, requester, payment in KAY9, transaction, requested at block/time, status, operators who signed, engine version, report hash, report pointer). Right: risk breakdown as seven horizontal bars (contract, liquidity, holder concentration, linked wallets, creator history, trading behaviour, bots/snipers), single-hue sequential fill mapped to the band, direct-labelled with the number, each with a one-line "why" beneath drawn from the flags bitmask. Flags render as chips with the flag name; hovering shows the definition.
Status timeline (numbered, vertical): Requested → Detected by operators → Analysis complete → Signed (n of m) → Committed on-chain. Each step shows its evidence (block, tx, signature count) or "waiting" in --ink-3.
Report integrity: "Report hash on-chain: 0x…; pointer: ipfs://…; hash of fetched report: matches ✓ / does not match ✗". If the pointer cannot be fetched, the on-chain fields still render.
6.7 Registry
A filterable ledger (chain, band, date). Columns: Asset (chain chip + address/mint), Latest score ring (48 px), Band, Reports (count), Last committed (block/time), Engine. Clicking a row opens /registry/[chain]/[asset] which lists the full report history newest first without overwriting older ones, plus a small sparkline of score over time when ≥ 2 reports exist. No wallet needed.
6.8 Transparency
The Ledger, exceptionally clear. Intro line: "Every row is read from Robinhood Chain when you open this page." Then rows:
| Claim | Value | Enforced by | Proof |
|---|---|---|---|
| Fixed supply | Yes — 1,000,000,000 | KAY9Token has no mint function | source |
| Additional minting | No | no mint selector in bytecode | source |
| Buy / sell / transfer tax | 0 % / 0 % / 0 % | standard OZ ERC-20 transfer | source |
| Blacklist / freeze / pause | No | absent | source |
| Proxy upgrades | No | non-upgradeable, no admin | code hash |
| Hidden transfer restrictions | No | transfer is unmodified | source |
| Team allocation | 9 % | KAY9TeamVesting.TOTAL_ALLOCATION | call |
| Team lock | On-chain | three immutable unlock timestamps | call |
| LP withdrawal by team | Impossible | LP NFTs held by FeeSplitter, no withdrawal path | position IDs |
| Official LP fee | 1 % | pool key fee = 10000 | pool |
| Audit price target | $10 | KAY9Pricing.deepAuditUsdTarget (48 h timelock to change) | call |
| Fee split for audits | 70 / 20 / 10 | KAY9AuditHub constants | call |
Under the ledger: every contract address with verification status and a "Compare deployed bytecode hash" line. A final block: "What the owner can and cannot do" as two plain lists.
6.9 How it works and Contracts
How it works: the five-step audit sequence and the four-phase launch sequence, each as a numbered vertical rail with a diagram (SVG, brass strokes on --surface) and a paragraph. Contracts: one section per contract with the same six fields every time: address, network, verification, purpose, permissions (what the owner can change, with the timelock delay), upgradeable (always "No" for the token). Cross-links to source in the repository.
6.10 Docs
Renders /docs/*.md with the site type system, a left index, and anchors. Nothing else.
7. Components
Minimal set; each has loading, empty, error, and stale states.
Ring(value 0–100 or fraction, size, label, band). Loading: track only with a slow rotating 60° brass arc. Error: track in--warnwith a triangle in the middle.LiveValue(label, value, source, updatedAt). Shows the live dot state. Skeleton: a--surface-2block the width of the expected value, no shimmer animation (reduced motion friendly, and shimmer is a template tell).Ledger(rows of claim / value / enforced by / proof). Responsive: below 720 px it becomes a definition list with the claim as heading.AddressBlock(address, chain, verified). Copy on click, explorer link, verified check.Chip(status | chain | tier | demo).TxButton(idle → wallet → pending → confirmed → error). Contains the whole state machine so pages do not re-implement it.PhaseRail(steps, current). Horizontal on desktop, vertical on mobile.AllocationBar,VestingStepChart,RiskBars,PriceChart(auction),Sparkline. All charts: single y-axis, recessive--linegrid, tabular numbers, direct labels, hover tooltip with crosshair, a table toggle for accessibility, texture fill option for forced-colors.EmptyState(title, one line, one action). Never an illustration.WalletMenu(address, chain check, disconnect). If the wallet is on another chain the primary button becomes "Switch to Robinhood Chain".
8. Responsive rules
- Breakpoints: 480, 720, 1024, 1280. The claim/evidence split appears at ≥ 1024. Between 720 and 1024 evidence sits under the claim at full width. Below 720 tables become definition lists, rails go vertical, rings scale to 160 px, and the hero mascot sits above the title at 220 px.
- Touch targets ≥ 44 px. Charts get a 12 px hit slop around marks.
- Horizontal overflow is only allowed inside
overflow-x: autowrappers (addresses, calldata, wide tables). The body never scrolls sideways.
9. Accessibility and performance floor
- Color is never the only channel: bands have icons and words; series have labels and a legend; live status has words.
- Text contrast ≥ 4.5:1 for body (
--inkon--surfaceis 15.6:1;--ink-2on--surfaceis 8.9:1), ≥ 3:1 for--ink-3micro text on--surface-2. - Visible focus: 2 px
--brassoutline with 2 px offset on every interactive element. - Reduced motion respected globally; rings and counters render final values.
- Fonts: preloaded,
font-display: swap, subset to Latin. The mascot is served as a 1024 px PNG withpriorityon the home hero only; elsewhere 256 px or smaller. LCP target < 2.0 s on a mid-range phone, CLS 0 (all live values reserve width). - Semantic landmarks, one
h1per page, tables with<th scope>, charts with an accessible table alternative.
10. Metadata and PWA
- Canonical
https://kay9.io. Titles: "KAY9 — The On-Chain Watchdog" (home), "Page · KAY9" elsewhere. - OpenGraph/Twitter:
og-image.jpg(same 1200×630 card asog-image.png, 74% smaller),summary_large_image. manifest.webmanifest: name KAY9, short name KAY9, theme#000000, background#000000, icons 192/512 plus maskable.- JSON-LD
OrganizationandWebSiteon the home page;SoftwareSourceCodeon the contracts page;HowToon how-it-works;WebApplicationon audit. llms.txtat the root: a plain-text summary for AI assistants, androbots.tsnames the major ones (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, CCBot, …) explicitly rather than leaving their access to the wildcard rule.
11. Self-review against the generic defaults
- Warm cream + serif + terracotta: not used. The site is black/brass because the asset is black/brass.
- Near-black + single acid accent: the accent is brass from the logo, the ground is true black under the hero and a warm charcoal elsewhere; no tinted
#111. The accent is not acid and it is tied to the ring motif rather than sprinkled. - Broadsheet hairlines: hairlines exist only in the Ledger, where the content is a ledger. Sections are separated by space.
- SaaS card kit: static content has no cards, no radius, no shadows. Only interactive things get a radius.
- Template chrome: no uppercase eyebrows, no middle-dot meta strings, no arrows on links, no mono for labels, no scroll-triggered fades. One orchestrated load moment on the home page.
The one bold thing is the ring: the logo's ring becomes the scanning ring, the score ring, the timelock ring, and the transaction ring. Everything else stays quiet.
Part two: dynamic experience, theming and languages
This part supersedes the single-theme, low-motion decisions above where they conflict. The brand foundation, the ring motif, the type roles and the copy voice in Part one all still hold.
12. Two themes, system-led on the first visit
The site follows the visitor's operating system preference on first visit and offers a toggle. Neither theme is an inversion of the other: each is designed, and each has its own validated chart palette.
The toggle is binary: light or dark. There is no visible "system" option, and no way back to following the system once a visitor has chosen — a choice is meant to stick. The resolution order, implemented identically in the pre-paint inline script and in ThemeToggle, is:
- An explicit choice in
localStorage['kay9-theme']wins. The only values that mean anything arelightanddark. - With nothing stored, the operating system decides:
prefers-color-scheme: darkgives dark, an unset or light system gives light. This is the absence ofdata-theme, which is what the CSS media query is written against. - The toggle always writes an explicit value and sets
data-themeon the root element.
Any other stored value — including the system this document used to specify — is removed from storage on read, and the theme then resolves from the operating system. The inline script runs before first paint, so the page never flashes the wrong theme.
12.1 Dark theme
The palette in section 2.1 is unchanged. It remains the identity theme: true black under the hero where the mascot sits, warm charcoal elsewhere, brass accent.
12.2 Light theme
Warm and papery rather than clinical white, so the brass accent still reads as brass and the mascot's black ground becomes a deliberate object on the page rather than a hole in it.
| Token | Light value | Role |
|---|---|---|
--ground | #FFFFFF | Page ground behind the hero |
--surface | #FBF7EF | Default section and card surface, warm paper |
--surface-2 | #F2EADC | Raised surfaces, table headers, inputs |
--ink | #1A1610 | Primary text |
--ink-2 | #4A4234 | Secondary text |
--ink-3 | #6E6656 | Muted text, timestamps |
--line | #E4DACA | Hairlines, table rules |
--line-strong | #C9BCA6 | Input borders, active rules |
--brass | #9A6B00 | Accent at text contrast on paper |
--brass-ink | #7A5400 | Brass text on --surface-2 |
--copper | #8A4A1E | Deep accent, unchanged |
--brass-soft | rgba(154,107,0,0.10) | Tint for chips and selected rows |
The brass accent darkens in light mode because #F2B62A on paper fails text contrast. Large display type may still use #F2B62A over the dark hero panel, which is retained in light mode as an intentional dark island holding the mascot.
In light mode the mascot sits on a rounded dark panel (#0B0906) rather than floating, because the asset has a black ground. The panel is part of the composition, not a workaround.
12.3 Chart palettes, both validated
Dark, on surface #15120E, all six checks pass: #B98418, #5E8FD6, #C0602A, #8B7BE8. Status: #2FA377 good, #C77D1A warning, #D8434A critical.
Light, on surface #FBF7EF, all six checks pass: #8A5A00, #2C5FA8, #A03E12, #5B3FBF. Status: #1F7A55 good, #B07000 warning, #C2185B critical.
The light status palette uses crimson rather than red for critical, because red against the warning amber failed both the colour-blind separation check and the normal-vision floor. Every status still ships with an icon and a word, so colour is never the only channel.
13. Motion: a scrolled narrative
Part one budgeted one orchestrated moment per page. The home page now earns more: it is a scrolled argument, and motion is what paces it. The discipline is unchanged in substance; the budget is larger and spent deliberately.
Rules that do not bend:
prefers-reduced-motion: reducedisables every scroll-driven and decorative animation. Content appears at its final state, counters show final values, and sticky sequences become ordinary stacked sections. Nothing is only reachable through motion.- Motion never gates content. Every section is fully readable with JavaScript disabled and with animations off. Reveals start at full opacity in the no-JS case.
- Nothing loops forever except the two live indicators: the brass status dot and the scanning ring, both slow and both meaning "this is live".
- No parallax on text. No motion that moves the thing a person is trying to read.
Implementation is CSS-first: native scroll-driven animations (animation-timeline: view() and scroll()) where the browser supports them, with an IntersectionObserver fallback that sets a single data-revealed attribute. No animation library, because the whole site is a static export and every kilobyte is on the critical path.
13.1 The home sequence
- Hero. As Part one: the ring draws once, the radar sweeps once, four status ticks light as each chain read resolves. On scroll the ring scales down slightly and docks, becoming the scroll-progress indicator for the rest of the page.
- What is proven. Six claims. Each row's brass rule draws left to right as it enters, and the contract name fades in a beat later. The rule is the evidence appearing, not decoration.
- Supply. A sticky section: the allocation bar stays pinned while three captions advance, and the bar's three segments fill in sequence to their final widths. Numbers count up once, from zero to the real figure, and never re-run.
- Team unlock. The step chart draws its line as the section scrolls, the three rings fill to their positions, and the "today" marker slides along.
- Where the ETH goes. The three flow bands draw their arrows in order, then the fee split box counts up its percentages.
- The audit sequence. The five-step rail advances a brass token along the path as the section scrolls, with the off-chain step visibly outside the brass line, so the trust boundary is felt rather than annotated.
- Cross-chain. The tree draws its branches outward from KAY9 core; planned chains stay outlined and do not animate, which is itself the message.
- Close. A single call to action. No animation.
13.2 Timing
Entrances 320 ms, ease-out. Counters 900 ms. Sticky sequences advance over one viewport height each. Sequential reveals stagger by 60 ms, never more than four items, so a list never feels like it is being typed out.
14. Thirteen languages
| Code | Language | Script | Direction |
|---|---|---|---|
en | English | Latin | ltr |
es | Spanish | Latin | ltr |
de | German | Latin | ltr |
fr | French | Latin | ltr |
pt | Portuguese | Latin | ltr |
tr | Turkish | Latin | ltr |
hi | Hindi | Devanagari | ltr |
th | Thai | Thai | ltr |
ja | Japanese | Japanese | ltr |
zh-Hans | Chinese, Simplified | Han | ltr |
zh-Hant | Chinese, Traditional | Han | ltr |
fa | Farsi | Arabic | rtl |
ar | Arabic | Arabic | rtl |
14.1 What is translated, and what is not
Translated: navigation, headings, body copy, button labels, empty states, error messages, chart labels and axis titles, and every explanatory sentence.
Not translated, by decision: the transparency ledger's claim and enforcement columns, contract names, function signatures, addresses, hashes, numbers and units. These are the canonical record. A mistranslated sentence about a tax, a lock or a risk score is a false statement about money, and the protection is that these read identically in every language. Each localised page carries one line, itself translated, saying that English governs the contract statements and that the numbers come from the chain.
14.2 Routing
Every locale is prefixed: /en/token, /es/token, /fa/token. The site is a static export, so there is no server to negotiate a language; the host rewrites / to /en and the language switcher is an ordinary link. Each page emits hreflang alternates for all thirteen locales plus x-default pointing at English. The switcher preserves the current path.
14.3 Right to left
For fa and ar the root element sets dir="rtl". Layout uses logical properties throughout (margin-inline-start, padding-inline-end, border-inline-start) so mirroring is automatic. Specifically:
- The claim and evidence columns swap sides. Evidence still sits on the reading-end side.
- Directional icons and arrows mirror. The ring's sweep does not: a clock runs the same way in every language, and the ring is a scanner, not an arrow.
- Numbers, addresses, hashes and code stay left to right inside an RTL paragraph, wrapped so the browser's bidirectional algorithm cannot reorder them. Mono text is always
dir="ltr". - Charts mirror their axis placement but not their data direction: time still runs left to right, because a falling line must not read as rising.
14.4 Type per script
Bricolage Grotesque and IBM Plex cover Latin only. Each non-Latin locale loads one Noto family and nothing else, so a Spanish visitor never downloads a Han font:
| Script | Display and text |
|---|---|
| Latin | Bricolage Grotesque, IBM Plex Sans |
| Devanagari | Noto Sans Devanagari |
| Thai | Noto Sans Thai |
| Japanese | Noto Sans JP |
| Han Simplified | Noto Sans SC |
| Han Traditional | Noto Sans TC |
| Arabic | Noto Kufi Arabic for display, Noto Naskh Arabic for body |
Arabic uses Kufi for headings because Naskh at display sizes with heavy weight becomes hard to read, and Naskh for body because it is what long-form Arabic expects. IBM Plex Mono stays the mono face in every locale; hashes and addresses are Latin regardless.
The display type scale drops one step for Han, Japanese and Thai, whose glyphs carry more ink at the same point size, and line-height rises to 1.7 for Thai and Arabic body text to make room for marks and descenders.
15. What this does not become
The reference the owner supplied is a presale landing page. KAY9 borrows its energy, not its content or its claims. Specifically, the site never gains: a presale or countdown-to-buy, staking or governance promises the contracts do not implement, "guaranteed" or "rewards" language, radiating gradient cards as the primary layout device, or stock 3D renders standing in for real data. Every animated number on the KAY9 site is either a contract constant or a value read from the chain.
Part three: the access model, and being on the buyer's side
This part supersedes Part one and Part two wherever they conflict. The brand foundation, the tokens, the two themes, the thirteen languages and the motion budget are all unchanged. What changes is who the site is addressed to and what it is asking the visitor to do.
16. The audience changed, so the argument changed
Part one's home page argued this token is honestly constructed. That is still true and still worth saying, but it is the wrong opening for the actual visitor. The visitor is somebody who found a token an hour ago and wants to know what it can do to them. The token's own fairness is the second question, not the first.
So the order of the argument is now:
- The hero states what KAY9 does for the reader, not what KAY9 is.
- Who this is for, immediately, three cards, traders first and visually primary. The single most common reason a reader leaves is not knowing whether a product is aimed at them, and the answer here is specific enough to be worth one section.
- Then Part two's sequence continues unchanged: what is proven, supply, unlock, ETH flow, the audit sequence, cross-chain.
- What runs where, a table, immediately before the close. This is new and it earns its place by making "the website is a window" checkable instead of a slogan: every row names the contract or the machine that holds the thing. Its last block is the limitation, in
--warn, admitting what a two-of-three quorum does not protect against. - The close, unchanged.
Navigation reorders for the same reason. Audit is first, Access second, Registry third; the token's own pages follow. Seven items, as before.
17. Designing a lock so it does not look like a scam
This is the hardest design problem on the site. "Lock your tokens" is a sentence a fraud uses, usually next to a yield figure. KAY9 asks for exactly that action and offers no yield, so the design has to carry the difference rather than the copy alone.
Decisions, all of them deliberate:
- The guarantee list sits beside the button, not further down the page. Six lines: three things that hold, shown in
--okwith a tick, and three things that are absent, shown in--ink-2with an em dash rather than a cross. An absent guarantee is not a warning, and a red cross beside "no APY" would read as a defect rather than a design. - The required amount is never a constant in the code. It is read from the vault on every render. When the oracle refuses to answer, the card says the amount is not quotable yet and explains that the price does not exist before the token trades. A placeholder number here would be the single most damaging thing the site could print.
- The oracle's own checks are a panel, not an error.
PricingChecksrenders the four conditions the contract tests before it will quote, in the contract's own terms. A closed lock is a safety mechanism working, and it is presented that way. - No countdown, no scarcity, no total-value-locked hero number. The vault's
totalLockedis available and is deliberately not the headline; it is a number that would only ever be used to create pressure. - The three tiers are one grid, and basic is one of the three. Basic has no amount, no period and no button, which is the point of it. Rendering it as an aside would imply the free tier is a teaser for the paid one.
18. Job states now include disagreement
Requested, Fulfilled, Disputed, Expired. The two new states are failures of the network, never of the requester, and the page says so and says the allowance was returned.
Disputed gets two sentences rather than one: what happened, and then why it is published rather than resolved. A reader's instinct on seeing "disputed" is that something is broken, and the second sentence has to arrive fast enough to correct that — averaging two contradictory findings would produce a number no auditor stands behind, and hiding the disagreement would be worse than either finding.
The attestation count replaces the signature count in the rail, because before a job settles the interesting number is how many auditors have taken a position, not how many agreed.
19. Risk over time
An audit is a snapshot and the registry keeps every snapshot, so the asset page now carries a real chart rather than a sparkline.
It is drawn as a step, not a line. A score did not drift between two audits; it was one number until a new report replaced it, and a sloping segment would claim knowledge of values nobody measured. Each measurement is a mark coloured by its risk band, so a token crossing from good to critical is visible before the axis is read. Time runs left to right in every locale, including the two right-to-left ones, because a falling line must never read as rising.
Beneath the latest score, one line, always: the latest report is a measurement of one past block and is not a statement about the token now. No surface may render a score without its date.
20. The badge
A badge is the one piece of KAY9 that appears on somebody else's page, so it is the one most likely to be used to mislead.
- It reads
KAY9Registry.latestSummaryon render. It is never an image. A static "audited" picture keeps saying 88 after a later audit says 43, which is precisely the failure this product exists to catch. - It is delivered as an iframe, not a script. A script would run KAY9's code inside a publisher's page, which is more access than a badge needs and more trust than a publisher should have to extend.
- Its wording is fixed by policy: a number, a band, and a date.
KAY9 Audit Completed,KAY9 Technical Risk,KAY9 Monitored. There is no arrangement of the design system that produces the words "verified safe", and there is not going to be. - The badge page also documents the two contract reads, because the honest form of this feature is that a builder does not need the badge, or this site, at all.
21. What Part three does not change
The palettes, both validated. The type scale and the per-script families. The motion budget and the reduced-motion contract. The thirteen locales and the rule that contract statements, numbers and identifiers read identically in every one. The rule that every animated number is either a contract constant or a value read from the chain — which now includes every KAY9 amount on the access page, none of which is written into the code.