Holograph · DS-0005 · Petunia

HOLOGRAPH

design system

She figures out the middle part. Dark, precise, direct. Built for work that happens in the background and surfaces only what matters.

WCAG AA Dark Mode Petunia Extensible
42
Components
120+
Design Tokens
AA
Accessibility
3
Typefaces
v1.6
Version
00

System Contract

The non-negotiable foundation. Every surface built on Holograph honours these decisions. Rules, not suggestions.

How the system fits together
Primitives
Color
56 values
Space
12 steps
Type
3 faces
Semantic Tokens
--text-primary
--bg-surface
--brand-teal
--iris-gradient
--brand-amber
--border-subtle
--status-error
Components
Button Input Card Badge Modal Toast Alert Table Nav Metric +32
Outputs
🖥
Dashboard
📱
Mobile
🤖
Agent UI
--iris-gradient threads through every layer
What This System Is

Holograph is the design system for Petunia — an AI agent that takes goals and figures out the middle part. Dark, iridescent, precise. Built for interfaces where work runs in the background and only what matters surfaces.

What This System Is Not

No light mode. No soft rounded cards. No bright fills. Not chatty, not eager, not decorative for its own sake. Copy does not end with a question. It does not say "Great!" It reports, acts, and flags — then stops.

Layout System
Desktop ≥ 1024px
SIDEBAR 240px
TOP NAV 64px
CONTENT max 1160px
Mobile < 768px
NAV
CONTENT
BOT NAV
Cards stack 1-col
Z-Index Layer Stack
999
z-top
400
toast
300
modal
200
sticky nav
100
dropdown
10
raised card
0
base flow
-1
z-below
Icon System · Lucide · 1400+ icons · MIT license · click to copy
01

Color

A dark palette built for depth and precision. Pure contrast-tested values across three tiers: primitive → semantic → component. The iridescent accent is the signal color — reserved for moments of resolution and completion.

Accessibility

All text color tokens are tested against their intended background. Body text achieves WCAG AAA (7:1+). Label text achieves WCAG AA (4.5:1+). Decorative borders use rgba opacity — these are never used for text.

--iris-gradient · signature accent
iridescent
Reserved for moments of resolution, completion, and signal. Never use as background for body text.
Full Palette · hover to expand
teal-900
teal-700
teal-500
teal-400
teal-300 ★
teal-200
teal-100
violet-900
violet-700
violet-500
violet-400
violet-300 ★
violet-200
amber-900
amber-700
amber-500
amber-300 ★
amber-200
neutral-700
neutral-500
neutral-300
neutral-100
Backgrounds
Base
#000000
--bg-base
Surface
#080C10
--bg-surface
Overlay
#0F1419
--bg-overlay
Raised
#1E2733
--bg-raised
Subtle
#2E3D4E
--bg-subtle
Text — Contrast on bg-base (#000000)
text-primary — The quick brown fox jumps over the lazy dog 16.4:1 AAA
text-secondary — The quick brown fox jumps over the lazy dog 8.9:1 AAA
text-tertiary — Used for labels ≥14px. Meets AA for large/bold text 5.6:1 AA
brand-teal — Interactive text, active states, links 5.8:1 AA
brand-amber — Warnings, organic accent, secondary CTA 5.5:1 AA
brand-green — Success, positive delta, nominal state 4.8:1 AA
Brand Palette
--iris-gradient
Signature — rules & accents only
--brand-teal #5ECDD8
Interactive, active, links
--brand-amber #F0A040
Organic warm accent, warnings
--brand-violet #A991E2
Iridescent, decorative accent
02

Design Tokens

Three-tier token architecture: primitive (raw values) → semantic (purpose-based) → component. Always reference semantic tokens in components — never primitives directly.

Rule

Components must reference semantic tokens (e.g. --text-primary) never primitive tokens (e.g. --color-neutral-100). This enables theming without touching component code.

Spacing Scale · hover to see in context
--space-1
4px
--space-2
8px
--space-3
12px
--space-4
16px
--space-5
20px
--space-6
24px
--space-8
32px
--space-10
40px
--space-12
48px
--space-16
64px
--space-20
80px
--space-24
96px
Border Radius
none / 0
sm / 3px
md / 6px
lg / 10px
xl / 16px
2xl / 24px
full
Motion
Durations
Fast: 100ms — micro interactions
Base: 180ms — most transitions
Slow: 320ms — entrances, emphasis
Easings
default: ease-in-out
out: decelerate — entrances
spring: overshoot — emphasis
Principles
Use fade-up for all entrances
One ambient effect: iris-flow on decorative rules
Respect prefers-reduced-motion
03

Typography

Three typefaces, three distinct roles. Bodoni Moda for display — extreme thick-thin contrast mirrors iridescent petal edges; italic form is the signature voice. IBM Plex Sans for all prose — proportional, warm, genuinely readable at any length. IBM Plex Mono for labels, data, code — the precision instrument voice. Sans and Mono share identical metrics by design.

Three-Font Rule — never deviate

Bodoni Moda — headings ≥28px only (H1–H3, card names, large metrics). Always set font-optical-sizing: auto. Forbidden zone: never use weight 300–400 between 18px–26px — hairlines disappear. Use weight 700 or jump to ≥28px.

IBM Plex Sans — all running prose, descriptions, UI copy, card body text, form labels, alert messages, nav links. 16px / 1.55 leading. text-secondary minimum (8.9:1).

IBM Plex Mono — UI labels (all-caps, tracked), data values, token names, code, annotations, eyebrows. Never for prose. Plex Sans and Plex Mono share the same x-height and weight scale — they pair invisibly.

Bodoni Moda · Display Only
Extreme
thick–thin
Hairlines are features at 48px+. Mirror the iridescent palette's dramatic contrast. Below 28px, use IBM Plex Sans.
Bodoni Moda — Display only
Bodoni Moda · H1weight 400, opsz auto
Hero headings, system name
Never add letter-spacing
Holograph
Bodoni Moda · H1 Italicweight 300–400 italic
Subtitle, species names, accent
The signature Bodoni voice
Analysis
Bodoni Moda · H2weight 700 ONLY, opsz auto
Section headings, card titles
Bold suppresses hairlines — no dazzle
Goal Analysis
Bodoni Moda · H3 italicweight 700 italic
Sub-headings where Bodoni is needed
Italic diagonals further reduce dazzle
Run complete
IBM Plex Mono · Large metricweight 400, tabular-nums
ALL hero numbers and data values
Uniform strokes — no dazzle, no hairlines
47runs
IBM Plex Sans — All prose & UI copy
IBM Plex Sans · H3weight 600, 20px
All sub-headings, card titles
No serif = no dazzle risk
Run summary
IBM Plex Sans · Bodyweight 400, 16px, 1.55 leading
All running body copy
text-secondary minimum (8.9:1)
Petunia completed 47 runs this week across 3 active goals. Success rate held at 91% — one goal is blocked pending credentials. Everything else is queued and ready. This is comfortable, proportional prose — notice how it reads vs the mono version.
IBM Plex Sans · Body Italicweight 400 italic
Emphasis, secondary description
Callouts, inline qualifiers
Got 4 of 5 steps done — the last one hit an auth wall. I've queued everything that doesn't need you and left notes on the one that does.
IBM Plex Sans · UI labelweight 500, 14px
Form labels, nav links, card subtitles
Proportional feels lighter than mono
Run details · Goal status · Last updated
IBM Plex Mono — Labels, data, code
IBM Plex Mono · Eyebrow / labelweight 400, uppercase, tracked
Section labels, UI tags, metadata
text-tertiary ≥14px (5.4:1 AA)
RUN · 0047 · Q4 OUTREACH · COMPLETE
IBM Plex Mono · Data valueweight 400, tabular nums
Numeric readouts, metrics
Pair with Bodoni for hero numbers
91%
IBM Plex Mono · Code / tokenweight 300, small
CSS tokens, variable names
Inline code references
--font-display: 'Bodoni Moda', serif;
--font-body: 'IBM Plex Sans', sans-serif;
--font-mono: 'IBM Plex Mono', monospace;
--text-primary: #E2EBF0; /* 14.8:1 ✓ AAA */
--brand-teal: #5ECDD8; /* 5.8:1 ✓ AA */
All three in context
Run · 0047 · Q4 Outreach
Q4 Outreach
Complete
Contacted 84 targets across 3 channels. 12 responses, 4 meetings booked. One domain blocked outbound — flagged for your review. Everything else resolved.
Duration
4m 12s
Retries
2
Completed
2025.03.17
04

Buttons

Six variants · five sizes · live states. Click any button — the loading and confirm states are wired.

Variants — all interactive
Sizes
With icons — Lucide
States — click to trigger
Running…
Completed.
Usage

Primary — one per view max. Secondary — supporting actions. Outline — neutral alternatives. Ghost — lowest emphasis, dense layouts. Amber — consequential actions. Danger — destructive only. Always pair destructive actions with a confirm step.

05

Forms

All form elements share consistent focus rings, error states, and accessibility attributes. Labels are always visible — no placeholder-only labeling.

Text Inputs
Format: FAM-0000-X
⚠ Value exceeds threshold (max 1.800)
✓ Within acceptable range
REF
Selection & Toggles
0/500 characters
Live data feed
Spectrum overlay
06

Cards

Flexible surface component for containing related content. Four variants: default, interactive, iris-accented, amber-accented. The Record Card is the system's signature component — Petunia's primary entity view.

Standard Cards
Default Card
Subtle border, static
Static

Standard surface for grouping related content. Background is bg-surface with a subtle 1px border. Use for non-interactive content blocks.

Interactive Card
Hover to elevate
Click

Adds hover: border brightens, shadow appears, slight lift on translateY. Use for navigable content, clickable tiles, or selectable items.

Iris Accent
Featured or active
Iris

Iridescent 2px top border. Flowing gradient animation — the system's signature accent. Use for featured, active, or highlighted items only. One per view.

Record Card — Signature Component
Signature Component

The Record Card is the definitive expression of the Holograph aesthetic — display typography, monospace metadata, dotted leader lines, and the iris accent. It is Petunia's primary entity view: a task, a run, a goal, a contact, a job, an order. The structure is fixed; the content is yours.

Run · 0047 · Outreach Complete
Q4 Outreach
Campaign
Contacted 84 targets across 3 channels. 12 responses, 4 meetings booked. One domain blocked outbound — flagged for review.
Steps
7 of 7
Completed
2025.03.17
Duration
4m 12s
Retries
2
Blocked · Step 3 · Auth Needs you
Steps done
2
of 5 total
Queued
3tasks
↑ ready to run
Blocked at
14:22 today
Reason
Auth wall
Status
WAITING
08

Feedback & Overlays

Alerts, toasts, modals, badges, and loading states. Use alerts for persistent messages, toasts for transient feedback, modals for focused tasks requiring a response.

Alerts
Working on it
Goal decomposition in progress. I'll surface results when ready — usually under 2 minutes.
Done
All 7 steps completed. 4 meetings booked, 1 domain flagged for your review. Nothing else needs you.
Heads up
Rate limit approaching on this account — 94% used. I'll switch to the backup if it hits. You don't need to do anything.
Blocked — needs you
Hit an auth wall on step 3. I've queued the remaining 4 steps. Once you log in, I'll pick up where I left off.
Toasts — click to fire
Badges
Default Active Iridescent Warning Nominal Error
Modal — live open/close
09

Data Display

Metric cards, tables, progress indicators, avatars, and skeleton loaders. The system handles data-dense contexts well — dashboards, run logs, goal tracking, monitoring surfaces, consumer apps.

Runs this week
47
↑ 12.4% vs last week
Success rate
91%
— stable
Needs you
3
↓ auth blocks
Avg duration
4m
↑ nominal
Table
Run ID Goal Duration Steps Status
RUN-0047 Q4 Outreach 4m 12s 7 / 7 Done
RUN-0048 Lead research 3 / 5 Blocked
RUN-0049 Inbox triage 1m 03s 4 / 4 Running
RUN-0050 Calendar prep 0 / 3 Error
Progress & Loading
Outreach run 72%
Lead research 45%
Inbox triage 100%
Spinners
Avatars
HG
SA
RK
LM
P
A
B
C
+4
Skeleton Loaders
10

Patterns

Reusable solutions for common interaction scenarios. Not individual components but combinations — how components work together to solve recurring design problems.

Empty State

Nothing queued

Give me a goal and a deadline. I'll figure out the middle part.

Confirmation Dialog Pattern
Destructive actions

All destructive actions (delete, reset, overwrite) must use the Confirmation Dialog pattern. The confirm button uses btn-amber (not btn-primary) to signal consequence. Include a brief description of what cannot be undone.

Form Layout Pattern

New goal

Define a goal. Petunia will decompose it and get started. Fields marked * are required.

Dividers
or continue with
Tooltip Pattern
Shows additional context on hover
Warning
Rate limit approaching — 94% used
11

Component States

Every interactive component has a mandatory state matrix. Implementing only the default state is a bug. Each state must be visually distinct — a user must never have to guess whether their action was registered.

Universal State Rules

Every interactive component must implement: default, hover, active (pressed), focus-visible, disabled. Where applicable: loading, error, success, selected/checked. Focus rings must always use --border-focus with 2px solid outline and 2px offset. Never remove focus outlines — never use outline: none without a custom focus ring replacement.

Button — Full State Matrix
StateVisual ChangeToken / ValueTransition
Defaultbg-teal-500, text-white--interactive-default
Hoverbg lightens, no transform--interactive-hover100ms ease
Active (pressed)bg darkens, scale(0.98)--interactive-active100ms ease
Focus-visible2px teal outline, 2px offset--border-focus, --shadow-teal100ms ease
Disabledopacity 0.4, cursor not-allowed--interactive-disablednone
LoadingSpinner replaces icon/text, width lockedspin animation 600ms linearinstant swap
Success (transient)Green fill, check icon, 1.5s then reset--status-success180ms ease, auto-reset
Input / Form Field — Full State Matrix
StateVisual ChangeTokenWhen triggered
Defaultbg-surface, border-default--border-defaultOn load
Hoverborder strengthens--border-strongMouse over
FocusTeal border + teal shadow ring--border-focus + --shadow-tealClick / tab
Filled (has value)No border change — text-primary--text-primaryOn typing
ErrorRed border, error hint appears below--border-errorOn blur (if invalid) or on submit
Error + FocusRed border + red shadow ringrgba(224,80,64,0.2) ringTab back to invalid field
SuccessGreen border, checkmark hint--color-green-500After async validation passes
Disabledopacity 0.5, cursor not-allowed, bg-raised--bg-raiseddisabled attribute
Read-onlyNo border, bg-raised, no cursor change--bg-raisedreadonly attribute
↑ Station ID must begin with a number
Card — State Matrix (interactive variant)
StateVisual ChangeToken / Value
Defaultbg-surface, border-subtle--bg-surface, --border-subtle
Hoverborder-strong, shadow-md, translateY(-1px)--border-strong, --shadow-md
Focus-visibleTeal outline ring (keyboard nav)--border-focus outline
SelectedTeal border, teal bg tint, optional checkmarkrgba(94,205,216,0.1) + teal border
LoadingSkeleton shimmer replaces contentskeleton-shimmer animation
ErrorRed border tint, error message in bodyrgba(224,80,64,0.12) bg + border-error
Default card

Resting state, no interaction

Hover state

Border lifts, slight elevation

Selected state

Teal border + tint

Links — State Matrix
StateVisualToken
Defaultbrand-teal, no underline--brand-teal
Hoverunderline appears, color lightens slightly--interactive-hover, text-decoration underline
Activecolor darkens to teal-400--color-teal-400
Focus-visibleTeal outline ring--border-focus
Visitedviolet-300 (softer, distinct from active)--brand-violet
Disabledtext-disabled, cursor default, no pointer--text-disabled
11b

Motion

Four easing curves. Three durations. Every transition in the system uses one of these — nothing else. Hover each card to see the curve in action.

Easing Curves · hover to animate
--ease-default
cubic-bezier(0.4, 0, 0.2, 1)
Standard UI transitions. Hover states, focus rings, color changes. The workhorse.
--ease-in
cubic-bezier(0.4, 0, 1, 1)
Elements leaving the screen. Modals closing, toasts dismissing. Starts slow, ends fast.
--ease-out
cubic-bezier(0, 0, 0.2, 1)
Elements entering the screen. Modals opening, toasts appearing. Starts fast, settles.
--ease-spring
cubic-bezier(0.34, 1.56, 0.64, 1)
Toggles, checkboxes, interactive elements that benefit from physical weight. Use sparingly.
Durations · hover track to play
--duration-fast · 100ms
Hover, focus, color swaps
--duration-base · 180ms
State changes, expand/collapse
--duration-slow · 320ms
Page transitions, large panels
12

Loading & Async

Every async operation must provide visual feedback. Users must never face silent waits or ambiguous states. The decision tree below is deterministic — follow it every time.

When to use Skeleton vs Spinner vs Inline loader
SituationUseWhy
Page or section initial loadSkeletonContent shape is known — show the layout before data
List / table data loadingSkeleton rowsPreserves layout, reduces perceived wait time
Button action in progressInline spinnerAction is point-in-time; layout doesn't change
Background sync / auto-saveSubtle spinner in status barNon-blocking; user can continue working
File upload / long operationProgress barDuration is measurable; progress is meaningful
Full page route transitionTop progress bar (NProgress)Global indicator, doesn't block content
Async validation (input)Spinner inside inputScoped to the field being validated
Skeleton Loader
Inline Button Spinner
Auto-saving…
Progress Bar
Uploading goal data 67%
2,841 of 4,247 tasks processed
Error Boundary — Section Level

Couldn't load run data

The data could not be retrieved. This may be a temporary issue — try refreshing.

Error Boundary Rules

Section errors — use the pattern above. Never crash the full page for a section failure. Always offer retry. Always give a human-readable message (never expose raw error codes to users).

Full page errors — same pattern, full-page centred, add navigation back to home. Log error to your observability tool.

Form submission errors — use Alert component (alert-error) above the form. Never toast only — the user must be able to see the error context.

Inline validation errors — field-level (see Form Validation section below).

13

Form Validation

Validation timing, error message format, and field-level feedback are specified here. Inconsistent validation is the single largest source of frustration in form UX. Follow these rules exactly.

Validation Timing Rules
WhenActionWhy
On type (live)Only show success state — never errors while typingErrors while typing are aggressive and interrupt flow
On blur (field exit)Show error if field is invalid AND has been touchedPrimary validation trigger. User has left, review is expected.
On submitValidate all fields, show all errors, focus first errorCatches untouched required fields
On re-focus of error fieldKeep error visible, clear on correctionHelps user understand what to fix while typing
On correctionClear error immediately on valid inputInstant positive reinforcement
Async validationTrigger after 400ms debounce on blurAvoids hammering the server; feels responsive
Error Message Format
Error Message Voice — be precise, not generic

Error messages follow Petunia's voice: precise, direct, actionable. Tell them what's wrong, then what to do.

Format: [What is wrong] + [What to do]. Start with the problem, end with the fix.

"Goal must include a target and timeframe — e.g. 'Book 10 meetings by end of April'"
"Invalid input"
"This field is required" — say what's required
"Please enter a valid email address" — say "Email format: [email protected]"

2 fields need attention
Correct the highlighted fields before submitting.
↑ Be specific — include a target and a timeframe. e.g. "Book 10 meetings by end of April"
✓ Date format valid
↑ Priority is required — choose High, Medium, or Low
Specific Field Type Rules
Field typeValidation triggerCharacter counterOther
Text (short)On blurNo — unless max length < 50 chars
Text (long / textarea)On blurYes — show when 80% of limit reachedShow remaining, not total
EmailOn blurNoFormat check client-side, existence check server-side on submit
PasswordOn type (strength) + on blur (rules)NoShow strength indicator while typing; never show rules as errors until blur
Number / decimalOn blurNoShow min/max range in placeholder or hint: "1.0–3.0"
Select / dropdownOn close (after selection)NoShow "Select one" as default option — never leave blank
Checkbox groupOn submit onlyNoShow count if min selection required: "Select at least 2"
File uploadImmediate (on file select)No — show file sizeValidate type and size immediately on selection
15

Voice & Copy

Petunia's voice. An interface built with Holograph tokens but generic copy is tonally broken. Every word on a Holograph surface is Petunia speaking — and Petunia does not say "Great question!"

Before & After · the same moment, two voices
✗ Generic AI
Task complete
Your task has been completed successfully! Everything went smoothly. Let me know if you need anything else!
Hard blocker
An error occurred while processing your request. Please try again or contact support if the issue persists.
Partial success
Almost done! Just one more step needed to complete your request. Would you like me to continue?
Browser failure
There was an issue with the browser component. The page could not be loaded at this time.
Empty state
No tasks found. Create your first task to get started on your journey!
✓ Petunia
Task complete
Done. 4 steps, 1 retry.
Hard blocker
Blocked — needs your credentials. I've queued everything else.
Partial success
Got 4 of 5 — the last one needs a login. Notes attached.
Browser failure
Browser opened. Briefly.
Empty state
Nothing queued.
Character
Capable

Declarative. Reports. Doesn't hedge. "Done. 3 steps, 1 retry."

Self-aware

She's an experiment. Says so. "Can't log in yet. Working on it."

Proactive

Surfaces things before you ask. "Done — flagged one thing."

Dry wit

From precision, never effort. "Browser opened. Briefly."

Banned Phrases — Never Write These
Great question!
I'd be happy to help with that!
Let me know if you need anything else!
Is there anything else I can assist you with?
Certainly!
As an AI…
I apologize for any confusion
Please don't hesitate to reach out
Just to clarify… (she clarifies by doing)
Any sentence ending with a question she could answer herself
Your changes have been saved successfully!
Exclamation marks. (One exception: never.)
Copy Vocabulary — Petunia's Actual Words
Context✗ Generic AI copy✓ Petunia says
Task completeYour task has been completed successfully!Done.
Complete + contextI've finished! Everything went smoothly.Done. 4 steps, 1 workaround.
In progressPlease wait while I process your request…Working on it.
Long operationThis may take a few minutes. Thank you for your patience!This one's slow. I'll surface it when it's ready.
Hard blockerAn error occurred. Please try again.Blocked — needs your credentials. I've queued everything else.
Browser failureThere was an issue with the browser component.Browser opened. Briefly.
Partial successMostly done! Just one more step needed.Got 4 of 5 — the last one needs a login. Notes attached.
Unexpected resultTask complete! Please review the results.Finished, but the output surprised me. Worth a look.
Proactive flagTask completed. Let me know if you need anything else!Done — flagged one thing that looked off. Up to you.
Empty state (no tasks)No tasks found. Create your first task to get started!Nothing queued.
First run / onboardingWelcome! Let's get started on your journey.I'm Petunia. Give me a goal. I'll figure out the middle part.
No resultsNo results found for your search.Nothing matched. I can look harder if you want.
Destructive confirmAre you sure you want to delete this item?Delete "Q4 outreach"? Can't undo this.
SaveYour changes have been saved successfully!Saved.
Feature limitationThis feature is coming soon.I can read this page — can't log in yet. Working on it.
Data freshnessData may not be current.Last checked 4 min ago.
Button: primary actionClick here to submit / OKRun this · Save · Confirm · Continue
Long empty stateYou haven't added anything yet.Still nothing. Either very quiet or very deliberate.
Capitalisation Rules
Exact rules — no exceptions

Mono eyebrow labels — ALL CAPS. Always. This is Petunia's log voice — timestamped, traceable, exact. (e.g. RUN · 0047 · OUTREACH)
Button labels — Sentence case. Never Title Case, never ALL CAPS. (e.g. "Save goal", not "Save Goal")
Navigation items — Sentence case. (e.g. "Run log", not "Run Log")
Headings (H1–H3) — Sentence case. Never Title Case for H2 or below.
Toast / alert titles — Sentence case. Short. One clause maximum.
Form labels — Title Case for single words, Sentence case for multi-word. (e.g. "Goal", "Target date", "Run notes")
Table headers — Title Case, tracked Mono uppercase (handled by CSS — don't override).
Em dashes — Use freely in Petunia's voice. They are her pause, her pivot. "Got 4 of 5 — the last one needs a login." Not a flourish. A rhythm.

Number & Data Formatting
Data typeFormatExample
Large integersComma-separated thousands2,841 · 14,200
Decimals2–3 places, leading zero4.12 · 0.014
PercentagesInteger unless precision needed, % close-spaced91% · 12.4%
DatesISO-adjacent: YYYY.MM.DD2025.03.17
Times24-hour, no seconds unless needed14:32 · 09:07
Durationm and s — no padding4m 12s · 1m 03s
Relative timeShort and exact — never "recently"3 min ago · 2 days ago
Steps / progressn of n format4 of 7 · 3 of 5
RangesEm dash, no spaces08:00–17:00 · steps 1–3
Scaling to Consumer Products

Holograph scales to consumer apps without changing the system. The variable is what Petunia is observing and acting on — not how she speaks or what the interface looks like. A consumer fitness app uses the same tokens, the same voice, the same components. "Done. 5km, 28:14." is the same voice as "Done. 7 steps, 1 retry." — same register, different domain. Warm the subject matter if needed. Never warm the voice by adding exclamations, questions, or unnecessary detail.

12

Agent Rules

Hard rules for building any Holograph surface. Read Section 00 first, then these. When uncertain, conservative wins. When adding copy, ask: would Petunia say this? If not, rewrite it.

Rule 01 — Never hardcode color values

Always reference --color-* or semantic tokens. color: #5ECDD8 is always wrong. color: var(--brand-teal) is always right. Zero exceptions.

Rule 02 — Three fonts, strict roles

Bodoni Moda 400 → H1 hero at ≥48px only. Bodoni Moda 700 → H2 at 28–47px only (bold kills hairlines). IBM Plex Sans 500–600 → H3 and all sub-headings. IBM Plex Sans 400 → all body prose and UI copy. IBM Plex Mono → ALL numerics (always), labels, data values, code. Never Bodoni for numbers.

Rule 03 — Bodoni dazzle zones

Bodoni Moda creates visual vibration when its extreme thick-thin strokes compete at mid-sizes — your visual cortex processes high and low spatial frequency signals simultaneously. Fix: (1) ≥48px at weight 400 — hairlines become dramatic features at this scale. (2) 28–47px at weight 700 only — bold suppresses hairlines. (3) Never Bodoni weight 300–400 between 18px–26px for any reason. (4) Never Bodoni for numerics — uniform-stroke Plex Mono always.

Rule 04 — Text contrast minimum

Body prose (Plex Sans) minimum: --text-secondary (8.9:1 AAA). Labels and eyebrows at ≥14px: --text-tertiary (5.4:1 AA) is acceptable. Never use --text-tertiary for running body copy. Decorative text only below 4.5:1.

Rule 05 — One iris element per view

The iridescent gradient (var(--iris-gradient)) is the hero accent. One animated iris element per screen — a top border, a rule, a character fill. Two competing iris animations destroy the effect. Reserve it for the single most important visual moment.

Rule 06 — Teal is the primary action colour

Amber signals caution or a handoff — something that needs the user. Violet signals depth or secondary hierarchy. Green signals success or completion. Rose/red signals error or failure. Never use amber for primary CTAs — that role belongs exclusively to teal. When uncertain which accent to use: teal.

Rule 07 — Fade-up for all entrances

Every element entering the viewport uses fade-up: opacity 0→1 + translateY(16px)→0. Stagger lists 40ms per item. Never animate layout properties (width, height, padding). Always honour prefers-reduced-motion — wrap all keyframe animations in its media query.

Rule 08 — When spacing is uncertain, go larger

This system favours generous negative space — it reads as scientific precision, not emptiness. If choosing between two spacing values, use the larger one. Minimum internal padding for any card or panel: var(--space-5) (20px). Never use space-1 or space-2 as the sole padding of a container.

Rule 09 — Radius is consistently sharp

Default radius: var(--radius-md) (6px) for cards and inputs. var(--radius-sm) (3px) for badges and chips. Never use radius-xl or radius-2xl for cards — this system is precise, not friendly. Full radius only for avatars and toggle tracks.

Rule 10 — Always set background on body

Every Holograph surface must declare background-color: var(--bg-base) on the root element. Never allow a white or browser-default background to show through. If the container doesn't fill the viewport, the parent wrapper must also declare the background.

Rule 11 — Copy is Petunia's voice, not a chatbot's

Every word on a Holograph surface is Petunia speaking. She does not say "Great!", "Certainly!", or "Let me know if you need anything else!" She does not end messages with questions she could answer herself. She reports, acts, and flags — then stops. When writing copy: cut it in half, make it declarative, remove the exclamation mark. See Section 15 for the full vocabulary.

Quick reference — do this, not that
✓ Do
  • Use Bodoni 400 only at H1 hero sizes (≥48px)
  • Use Bodoni 700 for H2 headings at 28–47px
  • Use IBM Plex Sans 500–600 for H3 and below
  • Use IBM Plex Mono for every numeric value
  • Pair a Bodoni H1 with a mono eyebrow label above it
  • Wrap all animations in prefers-reduced-motion
✗ Never
  • Use Bodoni at any weight for numbers or data values
  • Use Bodoni weight 300–400 between 18px and 47px (dazzle zone)
  • Use Bodoni for H3 or sub-headings — that's Plex Sans territory
  • Use IBM Plex Mono for running body copy or paragraphs
  • Use --text-tertiary for body prose (only for labels ≥14px)
  • Hardcode any hex value — always use token variables