diff --git a/design/receipt-card/README.md b/design/receipt-card/README.md new file mode 100644 index 0000000..fbec042 --- /dev/null +++ b/design/receipt-card/README.md @@ -0,0 +1,58 @@ +# x402 Micropayment Receipt Card — Design Package + +> GrantFox UX Bounty deliverable for [nirium-protocol/nirium-sdk#13](https://github.com/nirium-protocol/nirium-sdk/issues/13). + +A complete, designer-ready package that any Figma user can import as a reference, plus a runnable HTML/CSS prototype and exportable SVG mockups. + +## Contents + +| Path | What it is | +|---|---| +| `SPEC.md` | Full design specification — typography, color tokens, spacing, components, motion, accessibility | +| `components/` | Figma-ready component breakdown (JSON + human-readable) for direct import into Figma | +| `mockups/` | SVG mockups — Settled state, Pending state, Failed state, Mobile compact, Desktop expanded | +| `prototype/` | Runnable HTML/CSS/JS interactive prototype with live state toggle | +| `tokens/` | Design tokens (JSON + CSS custom properties) for Figma Variables / Tailwind / CSS | + +## Live prototype + +Open `prototype/index.html` in a browser, or serve the folder: + +```bash +cd prototype && python3 -m http.server 8000 +# then visit http://localhost:8000 +``` + +The prototype shows the receipt card in all three states (Settled / Pending / Failed) with a click-to-toggle demo, real Stellar Expert links, and copyable transaction hashes. + +## Figma handoff + +`components/figma-component-map.json` is structured for one-click import via [Figma's REST API](https://www.figma.com/developers/api) or paste into a Figma file via the [Tokens Studio](https://tokens.studio) plugin. The tokens file (`tokens/tokens.json`) is also Tokens-Studio-compatible out of the box. + +If a Figma desktop license is available, the recommended import flow: + +1. Open Figma → New design file → Plugins → Tokens Studio → Sync → `tokens/tokens.json` +2. All color, typography, spacing, radius, and shadow variables populate. +3. Drop the SVG mockups from `mockups/` onto the canvas — they import as editable vectors. +4. Use `components/figma-component-map.json` as the component tree reference. + +## Design rationale + +- **Dark mode + glassmorphism** — matches the Nirium SDK dark surface (see `nirium-sdk/packages/sdk` reference screenshots in the issue thread). Glassmorphism with a subtle indigo accent layer reads as "crypto-native / premium" without looking generic. +- **Settled vs Pending vs Failed** — color-coded status pill (emerald / amber / rose) on a frosted background. State change is animated (220ms) for clear feedback. +- **Stellar Expert link** — opens the live ledger transaction in a new tab. IPFS CID renders as a clickable link to `ipfs.io/ipfs/` (gateway). +- **Truncation policy** — hashes and CIDs use `text-overflow: ellipsis` with a one-click copy button, full value in `title` for hover. +- **Accessibility** — WCAG AA contrast on every text element against the darkest card surface. `prefers-reduced-motion` disables the state-change animation. Full keyboard navigation, visible focus rings, `aria-live="polite"` on status changes. +- **Mobile-first** — single-column on `<480px`, two-column metadata grid at desktop widths, all tap targets ≥ 44px. + +## Acceptance criteria (mapped to issue #13) + +| Issue requirement | Where delivered | +|---|---| +| Sleek modern dark mode + glassmorphism | `prototype/index.html`, `mockups/settled.svg`, `SPEC.md` § Visual language | +| Amount (USDC), Tx Hash, Stellar Expert link, IPFS CID, Status (Settled/Pending) | `prototype/index.html` § Card layout, `mockups/*.svg` | +| Exportable Figma components & design specs | `components/figma-component-map.json`, `tokens/tokens.json`, `SPEC.md` | + +## Reward eligibility + +Issue is labeled `GrantFox OSS`, `Official Campaign | FWC26`, `Maybe Rewarded`, body names "Funded via Stellar Foundation / GrantFox Campaign (Organization ID: b6550049-a239-4dc1-b95e-2fb28ded7ea6)". Reservation comment left by `@jdjioe5-cpu` on 2026-07-23T07:42:25Z (`#issuecomment-5055782193`). No formal assignee conflict on the issue at PR time. A compatible public USDC receive address is provided in the PR body for the maintainer to wire. diff --git a/design/receipt-card/SPEC.md b/design/receipt-card/SPEC.md new file mode 100644 index 0000000..30efd6f --- /dev/null +++ b/design/receipt-card/SPEC.md @@ -0,0 +1,231 @@ +# Design Specification — x402 Micropayment Receipt Card + +> Closes nirium-protocol/nirium-sdk#13. Dark-mode glassmorphism receipt card for the Nirium SDK x402 payment flow. + +--- + +## 1. Visual language + +### Aesthetic direction + +A near-future fintech feel: deep cosmic background, layered frosted glass, precise 1px hairlines, and a single accent color (Indigo 500) reserved for state + interaction. The card should feel like an artifact pulled from a wallet app, not a generic "SaaS receipt". + +### Mood references + +- Linear (dark) +- Stripe Issuing (dark) +- Rainbow wallet receipts (dark) +- Apple Card transaction detail + +### Anti-references (avoid) + +- Bootstrap defaults +- Generic "crypto coin" iconography +- Excessive gradients / glows that fight readability + +--- + +## 2. Color tokens + +All colors live in `tokens/tokens.json` and `tokens/tokens.css`. Imported as Figma Variables via Tokens Studio. + +### 2.1 Surface + +| Token | Hex | Usage | +|---|---|---| +| `surface/canvas` | `#07080C` | App background (deepest) | +| `surface/card-base` | `#101218` | Card solid fallback before blur | +| `surface/card-glass` | `rgba(255,255,255,0.04)` | Glass layer, blurred backdrop | +| `surface/card-glass-strong` | `rgba(255,255,255,0.07)` | Glass layer on darker sections | +| `surface/hairline` | `rgba(255,255,255,0.08)` | 1px dividers, input borders | +| `surface/hairline-strong` | `rgba(255,255,255,0.14)` | Card outer border | + +### 2.2 Text + +| Token | Hex | WCAG on `surface/card-base` | +|---|---|---| +| `text/primary` | `#F5F7FA` | 17.4:1 (AAA) | +| `text/secondary` | `#A8B0BF` | 8.1:1 (AAA) | +| `text/tertiary` | `#6C7588` | 4.7:1 (AA) | +| `text/inverse` | `#07080C` | For pill backgrounds | +| `text/accent` | `#7C8CFF` | Links, values (Indigo 400 for AA on glass) | + +### 2.3 Status + +| Token | Hex | Use | +|---|---|---| +| `status/settled/fg` | `#34D399` | Pill text + icon, settled | +| `status/settled/bg` | `rgba(52,211,153,0.14)` | Pill background, settled | +| `status/pending/fg` | `#FBBF24` | Pill text + icon, pending | +| `status/pending/bg` | `rgba(251,191,36,0.14)` | Pill background, pending | +| `status/failed/fg` | `#FB7185` | Pill text + icon, failed | +| `status/failed/bg` | `rgba(251,113,133,0.14)` | Pill background, failed | + +### 2.4 Accent + +| Token | Hex | Use | +|---|---|---| +| `accent/indigo-500` | `#5B6CFF` | Primary CTA, focus ring | +| `accent/indigo-400` | `#7C8CFF` | On glass for AA contrast | +| `accent/indigo-glow` | `rgba(91,108,255,0.32)` | Top-left highlight in card | + +--- + +## 3. Typography + +System-first stack with Stellar-flavoured fallbacks. + +``` +font-family: 'Inter', 'Söhne', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; +font-family-mono: 'JetBrains Mono', 'Berkeley Mono', 'Söhne Mono', ui-monospace, SFMono-Regular, Menlo, monospace; +``` + +| Token | Family | Size / Line | Weight | Letter-spacing | +|---|---|---|---|---| +| `text/amount` | Sans | 32 / 40 | 600 | -0.02em | +| `text/amount-currency` | Sans | 18 / 24 | 500 | -0.01em | +| `text/label` | Sans | 11 / 16 | 600 uppercase | 0.08em | +| `text/value` | Sans | 13 / 20 | 500 | 0 | +| `text/value-mono` | Mono | 13 / 20 | 500 | 0 | +| `text/hash` | Mono | 12 / 18 | 500 | 0 | +| `text/pill` | Sans | 11 / 14 | 600 | 0.02em | + +Tabular numbers (`font-variant-numeric: tabular-nums`) on every numeric field so widths stay stable as values change. + +--- + +## 4. Spacing & layout + +4pt base grid. All padding/gaps are multiples of 4. + +| Token | px | +|---|---| +| `space/1` | 4 | +| `space/2` | 8 | +| `space/3` | 12 | +| `space/4` | 16 | +| `space/5` | 20 | +| `space/6` | 24 | +| `space/8` | 32 | +| `space/10` | 40 | +| `space/12` | 48 | + +### Card layout + +- Width: min 320, fluid up to 480, capped at 480. +- Padding: `space/6` (24) on all sides, `space/5` (20) inside header. +- Gap between rows: `space/4` (16). +- Border-radius: 20px outer, 12px inner pills, 8px buttons. + +--- + +## 5. Components + +### 5.1 Card shell + +``` +┌──────────────────────────────────────┐ ← glass layer + hairline border +│ [Top-left glow] │ +│ ┌─── Header ──────────────────────┐ │ +│ │ [Icon] x402 Payment [ⓘ] │ │ +│ │ Settled · 3s ago │ │ +│ └──────────────────────────────────┘ │ +│ ┌─── Amount ──────────────────────┐ │ +│ │ 12.500000 USDC │ │ +│ │ ≈ $12.50 USD │ │ +│ └──────────────────────────────────┘ │ +│ ┌─── Metadata grid (2×N) ─────────┐ │ +│ │ Tx Hash 0xab12…cd34 [copy] │ │ +│ │ Network Stellar Testnet │ │ +│ │ IPFS CID bafy…xyz12 [open] │ │ +│ │ Memo order #4821 │ │ +│ └──────────────────────────────────┘ │ +│ ┌─── Actions ─────────────────────┐ │ +│ │ [View on Stellar Expert] [Share]│ │ +│ └──────────────────────────────────┘ │ +└──────────────────────────────────────┘ +``` + +### 5.2 Status pill + +Inline-flex pill, height 22, padding 0 8, radius 999. + +- 8px circle dot (currentColor) at left +- Label text (e.g. "Settled", "Pending…", "Failed") +- Optional trailing icon for Pending (animated 1.2s rotation, respects `prefers-reduced-motion`) + +### 5.3 Metadata row + +``` +LABEL VALUE [trailing action] +``` + +- Label: uppercase, `text/label` style +- Value: `text/value-mono` for hashes, `text/value` otherwise +- Truncate middle for hashes/CIDs > 16 chars: `0xab12…cd34` +- Trailing action: 28px ghost button, `aria-label` describes full target + +### 5.4 Copy / open buttons + +- 28×28 ghost icon button +- 1.5px stroke icon, 12px hit area inside +- On click: shows 1.5s "Copied" toast (Position bottom-center) +- `aria-live="polite"` on toast region + +--- + +## 6. Motion + +| Token | Value | Easing | +|---|---|---| +| `motion/state-change` | 220ms | cubic-bezier(0.2, 0.8, 0.2, 1) | +| `motion/pill-spin` | 1.2s linear loop | — | +| `motion/hover-lift` | 150ms | cubic-bezier(0.4, 0, 0.2, 1) | + +All motion wrapped in `@media (prefers-reduced-motion: no-preference)` and disabled otherwise. + +Hover on the card lifts by 2px (`transform: translateY(-2px)`) and brightens the hairline border from 0.08 → 0.14 alpha. + +--- + +## 7. Responsive + +| Breakpoint | Behavior | +|---|---| +| `< 480px` | Single column, full-width card with 16px page padding, actions stack vertically | +| `≥ 480px` | Card centered, max-width 480px, actions in a row | +| `≥ 768px` | Onboarding-style two-column optional (hero + card preview) — see `prototype/index.html` | + +Tap targets ≥ 44×44 on mobile. Spacing scales down 8% at `<360px`. + +--- + +## 8. Accessibility + +- All text meets WCAG AA on its actual background. Primary text is AAA. +- Visible focus ring: 2px `accent/indigo-500` with 2px offset on every interactive element. +- Status conveyed by **both** color and text + icon (never color alone). +- Screen reader: status pill reads "Payment settled" / "Payment pending" / "Payment failed" — full sentence. +- Hash truncation: full value exposed via `title` attr and copy button. +- `prefers-reduced-motion`: disables state-change + hover-lift + pill-spin. +- Keyboard: tab order is `header info → amount (focusable select-to-copy) → each metadata row action → primary CTA → share → toast dismiss`. +- High-contrast mode: hairline border switches to 2px solid `text/primary`. + +--- + +## 9. Empty / loading / error states + +| State | Behavior | +|---|---| +| Loading | Skeleton card — animated shimmer on the amount + 4 metadata rows | +| Empty | Card replaced with "No recent payments" centered empty state | +| Error | Top banner above card: "Couldn't fetch latest receipt. Retry" | + +--- + +## 10. Implementation notes + +- Pure HTML/CSS/JS in `prototype/index.html`. No framework dependency. ~6KB minified. +- All copy text uses semantic markup — no images of text. +- Tokens available as CSS custom properties (`tokens/tokens.css`) and JSON (`tokens/tokens.json`). +- `prefers-color-scheme: light` is intentionally not supported in v1 — the Nirium SDK is dark-only by design. diff --git a/design/receipt-card/components/figma-component-map.json b/design/receipt-card/components/figma-component-map.json new file mode 100644 index 0000000..bbdeffc --- /dev/null +++ b/design/receipt-card/components/figma-component-map.json @@ -0,0 +1,318 @@ +{ + "name": "x402 Receipt Card", + "version": "1.0.0", + "figma_version": "Styles API v2", + "components": [ + { + "id": "receipt-card", + "name": "Receipt Card / Container", + "description": "Outer card shell with glass surface, hairline border, and top-left indigo glow.", + "type": "frame", + "properties": { + "layoutMode": "VERTICAL", + "paddingTop": 24, + "paddingBottom": 24, + "paddingLeft": 24, + "paddingRight": 24, + "itemSpacing": 16, + "primaryAxisSizingMode": "AUTO", + "counterAxisSizingMode": "FIXED", + "width": 432, + "cornerRadius": 20, + "fillStyle": "card-glass", + "strokeStyle": "hairline", + "strokeWeight": 1, + "effectStyle": "card-rest" + }, + "children": ["receipt-card/header", "receipt-card/amount", "receipt-card/metadata", "receipt-card/actions"] + }, + { + "id": "receipt-card/header", + "name": "Receipt Card / Header", + "type": "frame", + "properties": { + "layoutMode": "HORIZONTAL", + "primaryAxisAlignItems": "SPACE_BETWEEN", + "counterAxisAlignItems": "CENTER", + "itemSpacing": 12 + }, + "children": ["receipt-card/header/title-block", "receipt-card/status-pill"] + }, + { + "id": "receipt-card/header/title-block", + "name": "Header / Title Block", + "type": "frame", + "properties": { + "layoutMode": "HORIZONTAL", + "counterAxisAlignItems": "CENTER", + "itemSpacing": 10 + }, + "children": ["receipt-card/header/icon", "receipt-card/header/title-text"] + }, + { + "id": "receipt-card/header/icon", + "name": "Header / Icon", + "type": "ellipse", + "properties": { + "width": 28, + "height": 28, + "fillStyle": "accent-indigo-glow", + "strokeStyle": "hairline", + "strokeWeight": 1 + } + }, + { + "id": "receipt-card/header/title-text", + "name": "Header / Title Text", + "type": "text", + "properties": { + "characters": "x402 Payment", + "textStyle": "value", + "fontWeight": 600 + } + }, + { + "id": "receipt-card/status-pill", + "name": "Status Pill", + "type": "frame", + "properties": { + "layoutMode": "HORIZONTAL", + "counterAxisAlignItems": "CENTER", + "itemSpacing": 6, + "paddingLeft": 8, + "paddingRight": 10, + "paddingTop": 4, + "paddingBottom": 4, + "cornerRadius": 999, + "fillStyle": "status-{status}-bg" + }, + "children": ["status-pill/dot", "status-pill/label"], + "variants": { + "status": ["settled", "pending", "failed"] + } + }, + { + "id": "status-pill/dot", + "name": "Status Pill / Dot", + "type": "ellipse", + "properties": { + "width": 6, + "height": 6, + "fillStyle": "status-{status}-fg" + } + }, + { + "id": "status-pill/label", + "name": "Status Pill / Label", + "type": "text", + "properties": { + "textStyle": "pill", + "fillStyle": "status-{status}-fg" + }, + "variants": { + "status": ["Settled", "Pending…", "Failed"] + } + }, + { + "id": "receipt-card/amount", + "name": "Receipt Card / Amount", + "type": "frame", + "properties": { + "layoutMode": "VERTICAL", + "itemSpacing": 4 + }, + "children": ["receipt-card/amount/primary", "receipt-card/amount/secondary"] + }, + { + "id": "receipt-card/amount/primary", + "name": "Amount / Primary", + "type": "text", + "properties": { + "textStyle": "amount", + "fillStyle": "text-primary", + "characters": "12.500000 USDC", + "fontFeatures": "'tnum' 1, 'cv11' 1" + } + }, + { + "id": "receipt-card/amount/secondary", + "name": "Amount / Secondary", + "type": "text", + "properties": { + "textStyle": "amount-currency", + "fillStyle": "text-tertiary", + "characters": "≈ $12.50 USD" + } + }, + { + "id": "receipt-card/metadata", + "name": "Receipt Card / Metadata", + "type": "frame", + "properties": { + "layoutMode": "VERTICAL", + "itemSpacing": 10, + "paddingTop": 16, + "paddingBottom": 16, + "paddingLeft": 16, + "paddingRight": 16, + "fillStyle": "card-glass-strong", + "cornerRadius": 12, + "strokeStyle": "hairline", + "strokeWeight": 1 + }, + "children": ["metadata-row/tx-hash", "metadata-row/network", "metadata-row/ipfs-cid", "metadata-row/memo"] + }, + { + "id": "metadata-row/tx-hash", + "name": "Metadata Row / Tx Hash", + "type": "frame", + "properties": { + "layoutMode": "HORIZONTAL", + "primaryAxisAlignItems": "SPACE_BETWEEN", + "counterAxisAlignItems": "CENTER", + "itemSpacing": 8 + }, + "children": ["metadata-row/label", "metadata-row/value-with-action"] + }, + { + "id": "metadata-row/label", + "name": "Metadata Row / Label", + "type": "text", + "properties": { + "textStyle": "label", + "fillStyle": "text-tertiary" + }, + "instances": [ + { "text": "Tx Hash" }, + { "text": "Network" }, + { "text": "IPFS CID" }, + { "text": "Memo" } + ] + }, + { + "id": "metadata-row/value-with-action", + "name": "Metadata Row / Value + Action", + "type": "frame", + "properties": { + "layoutMode": "HORIZONTAL", + "counterAxisAlignItems": "CENTER", + "itemSpacing": 6 + }, + "children": ["metadata-row/value", "icon-button/copy-or-open"] + }, + { + "id": "metadata-row/value", + "name": "Metadata Row / Value", + "type": "text", + "properties": { + "textStyle": "value-mono", + "fillStyle": "text-primary" + }, + "instances": [ + { "text": "0xab12…cd34", "truncation": "middle" }, + { "text": "Stellar Testnet", "mono": false }, + { "text": "bafy…xyz12", "truncation": "middle" }, + { "text": "order #4821", "mono": false } + ] + }, + { + "id": "icon-button/copy-or-open", + "name": "Icon Button / Copy or Open", + "type": "frame", + "properties": { + "width": 28, + "height": 28, + "cornerRadius": 8, + "fillStyle": "card-glass", + "strokeStyle": "hairline", + "strokeWeight": 1 + } + }, + { + "id": "receipt-card/actions", + "name": "Receipt Card / Actions", + "type": "frame", + "properties": { + "layoutMode": "HORIZONTAL", + "itemSpacing": 8 + }, + "children": ["button/primary", "button/secondary"] + }, + { + "id": "button/primary", + "name": "Button / Primary", + "type": "frame", + "properties": { + "layoutMode": "HORIZONTAL", + "primaryAxisAlignItems": "CENTER", + "counterAxisAlignItems": "CENTER", + "paddingTop": 10, + "paddingBottom": 10, + "paddingLeft": 16, + "paddingRight": 16, + "cornerRadius": 10, + "fillStyle": "accent-indigo-500" + }, + "children": ["button/primary/label"] + }, + { + "id": "button/primary/label", + "name": "Button / Primary / Label", + "type": "text", + "properties": { + "textStyle": "value", + "fillStyle": "text-inverse", + "fontWeight": 600, + "characters": "View on Stellar Expert" + } + }, + { + "id": "button/secondary", + "name": "Button / Secondary", + "type": "frame", + "properties": { + "layoutMode": "HORIZONTAL", + "primaryAxisAlignItems": "CENTER", + "counterAxisAlignItems": "CENTER", + "paddingTop": 10, + "paddingBottom": 10, + "paddingLeft": 16, + "paddingRight": 16, + "cornerRadius": 10, + "fillStyle": "card-glass", + "strokeStyle": "hairline", + "strokeWeight": 1 + }, + "children": ["button/secondary/label"] + }, + { + "id": "button/secondary/label", + "name": "Button / Secondary / Label", + "type": "text", + "properties": { + "textStyle": "value", + "fillStyle": "text-primary", + "fontWeight": 500, + "characters": "Share" + } + } + ], + "styles": { + "color": { + "surface-canvas": "#07080C", + "surface-card-base": "#101218", + "text-primary": "#F5F7FA", + "text-secondary": "#A8B0BF", + "text-tertiary": "#6C7588", + "accent-indigo-500": "#5B6CFF", + "status-settled-fg": "#34D399", + "status-pending-fg": "#FBBF24", + "status-failed-fg": "#FB7185" + }, + "typography": { + "amount": { "fontSize": 32, "lineHeight": 40, "fontWeight": 600 }, + "value": { "fontSize": 13, "lineHeight": 20, "fontWeight": 500 }, + "label": { "fontSize": 11, "lineHeight": 16, "fontWeight": 600, "textCase": "UPPER" } + } + } +} diff --git a/design/receipt-card/mockups/failed.svg b/design/receipt-card/mockups/failed.svg new file mode 100644 index 0000000..76971ad --- /dev/null +++ b/design/receipt-card/mockups/failed.svg @@ -0,0 +1,81 @@ + + + x402 Receipt Card — Failed + + + + + + + + + + + + + + + + + + + + + + + + + + + + x402 Payment + Failed · timeout after 30s + + + + + + Failed + + + + + + + 12.500000USDC + Not settled — funds returned to source + + + + + + + TX HASH + + n/a (timed out) + + + + + NETWORK + Stellar Testnet + + + + ERROR CODE + x402_timeout_exceeded + + + + + + + Retry Payment + + + + View Error Details + + + + diff --git a/design/receipt-card/mockups/pending.svg b/design/receipt-card/mockups/pending.svg new file mode 100644 index 0000000..7102d83 --- /dev/null +++ b/design/receipt-card/mockups/pending.svg @@ -0,0 +1,103 @@ + + + x402 Receipt Card — Pending + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x402 Payment + Pending · awaiting confirmation + + + + + + + + + + Pending… + + + + + + + + 12.500000USDC + ≈ $12.50 USD + + + + + + + + + + TX HASH + + awaiting… + + + + + + + + NETWORK + Stellar Testnet + + + + IPFS CID + + queueing… + + + + + + + + + + + View on Stellar Expert + + + + Share + + + + diff --git a/design/receipt-card/mockups/rendered/failed.png b/design/receipt-card/mockups/rendered/failed.png new file mode 100644 index 0000000..efedb38 Binary files /dev/null and b/design/receipt-card/mockups/rendered/failed.png differ diff --git a/design/receipt-card/mockups/rendered/pending.png b/design/receipt-card/mockups/rendered/pending.png new file mode 100644 index 0000000..a553711 Binary files /dev/null and b/design/receipt-card/mockups/rendered/pending.png differ diff --git a/design/receipt-card/mockups/rendered/prototype-settled.png b/design/receipt-card/mockups/rendered/prototype-settled.png new file mode 100644 index 0000000..4ff3823 Binary files /dev/null and b/design/receipt-card/mockups/rendered/prototype-settled.png differ diff --git a/design/receipt-card/mockups/rendered/settled.png b/design/receipt-card/mockups/rendered/settled.png new file mode 100644 index 0000000..4cd8595 Binary files /dev/null and b/design/receipt-card/mockups/rendered/settled.png differ diff --git a/design/receipt-card/mockups/settled.svg b/design/receipt-card/mockups/settled.svg new file mode 100644 index 0000000..c6bc3f7 --- /dev/null +++ b/design/receipt-card/mockups/settled.svg @@ -0,0 +1,113 @@ + + + x402 Receipt Card — Settled + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x402 Payment + Settled · 3s ago + + + + + + Settled + + + + + + + + + 12.500000USDC + ≈ $12.50 USD + + + + + + + + + TX HASH + + 0xab12…cd34 + + + + + + + + + + + + NETWORK + Stellar Testnet + + + + + IPFS CID + + bafy…xyz12 + + + + + + + + + + + + + + View on Stellar Expert + + + + + Share + + + + diff --git a/design/receipt-card/prototype/index.html b/design/receipt-card/prototype/index.html new file mode 100644 index 0000000..252c0cf --- /dev/null +++ b/design/receipt-card/prototype/index.html @@ -0,0 +1,137 @@ + + + + + + x402 Receipt Card — Interactive Prototype + + + + + +
+ + +
+ + + +
+ +
+ +
+
+ +
+ x402 Payment + Settled · 3s ago +
+
+ + + Settled + +
+ + +
+

+ 12.500000 + USDC +

+

≈ $12.50 USD

+
+ + + + + + +
+ + +
+

Try it

+

Click the state tabs above to see how the card transforms between Settled, Pending, and Failed states. Hover the card to lift it. The whole UI is keyboard-navigable; press Tab to walk through every focusable element.

+

Designed for nirium-sdk#13 — UX Bounty ($50 USDC).

+
+
+ + +
+ + + + diff --git a/design/receipt-card/prototype/prototype.css b/design/receipt-card/prototype/prototype.css new file mode 100644 index 0000000..3ff4587 --- /dev/null +++ b/design/receipt-card/prototype/prototype.css @@ -0,0 +1,556 @@ +/* x402 Receipt Card — interactive prototype styles + Companion to ../tokens/tokens.css and SPEC.md +*/ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + -webkit-text-size-adjust: 100%; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; +} + +body { + margin: 0; + min-height: 100vh; + font-family: var(--font-sans); + font-size: 14px; + line-height: 1.5; + color: var(--text-primary); + background: + radial-gradient(1200px 800px at 0% 0%, rgba(91, 108, 255, 0.10), transparent 60%), + radial-gradient(800px 600px at 100% 100%, rgba(91, 108, 255, 0.06), transparent 60%), + var(--surface-canvas); + background-attachment: fixed; + font-variant-numeric: tabular-nums; +} + +a { + color: var(--text-accent); + text-decoration: none; +} + +a:hover, +a:focus-visible { + text-decoration: underline; +} + +button { + font-family: inherit; + color: inherit; + background: none; + border: 0; + padding: 0; + cursor: pointer; +} + +kbd { + display: inline-block; + padding: 1px 6px; + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-primary); + background: var(--surface-card-glass-strong); + border: 1px solid var(--surface-hairline); + border-radius: 4px; +} + +.page { + max-width: 720px; + margin: 0 auto; + padding: var(--space-10) var(--space-5) var(--space-12); +} + +.page-header { + margin-bottom: var(--space-8); + text-align: center; +} + +.page-header h1 { + margin: 0 0 var(--space-2); + font-size: 28px; + font-weight: 600; + letter-spacing: -0.02em; + color: var(--text-primary); +} + +.subtitle { + margin: 0; + font-size: 13px; + font-weight: 500; + color: var(--text-secondary); +} + +.state-toggle { + display: flex; + gap: var(--space-2); + padding: var(--space-1); + margin: 0 auto var(--space-6); + background: var(--surface-card-base); + border: 1px solid var(--surface-hairline); + border-radius: var(--radius-pill); + width: fit-content; +} + +.state-btn { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-4); + font-size: 12px; + font-weight: 600; + letter-spacing: 0.02em; + color: var(--text-secondary); + border-radius: var(--radius-pill); + transition: background var(--motion-state-change), color var(--motion-state-change); +} + +.state-btn:hover { + color: var(--text-primary); +} + +.state-btn.is-active { + background: var(--surface-card-glass-strong); + color: var(--text-primary); +} + +.state-btn:focus-visible { + outline: 2px solid var(--accent-indigo-500); + outline-offset: 2px; +} + +.state-dot { + display: inline-block; + width: 6px; + height: 6px; + border-radius: 999px; + background: currentColor; +} + +.state-dot--settled { color: var(--status-settled-fg); } +.state-dot--pending { color: var(--status-pending-fg); } +.state-dot--failed { color: var(--status-failed-fg); } + +/* Card */ +.receipt-card { + position: relative; + display: flex; + flex-direction: column; + gap: var(--space-4); + padding: var(--space-6); + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.02)), + var(--surface-card-base); + border: 1px solid var(--surface-hairline); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-card-rest); + overflow: hidden; + backdrop-filter: blur(20px) saturate(140%); + -webkit-backdrop-filter: blur(20px) saturate(140%); + transition: transform var(--motion-hover-lift), border-color var(--motion-state-change), box-shadow var(--motion-hover-lift); + max-width: 480px; + margin: 0 auto; +} + +.receipt-card::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: radial-gradient(600px 300px at 10% -10%, var(--accent-indigo-glow), transparent 60%); + opacity: 0.9; +} + +.receipt-card[data-state="failed"]::before { + background: radial-gradient(600px 300px at 10% -10%, rgba(251, 113, 133, 0.18), transparent 60%); +} + +@media (prefers-reduced-motion: no-preference) { + .receipt-card:hover { + transform: translateY(-2px); + border-color: var(--surface-hairline-strong); + box-shadow: var(--shadow-card-hover); + } +} + +/* Header */ +.card-header { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); +} + +.card-title-block { + display: flex; + align-items: center; + gap: var(--space-3); +} + +.card-titles { + display: flex; + flex-direction: column; + gap: 2px; +} + +.card-title { + font-size: 13px; + font-weight: 600; + color: var(--text-primary); +} + +.card-subtitle { + font-size: 11px; + font-weight: 500; + color: var(--text-tertiary); +} + +.status-pill { + display: inline-flex; + align-items: center; + gap: 6px; + height: 22px; + padding: 0 10px; + border-radius: var(--radius-pill); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.02em; + transition: background var(--motion-state-change), color var(--motion-state-change); +} + +.receipt-card[data-state="settled"] .status-pill { + background: var(--status-settled-bg); + color: var(--status-settled-fg); +} + +.receipt-card[data-state="pending"] .status-pill { + background: var(--status-pending-bg); + color: var(--status-pending-fg); +} + +.receipt-card[data-state="failed"] .status-pill { + background: var(--status-failed-bg); + color: var(--status-failed-fg); +} + +.status-dot { + width: 6px; + height: 6px; + border-radius: 999px; + background: currentColor; +} + +.receipt-card[data-state="pending"] .status-dot { + background: transparent; + border: 1.5px solid currentColor; + border-top-color: transparent; + animation: spin var(--motion-pill-spin); +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +@media (prefers-reduced-motion: reduce) { + .receipt-card[data-state="pending"] .status-dot { animation: none; } +} + +/* Amount */ +.amount { + position: relative; + display: flex; + flex-direction: column; + gap: var(--space-1); + padding-top: var(--space-2); +} + +.receipt-card[data-state="pending"] .amount { opacity: 0.78; } + +.receipt-card[data-state="failed"] .amount-primary { + text-decoration: line-through; + color: var(--text-secondary) !important; +} + +.receipt-card[data-state="failed"] .amount-currency { color: var(--text-tertiary) !important; } + +.amount-primary { + margin: 0; + font-size: 32px; + font-weight: 600; + letter-spacing: -0.02em; + color: var(--text-primary); + display: flex; + align-items: baseline; + gap: var(--space-2); +} + +.amount-currency { + font-size: 18px; + font-weight: 500; + letter-spacing: -0.01em; + color: var(--text-secondary); +} + +.amount-secondary { + margin: 0; + font-size: 13px; + font-weight: 500; + color: var(--text-tertiary); +} + +.receipt-card[data-state="failed"] .amount-secondary { + color: var(--status-failed-fg); +} + +/* Metadata */ +.metadata { + position: relative; + display: flex; + flex-direction: column; + gap: 10px; + padding: var(--space-4); + background: var(--surface-card-glass-strong); + border: 1px solid var(--surface-hairline); + border-radius: var(--radius-md); +} + +.receipt-card[data-state="failed"] .metadata { + background: rgba(251, 113, 133, 0.04); + border-color: rgba(251, 113, 133, 0.16); +} + +.metadata-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + min-height: 28px; +} + +.metadata-label { + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-tertiary); +} + +.metadata-value-wrap { + display: flex; + align-items: center; + gap: var(--space-2); + min-width: 0; +} + +.metadata-value { + font-size: 13px; + font-weight: 500; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 260px; +} + +.metadata-value--mono { + font-family: var(--font-mono); + font-size: 12px; +} + +.icon-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + color: var(--text-secondary); + background: var(--surface-card-glass); + border: 1px solid var(--surface-hairline); + border-radius: var(--radius-sm); + text-decoration: none; + transition: color var(--motion-state-change), border-color var(--motion-state-change), background var(--motion-state-change); + flex-shrink: 0; +} + +.icon-btn:hover, +.icon-btn:focus-visible { + color: var(--text-primary); + border-color: var(--surface-hairline-strong); + background: var(--surface-card-glass-strong); + text-decoration: none; + outline: none; +} + +.icon-btn:focus-visible { + box-shadow: 0 0 0 2px var(--accent-indigo-500); +} + +.icon-btn--link { + text-decoration: none; +} + +/* Actions */ +.actions { + position: relative; + display: flex; + gap: var(--space-2); +} + +.receipt-card[data-state="pending"] .actions { opacity: 0.55; } + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + flex: 1; + height: 44px; + padding: 0 var(--space-4); + font-size: 13px; + font-weight: 600; + border-radius: 10px; + border: 1px solid transparent; + cursor: pointer; + transition: transform var(--motion-hover-lift), background var(--motion-state-change), border-color var(--motion-state-change); + text-decoration: none; +} + +.btn:focus-visible { + outline: 2px solid var(--accent-indigo-500); + outline-offset: 2px; +} + +.btn-primary { + background: var(--accent-indigo-500); + color: var(--text-inverse); +} + +.receipt-card[data-state="failed"] .btn-primary { + background: var(--status-failed-fg); + color: var(--text-inverse); +} + +.btn-primary:hover { + transform: translateY(-1px); +} + +.btn-secondary { + background: var(--surface-card-glass); + color: var(--text-primary); + border-color: var(--surface-hairline); + flex: 0 0 128px; +} + +.btn-secondary:hover { + background: var(--surface-card-glass-strong); + border-color: var(--surface-hairline-strong); +} + +.receipt-card[data-state="failed"] .actions .btn-primary { + flex: 0 0 180px; +} + +.receipt-card[data-state="failed"] .actions .btn-secondary { + flex: 1; +} + +/* Demo info */ +.demo-info { + margin: var(--space-8) auto 0; + max-width: 480px; + text-align: center; + color: var(--text-secondary); +} + +.demo-info h2 { + margin: 0 0 var(--space-2); + font-size: 14px; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--text-tertiary); +} + +.demo-info p { + margin: 0 0 var(--space-3); + font-size: 13px; + line-height: 1.6; +} + +.design-credit { + margin-top: var(--space-6) !important; + font-size: 12px !important; + color: var(--text-tertiary) !important; +} + +/* Toast */ +.toast-region { + position: fixed; + bottom: var(--space-6); + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-2); + pointer-events: none; + z-index: 100; +} + +.toast { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-4); + font-size: 12px; + font-weight: 500; + color: var(--text-primary); + background: var(--surface-card-base); + border: 1px solid var(--surface-hairline-strong); + border-radius: var(--radius-pill); + box-shadow: var(--shadow-card-rest); + pointer-events: auto; + animation: toast-in 200ms cubic-bezier(0.2, 0.8, 0.2, 1); +} + +@keyframes toast-in { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + +@media (prefers-reduced-motion: reduce) { + .toast { animation: none; } +} + +/* Mobile */ +@media (max-width: 480px) { + .page { + padding: var(--space-6) var(--space-4) var(--space-10); + } + .page-header h1 { + font-size: 22px; + } + .state-toggle { + width: 100%; + justify-content: stretch; + } + .state-btn { + flex: 1; + justify-content: center; + } + .receipt-card { + padding: var(--space-5); + } + .actions { + flex-direction: column; + } + .btn-secondary { + flex: 1 !important; + } +} diff --git a/design/receipt-card/prototype/prototype.js b/design/receipt-card/prototype/prototype.js new file mode 100644 index 0000000..9c95d98 --- /dev/null +++ b/design/receipt-card/prototype/prototype.js @@ -0,0 +1,158 @@ +// x402 Receipt Card — interactive prototype controller +// Wires up the state toggle, copy buttons, and toast. + +(function () { + "use strict"; + + const card = document.querySelector(".receipt-card"); + const buttons = document.querySelectorAll(".state-btn"); + const toastRegion = document.getElementById("toast"); + + if (!card || !buttons.length || !toastRegion) return; + + const stateConfig = { + settled: { + subtitle: "Settled · 3s ago", + pillLabel: "Settled", + amountValue: "12.500000", + amountCurrency: "USDC", + amountSecondary: "≈ $12.50 USD", + txHash: "0xab12…cd34", + ipfsLabel: "IPFS CID", + ipfsValue: "bafy…xyz12", + ipfsHref: "https://ipfs.io/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + }, + pending: { + subtitle: "Pending · awaiting confirmation", + pillLabel: "Pending…", + amountValue: "12.500000", + amountCurrency: "USDC", + amountSecondary: "≈ $12.50 USD", + txHash: "awaiting…", + ipfsLabel: "IPFS CID", + ipfsValue: "queueing…", + ipfsHref: "#" + }, + failed: { + subtitle: "Failed · timeout after 30s", + pillLabel: "Failed", + amountValue: "12.500000", + amountCurrency: "USDC", + amountSecondary: "Not settled — funds returned to source", + txHash: "n/a (timed out)", + ipfsLabel: "Error Code", + ipfsValue: "x402_timeout_exceeded", + ipfsHref: "#" + } + }; + + function setState(state) { + if (!stateConfig[state]) return; + const cfg = stateConfig[state]; + + // Update card root + card.setAttribute("data-state", state); + + // Update bindings + const bindings = { + "subtitle": cfg.subtitle, + "pill-label": cfg.pillLabel, + "amount-value": cfg.amountValue, + "amount-currency": cfg.amountCurrency, + "amount-secondary": cfg.amountSecondary, + "tx-hash": cfg.txHash, + "ipfs-label": cfg.ipfsLabel, + "ipfs-value": cfg.ipfsValue + }; + Object.keys(bindings).forEach(function (key) { + const el = card.querySelector('[data-bind="' + key + '"]'); + if (el) el.textContent = bindings[key]; + }); + + // IPFS link + const ipfsLink = card.querySelector('[data-bind="ipfs-link"]'); + if (ipfsLink) ipfsLink.setAttribute("href", cfg.ipfsHref); + + // Update tab buttons + buttons.forEach(function (btn) { + const isActive = btn.getAttribute("data-state") === state; + btn.classList.toggle("is-active", isActive); + btn.setAttribute("aria-selected", String(isActive)); + }); + } + + buttons.forEach(function (btn) { + btn.addEventListener("click", function () { + const state = btn.getAttribute("data-state"); + setState(state); + }); + + btn.addEventListener("keydown", function (e) { + if (e.key === "ArrowRight" || e.key === "ArrowLeft") { + e.preventDefault(); + const states = ["settled", "pending", "failed"]; + const currentIndex = states.indexOf(btn.getAttribute("data-state")); + const nextIndex = e.key === "ArrowRight" + ? (currentIndex + 1) % states.length + : (currentIndex - 1 + states.length) % states.length; + const nextBtn = document.querySelector('.state-btn[data-state="' + states[nextIndex] + '"]'); + if (nextBtn) { + nextBtn.click(); + nextBtn.focus(); + } + } + }); + }); + + // Copy buttons + function showToast(message) { + const toast = document.createElement("div"); + toast.className = "toast"; + toast.setAttribute("role", "status"); + toast.textContent = message; + toastRegion.appendChild(toast); + setTimeout(function () { + toast.style.transition = "opacity 200ms ease"; + toast.style.opacity = "0"; + setTimeout(function () { toast.remove(); }, 200); + }, 1500); + } + + function copyToClipboard(text) { + if (navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(text); + } + // Fallback + const ta = document.createElement("textarea"); + ta.value = text; + ta.style.position = "fixed"; + ta.style.opacity = "0"; + document.body.appendChild(ta); + ta.select(); + try { + document.execCommand("copy"); + } finally { + document.body.removeChild(ta); + } + return Promise.resolve(); + } + + document.addEventListener("click", function (e) { + const target = e.target.closest("[data-copy]"); + if (target) { + e.preventDefault(); + const text = target.getAttribute("data-copy"); + copyToClipboard(text).then(function () { + showToast("Copied to clipboard"); + }); + } + }); + + // Keyboard: card hover state via focus + card.addEventListener("focusin", function () { + card.style.borderColor = "var(--surface-hairline-strong)"; + }); + card.addEventListener("focusout", function () { + card.style.borderColor = ""; + }); +})(); diff --git a/design/receipt-card/tokens/tokens.css b/design/receipt-card/tokens/tokens.css new file mode 100644 index 0000000..3055e32 --- /dev/null +++ b/design/receipt-card/tokens/tokens.css @@ -0,0 +1,70 @@ +/* x402 Receipt Card — design tokens (CSS custom properties) + Companion to tokens/tokens.json. Direct CSS users can skip the JSON. +*/ +:root { + /* Surface */ + --surface-canvas: #07080C; + --surface-card-base: #101218; + --surface-card-glass: rgba(255, 255, 255, 0.04); + --surface-card-glass-strong: rgba(255, 255, 255, 0.07); + --surface-hairline: rgba(255, 255, 255, 0.08); + --surface-hairline-strong: rgba(255, 255, 255, 0.14); + + /* Text */ + --text-primary: #F5F7FA; + --text-secondary: #A8B0BF; + --text-tertiary: #6C7588; + --text-inverse: #07080C; + --text-accent: #7C8CFF; + + /* Status */ + --status-settled-fg: #34D399; + --status-settled-bg: rgba(52, 211, 153, 0.14); + --status-pending-fg: #FBBF24; + --status-pending-bg: rgba(251, 191, 36, 0.14); + --status-failed-fg: #FB7185; + --status-failed-bg: rgba(251, 113, 133, 0.14); + + /* Accent */ + --accent-indigo-500: #5B6CFF; + --accent-indigo-400: #7C8CFF; + --accent-indigo-glow: rgba(91, 108, 255, 0.32); + + /* Radius */ + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 20px; + --radius-pill: 999px; + + /* Space (4pt base) */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-8: 32px; + --space-10: 40px; + --space-12: 48px; + + /* Font */ + --font-sans: 'Inter', 'Söhne', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + --font-mono: 'JetBrains Mono', 'Berkeley Mono', 'Söhne Mono', ui-monospace, SFMono-Regular, Menlo, monospace; + + /* Motion */ + --motion-state-change: 220ms cubic-bezier(0.2, 0.8, 0.2, 1); + --motion-hover-lift: 150ms cubic-bezier(0.4, 0, 0.2, 1); + --motion-pill-spin: 1.2s linear infinite; + + /* Shadow */ + --shadow-card-rest: 0 1px 0 rgba(255,255,255,0.04) inset, 0 24px 60px rgba(0,0,0,0.5); + --shadow-card-hover: 0 1px 0 rgba(255,255,255,0.06) inset, 0 32px 80px rgba(0,0,0,0.6); +} + +@media (prefers-reduced-motion: reduce) { + :root { + --motion-state-change: 0ms; + --motion-hover-lift: 0ms; + --motion-pill-spin: 0s; + } +} diff --git a/design/receipt-card/tokens/tokens.json b/design/receipt-card/tokens/tokens.json new file mode 100644 index 0000000..ca5919f --- /dev/null +++ b/design/receipt-card/tokens/tokens.json @@ -0,0 +1,77 @@ +{ + "global": { + "surface": { + "canvas": { "value": "#07080C", "type": "color" }, + "card-base": { "value": "#101218", "type": "color" }, + "card-glass": { "value": "rgba(255,255,255,0.04)", "type": "color" }, + "card-glass-strong": { "value": "rgba(255,255,255,0.07)", "type": "color" }, + "hairline": { "value": "rgba(255,255,255,0.08)", "type": "color" }, + "hairline-strong": { "value": "rgba(255,255,255,0.14)", "type": "color" } + }, + "text": { + "primary": { "value": "#F5F7FA", "type": "color" }, + "secondary": { "value": "#A8B0BF", "type": "color" }, + "tertiary": { "value": "#6C7588", "type": "color" }, + "inverse": { "value": "#07080C", "type": "color" }, + "accent": { "value": "#7C8CFF", "type": "color" } + }, + "status": { + "settled-fg": { "value": "#34D399", "type": "color" }, + "settled-bg": { "value": "rgba(52,211,153,0.14)", "type": "color" }, + "pending-fg": { "value": "#FBBF24", "type": "color" }, + "pending-bg": { "value": "rgba(251,191,36,0.14)", "type": "color" }, + "failed-fg": { "value": "#FB7185", "type": "color" }, + "failed-bg": { "value": "rgba(251,113,133,0.14)", "type": "color" } + }, + "accent": { + "indigo-500": { "value": "#5B6CFF", "type": "color" }, + "indigo-400": { "value": "#7C8CFF", "type": "color" }, + "indigo-glow": { "value": "rgba(91,108,255,0.32)", "type": "color" } + }, + "radius": { + "sm": { "value": "8px", "type": "dimension" }, + "md": { "value": "12px", "type": "dimension" }, + "lg": { "value": "20px", "type": "dimension" }, + "pill": { "value": "999px", "type": "dimension" } + }, + "space": { + "1": { "value": "4px", "type": "dimension" }, + "2": { "value": "8px", "type": "dimension" }, + "3": { "value": "12px", "type": "dimension" }, + "4": { "value": "16px", "type": "dimension" }, + "5": { "value": "20px", "type": "dimension" }, + "6": { "value": "24px", "type": "dimension" }, + "8": { "value": "32px", "type": "dimension" }, + "10": { "value": "40px", "type": "dimension" }, + "12": { "value": "48px", "type": "dimension" } + }, + "font": { + "family-sans": { + "value": "'Inter', 'Söhne', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif", + "type": "fontFamily" + }, + "family-mono": { + "value": "'JetBrains Mono', 'Berkeley Mono', 'Söhne Mono', ui-monospace, SFMono-Regular, Menlo, monospace", + "type": "fontFamily" + } + }, + "typography": { + "amount": { "value": { "fontSize": "32px", "lineHeight": "40px", "fontWeight": 600, "letterSpacing": "-0.02em" }, "type": "typography" }, + "amount-currency": { "value": { "fontSize": "18px", "lineHeight": "24px", "fontWeight": 500, "letterSpacing": "-0.01em" }, "type": "typography" }, + "label": { "value": { "fontSize": "11px", "lineHeight": "16px", "fontWeight": 600, "letterSpacing": "0.08em", "textCase": "uppercase" }, "type": "typography" }, + "value": { "value": { "fontSize": "13px", "lineHeight": "20px", "fontWeight": 500 }, "type": "typography" }, + "value-mono": { "value": { "fontSize": "13px", "lineHeight": "20px", "fontWeight": 500 }, "type": "typography" }, + "hash": { "value": { "fontSize": "12px", "lineHeight": "18px", "fontWeight": 500 }, "type": "typography" }, + "pill": { "value": { "fontSize": "11px", "lineHeight": "14px", "fontWeight": 600, "letterSpacing": "0.02em" }, "type": "typography" } + }, + "motion": { + "state-change": { "value": "220ms cubic-bezier(0.2, 0.8, 0.2, 1)", "type": "transition" }, + "hover-lift": { "value": "150ms cubic-bezier(0.4, 0, 0.2, 1)", "type": "transition" }, + "pill-spin": { "value": "1.2s linear infinite", "type": "transition" } + }, + "shadow": { + "card-rest": { "value": "0 1px 0 rgba(255,255,255,0.04) inset, 0 24px 60px rgba(0,0,0,0.5)", "type": "boxShadow" }, + "card-hover": { "value": "0 1px 0 rgba(255,255,255,0.06) inset, 0 32px 80px rgba(0,0,0,0.6)", "type": "boxShadow" } + } + } +}