Foundation
Brand Identity
Pactable is a voice-first agreements platform — modern, trustworthy, and distinctly human. The brand system balances warmth (blush surfaces, flowing waveforms) with precision (tight type, clean structure).
Core Attributes
Voice-native
Sound is the primary input method. The waveform icon and animated orbs make voice feel tangible.
Legally considered
Agreements are serious. Clean data blocks, verification states, and executed status reinforce trust.
Modern precision
All-sans type system, tight tracking on headings, single gradient family. No ornament without purpose.
Human warmth
Blush surfaces, soft radii, and conversational copy keep a legal product approachable. It talks like a person, not a contract.
Ownable
The rose-to-violet gradient, waveform mark, and voice orb are distinctly Pactable — recognizable at a glance.
Trustworthy
Verification badges, sealed Traces, and quiet consistency. Every state change is visible and recorded.
Gradient — The Brand Mark
pactable
#C2185B → #7B1FA2 · 135°
145° — Icon tile
App icon, notification icon
135° — UI primary
Buttons, progress, active
Pressed state
#AD1457 → #6A1B9A
Foundation
Color
Rose Aurora + Steel Slate. Two-color grammar: rose = action (CTAs, deadlines, user voice, brand mark), steel blue = structure (labels, roles, section headers, inactive borders). Cool steel surfaces make rose read sharper.
Brand Palette
Rose
#C2185B
--rose · Primary accent, CTAs
Rose Dark
#880E4F
--rose-dark · Wordmark, deep accent
Rose Light
#E91E8C
--rose-light · Hover, highlight
Violet
#7B1FA2
--violet · Gradient end, secondary
Violet Dark
#4A148C
--violet-dark · Deep gradient
Steel Blue
#1B4D8B
--steel · Structural labels, roles, borders
Navy Dark
#0F1B2D
--steel-dark · Shell, frame background
Surface Palette
Steel Surface
#EEF4FB
--blush · App screen bg (steel-tinted)
Steel Surface 2
#E8F0F9
--blush-2 · Secondary surface
Card
rgba(255,255,255,0.65)
--card · Frosted card bg
Navy Shell
#0A1628
--shell · Prototype background
Semantic Colors
Verified
rgba(80,170,100,0.7)
--verified · Verified status
Pending
rgba(200,140,30,0.6)
--pending · Awaiting response
Error
rgba(200,80,80,0.7)
--error · Decline / error
Email
#3B82F6
--email · Email delivery channel
Text Colors
Primary text rgba(12,28,58,0.9) Body, card values
Mid text rgba(20,48,95,0.72) Secondary body
Sub text rgba(25,50,100,0.5) Helper, detail
Faint text rgba(25,50,100,0.35) Timestamps, metadata
Accent text #C2185B Links, active
Label text rgba(194,24,91,0.6) Uppercase section labels
Foundation
Typography
Plus Jakarta Sans — a single family across all weights. Weight contrast (800→400) creates all hierarchy. No serif mixing. No system fonts.
Typeface
Aa
Plus Jakarta Sans
pactable
Voice agreements between people who trust each other — sealed in seconds.
300 · Light
400 · Regular
500 · Medium
600 · Semibold
700 · Bold
800 · Extrabold
/* Google Fonts import */ @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,400&display=swap'); /* CSS variable */ --font: 'Plus Jakarta Sans', sans-serif;
Type Scale
pactable
display-xl
52px · 800 · -0.05em
Voice agreements
display-lg
40px · 800 · -0.05em
Personal Loan Agreement
display-md
32px · 800 · -0.04em
Good morning, Damon.
title-lg
26px · 800 · -0.04em
Agreement — $4,000
title-md
22px · 700 · -0.035em
Loan to William Jennings
body-lg
16px · 600 · 0
I lent my nephew four thousand dollars to consolidate some bills.
body-md
15px · 400 · 0
Two payments of $2,000 due March 1 and April 1.
body-xs
13px · 400 · 0
Loan Amount
label-md
11px · 600 · 0.07em · uppercase
Review
label-sm
10px · 700 · 0.1em · uppercase
Foundation
Spacing & Radius
4px base unit. Screen margin 18px. Cards use 14px/16px internal padding with 10px vertical gap between them.
Key Spatial Tokens
Screen edge18px--sp-screen
Card padding14px 16px--sp-card
Card gap10px--sp-gap
Section gap16pxWithin scrollable content
Status bar height54pxContent starts below
Bottom bar total94px12px + button + 42px safe area
Border Radius
--r-xs
6px
--r-sm
8px
--r-md
10px
--r-lg
14px · Cards
--r-xl
16px · Buttons
--r-3xl
20px · Drawer
--r-full
9999px · Avatar
Identity
App Icon
The Waveform — 8 bars split symmetrically around a lock-in-circle. Voice sealed into agreement. The only icon that couldn't exist without Pactable.
Size Variants
120pt
Home screen
76pt
iPad home
60pt
iPhone @1x
40pt
Spotlight
29pt
Settings
20pt
Notification
Icon Anatomy
Canvas200×200 viewBoxScales to any size
Outer ringr=68, stroke opacity 0.22Subtle boundary
Bar count8 bars · symmetric4 left + 4 right
Bar width11px · rx=5.5Fully rounded ends
Bar opacities30 / 52 / 72 / 90%Outside→center
Bar gap14px between barsCenter gap 21px
Lock circler=18, dark fillrgba(12,20,50,0.90)
Lock shacklestroke=3.5, round capWhite, no fill
Lock body18×13px · rx=3.5White fill
Identity
Wordmark
Lowercase. Extrabold. Tight tracking. The wordmark always appears with the icon in-app; standalone use is rare.
pactable
pactable
FontPlus Jakarta Sans
Weight800 Extrabold
Tracking-0.05em
Caselowercase
Color — light#880E4F (Rose Dark)
Color — darkrgba(255,255,255,0.92)
Components
Voice Orb
The primary input affordance. A glowing frosted-glass circle housing animated waveform bars. The entire home screen experience is built around this element.
States
Idle
Tap to Start
190×190px
User Speaking
Listening...
Active pulse ring
Orb Specs
Size190×190pxborder-radius: 50%
Backgroundradial-gradient from white → rgba(232,228,242)Frosted glass effect
Idle shadowbuttonGlow keyframe0→40px spread at 50%
Active shadowactivePulse keyframeRose ring 0→3px
Bar count (idle)8 barswidth 5px, gap 6px
Bar count (active)Live waveform (44 bars)width 3px, gap 2px, full width
Bar stagger0.12s between barsDuration 1.5s → 3.04s across 8 bars
Label"Tap to Start" / "Listening..."14px/500 idle, 12px/600 active
Conversation Orb
64px micro orb at the bottom of the conversation screen. State-aware: bars animate continuously with a traveling sine wave (same approach as the home VoiceOrb) and respond to conversational phase through energy and opacity multipliers. In the done state, swaps to a rose gradient with a forward arrow.
Size64×64pxborder-radius: 50%
Surfacergba(255,255,255,0.95)Glass orb with 1px border rgba(200,220,248,0.6)
Bar count5 barswidth 3px, gap 3px, max height 20px
Wave cycle1.8s linearContinuous traveling sine wave, no reverse
Energy: waiting0.5× · opacity 0.2Gentle ambient motion
Energy: user-speaking1.2× · opacity 0.7Bright, energetic response
Energy: ai-speaking0.8× · opacity 0.45Medium, indicating output
Pulsescale 1 → 1.042000ms ease-in-out, infinite
Done stateRose gradient + ">" arrow"Tap to Review" label
LabelslabelSm below orb"Tap to speak" / "Listening" / "Speaking" / "Thinking..." / "Tap to Review"
Inline Orb Editor
36px mini orb for AI-powered editing of individual agreement blocks on the review screen. Voice-first on device (TTS greeting + mic input), text fallback in simulator. When AI is speaking, the static icons swap to animated wave bars matching the traveling sine wave pattern used in the VoiceOrb and ConversationOrb.
Size36×36pxborder-radius: 50%
Active gradient#C2185B → #7B1FA2Full brightness when speaking/listening/processing
Inactive gradientrgba(123,31,162,0.3) → rgba(194,24,91,0.3)Muted when idle
Bar count5 barswidth 2px, gap 2px, max height 14px
Wave cycle1.4s linearFaster cycle for small orb
Pulsescale 1 → 1.081000ms ease-in-out, infinite
IconsMic (idle), Wave bars (speaking), Spinner (processing)White on gradient
Components
Buttons
Three button types. Primary CTAs always use the brand gradient. Secondary uses frosted white. Destructive uses error red.
Button Variants
Primary — Active
Send to William
Primary — Disabled
Send to William
Secondary Row
Suggest Changes
Decline
Accept — Verified
Accept Agreement
/* Primary button — active */ padding: 15px 20px; border-radius: 16px; background: linear-gradient(135deg, #C2185B 0%, #7B1FA2 100%); box-shadow: 0 4px 20px rgba(194,24,91,0.25), 0 1px 3px rgba(0,0,0,0.1); font-size: 16px; font-weight: 600; color: rgba(255,255,255,0.95); /* Disabled state */ background: linear-gradient(135deg, rgba(194,24,91,0.18), rgba(123,31,162,0.15)); color: rgba(255,255,255,0.35); cursor: default;
Components
Data Blocks & Cards
Frosted glass cards on the blush background. All use the same surface token. Labels are uppercase rose, values are dark primary.
Data Block
Loan Amount
$4,000.00
Four thousand dollars, USD
◷ Payment Schedule
$2,000.00
Payment 1 of 2
Mar 1
$2,000.00
Payment 2 of 2 — Final
Apr 1
/* DataBlock wrapper */ padding: 14px 16px; background: rgba(255,255,255,0.6); border-radius: 14px; border: 1px solid rgba(255,255,255,0.5); box-shadow: 0 1px 6px rgba(0,0,0,0.03); /* Label */ font-size: 11px; font-weight: 600; color: rgba(194,24,91,0.55); letter-spacing: 0.07em; text-transform: uppercase; /* Value */ font-size: 16px; font-weight: 600; color: rgba(26,10,18,0.85);
Components
Status Badges
Agreement and verification state indicators. Two variants: inline tag (border-radius 6px, used inside AgreementCard) and standalone pill (border-radius 9999px, used in detail headers and standalone contexts). All 11 agreement statuses have a corresponding icon. Green = Verified/Executed (checkmark). Amber = Pending or Expired (clock). Rose-red = Declined (circle-X). Deep red = Voided (circle-X). Neutral = Draft/Sent (pencil/send). Violet = Changes Proposed (pencil). Color families are semantically consistent: amber = time/waiting, green = success/complete, red family = cancellation/refusal, violet = negotiation in progress.
Verified
Pending
Verify
Unverified
Executed
Expired
Voided
Declined
Awaiting William's Response
Expired
Badge for the expired agreement status. Amber keeps it in the time/waiting family — the agreement was pending and time ran out. Clock icon with hands reinforces the elapsed-time concept. Distinct from Pending's dashed amber circle: Pending = waiting, Expired = deadline passed.
Expired
bg: rgba(240,175,50,0.1)
color: rgba(200,128,18,0.85)
border: 1px solid rgba(220,160,40,0.22) — optional, used on banner components
icon: clock with hands (circle + path to 6:30 position)
Voided
Badge for the voided agreement status. Deep red (#B71C1C family) is intentionally darker and more saturated than the Declined rose-red — Voided is a creator-initiated cancellation, more deliberate than a counterparty decline. Circle-with-X icon (prohibited mark) distinguishes it from Declined's plain X. Both are red-family but visually distinct.
Voided
bg: rgba(183,28,28,0.06)
color: rgba(183,28,28,0.82)
border: 1px solid rgba(183,28,28,0.18) — used on banner components
icon: circle with X cross (prohibited mark)
destructive action color: #B71C1C (solid button fill — not a badge, used on Void CTA only)
Declined
Badge for the declined agreement status. Rose-red family — lighter and less saturated than Voided. Counterparty said no; creator can archive and move on. Uses circle-X icon like Voided; the color difference (rose-red vs deep red) provides the visual distinction between the two states.
Declined
bg: rgba(200,80,80,0.08)
color: rgba(200,80,80,0.75)
icon: circle-X (same shape as Voided; color distinguishes the two states)
Changes Proposed
Badge for the changes_proposed agreement status. Signals that the counterparty has submitted proposals and the creator needs to review. Violet keeps it distinct from all other semantic colors and away from the rose action family. Pencil icon ties back to the Suggest Changes block affordance.
Changes Proposed
Changes Proposed
bg: rgba(123,31,162,0.1)
color: rgba(123,31,162,0.7)
icon: pencil — matches suggest block affordance
/* Two variants: Inline tag (AgreementCard): border-radius 6px, padding 3px 8px Standalone pill (detail headers): border-radius 9999px, padding 4px 10px */ /* All statuses have icons — full mapping: draft: pencil sent: paper-plane pending: clock changes_proposed: pencil accepted: checkmark executed: checkmark void_requested: warning-triangle voided: circle-X declined: circle-X expired: clock cancelled: circle-slash */ /* Verified badge */ background: rgba(80,170,100,0.1); color: rgba(80,170,100,0.7); icon: checkmark; /* Pending badge */ background: rgba(230,170,60,0.1); color: rgba(200,140,30,0.6); icon: clock; /* Expired badge — amber, clock icon */ background: rgba(240,175,50,0.1); color: rgba(200,128,18,0.85); icon: clock; /* Voided badge — deep red, circle-X icon */ background: rgba(183,28,28,0.06); color: rgba(183,28,28,0.82); icon: circle-X; /* Declined badge — rose-red, circle-X icon (color distinguishes from Voided) */ background: rgba(200,80,80,0.08); color: rgba(200,80,80,0.75); icon: circle-X; /* Changes Proposed badge — violet, pencil icon */ background: rgba(123,31,162,0.1); color: rgba(123,31,162,0.7); icon: pencil;
Components
Party Cards
Shown stacked vertically on the Review screen — one card per party. Identity status is communicated through two coordinated signals: the card border color and a footer strip. This eliminates the top-right badge/edit icon conflict that arose when edit affordances were added. The edit icon lives in the top-right of the card body and is only shown when the agreement is in an editable state (draft, pending). It is hidden once the agreement is executed.
States
Review — awaiting counterparty
Party A
DD
Damon Dean
(415) 602-1994
Identity verified
Party B
WJ
William Jennings
(302) 555-0148
Pending verification
Executed — both verified
Party A
DD
Damon Dean
(415) 602-1994
Identity verified
Party B
WJ
William Jennings
(302) 555-0148
Identity verified
Border color and footer strip are always updated together as a unit — they cannot be out of sync. Edit icon is present in editable states only (draft, pending); it is hidden in executed, voided, and expired states. Counterparty avatar picks up the full brand gradient once verified, matching the creator avatar. Cards are always stacked vertically (never side-by-side) on the Review screen.
Anatomy
Card backgroundrgba(255,255,255,0.75)
Border radius12px
Border — verified1.5px solid rgba(80,170,100,0.45)
Border — pending1.5px solid rgba(200,140,30,0.45)
Card body padding10px 11px 9px
Avatar size30×30px, 50% radius
Avatar — unverifiedrgba(0,0,0,0.05) bg · rgba(30,20,45,0.28) initials · muted name
Avatar — verifiedBrand gradient bg · rgba(194,24,91,0.75) initials · full-weight name
Edit icon20×20px circle · rgba(25,75,155,0.07) bg · pencil SVG · editable states only
Footer strip padding4px 11px
Footer — verified bgrgba(80,170,100,0.1) · border-top rgba(80,170,100,0.2)
Footer — pending bgrgba(200,140,30,0.08) · border-top rgba(200,140,30,0.2)
Footer label — verified"Identity verified" · 9px 700 · rgba(46,125,60,0.85)
Footer label — pending"Pending verification" · 9px 700 · rgba(160,100,15,0.85)
Overflowhidden (clips footer strip to card radius)
Components
Contact Card
Inline card shown in the conversation flow when the AI identifies a counterparty. Displays the resolved contact with a "Change" affordance for correction. The card is a compact data display — it shows identity and available contact methods but does not indicate or control the delivery channel. Supports three states: interactive (default), updated (confirmation), and read-only (while a sheet is open). Tapping "Change" opens the contact sheet in Edit mode by default, pre-filled with the current contact's details. After the 3-second "Updated" badge clears, the card returns to interactive — multiple corrections are allowed.
States
Interactive — Default
Borrower
Change
WJ
William Jennings
(302) 555-0147 · [email protected]
Phone only — no email on file
Borrower
Change
WJ
William Jennings
(302) 555-0147
Updated — 3s confirmation
Borrower
Updated
WC
William Chen
(415) 555-0183 · [email protected]
The card is a compact data display only — it does not indicate or control the delivery channel. Phone and email are shown on a single secondary line separated by a dot (·). If only one contact method exists, just that one is shown. The delivery channel choice lives entirely on the Send button dropdown on the review screen. Updated state shifts avatar gradient and border to green semantic color. After 3s timeout, "Updated" badge fades and card returns to interactive state.
Anatomy
Card background rgba(255,255,255,0.75) Same as conversation bubble
Border radius 14px (--r-lg) Standard card radius
Padding 14px All sides
Border — default 1px solid rgba(25,75,155,0.12) Steel structural color
Border — updated 1px solid rgba(80,170,100,0.25) Green semantic, 0.3s transition
Role label 10px / 600 / 0.08em ↑ Uppercase, steel blue
Avatar 44×44px circle Initials · 17px / 600
Avatar gradient — default 135deg steel→lavender rgba(25,75,155,0.15) → rgba(180,150,220,0.15)
Avatar gradient — updated 135deg green→green light rgba(80,170,100,0.15) → rgba(120,190,130,0.1)
Name 15px / 600 rgba(30,20,45,0.85)
Contact line 13px / 400 rgba(25,50,100,0.5) · phone · email separated by " · "
"Change" pill bg No background Pencil icon (11px rose) + label — no fill, tap target only
"Change" text 10px / 600 #C2185B (rose) — action color. Opens sheet in Edit mode.
Behavior
Tapping "Change" opens the contact search bottom sheet. The user can also voice-correct ("That's the wrong person," "His number is different") and the AI companion opens the appropriate sheet automatically. The tap affordance is the fallback for when voice feels awkward. While any bottom sheet is open, the card becomes read-only (no "Change" button). After confirmation, the card shows the green "Updated" badge for 3 seconds, then returns to interactive state with the "Change" affordance restored. The card does not participate in delivery channel selection — that is handled by the Send button dropdown on the review screen.
Components
Bottom Sheet
Reusable overlay pattern for contextual actions and forms. Used for contact search, contact editing, manual entry, and future flows (agreements drawer, settings). Slides up from the bottom with a blurred backdrop.
Anatomy
Backdrop · rgba(10,22,40,0.5) · no blur
Sheet Title
Scrollable content area
Sheet slides up with cubic-bezier(0.32, 0.72, 0, 1) over 300ms. Backdrop fades in at 250ms. Tapping backdrop or close icon dismisses. Max height: 75% of screen.
Sheet Variants — Contact Flow
Search Contacts
Change Borrower
Search contacts...
Edit current
Enter manually
Your Contacts
WJ
William Jennings
(302) 555-0147 · [email protected]
WC
William Chen
(415) 555-0183 · [email protected]
Manual Entry
Add Contact
Enter the details for someone not in your contacts.
Full Name *
e.g. William Jennings
Phone Number *
(555) 555-0123
Email Address optional
Add as Borrower
Contact search sheet title is role-aware: "Change Borrower." Selected contact is highlighted in rose. Search results show name, phone, and email when available. Action row provides "Edit current" (opens edit sheet pre-filled) and "Enter manually" (opens blank form). Manual entry requires name + phone; email is optional. CTA button uses primary gradient when required fields are filled; disabled state uses rgba(25,75,155,0.1).
Specs
Backdrop rgba(10,22,40,0.5) No blur — solid overlay
Sheet background linear-gradient(180deg, #F5F8FC, #EEF3FA) Cool blush gradient
Border radius 20px 20px 0 0 (--r-3xl) Top corners only
Shadow 0 -8px 40px rgba(0,0,0,0.12) Upward cast
Max height 75% Of screen height
Drag handle 32×4px · r2 rgba(25,75,155,0.15)
Header title 17px / 700 --text-primary · -0.01em tracking
Close icon 18×18px × icon rgba(25,50,100,0.4) · 4px padding
Content padding 0 20px 34px Home indicator clearance at bottom
Enter animation sheetSlideUp 300ms cubic-bezier(0.32, 0.72, 0, 1)
Backdrop animation sheetFadeIn 250ms ease
Components
Form Fields
Input fields used inside bottom sheets for contact editing and manual entry. Designed to feel lightweight and conversational — not like a traditional form. Labels include inline icons and use the structural steel-blue palette.
Field Anatomy
Required — Empty
Full Name *
e.g. William Jennings
Required — Focused
Phone Number *
(302) 555-01|
Optional — Empty
Email Address optional
Required fields show a rose asterisk after the label. Optional fields (email) show "optional" in lowercase at reduced weight. Focus state transitions border color to rgba(194,24,91,0.3). Icons are part of the label row, not the input. Phone is always required because it serves as the identity credential and authentication fallback. Email is optional and enables email delivery.
Specs
Label text 11px / 600 / 0.06em ↑ Uppercase · rgba(25,75,155,0.5)
Label icon 12×12px rgba(25,75,155,0.4) · 5px gap
Required indicator * at 10px rgba(194,24,91,0.5)
Optional indicator "optional" at 10px / 400 rgba(25,50,100,0.3) · no uppercase · used for email field
Input padding 12px 14px Comfortable touch target
Input radius 12px Slightly less than card
Input background rgba(255,255,255,0.7) Frosted glass on blush
Border — default 1px solid rgba(25,75,155,0.1) Nearly invisible
Border — focused 1px solid rgba(194,24,91,0.3) Rose accent on focus
Input text 16px / 500 --text-primary · avoids iOS zoom
Placeholder text 16px / 500 rgba(25,50,100,0.3)
Label → input gap 6px Tight association
Field → field gap 14px Clear separation
CTA Button States
Active — Required fields filled
Add as Borrower
Disabled — Missing required fields
Add as Borrower
CTA label is role-aware: "Add as Borrower," "Save Changes," etc. Active state uses primary gradient with rose shadow. Disabled state uses rgba(25,75,155,0.1) flat background. Transition between states: all 0.2s ease.
Components
Send Dropdown
Delivery channel selector built into the Send button on the review screen. The button displays the current delivery method with a chevron; tapping opens a dark dropdown menu above it with three options. The dropdown follows the "More ▾" pattern — a compact trigger that reveals an action menu. Party cards remain balanced with no delivery indicators.
Trigger Button — Three Variants
Default — SMS
Send via SMS
Email selected
Send via Email
Copy Link selected
Copy Link
Button gradient, icon, and label update to match the selected channel. Default is SMS when phone exists, Email when only email is available. Chevron rotates 180° when dropdown is open. Transition: all 0.3s ease.
Dropdown Menu — Open State
Deliver via
Send via SMS
Send via Email
Copy Link
Dropdown appears above the Send button. Dark frosted glass treatment contrasts with the light review screen. Active option has highlighted background + channel-colored checkmark + 600 weight. Inactive options use 400 weight + muted icons. A tap-anywhere backdrop closes the dropdown without changing selection.
Anatomy
Trigger — padding 14px 20px Same as primary button
Trigger — radius 14px (--r-lg) Standard button radius
Trigger — SMS gradient 135deg #C2185B → #7B1FA2 Primary brand gradient (--grad)
Trigger — Email gradient 135deg #2563EB → #3B82F6 --email-grad
Trigger — Link gradient 135deg #7B1FA2 → #9C27B0 --link-grad
Trigger — label 16px / 600 / 0.02em rgba(255,255,255,0.95)
Trigger — icon 16×16px Channel-specific · rgba(255,255,255,0.85)
Trigger — chevron 12×12px rgba(255,255,255,0.6) · rotates 180° on open
Menu — background rgba(40,36,52,0.92) Dark glass · backdrop-filter: blur(24px)
Menu — radius 16px (--r-xl) Slightly larger than trigger
Menu — shadow 0 12px 40px rgba(0,0,0,0.25) + 1px inset rgba(255,255,255,0.06)
Menu — padding 6px Inner content has its own padding
Section label 11px / 500 / 0.02em rgba(255,255,255,0.35) · "Deliver via"
Option row — padding 11px 14px Comfortable touch target
Option row — radius 10px (--r-md) Inner row highlight
Icon container 28×28px · r8 Active: channel color at 0.15 · Inactive: rgba(255,255,255,0.06)
Active row bg rgba(255,255,255,0.08) Subtle highlight
Active label 15px / 600 rgba(255,255,255,0.95)
Inactive label 15px / 400 rgba(255,255,255,0.55)
Checkmark 14×14px Channel color · 2px stroke
Position above trigger, 8px gap bottom: 100% + marginBottom: 8px
Enter animation dropdownSlideUp 200ms ease translateY(8px) scale(0.96) → identity
Behavior
The Send button serves dual purpose: delivery channel selector and send action. The button label area ("Send via SMS") is the primary tap target and sends immediately. The chevron is a separate tap target that opens the dropdown for changing the channel. Default channel is always SMS since phone is always required. Copy Link is always available. The dropdown does not appear on the contact card or conversation screen — delivery selection is centralized on the review screen's Send button.
Components
AI Companion Orb
A smaller version of the voice orb — 48×48px — used in the bottom bar alongside the primary CTA. Pulses differently when speaking vs idle.
States
Idle
Speaking
In bottom bar context
Send to William
Components
Account Setup
Inline account creation components that appear on the Review screen between Face ID verification and Send activation. No separate "sign up" screen — the account assembles from information already captured during agreement creation. Passwordless auth via phone number + biometric.
Section Divider
Secure Your Agreement
Appears below agreement data blocks after Face ID verification. Separates agreement content from account setup. Uses steel-blue structural color — never rose.
Phone Confirmation Card
Let's set up your account on Pactable
So you can get back to this agreement anytime
(925) 555-0182
From your contacts
That's my number
Use a different number
Phone number pulled from contacts during conversation. Confirm button uses primary gradient. "Different number" link is structural blue, not rose — it's a secondary action.
SMS Code Entry
Awaiting Input
Enter verification code
Sent to (925) 555-0182
Waiting for auto-fill
Auto-filled
Enter verification code
Sent to (925) 555-0182
8
4
7
2
1
9
Resend code
Six digit code cells. Empty = steel-blue border, focused = darker steel border, filled = rose border with rose tint. iOS auto-fill from SMS will populate all 6 simultaneously. Prototype simulates this with a 2.5s delay.
Account Confirmed
You're set up on Pactable
Face ID enabled · (925) 555-0182
Transitions from SMS code card using fadeIn. Uses verified green semantic color. Confirms both auth methods (Face ID + phone) in subtitle. Background shifts to green-tinted atmospheric blobs at this point. Send button activates.
OTP Error States
Wrong Code
Enter verification code
Sent to (925) 555-0182
3
9
1
0
4
2
Incorrect code — 3 attempts remaining
Resend code
Max Attempts Reached
Too many attempts
Request a new code to try again
Request New Code
Wrong code: cells shift to decline-red border + tint (rgba(200,80,80,0.4) border, rgba(200,80,80,0.04) fill). Inline error appears below cells with warning icon — never a toast or modal. Attempt counter shown from the 2nd failure onward ("3 attempts remaining", "2 attempts remaining", etc.). On first failure, show only "Incorrect code" without the counter. Card shakes on wrong code entry (CSS keyframe: translate ±6px, 300ms, 3 cycles). Max attempts: entire card replaces to the locked state shown above. "Request New Code" CTA sends the user back through the send-otp Edge Function, which enforces its own rate limit.
Rate Limited State
Too many requests
Try again in 7 minutes
OK
Uses amber (pending/time family) — not decline red. This is a temporary hold, not a failure. Clock icon reinforces the wait concept. Countdown is calculated from retry_after_seconds returned by the send-otp Edge Function and displayed rounded to the nearest minute. "OK" CTA is disabled-style (steel blue at low opacity) — it only dismisses; there is no action available until the window clears. Do not show a live countdown timer in this state — the minute approximation is sufficient and avoids complexity.
Resend Code — Countdown State
Countdown Active (first 30s)
Enter verification code
Sent to (925) 555-0182
Resend in 0:22
Resend Available (after 30s)
Enter verification code
Sent to (925) 555-0182
Resend code
Resend link is suppressed for the first 30 seconds after code dispatch, replaced by a live "Resend in M:SS" countdown at reduced opacity (rgba(25,50,100,0.25)). After 30 seconds the countdown is replaced by the active "Resend code" link at higher opacity with underline. Tapping resend calls send-otp again, resets the attempt counter for the new verification SID, and restarts the 30-second countdown. If the rate limit fires on resend, show the Rate Limited card instead.
Face ID App Unlock Screen
Default
Pactable
Unlock with Face ID to continue
or use a code instead
Face ID Failed
Pactable
Face ID not recognised
Try Again
or use a code instead
This screen appears on cold start (force-quit + reopen) and when the app has been backgrounded for more than 1 hour. It does not appear on shorter background periods — the app resumes without interruption. The screen sits above the app content (full-screen overlay, z-index top) — no app UI is visible until unlocked. "or use a code instead" is a plain text link that navigates to phone entry → OTP. After 3 consecutive Face ID failures in a single session, the "Try Again" button is replaced by "Use a Code Instead" as a primary CTA. The Face ID icon box uses decline-red tint on failure. Background is the standard lavender atmospheric — not a black lock screen. No device passcode fallback — Pactable uses OTP, not the iOS passcode.
Save Face ID for This Device — Bottom Sheet
Save Face ID for this device?
Sign in faster next time — no code needed. Your Face ID never leaves this device.
Set Up Face ID
Not now
Shown after successful OTP verification on a device with no registered passkey. Surfaces for both creators (new device) and counterparties (new browser or incognito). "Not now" dismisses and sets a per-device passkey_declined preference — the prompt will not appear again on that device unless the user navigates to account settings. "Your Face ID never leaves this device" is a trust line — accurate (WebAuthn private key is Secure Enclave only) and addresses the likely hesitation. Sheet uses standard bottom sheet anatomy: drag handle, rounded top corners, no close button (dismiss via "Not now" or swipe down).
AI Companion Dialogue
Post-verify "Great, you're verified. Before I send this, let's get your agreement set up on Pactable."
Code sent "I just sent a code to your number."
Account ready "You're all set. Face ID will get you back in anytime. Ready to send to William?"
AI companion narrates each state transition via speech synthesis. The orb pulses in speaking mode during each line. Language frames Pactable as a place ("on Pactable"), never as a possession.
Spec
Auth model Passwordless Phone + biometric, no passwords
Identity anchor Phone number Unique key — WebAuthn bootstrapped from phone OTP
Session auth Face ID / WebAuthn passkey Registered after OTP; stored in Secure Enclave
Session token iOS Keychain (rolling 30-day) WHEN_UNLOCKED_THIS_DEVICE_ONLY; silent refresh
Face ID gate Cold start + background > 1 hour Local challenge only — no server round-trip
New device flow Phone → SMS OTP → passkey prompt One-time per device; prompt dismissible
Timing in flow After Face ID, before Send Gates send — agreement needs an owner
OTP auto-fill iOS SMS auto-fill ~2.5s simulated in prototype
OTP rate limit 3 requests / 10 min per phone Enforced at Edge Function before Twilio call
OTP max attempts 5 per verification SID After 5 failures: locked, must request new code
Full spec pactable-auth-otp-implementation-plan.md OTP flows, WebAuthn lifecycle, session schema, Edge Functions
Identity
Auth Flows
Components and states for OTP error handling, rate limiting, app unlock (Face ID gate), and the new-device passkey prompt. The Account Setup section covers the first-send happy path. This section covers everything else: failure states, return login, and cross-device flows. Implementation detail in pactable-auth-otp-implementation-plan.md.
OTP Code Entry — Error States
Wrong Code
Enter verification code
Sent to (925) 555-0182
3
9
1
4
7
2
Incorrect code
Resend code
2nd+ Failure — Counter Shown
Enter verification code
Sent to (925) 555-0182
5
5
5
5
5
5
Incorrect code
3 attempts remaining
Resend code
Wrong code: cells shift to decline red border + faint red tint. Inline error appears below cells with warning icon. Input clears and shakes (CSS shake keyframe, 0.4s). Resend link always visible. Attempt counter appears from the 2nd failure onward — never on the first failure. Counter uses decline red, reduced weight. Counter format: "N attempts remaining" counting down from 4 → 3 → 2 → 1.
OTP Code Entry — Max Attempts
Too many attempts
Request a new code to try again.
Request New Code
Fires after 5 failed attempts on a single verification SID. The 6-digit input is replaced entirely by this state card — no partial input remains. CTA routes back to the phone confirmation step. The verification SID is invalidated server-side at this point. The new-code request is itself subject to the rate limiter.
OTP — Rate Limited
Too many requests
Try again in
7:00
Too many code requests were sent to this number. Please wait before requesting another.
Fires when the Edge Function's rate limiter blocks the request (3 OTP requests per phone per 10 minutes). Uses amber — not red — because this is a time constraint, not an error. Countdown timer counts down in real time from the retry_after_seconds value returned by the API. No CTA — the only action is to wait. When the countdown reaches zero, the phone confirmation card reappears automatically.
Resend Code — Countdown State
Counting Down (first 30s)
Enter verification code
Sent to (925) 555-0182
Resend in 0:22
Ready to Resend (after 30s)
Enter verification code
Sent to (925) 555-0182
Resend code
Resend is unavailable for the first 30 seconds. During cooldown, the link reads "Resend in 0:NN" in faint steel blue — visually present but clearly inactive (no underline, reduced opacity). After 30s it transitions to the tappable "Resend code" link (underlined, slightly stronger opacity). Requesting a resend resets the attempt counter and issues a new verification SID.
Face ID App Unlock Screen
pactable
Unlock Pactable
Use Face ID to continue
Use SMS code instead
Shown on cold start (force-quit and reopen) and when the app has been backgrounded for more than 1 hour. Not shown on foreground within the 1-hour window. Full-screen overlay on the lavender atmospheric background — same ambient blob animation continues behind it. Wordmark top-centre. Lock icon with frosted circle. Face ID prompt button uses the brand gradient. "Use SMS code instead" link triggers the OTP fallback flow. After 3 Face ID failures, this link is promoted to a more prominent CTA.
Face ID — 3 Failures Escalation
pactable
Face ID not working?
Verify your identity with a code sent to your phone instead.
Send me a code
or try Face ID again
After 3 consecutive Face ID failures, the lock icon shifts to a red-tinted variant and the fallback link is promoted to a full-width primary CTA ("Send me a code"). A secondary "or try Face ID again" link is offered below in case the failure was environmental (bad light, glasses). This state persists until the user either authenticates via OTP successfully or exits the app.
Save Passkey — New Device Prompt
Creator — New Device
Save Face ID for this device?
Sign in faster next time — no code needed.
Set Up Face ID
Not now
Counterparty — New Browser
Save a passkey for this browser?
No code needed next time you access your agreements.
Save Passkey
Not now
Appears after successful OTP on a device with no registered passkey. Applies to: (1) creator returning on a new iPhone after OTP fallback, (2) counterparty accessing web vault on a new browser after OTP fallback. Bottom sheet anchors to the bottom of the screen over a dimmed overlay. "Not now" dismisses permanently for that device — the prompt does not repeat on subsequent sessions on the same device. CTA copy differs: iOS native app uses "Set Up Face ID" (platform-specific); web uses "Save Passkey" (browser-agnostic). Both trigger the platform's native WebAuthn credential creation flow.
Spec
Identity anchor Phone number Unique key · recovery mechanism · always required
Auth method WebAuthn (Face ID) Primary · bootstrapped from OTP · never standalone
OTP provider Twilio Verify Never Programmable Messaging · Twilio SID logged to Trace
Session storage iOS Keychain (creator) Secure Enclave · survives reinstall · 30-day rolling
Web session httpOnly cookie (counterparty) 7-day expiry · SameSite=Strict · no localStorage
Unlock trigger Cold start or >1hr background Not on every foreground — only cold start or after 1 hour idle
OTP rate limit 3 requests / 10 min / phone Edge Function gate · Twilio Verify as backstop
Max attempts 5 per verification SID Counter shown from 2nd failure · SID invalidated at 5
Resend cooldown 30 seconds Countdown shown · link inactive during cooldown
Passkey prompt After OTP on new device One-time per device · "Not now" dismisses permanently
Motion
Animation System
Three motion categories: ambient (always running), entrance (content arriving), and interaction (response to touch). All CSS keyframes — no JS animation library required for core flows.
Ambient Animations
barWave scaleY 0.3→1→0.3 1.5–3s per bar · stagger 0.12s
gentleBreathe scale 1→1.015→1 5s · ease-in-out · infinite
buttonGlow box-shadow spread 0→40px 4s · ease-in-out · idle orb only
speakingPulse rose ring 2→2.5px + glow 2s · AI orb when speaking
activePulse rose ring 2→3px + spread 2s · orb when user speaking
cloudDrift1/2/3 translate + scale loop 22–30s · background blobs
Entrance Animations
fadeInUp opacity 0→1, translateY 12→0 350ms · cards, messages
slideUp opacity 0→1, translateY 20→0 350ms · modals, menus
slideDown opacity 0→1, translateY -16→0, scale 0.97→1 400ms · lock screen notif + in-app banner
messagePop scale 0.95→1.01→1 + fade 400ms · iMessage bubble
staggered reveal stg(i) helper: 0.08s × i delay Cards on review/executed screens
Interaction Animations
checkDraw stroke-dashoffset 24→0 500ms · success checkmark
circleGrow scale 0→1.1→1 500ms · success circle
sendPulse scale 1→1.2→1, opacity 1→0.6→1 1.5s · while sending
cursorBlink opacity 1→0→1 0.7s · chat typing cursor
shakeX translateX ±8px 500ms · wrong password
/* Staggered card reveal helper */ const stg = (i) => ({ opacity: loaded ? 1 : 0, transform: loaded ? 'translateY(0)' : 'translateY(12px)', transition: `all 0.5s cubic-bezier(0.4,0,0.2,1) ${i * 0.08}s` });
Components
Suggest Changes
The Suggest Changes flow transforms the counterparty Safari landing page into a negotiation surface. It is an exception path — the majority of agreements are accepted as-sent — but should feel considered and on-brand. Three visual states: suggest mode activated, block tapped with input open, block annotated and ready to submit.
Suggest Mode — Page State
Tapping "Suggest Changes" transitions the entire page into suggest mode. The background shifts to a subtly cooler lavender tint (suggest-bg). Party cards and the agreement header dim to 45% opacity — they are identity fields, not negotiable. A Callout Banner slides in below the header. Suggestable data blocks gain a rose left border affordance. The bottom action bar replaces Accept/Suggest/Decline with Send Suggestions (inactive) + Cancel.
suggest-bg token
linear-gradient(175deg, #EAF1FB 0%, #E3EEF9 15%,
#E8F1FA 30%, #E0EAF7 50%, #E5EEF9 70%, #DDE8F6 100%)
Non-suggestable opacity
opacity: 0.45 · transition: 300ms ease
Tap a highlighted term to suggest a change
Callout Banner — dismisses on first block tap
Suggestable Data Block — Three States
Idle — suggestable
Amount
$4,000.00
Four thousand dollars, USD
border-left: 3px solid rgba(194,24,91,0.35)
pencil icon: rgba(194,24,91,0.45)
Open — input active
Amount
$4,000.00
Four thousand dollars, USD
Your suggestion
e.g. $3,500 — I can only do that amount
border-left: 3px solid rgba(194,24,91,0.7)
input border: 1.5px solid rgba(194,24,91,0.3)
input ring: 0 0 0 3px rgba(194,24,91,0.06)
Annotated — still editable
Amount
$4,000.00
Four thousand dollars, USD
Your suggestion
$3,500 — I can only manage that amount
dot + pencil both visible when annotated
pencil opacity: 0.45 idle → 0.65 annotated
suggestion card bg: rgba(194,24,91,0.05)
Rules
✦ Pencil icon is always visible on suggestable blocks in suggest mode — including when annotated. The rose dot appears alongside it once a suggestion exists. Pencil opacity steps from 0.45 → 0.65 when annotated so dot + pencil read as a unit.
✦ Non-suggestable blocks (party cards, agreement type, phone numbers) render at 45% opacity in suggest mode with no tap response. Border-left accent and pencil icon are absent. Never show pencil on locked fields.
Bottom Bar — Suggest Mode
No suggestions yet — inactive
Send Suggestions
Cancel
Suggestions entered — active
2 suggestions ready to send
Send Suggestions
Cancel
Components
Review Changes
The creator-side flow for responding to a counterparty's proposed changes. Triggered from the Agreement Detail screen when status is changes_proposed. Three screens: Agreement Detail (changes_proposed state) → Review Changes → Confirmation.
Agreement Detail — changes_proposed State
The standard Agreement Detail screen gains two additions when status is changes_proposed: the header card shows the violet "Changes Proposed" badge (see § Status Badges), and a CTA banner appears below the party cards. The banner uses violet border and background tones — distinct from the rose action family — signalling that this is an inbound event, not a creator-initiated action. All other screen elements are unchanged.
banner bg: rgba(123,31,162,0.05)
banner border: 1.5px solid rgba(123,31,162,0.22)
icon container: gradient (rose → violet)
CTA button: full gradient — same as primary
Alex proposed some changes
Review their notes on the loan amount and repayment schedule.
Review Changes
CTA Banner — violet container, gradient button
Review Changes — Progress Unit
Notes pending
A
Alex left 2 notes
0 of 2 resolved
0/2
bg: rgba(123,31,162,0.05) · border: rgba(123,31,162,0.22)
counter + track: violet family
All resolved
A
All notes resolved
Ready to continue
2/2
bg: #F0FDF4 · border: #86EFAC (green family)
counter: #16A34A · track: #DCFCE7
Annotated Block — Three Resolution States
The annotation thread model keeps the current term value clean and authoritative at the top. The counterparty's verbatim note appears below attributed with their name. Pact AI proposes a structured interpretation. The creator resolves each block independently via one of three actions.
Pending — unresolved
Amount
$1,500
A
ALEX
"Could we do $2,000?"
Pact AI suggests
Update amount to $2,000
Apply this
Edit instead
Keep
border-left: 3px solid rgba(123,31,162,0.4)
note + AI card: violet family
Applied — term updated
Amount
✓ UPDATED
$2,000
Change applied · logged
border-left: 3px solid rgba(80,170,100,0.6)
value updates immediately on apply
annotation thread collapses
Kept — original stands
Amount
ORIGINAL ·
undo
$1,500
Alex's note logged · original term stands
border-left: 3px solid rgba(25,75,155,0.25) — steel
undo affordance: rose underline link
undo returns block to pending state
Border-left color grammar
Violet — pending. Counterparty note present, awaiting creator decision.
Green — applied. AI proposal accepted, term updated in document.
Steel — kept. Original term stands, note dismissed and logged.
Footer CTA — Adaptive Button
Single button, always present. Ghost-styled when notes remain unresolved. Activates with full gradient once all notes resolved. Label adapts based on outcome — whether the agreement document changed at all.
Pending — ghost
Re-send Agreement
border: 1.5px solid rgba(25,75,155,0.25) · not tappable
Active — changes applied
Re-send Agreement
any block applied → this label · new doc version sent
Active — all kept
Let Alex Know
all blocks kept → notification only, no doc change
Abandon Sheet
Triggered by back chevron during review. Proportionate to stakes — copy and destructive action adapt based on whether changes have been applied.
Changes applied — high stakes
Leave review?
You've made some changes. If you leave now, they won't be saved.
Leave & discard changes
Stay and keep reviewing
No changes yet — low stakes
Leave review?
You can come back to this any time.
Leave
Components
Push Notifications
APNs push is creator-only in Phase 1. The counterparty is web-only and receives SMS or email. One payload serves both delivery contexts — iOS handles presentation based on app state. The same slideDown keyframe drives both the lock screen bubble and the in-app banner; only the container and background differ. Full payload definitions, copy, and variable substitution tables live in pactable-messaging-integration-plan.md.
Lock Screen Notification Bubble
Delivered when the app is backgrounded or the device is locked. Frosted glass container on a dark wallpaper. Left accent stripe uses event sentiment color. App icon in rounded-square container. Standard iOS notification chrome (app name + timestamp).
Changes Proposed · violet stripe
Pactable now
Alex proposed changes
Personal Loan · $1,500 — Alex Rivera suggested 2 change(s). Tap to review.
Accepted · green stripe
Pactable now
William accepted your agreement
Loan to William Jennings — $4,000 is now fully executed. Both parties verified.
Anatomy
Containerborder-radius 20px · background rgba(255,255,255,0.12) · backdrop-filter blur(30px) · border 0.5px rgba(255,255,255,0.15)
Accent stripe3px wide · full height · left edge · border-radius matches container left · color = event sentiment (violet / green / red)
App icon36×36px · border-radius 8px · rose→violet gradient · Pactable waveform mark
App name14px · weight 600 · rgba(255,255,255,0.9)
Timestamp12px · rgba(255,255,255,0.35) · right-aligned · "now" on fresh delivery
Title15px · weight 600 · rgba(255,255,255,0.88) · left-offset 46px (icon width + gap)
Body13px · weight 400 · rgba(255,255,255,0.52) · line-height 1.45 · left-offset 46px
AnimationslideDown · 400ms · 700ms delay after screen load
Wallpaper tintDark background tinted to match event sentiment: violet (changes proposed) · green (accepted) · red (declined)
Accent Stripe Colors by Event
#7B1FA2 — Changes Proposed
#2E7D32 — Accepted
#C62828 — Declined
#F57F17 — Expired (creator)
#B71C1C — Voided (counterparty)
In-App Banner (Foreground Delivery)
When the creator has Pactable open, iOS delivers the same APNs payload as an in-app banner rather than a lock screen notification. The UNUserNotificationCenter delegate must return .banner for foreground delivery. The banner drops from the top of the screen over whatever the creator is looking at. Same copy, same deep-link — different container.
In-app banner — drops over vault screen
Agreements
Alex proposed changes
Personal Loan · $1,500 — Alex Rivera suggested 2 change(s). Tap to review.
Anatomy
Positionabsolute · top 54px (below status bar) · left/right 12px · z-index 50
Containerborder-radius 16px · background rgba(20,28,45,0.95) · backdrop-filter blur(20px) · border 0.5px rgba(255,255,255,0.12) · box-shadow 0 8px 32px rgba(0,0,0,0.4)
Accent stripe4px wide · full height · left edge · same sentiment color as lock screen
App icon30×30px · border-radius 7px · same as lock screen variant
Title13px · weight 700 · rgba(255,255,255,0.9) · single line · overflow ellipsis
Body12px · weight 400 · rgba(255,255,255,0.5) · single line · overflow ellipsis
AnimationbannerDrop · cubic-bezier(0.34,1.4,0.64,1) · 350ms · slight overshoot spring feel · 900ms delay after screen load
Tap behaviorSame deep-link as lock screen payload · navigates to relevant screen
iOS delegateReturn .banner from willPresent: to enable foreground delivery
/* bannerDrop keyframe — spring overshoot */ @keyframes bannerDrop { from { opacity: 0; transform: translateY(-20px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } /* easing: cubic-bezier(0.34, 1.4, 0.64, 1) — slight spring overshoot */ }
Phase 1 Events — Creator Only
Three events are wired in Phase 1. All fire to the creator. Counterparty push is out of scope until Phase 2. Full payload JSON, copy, and deep-link targets are in pactable-messaging-integration-plan.md.
Changes Proposed
Deep-link → review-changes screen · violet stripe · trigger: suggest-changes edge function
Agreement Accepted
Deep-link → agreement detail (executed) · green stripe · trigger: accept-agreement edge function
Agreement Declined
Deep-link → agreement detail (declined) · red stripe · trigger: decline-agreement edge function
Agreement Expired
Deep-link → agreement detail (expired) · amber stripe · trigger: expiry background job · payload spec in pactable-expiry-void-archive-plan.md
Note: Void does not trigger a creator push — the creator initiated the action. The counterparty receives SMS or email (not push) for both expired and voided events in Phase 1. Counterparty push is Phase 2.
Components
Lifecycle
Components for agreement expiry, voiding, and archiving. These cover the full spectrum of terminal states and the UX actions that lead to them. Color grammar: amber = time elapsed (expired), deep red = cancelled (voided), ghost steel = reversible secondary actions (archive, cancel). Rose gradient is reserved for constructive forward actions only — "Send Again" on an expired agreement is constructive, "Void Agreement" is not.
Agreement Card — Expiry Hint Row
When an agreement is pending and has an expiry date approaching, a subordinate hint row appears at the bottom of the card below the party chips. Amber clock icon + amber text. Shown when expiry is within the window (e.g., <7 days). Not shown on executed or terminal-state cards.
NDA — Alex Rivera
Sent March 10, 2026
Pending
DD
Creator
AR
Recipient
Expires in 4 days if Alex doesn't respond
hint row: border-top 1px solid rgba(0,0,0,0.05) · padding-top 7px
icon: clock · stroke rgba(200,128,18,0.85) · 11×11px
text: 11px · weight 500 · rgba(145,85,8,0.82)
Response Window Sheet
Bottom sheet for setting the expiry window before sending. Accessed from the review screen kebab menu ("Set response window") or the inline "change" link on the review screen. User-facing label is always "Response window" — not "expiry." Four presets: 3, 7, 14, 30 days. Selected preset highlighted in rose. Computed expiry date shown in an amber preview block below the presets. "Done" closes the sheet; no separate send step.
Response window

How many days does Alex have to accept or decline before the agreement expires?

3
days
7
days
14
days
30
days
Expires Mar 13, 2026 · Alex will be notified if the agreement expires without a response.
Done
sheet: white rgba(255,255,255,0.99) · border-radius 22px 22px 0 0 · shadow 0 -10px 40px rgba(0,0,0,0.12)
selected preset: bg rgba(194,24,91,0.08) · border rgba(194,24,91,0.26) · value color rgba(194,24,91,0.88)
unselected preset: bg rgba(25,75,155,0.04) · border rgba(255,255,255,0.5) · value color rgba(30,20,45,0.5)
amber preview: bg rgba(240,175,50,0.1) · border rgba(220,160,40,0.22) · text rgba(130,75,5,0.85)
entry point: kebab menu "Set response window [N]d" + inline "change" link on review screen
Void Bottom Sheet
Destructive confirmation sheet for voiding a pending or changes_proposed agreement. Accessed from the kebab menu ("Void Agreement" — shown in deep red in the menu). A void reason must be selected before the confirm button activates. "Other" reveals a textarea (max 140 chars). Confirm button uses solid #B71C1C fill — not rose gradient. Rose is reserved for constructive forward actions; destruction uses deep red.
Void this agreement?

This cancels the agreement and notifies Alex Rivera. This cannot be undone.

Reason
Sent in error
Terms need to change
Counterparty unresponsive
No longer needed
Other
Cancel
Void Agreement
selected reason: bg rgba(183,28,28,0.055) · border rgba(183,28,28,0.22) · text rgba(183,28,28,0.88)
unselected reason: bg rgba(25,75,155,0.025) · border rgba(255,255,255,0.5) · text rgba(30,20,45,0.72)
confirm button: bg #B71C1C solid fill · white text · NOT rose gradient
cancel button: ghost · border rgba(25,75,155,0.13) · text rgba(25,75,155,0.55)
confirm disabled (no reason selected): bg rgba(0,0,0,0.06) · text rgba(0,0,0,0.2)
"Other" selection: reveals textarea · border rgba(183,28,28,0.16) · max 140 chars
kebab entry: "Void Agreement" label in #B71C1C (not standard steel) — signals destructive action at menu level
Expired Agreement Banner
Non-dismissible banner shown at the top of the agreement detail content area when viewing an expired agreement (creator view only). White card with amber border. Clock icon + header + sub-text identifying the counterparty and date. Status badge in the header row (right-aligned). "Send Again" rose CTA is integrated inside the card at the bottom — it is the only forward action available. The muted agreement terms appear below the banner. Archive is accessible via the kebab menu only (not a bottom bar button) for expired agreements.
This agreement expired
Alex Rivera didn't respond before March 17
Expired
Send Again
card: bg rgba(255,255,255,0.7) · border 1px solid rgba(220,160,40,0.22) · border-radius 14px
icon tile: 36×36 · bg rgba(240,175,50,0.1) · border-radius 10px
header: 14px weight 700 rgba(30,20,45,0.86)
sub-text: 12px rgba(130,75,5,0.75) — amber text, not standard steel
"Send Again" CTA: rose gradient · inside card · 11px border-radius · shadow rgba(194,24,91,0.2)
copy: "Send Again" (not "Re-send Agreement") — short, action-first
Voided Agreement Banner
Two variants: creator view and counterparty web vault view. Both use the same deep red color family. No CTA inside the banner — informational only. The creator view has a ghost "Archive" button in the bottom action bar as the one forward action available. The counterparty view has no actions at all — terms remain visible below as a read-only record.
Creator view
You voided this agreement

Voided March 8, 2026 · Reason: Sent in error. Alex Rivera has been notified.

Archive
Ghost "Archive" button in bottom bar — the only forward action on a voided agreement. Uses steel ghost style, not rose.
Counterparty web vault
Damon voided this agreement

Voided March 8, 2026. No action is needed on your part. The original terms are shown below for your records.

No CTA — counterparty cannot take action. Terms remain visible below as a read-only record.
banner: bg rgba(183,28,28,0.06) · border rgba(183,28,28,0.18) · border-radius 13px
header: 13px weight 700 · rgba(183,28,28,0.82)
body: 12px · rgba(110,15,15,0.78) · line-height 1.5
creator copy: "You voided this agreement" · "Voided [date] · Reason: [reason]. [Name] has been notified."
counterparty copy: "[Creator] voided this agreement" · "Voided [date]. No action is needed on your part. The original terms are shown below for your records."
Archive button: ghost · border rgba(25,75,155,0.13) · text rgba(25,75,155,0.5) · height 50px · border-radius 14px
Vault Archive Navigation
Archive is a secondary surface — not a primary navigation destination. The default vault list shows active (non-archived) agreements. A subtle text link at the bottom of the active list shows the archived count. Tapping it switches the drawer into archived view. A "← Back to active" link returns to default. No persistent tab bar — archive is deliberately subordinate to the active list. Archived cards render at 0.72 opacity to signal inactive status.
Active list (default)
Agreements
2
NDA — Alex Rivera
Loan to William — $4,000
2 archived
Archived view
Archived
2
Archived agreements are kept for your records.
NDA — Jordan Park
Buy/Sell — Marcus Webb
← Back to active
archive link: 12px · rgba(25,75,155,0.32) · border-bottom rgba(25,75,155,0.14) — subtle, not a button
archived view header: "Archived" label + count badge — same structure as active header
info note: bg rgba(25,75,155,0.04) · text rgba(25,75,155,0.42) — steel, not amber, not red
archived cards: opacity 0.72 · bg rgba(255,255,255,0.36) — visually muted vs active rgba(255,255,255,0.62)
back link: "← Back to active" · 12px weight 500 · rgba(25,75,155,0.42)
unarchive: available via swipe action on archived card (steel) — same affordance as archive on active card
Components
Trace
Trace is Pactable's immutable session record for every agreement. It has two surfaces: a PDF (archival) and a web view (live verification). The web view is the single implementation — used in-app via SFSafariViewController and publicly via pactable.io/verify/[TRC-ID]. The design language is light/blush, not dark shell — Trace is a trust document, not an app screen.
Verified Badge
Used on the public verification screen and in the authenticated Trace view. Two sizes. Always uses the verified green semantic color, never rose or steel.
Verified on Base ✓
Verified on Base ✓
Large — public view header
gap: 10px · padding: 10px 20px
icon: 16×16 · font: 14px/700
Small — inline / auth gate strip
gap: 6px · padding: 5px 11px
icon: 12×12 · font: 11px/700
Chain Verification States
Shown on the public view while the live Base chain check resolves. The checking state uses a spinner and pending amber. The other three states are stable displays.
Checking Base blockchain…
~1.8s on page load
Base Mainnet · Block #28,441,902
Verified — hash matches chain
⏳ Blockchain registration pending
Hash recorded, chain registration in progress
⚠ Hash mismatch — do not rely on this Trace
Tampered / corrupted file
Web View Screen Flow
Linear four-screen funnel. Not tabs — each screen is the result of completing the previous step. The entry point is always a QR scan or direct link; nobody navigates to the verify URL and types a TRC-ID manually.
① Scanner
Phone mockup with QR viewfinder. Trace PDF peeking behind (source of the QR). Tap to simulate scan. Scan line animates to 100% then transitions.
② Public
URL bar shows TRC-ID. Chain check animates ~1.8s → Verified badge. Agreement type / amount / parties / status. "What this means" callout. Locked blur area + Sign in CTA.
③ Auth Gate
TRC-ID context strip + back to Public. Face ID path (simulated ~1.3s tap) or SMS OTP path (6-digit grid, auto-submits). Both land in Authenticated.
④ Full Trace
Agreement summary. Party cards (stacked). 10-event timeline with color-coded dots. Integrity section: hashes + chain detail + Basescan link. Share / Download PDF.
Back behavior: Auth Gate → Public. Full Trace → Public. Never scanner ← (entry point is external).
"What this means" Callout
Appears on the public view below the chain detail. Green-tinted. Copy is fixed — do not paraphrase or abbreviate.
What this means
This Trace record is unmodified since sealing. It contains a cryptographic fingerprint of the executed agreement, confirming both the agreement and this audit record are authentic.
Copy rationale: first sentence = tamper evidence of the Trace itself. Second sentence = what that proves about the underlying agreement. Two sentences, never collapsed into one.
Party Card — Trace Web View
Distinct from the app's party card — this is the authenticated web view version. Always stacked vertically, full width. Never side-by-side (breaks at 390px). Detail rows use a label/value pattern with label column min-width 88px.
DD
Damon Dean
Creator / Lender
Verified
IdentityFace ID (WebAuthn)
VerifiedMar 11 · 2:41 PM UTC
DeviceiPhone 16 Pro · Safari · iOS 18.3
Phoneending in 4521
IP address24.130.xxx.xxx
E-SIGN consentMar 11 · 2:41 PM UTC
Device and IP fields use JetBrains Mono for legibility and to signal machine-sourced data. All other fields use Plus Jakarta Sans.
Event Timeline — Dot Color Grammar
Each event dot is colored by party role. Time is shown above the label (not inline) for clean wrapping at narrow viewports. Detail line uses monospace.
Creator — Steel blue
Counterparty — Rose
System — Verified green
2:41 PM
Creator identity verified via Face ID
iPhone 16 Pro · Safari · iOS 18.3 · 24.130.xxx.xxx
2:42 PM
Delivery confirmed by carrier
SMS to number ending in 7744
2:46 PM
Counterparty identity verified via Face ID
iPhone 15 · Safari · iOS 17.6 · 98.71.xxx.xxx
Hash Row (Integrity Section)
SHA-256 hashes are truncated for display (first20…last8) with a copy button that copies the full 64-char value. Never display the full hash inline — it breaks layout at all viewport widths.
Terms hash (SHA-256)
a3f8c2e1d97b4056f2a8…b205f
On copy, button label changes to for 2 seconds then reverts. Color transitions to verified green on copy.
Entry Points
Creator — iOS App (kebab menu)
"View Trace" option in Agreement Detail kebab (⋮). Available for any agreement regardless of status. Opens SFSafariViewController modal → pactable.io/trace/[TRC-ID] with short-lived auth token → full Trace, no sign-in step. Presents as iOS modal sheet — swipe down to dismiss.
Counterparty — Safari Web
Post-acceptance screen: "Download your Trace" secondary CTA. Web vault Agreement Detail: "View Trace" link. Both follow same auth model as vault access.
QR Code — PDF & Completion Email
QR encodes pactable.io/verify/[TRC-ID]. Present in: Trace PDF integrity section, completion email body. Never on the web view itself — circular (you're already there).
Responsive Behavior
Mobile (primary target: 390px)
max-width: 560px
padding: 16px
party cards: stacked (1 col)
action buttons: flex full-width
hash: truncated display
Desktop (scales to 560px centered)
max-width: 560px centered
padding: 24px 16px
party cards: still stacked
no layout reflow needed
nav dots visible
Party cards are always stacked vertically regardless of viewport width. The 2-column layout was explicitly rejected — it breaks at 390px and provides no density benefit at larger sizes given the detail row content.
Components — Phase 2
Attachments
File attachments on agreements. Creator-only, pre-execution, review screen only. Photos and PDFs. Maximum 3 per agreement. Two surfaces: creator review screen (mobile app) and counterparty web view (Safari / Chrome / Firefox).
Creator — Review Screen
Empty State — tappable row
Add attachment up to 3
Border1.5px dashed rgba(194,24,91,0.22)
Border-radius--r-lg (14px) outer, 11px row
Icon18px circle, 1.5px outline, rose 0.3
Label12px / 500 / rose 0.6
Hint10px / faint / right-aligned
Tap targetEntire row → Add Attachment sheet
Populated State — 2 of 3
Attachments 2 of 3
vehicle_front.jpg
Photo · 2.1 MB
bill_of_sale_draft.pdf
PDF · 84 KB
Attachment Row Anatomy
Icon — photo32×32px · r-sm (8px) · rose bg rgba(194,24,91,0.08)
Icon — PDF32×32px · r-sm (8px) · steel bg rgba(25,75,155,0.08)
Filename11.5px / 600 / text-primary · ellipsis overflow
Meta10px / text-faint · "Photo · 2.1 MB" / "PDF · 84 KB"
Remove button18px circle · rgba(200,80,80,0.1) bg · immediate, no confirm
Row cardrgba(255,255,255,0.65) · 1px rgba(255,255,255,0.5) · r 11px
Section Header (populated)
Label9.5px / 700 / uppercase / rose 0.5
Count badgegradient pill · "2 of 3" · disappears at max
Max badge"3 of 3 · max reached"
+ button22px gradient circle · absent at max
+ tap targetOpens Add Attachment sheet
Color rulePhoto = rose · PDF = steel blue
Review Screen — Scroll Order
1 Nav bar · mini AI orb · spoken introduction
2 Party cards (Lender / Borrower)
3 Data blocks (Loan Amount, Schedule, Interest, Policy…)
4 Attachment section ← here
5 "Secure Your Agreement" divider
6 Phone confirmation card (account setup flow)
7 Send button · E-SIGN consent note
Add Attachment — Bottom Sheet
Review screen (dimmed)
Add Attachment
Photos or PDF documents only · max 3
Take Photo
Use camera now
Photo Library
Choose existing
PDF Document
Browse Files app
Cancel
Sheet Spec
Handle36×4px · rgba(0,0,0,0.15) · r 2px
Title15px / 700 / text-primary / centered
Subtitle11.5px / text-sub / centered
Photo options2-up grid · rose-tinted icon bg
PDF optionFull-width · steel-blue icon bg
Cancelrgba(200,80,80,0.07) · full width
Option cardrgba(255,255,255,0.75) · r 14px
Layout rationale
Take Photo and Photo Library are 2-up — functionally related (both produce a photo). PDF Document spans full width and uses steel blue to signal a different file type. Consistent with "blue = structure" design system principle.
Native pickers only
All three options invoke native OS pickers. No custom file browser is built or maintained. HEIC photos are converted to JPEG server-side.
Counterparty Web View — Collapsed / Expanded
Collapsed (default)
Attachments from Damon Dean
2 files · photo, PDF
↑ action buttons above ↑
Default stateCollapsed — counterparty opens on demand
Provenance label"Attachments from [Creator Full Name]"
Sub-label"N files · [type list]" — e.g. "2 files · photo, PDF"
Chevron▾ → ▴ on expand
PlacementBetween last data block and action buttons
Expanded
Attachments from Damon Dean
2 files · photo, PDF
vehicle_front.jpg
Photo · 2.1 MB
View
bill_of_sale_draft.pdf
PDF · 84 KB
Open
Photo tapFull-screen lightbox overlay · pure HTML/CSS/JS · cross-browser
PDF tapNew browser tab · Content-Disposition: inline · native viewer
ViewingOptional — never gates the Accept action
Affordance"View" for photos · "Open" for PDFs · steel blue / 9px / 600
CompatibilitySafari, Chrome, Firefox · iOS and Android
Color grammar
Rose = photos (camera, image). Steel blue = PDFs and documents. Consistent across creator and counterparty surfaces.
Provenance rule
Always label with the creator's full name — not "the sender" or "Pactable." Makes clear these files came from a specific person, not the platform.
Limits
Max 3 attachments. Photos (JPEG/PNG/HEIC) up to 10 MB. PDFs up to 25 MB. HEIC converted to JPEG server-side. Enforced at API layer.
Screens
Counterparty Web Experience
The complete three-screen counterparty flow running in Safari at app.pactable.io. This is Pactable's front door for new users. Full spec in pactable-counterparty-web-v1_0.md. Reference prototype: pactable-counterparty-v5.jsx.
Screen 1 — Context Banner
The first element on the Review screen. A frosted glass card that introduces Pactable and the agreement to a first-time counterparty. Replaces the old plain header treatment. Contains four layers: brand row, intro line, agreement title, divider, platform description.
Brand row
Brand icon 36px (static, no animation) + wordmark "Pactable" (14px/800) + tagline "Agreements Spoken Into Existence" (11px, rgba(25,50,100,0.35)). Gap 10px between icon and text column.
Intro line
"[Creator name] sent you an agreement to review and sign." 13px, rgba(25,50,100,0.5). Margin-bottom 8px.
Agreement title
"[Agreement Type] - $[Amount]" 20px/800, rgba(12,28,58,0.92), tracking -0.02em, line-height 1.2. Margin-bottom 12px.
Platform description
"Pactable uses plain structured terms - legally binding, without the walls of text. Everything you are agreeing to is listed below." 12px, rgba(25,50,100,0.5), line-height 1.65. Preceded by 0.5px rgba(25,50,100,0.08) divider.
Screen 1 — Legal Escape Hatch
Replaces "View as document." Sits below the last terms block. A low-prominence card that offers the legal text without implying it was hidden. The framing assumes the counterparty already has everything they need — this is an offering, not a disclosure. Tapping opens the Legal Terms Overlay.
Appearance
Background rgba(255,255,255,0.45), border 1px solid rgba(255,255,255,0.55), border-radius 14px, padding 13px 16px. Single row: text left, chevron right. Entire card is tappable.
Copy
"Prefer to read all the legal language? Go for it." — 13px, rgba(25,75,155,0.5), weight 500. Chevron: rgba(25,75,155,0.4).
Screen 1 — Legal Terms Overlay
Full-screen sheet on white background. Not a separate URL — client-side modal. Three zones: sticky header, scrollable body, sticky footer CTA.
Header
White background. "Complete Terms" (16px/700) + "[Type] - $[Amount]" subtitle (12px). X close button: 32px circle, rgba(0,0,0,0.06) bg. Padding 54px top (status bar clearance), 18px sides, 14px bottom. 0.5px rgba(0,0,0,0.08) bottom border.
Reassurance banner
Green-tinted card directly below header (margin 14px 18px 0). Background rgba(80,170,100,0.1), border 1px solid rgba(80,170,100,0.22), border-radius 12px, padding 11px 14px. Copy: "Every word you are legally bound to is here - nothing more, nothing less." 12px, rgba(20,48,95,0.72).
Legal sections
Each section: uppercase label (12px/700, rgba(20,48,95,0.72), letter-spacing 0.05em, margin-bottom 6px) + prose body (13px, rgba(25,50,100,0.5), line-height 1.7). Margin-bottom 20px between sections. Sections: Parties, Loan Amount, Repayment Schedule, Interest, Missed Payment Policy, Governing Law, Entire Agreement, Electronic Execution.
Footer CTA
"Back to review" — full-width, 14px padding, border-radius 14px, background rgba(25,77,139,0.07), 15px/600, steel blue label. Sticky: padding 12px 18px 34px (safe area), 0.5px rgba(0,0,0,0.06) top border.
Screen 2 — Sealing
Shown immediately after the counterparty taps "Accept Agreement." Replaces the immediate post-accept transition. Safari chrome hidden. The brand icon is the centrepiece — this is Pactable's moment. Four steps resolve sequentially, all turning green.
Brand icon
108px, centered. Waveform bars animate (scaleY 1 to 0.4, staggered) while steps are in progress. Breathing animation: scale 1 to 1.03, 3s ease-in-out infinite. Drop shadow: 0 8px 32px rgba(194,24,91,0.35). Bars stop animating once all steps complete.
Eyebrow
"Making it official" — 12px/700, uppercase, rgba(25,50,100,0.35), letter-spacing 0.08em. 8px below icon, 28px above step card.
Step card
Frosted glass card (rgba(255,255,255,0.65), border rgba(255,255,255,0.55), border-radius 20px, padding 6px 20px). Four steps separated by 1px rgba(25,50,100,0.06) dividers starting at 46px left (behind dot column). Each step: 32px dot indicator + label (14px) + sublabel (11px) + right indicator. Steps: Sealing the agreement / Notifying both parties / Creating your Trace / Emailing you a copy.
Step states
Waiting: 38% opacity, grey dot (rgba(25,50,100,0.2) 6px circle), grey label/sublabel. Active: Full opacity, rose spinner in dot (rgba(194,24,91,0.08) bg, rgba(194,24,91,0.2) border), dark label, steel sublabel. Done: Full opacity, green checkmark (popIn animation) in green-tinted dot (rgba(80,170,100,0.1) bg, rgba(80,170,100,0.22) border), green label and sublabel.
Timing
Step 1: 1100ms. Step 2: 900ms. Step 3: 1300ms. Step 4: 800ms. 400ms pause after final step, then transition to Screen 3. Do not compress — timing is intentional.
Brand Icon — Web Sizes
The Pactable brand icon (waveform + lock) is used at four sizes in the counterparty web flow. All sizes render from the same parameterised SVG component using a scale coefficient sc = size / 140 (designed at 140px). All coordinates derive from cx = size / 2.
108px
Sealing screen
animate=true
80px
Executed header
animate=false
38px
App Store card
animate=false
36px
Context banner
animate=false
Element — Outer ring
r=56sc, fill rgba(255,255,255,0.06), stroke rgba(255,255,255,0.18), strokeWidth 2sc
Element — Waveform bars
8 bars, symmetric. Width 7sc, gap 3sc, maxHeight 38sc. Opacities: [0.3, 0.52, 0.72, 0.9, 0.9, 0.72, 0.52, 0.3]. Heights: [0.28, 0.48, 0.70, 0.92, 0.92, 0.70, 0.48, 0.28]. Fill white at specified opacity. Animation when animate=true: scaleY 1 to 0.4, staggered 0.18s per bar, 0.1s delay per bar.
Element — Lock circle
r=16sc, fill rgba(12,20,50,0.88), stroke rgba(255,255,255,0.28), strokeWidth 2sc
Element — Shackle
Rounded U-path. Width 10sc, height 8sc. shx1=cx-5sc, shx2=cx+5sc. Path: M shx1 (cx-sc) L shx1 (cx-shH-sc) Q shx1 (cx-shH-shW/2-sc) cx (cx-shH-shW/2-sc) Q shx2 (cx-shH-shW/2-sc) shx2 (cx-shH-sc) L shx2 (cx-sc). Stroke white, 2.5sc, round caps.
Element — Lock body + keyhole
Body: rect width 18sc, height 13sc, rx 3sc, white fill. y=cx-2sc. Keyhole: circle r=3sc at (cx, cx+3sc) + rect 3sc x 3.5sc at (cx-1.5sc, cx+4.5sc). Both fill rgba(12,20,50,0.5).
Critical implementation note
All coordinates must be computed before the JSX return — do not inline complex expressions in SVG attribute strings. Compute cx first, then all derived values. The shackle path must be pre-computed as a string variable. See prototype for the correct computation order.
Screen 3 — Executed
Orientation screen for a first-time Pactable user. The agreement is done. No terms recap. Four cards answer the four questions a new user has: where is my copy, how do I get it again, what is a Trace, and how do I make my own.
Header
Brand icon 80px (static). Reveal animation: scale(0.75) rotate(-8deg) opacity 0 to scale(1) rotate(0) opacity 1, 550ms spring cubic-bezier(0.34,1.56,0.64,1). Drop shadow: 0 6px 20px rgba(194,24,91,0.22). Headline: "Agreement Executed" 22px/800, rgba(80,170,100,0.7), tracking -0.02em. Subhead: "Both parties are signed. This is official." 14px, rgba(25,50,100,0.5). Margin-bottom 28px.
Card anatomy (cards 1, 2, 4)
Frosted glass card (rgba(255,255,255,0.65), border rgba(255,255,255,0.55), border-radius 16px, padding 16px). Icon tile: 38px, border-radius 11px, rgba(25,75,155,0.07) background, rgba(25,75,155,0.1) border. Title 14px/700 + body 13px/400, rgba(25,50,100,0.5), line-height 1.55. Flex row, gap 12px.
Card 1 — Check your inbox
Icon: mail. Title: "Check your inbox". Body: "We emailed you a copy of the agreement and your Trace record. Sent from agreements@pactable.io."
Card 2 — Your agreement is always here
Icon: link. Title: "Your agreement is always here". Body: "Bookmark this page or use the link in your email to return anytime." URL pill below body: JetBrains Mono 11px, rgba(25,50,100,0.35), rgba(25,50,100,0.04) background, border-radius 8px, padding 7px 10px. Tapping the pill copies URL to clipboard + brief "Copied" confirmation.
Card 3 — What is a Trace? (green tint)
Green-tinted card: rgba(50,150,75,0.05) background, rgba(50,150,75,0.16) border. Icon tile: Trace icon, rgba(80,170,100,0.1) bg, rgba(80,170,100,0.22) border. Title: "What is a Trace?" rgba(80,170,100,0.7). Body: "A tamper-proof record of this agreement - who signed, when, and how identity was verified. Your evidence if anything is ever disputed." Footer row: TRC-ID (JetBrains Mono 10px, rgba(25,50,100,0.35), left) + "View Trace" (12px/700, rgba(80,170,100,0.7), right, tappable).
Card 4 — Create your own agreements
Same frosted glass card. Header row: brand icon 38px in gradient tile (grad background, same border-radius 11px) + title + body. "Personal loans, NDAs, roommate agreements - done in minutes with your voice." App Store button below: black pill, Apple logo SVG + "Download on the App Store" 13px/600 white. display inline-flex, padding 10px 18px, border-radius 10px.
Footer
"Powered by Pactable" — 11px, rgba(25,50,100,0.35), centered. No links.
Retired Patterns (v17)
These patterns from the previous counterparty web spec are removed in v17 and must not appear in the new implementation.
Removed: "View as document" text link. Replaced by the legal escape hatch card.
Removed: Email capture card ("Want a copy emailed to you?"). Email sent automatically at trace.sealed — no UI required.
Removed: "Agreement Accepted" / checkmark confirmation screen (old Screen 7). Replaced by Sealing + Executed screens.
Removed: Terms recap on post-accept screen. Counterparty just read and signed — no need to repeat.
Removed: "Download PDF" CTA on post-accept screen. PDF delivered by email.
Removed: "View Trace" as primary CTA. Trace is now introduced as a concept (Card 3) with a secondary text link — not a gradient button.
Reference
Changelog
What changed and where to look. Each entry notes the version, date, and affected sections so Claude Code can find relevant updates quickly without diffing the whole file.
v18 — August 2026
New — Green Confirm Pill (two-state)
Batch-confirm affordance from the receiving fill screen, now canon. Untapped = outline: 1.5px border rgba(27,138,90,0.5), frosted fill rgba(255,255,255,0.75), green text rgba(27,138,90,0.95) at 13.5px/700. Confirmed = solid: green gradient #1B8A5A → #157A4D (135deg), white text, label swaps to its confirmed form ("All confirmed ✓"), no longer pressable. Pill radius 99, padding 20px/10px, pressed state tints rgba(27,138,90,0.1) + scale 0.97. Rules: the FILL is the state signal — never render solid green for an unconfirmed action (reads as already done); rose/red never appears on confirm actions (red family = cancel/decline status semantics only). Component: src/components/ui/GreenPillButton.tsx.
New — Five-Bucket Triage Vocabulary (receiving)
The receiving triage buckets' visual grammar, one tint family per bucket, used on section rules, chips, and cards across plan + fill screens. I've got these — verified green rgba(27,138,90,0.95) text / 0.08 bg / 0.2 border. Already answered — steel rgba(25,50,100,0.9) / 0.06 / 0.16. Need you — rose #C2185B / rgba(194,24,91,0.07) / 0.2. Worth raising — amber rgba(200,140,30,0.95) / rgba(230,170,60,0.1) bg / rgba(200,140,30,0.35) border. Not yours — faded steel rgba(25,50,100,0.45) / 0.04 / 0.1, rendered at reduced prominence (named once, left visibly alone). Rose here is attention/action ("the conversation drives these"), NOT decline — decline/cancel stays in the deep-red status family. Source of truth: src/components/receiving/bucketStyle.ts.
New — Source-Crop Grammar (verification strips)
Every claim the companion makes about a document shows its receipts as an on-device crop of the source PDF. Windowed row strip: the crop is a horizontal strip ~70% of page width centered on the field with generous vertical padding — the whole row carries its own labels and neighbors; a tight crop of just the fill box is an illegible sliver and must not ship. Dashed-amber pre-expanded: worth-raising and uncertain claims render their crop pre-expanded inside a dashed amber frame — the user should never have to ask to see the evidence for a flagged read. Confirmed/presumptive claims may collapse the crop behind a tap. Page reference is inline in the card header ("page 2"), never an overlay badge on the crop image. Crops render on-device (PDFKit via modules/pdf-renderer) — the server never rasterizes.
New — Voice-First Input (the orb IS the input)
On receiving surfaces (read, fill) the voice orb is the primary and default input: it listens, speaks, and carries the conversation. Text input bars render ONLY when voice is unavailable (simulator, mic permission denied) — never as a co-equal affordance beside the orb. Field taps prime a spoken correction context: tapping a filled row or verify card does not open a keyboard editor — it arms the orb with the field's context ("Tell me the new date…") and listens. Narration queues rather than interrupting itself; long lines (page maps) are allowed to exceed the default speech-wait cap. There is no manual form-editor mode anywhere in the receiving arc — directed placement through the thread is the deepest fallback.
New — Sign & Return beats (Session 4)
Signature canvas: frosted white card (radius 20), finger-drawn strokes in steel #1B4D8B at 2.6px round caps, baseline sign line + "✕ [label]" hint, ghost Clear right-aligned below; strokes leave the device as normalized vector polylines (no image round-trip) and are drawn into the PDF as SVG paths. Component: src/components/receiving/SignatureCanvas.tsx. Hand-the-phone-over ceremony: cosigner marks (actor cosigner_present) get an interstitial card (🤝 + "hand the phone to whoever signs as [label]") before their canvas; copy states plainly that the mark is recorded as made in the account holder's presence, not identity-bound. Sealing steps: four-row progress list (flatten → place signature → hash → record), waiting rows at 35% opacity, active dot rose-tinted, done dot solid green with white check — the same resolving-to-green grammar as the counterparty Sealing Screen (v17). Return beat: recipient card (uppercase micro-label + email input), "WHAT THE EMAIL SAYS" preview card (steel-tinted, shows the disclosure line "[Name] added: TOTAL: $5,370" computed from session state), amber return-pending notes for saved/cancelled outcomes — return state is never assumed, and the share-sheet fallback gets an explicit "Did it send?" screen (primary "Yes — it went out" / ghost "Not yet"). Paper-return docs get an honest text link ("Returning it on paper? Mark as handled"), no pretend mechanics. Consent moment: one batched "Remember these?" list after signing — rows of key/value with green check circles (all pre-checked, tappable), provenance line ("Saved as: from [document] · today"), primary Remember CTA + ghost "Not now"; never silent, sensitive categories never listed. Received-document tray card: same card grammar as Agreement Card plus a violet inbound-arrow chip ("← Received", rgba(123,31,162,0.06) bg) — direction is the distinguishing signal (decision #4); status vocabulary: In progress (amber), Return pending (amber), Signed & returned (green + check). Expandable group rows (post-device rounds): grouped fields render ONE row titled by the group's SUBJECT (model-named group_label — "Physical description", never a member's label) with an n-of-m progress chip (rose tint open → green tint done) and a chevron; members are tappable sub-rows (dot/check + label + value); partially-done groups auto-expand, the first group starts open (the teaching affordance), and completed groups collapse to a GREEN summary row — never rose. Single-answer groups (radio choices, split boxes) never expand: members are options, not questions — one plain row showing the chosen option. Landscape signature pad: the draw step is a full-screen surface rotated 90° (the user turns the phone) — never a small portrait box, and never inside a scroll container (steals stroke touches). Review-before-send: the return screen leads with the executed document's rendered pages ("THE SIGNED DOCUMENT — LOOK IT OVER") — first page inline, expand for all, tap for full-screen pinch zoom; the review renders the actual attachment bytes. Editable-draft framing: the mail preview is labeled "STARTING DRAFT — EDIT ANYTHING IN MAIL" with "nothing sends until you hit Send there" sub-copy; the body carries voice-first provenance, never form data (material worth-raising write-ins are the sole disclosure exception); the share sheet is an always-visible "Send another way" secondary.
New — Receiving surface cards (page map · plan briefing · verify card)
Compact page-map cards (read screen): one card per physical page — mini page thumbnail with page-number dot, uppercase kind chip (SIGN rose / INFO steel / OPTIONAL faded), bold label, one-line description; cards stagger in as triage streams; the map compresses when the gist arrives (read and summary share ONE surface). Plan briefing rows (plan screen): one row per bucket with count, bucket-tinted chip, and Haiku-written copy scaled to actual profile depth; theirs-to-route fields render as a separate "Beyond today" row with plan-across-time copy — the briefing never promises single-sitting completion when routing exists. Verify card confirm/Fix pair (fill screen): presumptive reads show value + source strip with exactly two affordances — "Looks right" (green, confirming) and "Fix" (neutral steel, opens the thread primed) — equal-weight siblings, never OK/Cancel semantics. Prototypes: pactable-triage-patterns-v1.jsx, pactable-inbound-sign-v1.jsx; shipped surfaces receiving/read.tsx · plan.tsx · fill.tsx. Implementation notes: plan doc §7.1.
v17 — April 2026
New — Counterparty Web Experience (full redesign)
Added § Counterparty Web Experience covering all new and updated patterns for the three-screen counterparty flow. Full spec in pactable-counterparty-web-v1_0.md. Reference prototype: pactable-counterparty-v5.jsx. New patterns documented: Context Banner (brand icon + tagline + intro + platform description, replaces plain header); Legal Escape Hatch (replaces "View as document" — low-prominence card offering legal text without implying it was hidden); Legal Terms Overlay (full-screen sheet with sticky header, reassurance banner, scrollable legal sections, "Back to review" footer); Sealing Screen (brand icon 108px animated, "Making it official" eyebrow, four-step card with waiting/active/done states all resolving to green, timing table); Brand Icon Web Sizes (four sizes — 108/80/38/36px — parameterised SVG, full element anatomy, critical implementation note on computation order); Executed Screen (orientation screen with four cards: inbox confirmation, agreement URL with clipboard pill, Trace explanation green-tinted card, App Store CTA with brand icon tile); Retired Patterns (explicit list of six removed elements with replacement notes).
v13 — March 2026
New — Auth error states, Face ID unlock, passkey prompt + spec updates
Added five new components to § Account Setup. OTP Error States: wrong code (decline-red border + tint, inline error, attempt counter from 2nd failure), max attempts reached (locked card with Request New Code CTA, card shake animation on wrong entry). Rate Limited State: amber clock icon, retry-after time rounded to nearest minute, disabled-style OK CTA — uses pending/time color family, not decline red. Resend Countdown: 30-second suppress window shows "Resend in M:SS" at low opacity; after 30s transitions to active tappable link. Face ID App Unlock Screen: full-screen overlay on cold start or background > 1 hour; default and failed states; "or use a code instead" plain text fallback; after 3 failures "Try Again" replaced by primary OTP CTA. Save Face ID Bottom Sheet: post-OTP passkey registration prompt for new devices; "Not now" sets per-device dismissed preference; trust line "Your Face ID never leaves this device." Spec table updated: "Login credential" → "Identity anchor" with corrected description; added session token (Keychain, rolling 30-day), Face ID gate rule (cold start + >1 hour), OTP rate limit (3/10min), OTP max attempts (5), and reference to pactable-auth-otp-implementation-plan.md.
v12 — March 2026
Merge — combined divergent v11 branches
Two v11 files existed with non-overlapping changes. This v12 combines both. From v11-2 (authoritative base): Party Cards section fully updated to border + footer strip pattern (see v11 entry). From v11 (Contact Card refinements): "Change" pill corrected to rose (#C2185B) with no background fill — action color, not steel. Spec row updated. Section description updated to note that tapping "Change" opens the contact sheet in Edit mode, pre-filled with the current contact's details. See § Contact Card and § Party Cards.
v11 — March 2026
Updated — Party Cards: border + footer strip pattern
Replaced the top-right verified badge pattern with a coordinated border + footer strip approach. Previously, a verified/unverified badge in the top-right corner conflicted with the edit icon added for Review screen editing. New pattern: identity status is signalled by (1) the card's outer border color and (2) a tinted footer strip at the bottom of the card — both always updated as a unit. Verified = 1.5px rgba(80,170,100,0.45) border + green strip + "Identity verified" label. Pending = 1.5px rgba(200,140,30,0.45) border + amber strip + "Pending verification" label. Edit icon lives in the card body top-right, present only in editable states (draft, pending), hidden in executed/voided/expired. Cards are always stacked vertically — never side-by-side — on the Review screen. Counterparty avatar picks up the full brand gradient once verified. See § Party Cards.
v10 — March 2026
New — Attachments component set
Added full Attachments design spec covering both surfaces. Creator — Review Screen: Empty State row (dashed border, outlined + icon, tappable directly — not a redirect), Populated State with section header (label, count badge "N of 3", gradient + button, absent at max), Attachment Row anatomy (32×32 icon, rose for photos / steel blue for PDFs, filename, meta, ✕ remove), scroll-order table (data blocks → attachment section → Secure Your Agreement divider). Add Attachment Sheet: Take Photo + Photo Library 2-up (rose), PDF Document full-width (steel blue), Cancel, native OS pickers only — no custom browser. Counterparty Web View: Collapsed default row ("Attachments from [Creator Name]" + file count + type hints + chevron), Expanded state with per-file rows (thumbnail/icon + filename + meta + View/Open affordance), photo tap → lightbox overlay (pure HTML/CSS/JS), PDF tap → new browser tab with Content-Disposition: inline (cross-browser native viewer). Design rules: rose = photos, steel blue = PDFs (consistent both surfaces); provenance label always uses creator's full name; viewing never gates Accept. Implementation in pactable-attachments-integration-plan.md. See § Attachments.
v9 — March 2026
New — Lifecycle component set
Added full Agreement Lifecycle design spec covering all new terminal and in-progress states. Status Badges: added Expired (amber, clock icon), Voided (deep red, circle-X icon), and Declined (rose-red, X icon) to the badge inventory. New section — § Lifecycle: Agreement Card with expiry hint row; Response Window Sheet (presets 3/7/14/30 days, computed date, amber preview block, kebab entry point); Void Bottom Sheet (reason selector, destructive #B71C1C confirm button); Expired Agreement Banner (white card with amber border, clock icon, integrated "Send Again" rose CTA); Voided Agreement Banner in creator and counterparty variants; ghost Archive CTA on voided creator view; Vault Archive Navigation (subtle "N archived" text link, archived view at 0.72 opacity, "← Back to active"). Push Notifications: added Expired (amber stripe) and Voided counterparty (red stripe) to event table. Implementation detail in pactable-expiry-void-archive-plan.md.
v8 — March 2026
New — Trace component set
Added full Trace design spec: Verified Badge (large/small), Chain Verification States (checking/verified/pending/tampered), Web View Screen Flow (QR Scanner → Public → Auth Gate → Full Trace), "What this means" callout with canonical copy, Party Card web variant (always stacked, label min-width 88px, Device/IP in monospace), Event Timeline dot color grammar (steel=creator, rose=counterparty, verified green=system), Hash Row with truncated display and copy button, Entry Point inventory (iOS kebab → SFSafariViewController, counterparty Safari, QR on PDF/email only — never on web view), and Responsive Behavior spec (390px primary, 560px max, party cards always stacked). See § Trace.
v7 — March 2026
New — Push Notifications component set
Added full push notification spec: Lock Screen Notification Bubble (frosted glass, accent stripe, sentiment-tinted wallpaper), In-App Banner (dark container, spring animation, foreground delivery via UNUserNotificationCenter delegate), accent stripe color grammar (violet/green/red by event), and Phase 1 event summary. Updated § Animations — slideDown description updated to include in-app banner. bannerDrop keyframe added. See § Push Notifications.
v6 — March 2026
New — Review Changes component set
Added full creator-side Review Changes flow: Agreement Detail in changes_proposed state with violet CTA banner, Progress Unit (violet pending → green resolved), Annotated Block in three states (pending/applied/kept) with border-left color grammar, adaptive Footer CTA (Re-send Agreement vs Let [Name] Know), and Abandon Sheet with proportionate copy. See § Review Changes.
Updated — Status Badges: terminology fix
Corrected status name from changes_requested to changes_proposed throughout. See § Status Badges.
v1.5.1 — March 2026
Updated — Status Badges: Changes Proposed
Added changes_proposed badge for the changes_proposed agreement status. Violet recommended — rgba(123,31,162,0.1) bg / rgba(123,31,162,0.7) text. Pencil icon matches the Suggest Changes block affordance. Rose-red option also documented in § Status Badges for reference. See § Status Badges.
New — Suggest Changes component set
Added full Suggest Changes flow: suggest mode page state (suggest-bg token, 45% opacity on non-suggestable blocks), Callout Banner, Suggestable Data Block in three states (idle, open, annotated), inline suggestion input, dot + pencil affordance pattern, and bottom bar inactive/active states with suggestion count pill. See § Suggest Changes.
New — Changelog section
Added this changelog as a standing section at the end of the document. All future versions should add an entry here before bumping the version number.
New — Changelog section
Added this changelog as a standing section at the end of the document. All future versions should add an entry here before bumping the version number.
v16 — Completion Email component set
Added email design patterns for the new Agreement Completion email (Template 4 replacement). New components — § Email Patterns: Email Term Row — label/value pair rendered in a tinted surface block; amount value uses rose (#C2185B) at larger font weight to make the financial commitment visually prominent. Email Party Cards — two-column layout (desktop) / stacked (mobile <480px); same border + footer strip pattern as in-app party cards; green border + "Identity verified" footer on executed state; always both-verified at completion. Execution Record Block — green-tinted surface, "Execution record" label, two rows: executed timestamp and Trace ID in monospace. Trace Explainer Callout — violet-tinted surface (rgba(123,31,162,0.05) bg, rgba(123,31,162,0.15) border), info icon, prose copy explaining what a Trace is and when you'd need it; sits immediately above the primary View Trace CTA so the button has full context. CTA hierarchy: View Trace (primary gradient button) → Download Agreement PDF (secondary ghost button). Template retirement: old Template 4 (Executed Agreement PDF, minimal) and Template 4b (Trace completion from Trace spec) are both retired — this template supersedes both. Trigger is trace.sealed on executed agreements only. Reference prototype: pactable-completion-email-v2.html. Full template spec in pactable-email-delivery-integration-plan-v1_1.md Template 4.
v15 — Design system ↔ app sync
Synced design system to match current app implementation. Status badges: documented two variants (inline tag at 6px radius, standalone pill at 9999px radius); added icons to all 11 statuses (draft: pencil, sent: paper-plane, pending: clock, changes_proposed: pencil, accepted: checkmark, executed: checkmark, void_requested: warning-triangle, voided: circle-X, declined: circle-X, expired: clock, cancelled: circle-slash); updated Declined icon from plain X to circle-X (color alone distinguishes from Voided). Voice Orb: corrected idle bar specs to 5px width / 6px gap (was 4px/5px). Bottom Sheet: updated backdrop to rgba(10,22,40,0.5) with no blur (was rgba(12,28,58,0.3) + blur); corrected drag handle width to 32px (was 36px). New specs: added Conversation Orb (64px, 5-bar traveling sine wave, phase-responsive energy/opacity) and Inline Orb Editor (36px, animated wave bars when AI speaks, mic/spinner icons for other states). See § Voice Orb.
v14 — Profile feature
Added Profile section covering: Avatar component (small 36px header variant and large 76px hero variant, contact photo and initials/silhouette fallback states), Home screen header update (three-column layout, wordmark centering), Profile screen anatomy (nav bar, hero, verification row, account section, agreements summary, sign out), inline edit field pattern (name and email, active/saved/cancel states), and face ID verification row with last-verified timestamp formatting. See § Profile.
Components
Profile
The creator's profile is a full navigation destination accessed via the avatar in the home screen header. It surfaces identity, verification state, editable contact details, and account stats. All editing is inline — no sheet opens on top of this screen.
Avatar
Two sizes. Small (36px) lives in the home screen header. Large (76px) lives in the profile hero. Three states per size: contact photo, initials on brand gradient, and silhouette fallback. Photo state is used when profiles.photo_url is set. Initials state is used when no photo exists and the user is signed in. Silhouette is the anonymous state.
Small · 36px
Photo
DD
Initials
Anonymous
Large · 76px
Photo
DD
Initials
Anonymous
Small size36×36pxHome screen header · border 2px rgba(255,255,255,0.55)
Large size76×76pxProfile hero · border 3px rgba(255,255,255,0.65)
Photo gradientlinear-gradient(180deg, #c2d8ee, #7aadd6)Top-to-bottom — no diagonal shift
Initials gradientlinear-gradient(135deg, #C2185B, #7B1FA2)Brand gradient — same as orb bars
Head position (small)top: 7px · width: 14pxCentred via translateX(-50%)
Shoulders (small)bottom: -4px · width: 22px · height: 12pxClipped by overflow: hidden
Head position (large)top: 14px · width: 28pxCentred via translateX(-50%)
Shoulders (large)bottom: -10px · width: 60px · height: 32pxClipped by overflow: hidden
Shadow (both)0 2px 8px rgba(0,0,0,0.12)Subtle lift — not glow
Home Screen Header
Three-column flex row. Left slot: 36px invisible spacer. Centre slot: wordmark + tagline. Right slot: avatar. The spacer matches the avatar width so the wordmark is optically centred regardless of what is in the right slot.
pactable
Speak Agreements Into Existence
Layoutflex · space-between · align-items centerThree columns: spacer | wordmark | avatar
Left spacerwidth: 36px · flex-shrink: 0Invisible — balances avatar width
Wordmark28px · 800 · -0.05em · #880E4FLowercase per wordmark spec
Tagline13px · 400 · --text-subbody-xs · 0.01em tracking
Padding0 18px--sp-screen
Profile Screen
Full navigation screen. Background: linear-gradient(180deg, #F5F8FC 0%, #EEF3FA 100%). Nav bar has a back button (‹ Home, rose, 15px/500) on the left and "My Profile" centred (17px/700). Hero section is centred. Account and Agreements rows use the standard info-row pattern. Sign Out at bottom uses the error destructive style.
Home
My Profile
Damon Dean
Edit
Face ID verified
Last verified today at 9:41 AM
(925) 555-0182
Account
Email Add email →
Member since March 2026
Agreements
Completed 3
Pending 1
Voided 1
Sign Out
Screen backgroundlinear-gradient(180deg, #F5F8FC, #EEF3FA)Blush profile bg — distinct from phone-bg
Nav bar height96pxIncludes status bar clearance
Nav back label15px · 500 · --rose‹ Home — rose action colour
Nav title17px · 700 · --text-primary · -0.02emAbsolutely centred in nav bar
Hero padding22px 18px 18px
Display nametitle-lg · 26px / 800 / -0.04em--text-primary
Edit chiplabel-sm · 10px / 700 / 0.07em ↑Rose 7% bg · rose 65% text · r-xs 6px
Verified method13px · 600 · --verified
Verified timestamp11px · 400 · --text-faintRelative: today/yesterday/date
Phone13px · 500 · --text-subRead-only — no edit affordance
Section labellabel-sm · uppercase · rgba(25,75,155,0.38)
Info row label13px · 400 · --text-sub
Info row value13px · 500 · --text-primaryRose for empty/CTA states
Row divider0.5px rgba(25,75,155,0.06)
Completed value13px · 600 · --verifiedGreen — success semantic
Inline Edit Field
Used for name and email editing on the profile screen. Activates in place — no sheet. Auto-focuses on activation. Rose focus ring. Optional "Import from Contacts" secondary link (name field only). Save (gradient primary) and Cancel (muted secondary) below the input. Only one field active at a time.
Damon Dean
Import from Contacts
This name appears on all your agreements.
Save
Cancel
Input border (active)1.5px solid rgba(194,24,91,0.35)
Input focus ringbox-shadow: 0 0 0 3px rgba(194,24,91,0.07)
Input backgroundrgba(255,255,255,0.8)
Input text15px · 500 · --text-primary
Hint text11px · 400 · --text-faint
Save button--grad · 13px / 600 · r-md 10px
Cancel buttonsteel 6% bg · 13px / 600 · --text-sub · r-md
Mutual exclusivityOne active field at a timeOpening one cancels the other without saving
Voice Personalisation Moments
Three moments where the AI companion speaks a personalised line using profiles.first_name. All fall back to non-personalised lines when no profile exists.
1 — Sign in
"Welcome back, Damon."
Plays after home screen loads, not during Face ID. Orb animates briefly then returns to idle.
2 — Orb tap
"Good morning, Damon. What kind of agreement can I help you create?"
Part-of-day mapping: 5 AM–12 PM → morning · 12–5 PM → afternoon · 5–10 PM → evening · 10 PM–5 AM → "Hey" (no part-of-day). Orb enters speaking state, transitions to listening.
3 — Agreement sent
"Your agreement has been sent to William, Damon."
Ambient audio on the post-send screen — no orb present. Plays alongside the green atmospheric background shift. Counterparty first name from agreement record.
Reference
All Tokens
Complete token reference. Also available as pactable-tokens.json for Figma Tokens plugin and Style Dictionary.
Quick Reference
Color
--rose#C2185B
--rose-dark#880E4F
--rose-light#E91E8C
--violet#7B1FA2
--blush#EEF4FB
--shell#0A1628
--verifiedrgba(80,170,100,.7)
--pendingrgba(200,140,30,.6)
Typography
--fontPlus Jakarta Sans
display-xl52px / 800 / -0.05em
title-lg26px / 800 / -0.04em
title-md22px / 700 / -0.035em
body-lg16px / 600 / 0
body-md15px / 400 / 0
label-md11px / 600 / 0.07em ↑
label-sm10px / 700 / 0.1em ↑
Spacing
--sp-screen18px
--sp-card14px 16px
--sp-gap10px
--r-xs6px · badge
--r-lg14px · card
--r-xl16px · button
--r-3xl20px · drawer
Motion
standardcubic-bezier(.4,0,.2,1)
entercubic-bezier(0,0,.2,1)
fast200ms
standard300ms
slow500ms
stagger0.08s × index
bar-stagger0.12s × index