From 9eb2639e345f38426607cdb9fcef2d64ccc465b3 Mon Sep 17 00:00:00 2001 From: Tanapol Hongsuwan Date: Mon, 16 Feb 2026 11:53:28 +0900 Subject: [PATCH 1/5] chore(frontend): enable Tailwind directives in Biome --- biome.json | 5 +++++ packages/frontend/app/hooks/useActiveWallet.ts | 6 +----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/biome.json b/biome.json index da481c4..f572cef 100644 --- a/biome.json +++ b/biome.json @@ -14,6 +14,11 @@ "indentStyle": "space", "indentWidth": 2 }, + "css": { + "parser": { + "tailwindDirectives": true + } + }, "linter": { "enabled": true, "rules": { diff --git a/packages/frontend/app/hooks/useActiveWallet.ts b/packages/frontend/app/hooks/useActiveWallet.ts index 6d1fac7..3a3f804 100644 --- a/packages/frontend/app/hooks/useActiveWallet.ts +++ b/packages/frontend/app/hooks/useActiveWallet.ts @@ -32,11 +32,7 @@ interface ActiveWalletResult { * Selects Smart Account for embedded wallets, EOA otherwise */ export function useActiveWallet(): ActiveWalletResult { - const { - walletClient, - connectedWallet, - address: eoaAddress, - } = useWallet(); + const { walletClient, connectedWallet, address: eoaAddress } = useWallet(); const { smartAccountClient, smartAccountAddress, From d4779289f19213b4ec2193ec8dd7db651e9527e0 Mon Sep 17 00:00:00 2001 From: Tanapol Hongsuwan Date: Mon, 16 Feb 2026 11:54:37 +0900 Subject: [PATCH 2/5] feat(frontend): map FoR semantic tokens to shadcn theme --- packages/frontend/app/app.css | 194 +++++++++++++++++++++++++++++++-- packages/frontend/app/root.tsx | 2 +- 2 files changed, 187 insertions(+), 9 deletions(-) diff --git a/packages/frontend/app/app.css b/packages/frontend/app/app.css index 99345d8..dcd3201 100644 --- a/packages/frontend/app/app.css +++ b/packages/frontend/app/app.css @@ -1,15 +1,193 @@ @import "tailwindcss"; -@theme { - --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; -} +:root { + color-scheme: light; + + /* Semantic Color: Text */ + --for-text-primary: #161c18; + --for-text-secondary: #4f5a54; + --for-text-tertiary: #6d7872; + --for-text-inverse: #f7faf8; + + /* Semantic Color: Background */ + --for-bg-canvas: #f3f5f1; + --for-bg-surface: #ffffff; + --for-bg-elevated: #edf1eb; + --for-bg-inverse: #101513; + + /* Semantic Color: Stroke */ + --for-stroke-subtle: #d5ddd7; + --for-stroke-default: #b4c0b8; + --for-stroke-strong: #8a968f; + --for-stroke-focus: #f44250; -html, -body { - @apply bg-white dark:bg-gray-950; + /* Semantic Color: Button */ + --for-button-primary-bg: #121212; + --for-button-primary-fg: #ffffff; + --for-button-secondary-bg: #e7ece8; + --for-button-secondary-fg: #161c18; + --for-button-destructive-bg: #b42335; + --for-button-destructive-fg: #ffffff; + --for-button-disabled-bg: #d3d9d4; + --for-button-disabled-fg: #8c9790; - @media (prefers-color-scheme: dark) { + /* Semantic Color: Visual Accent */ + --for-accent-brand: #f44250; + --for-accent-success: #2f9e60; + --for-accent-warning: #f59e0b; + --for-accent-danger: #d92d20; + + /* Semantic spacing tokens (source of truth) */ + --for-space-0: 0px; + --for-space-2: 2px; + --for-space-4: 4px; + --for-space-6: 6px; + --for-space-8: 8px; + --for-space-12: 12px; + --for-space-16: 16px; + --for-space-20: 20px; + --for-space-24: 24px; + --for-space-28: 28px; + --for-space-32: 32px; + --for-space-40: 40px; + + /* shadcn/ui compatible tokens mapped from FoR semantics */ + --background: var(--for-bg-canvas); + --foreground: var(--for-text-primary); + --card: var(--for-bg-surface); + --card-foreground: var(--for-text-primary); + --popover: var(--for-bg-elevated); + --popover-foreground: var(--for-text-primary); + --primary: var(--for-button-primary-bg); + --primary-foreground: var(--for-button-primary-fg); + --secondary: var(--for-button-secondary-bg); + --secondary-foreground: var(--for-button-secondary-fg); + --muted: var(--for-bg-elevated); + --muted-foreground: var(--for-text-secondary); + --accent: var(--for-accent-brand); + --accent-foreground: var(--for-text-inverse); + --destructive: var(--for-button-destructive-bg); + --destructive-foreground: var(--for-button-destructive-fg); + --border: var(--for-stroke-default); + --input: var(--for-stroke-subtle); + --ring: var(--for-stroke-focus); + --radius: 12px; +} + +@media (prefers-color-scheme: dark) { + :root { color-scheme: dark; + + --for-text-primary: #ecf2ed; + --for-text-secondary: #b7c2ba; + --for-text-tertiary: #8d9b92; + --for-text-inverse: #121714; + + --for-bg-canvas: #101512; + --for-bg-surface: #171d19; + --for-bg-elevated: #1d241f; + --for-bg-inverse: #f3f6f4; + + --for-stroke-subtle: #28312b; + --for-stroke-default: #36433b; + --for-stroke-strong: #6a776f; + --for-stroke-focus: #ff6f7a; + + --for-button-primary-bg: #f3f6f4; + --for-button-primary-fg: #101512; + --for-button-secondary-bg: #2b342e; + --for-button-secondary-fg: #ecf2ed; + --for-button-destructive-bg: #e35b6b; + --for-button-destructive-fg: #101512; + --for-button-disabled-bg: #29322c; + --for-button-disabled-fg: #657168; + + --for-accent-brand: #ff7681; + --for-accent-success: #48c07f; + --for-accent-warning: #f6b344; + --for-accent-danger: #f37a72; + } +} + +@theme inline { + /* Typography */ + --font-ui: + "Zen Kaku Gothic New", "Roboto", ui-sans-serif, system-ui, sans-serif; + --font-latin: "Roboto", ui-sans-serif, system-ui, sans-serif; + --font-sans: var(--font-ui); + + --text-ui-10: 10px; + --text-ui-10--line-height: 1.3; + --text-ui-12: 12px; + --text-ui-12--line-height: 1.3; + --text-ui-14: 14px; + --text-ui-14--line-height: 1.3; + --text-ui-16: 16px; + --text-ui-16--line-height: 1.3; + --text-ui-18: 18px; + --text-ui-18--line-height: 1.3; + --text-ui-20: 20px; + --text-ui-20--line-height: 1.3; + --text-ui-24: 24px; + --text-ui-24--line-height: 1.3; + --text-ui-28: 28px; + --text-ui-28--line-height: 1.3; + --text-ui-32: 32px; + --text-ui-32--line-height: 1.3; + + /* shadcn/ui color namespace */ + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-button-disabled-bg: var(--for-button-disabled-bg); + --color-button-disabled-fg: var(--for-button-disabled-fg); + --color-accent-brand: var(--for-accent-brand); + --color-accent-success: var(--for-accent-success); + --color-accent-warning: var(--for-accent-warning); + --color-accent-danger: var(--for-accent-danger); + + /* Radius */ + --radius-sm: calc(var(--radius) - 6px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + + /* + Spacing operation rule: + - Tailwind spacing utilities are px-based via --spacing: 1px. + - Use only 0,2,4,6,8,12,16,20,24,28,32,40. + - Disallow arbitrary spacing values like p-[14px], m-[3px], gap-[22px]. + */ + --spacing: 1px; +} + +@layer base { + * { + @apply border-border; + } + + html, + body { + min-height: 100%; + } + + body { + @apply bg-background text-foreground font-ui text-ui-14 antialiased; } } diff --git a/packages/frontend/app/root.tsx b/packages/frontend/app/root.tsx index 9e549ff..8a18820 100644 --- a/packages/frontend/app/root.tsx +++ b/packages/frontend/app/root.tsx @@ -20,7 +20,7 @@ export const links: Route.LinksFunction = () => [ }, { rel: "stylesheet", - href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", + href: "https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Zen+Kaku+Gothic+New:wght@400;500;700&display=swap", }, ]; From d6dabcea9a776ac594ed64c464c17b498b422a2f Mon Sep 17 00:00:00 2001 From: Tanapol Hongsuwan Date: Mon, 16 Feb 2026 11:54:44 +0900 Subject: [PATCH 3/5] feat(frontend): add themed button showcase with lucide placeholders --- .../frontend/app/components/ui/button.tsx | 57 +++++ packages/frontend/app/lib/utils.ts | 3 + packages/frontend/app/routes/home.tsx | 10 +- packages/frontend/app/welcome/welcome.tsx | 240 ++++++++++++------ packages/frontend/package.json | 1 + pnpm-lock.yaml | 3 + 6 files changed, 232 insertions(+), 82 deletions(-) create mode 100644 packages/frontend/app/components/ui/button.tsx create mode 100644 packages/frontend/app/lib/utils.ts diff --git a/packages/frontend/app/components/ui/button.tsx b/packages/frontend/app/components/ui/button.tsx new file mode 100644 index 0000000..ffa8d8b --- /dev/null +++ b/packages/frontend/app/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from "react"; + +import { cn } from "~/lib/utils"; + +const buttonVariantClasses = { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/90", + secondary: + "border-border bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "border-transparent bg-transparent text-foreground hover:bg-muted", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/90", +} as const; + +const buttonSizeClasses = { + default: "h-40 px-16", + sm: "h-32 px-12 text-ui-12", + lg: "h-40 px-20 text-ui-16", + icon: "size-40 p-0", +} as const; + +type ButtonVariant = keyof typeof buttonVariantClasses; +type ButtonSize = keyof typeof buttonSizeClasses; + +export interface ButtonProps + extends React.ButtonHTMLAttributes { + variant?: ButtonVariant; + size?: ButtonSize; +} + +export const Button = React.forwardRef( + ( + { + className, + variant = "default", + size = "default", + type = "button", + ...props + }, + ref, + ) => { + return ( + + + + + + + + +
+

Semantic Color Mapping

+

+ Reference rule: component styles use shadcn tokens such as{" "} + bg-background, text-foreground, and{" "} + border-border. Raw FoR variables remain source-only. +

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

Typography Utilities

+

+ font-ui uses Zen Kaku Gothic New + Roboto, and every{" "} + text-ui-* token enforces line-height 130%. +

+
+ {typographyRows.map(({ className, label }) => ( +

+ {label} / line-height 130%

- - + ))}
- +
+ +
+

Spacing Scale Rule

+

+ Allowed spacing values are 0, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32, and + 40px. Avoid arbitrary spacing classes such as p-[14px]. +

+
+ {spacingScale.map((space) => ( +
+
+ + {space}px + +
+ ))} +
+
); } -const resources = [ - { - href: "https://reactrouter.com/docs", - text: "React Router Docs", - icon: ( - - - - ), - }, - { - href: "https://rmx.as/discord", - text: "Join Discord", - icon: ( - - - - ), - }, +function ColorChip({ + label, + swatchClass, +}: { + label: string; + swatchClass: string; +}) { + return ( +
+ + {label} +
+ ); +} + +const typographyRows = [ + { className: "text-ui-10", label: "UI 10" }, + { className: "text-ui-12", label: "UI 12" }, + { className: "text-ui-14", label: "UI 14" }, + { className: "text-ui-16", label: "UI 16" }, + { className: "text-ui-18", label: "UI 18" }, + { className: "text-ui-20", label: "UI 20" }, + { className: "text-ui-24", label: "UI 24" }, + { className: "text-ui-28", label: "UI 28" }, + { className: "text-ui-32", label: "UI 32" }, ]; + +const spacingScale = [0, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32, 40]; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 46c0886..15a65ca 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -14,6 +14,7 @@ "@react-router/node": "^7.9.2", "@react-router/serve": "^7.9.2", "isbot": "^5.1.31", + "lucide-react": "^0.554.0", "permissionless": "^0.2.57", "react": "^19.1.1", "react-dom": "^19.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b0a687..b830b7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: isbot: specifier: ^5.1.31 version: 5.1.32 + lucide-react: + specifier: ^0.554.0 + version: 0.554.0(react@19.2.0) permissionless: specifier: ^0.2.57 version: 0.2.57(viem@2.43.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76)) From 588192731b9a2aea106a990fc8e7f276dc80c17b Mon Sep 17 00:00:00 2001 From: Tanapol Hongsuwan Date: Mon, 16 Feb 2026 11:54:55 +0900 Subject: [PATCH 4/5] docs(frontend): document FoR token usage and spacing rules --- packages/frontend/README.md | 4 ++ packages/frontend/app/design-tokens.md | 55 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 packages/frontend/app/design-tokens.md diff --git a/packages/frontend/README.md b/packages/frontend/README.md index 5c4780a..3f4e4d7 100644 --- a/packages/frontend/README.md +++ b/packages/frontend/README.md @@ -82,6 +82,10 @@ Make sure to deploy the output of `npm run build` This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer. +FoR-specific design token operation rules live at: + +- `app/design-tokens.md` + --- Built with ❤️ using React Router. diff --git a/packages/frontend/app/design-tokens.md b/packages/frontend/app/design-tokens.md new file mode 100644 index 0000000..0fe03d7 --- /dev/null +++ b/packages/frontend/app/design-tokens.md @@ -0,0 +1,55 @@ +# FoR Token Operation Rules + +## 1) Semantic Color source of truth + +Use `--for-*` variables as the source layer and map them to shadcn-compatible +variables in `app.css`. + +- Text: `--for-text-*` +- Background: `--for-bg-*` +- Stroke: `--for-stroke-*` +- Button: `--for-button-*` +- Visual Accent: `--for-accent-*` + +## 2) Component reference rule + +Components should consume only the shadcn/Tailwind theme layer: + +- `bg-background`, `text-foreground`, `border-border` +- `bg-primary`, `text-primary-foreground` +- `bg-secondary`, `text-secondary-foreground` +- `bg-destructive`, `text-destructive-foreground` + +Do not reference `--for-*` values directly inside component files. + +## 3) Typography rule + +- `font-ui`: Zen Kaku Gothic New + Roboto (UI default) +- `font-latin`: Roboto +- `text-ui-*`: line-height fixed to `130%` + +Supported UI sizes: + +- `text-ui-10`, `text-ui-12`, `text-ui-14`, `text-ui-16`, `text-ui-18` +- `text-ui-20`, `text-ui-24`, `text-ui-28`, `text-ui-32` + +## 4) Spacing rule + +`--spacing: 1px` is used, so spacing utilities are pixel based. + +Allowed spacing values: + +- `0`, `2`, `4`, `6`, `8`, `12`, `16`, `20`, `24`, `28`, `32`, `40` + +Allowed examples: + +- `p-16`, `px-24`, `py-12`, `gap-8`, `mt-40` + +Disallowed examples: + +- `p-[14px]`, `m-[3px]`, `gap-[22px]` + +## 5) Icon policy + +Use `lucide-react` as temporary placeholder icons until FoR icon assets are +finalized. From 1a31f96bb589a3072a6dfa9e00eb53cb3076b205 Mon Sep 17 00:00:00 2001 From: Tanapol Hongsuwan Date: Mon, 16 Feb 2026 12:26:21 +0900 Subject: [PATCH 5/5] feat(frontend): align theme tokens with figma screenshot review --- packages/frontend/app/app.css | 274 ++++++++++++------ .../frontend/app/components/ui/button.tsx | 13 +- packages/frontend/app/design-tokens.md | 24 +- packages/frontend/app/welcome/welcome.tsx | 103 +++++-- 4 files changed, 286 insertions(+), 128 deletions(-) diff --git a/packages/frontend/app/app.css b/packages/frontend/app/app.css index dcd3201..424a49a 100644 --- a/packages/frontend/app/app.css +++ b/packages/frontend/app/app.css @@ -4,38 +4,76 @@ color-scheme: light; /* Semantic Color: Text */ - --for-text-primary: #161c18; - --for-text-secondary: #4f5a54; - --for-text-tertiary: #6d7872; - --for-text-inverse: #f7faf8; + --for-text-default: rgb(0 0 0 / 0.8); + --for-text-subtle: rgb(0 0 0 / 0.6); + --for-text-hint: rgb(0 0 0 / 0.3); + --for-text-invert: #ffffff; + --for-text-danger-default: #eb3d3d; + --for-text-danger-subtle: #ff6161; + --for-text-danger-strong: #8b1d1d; /* Semantic Color: Background */ - --for-bg-canvas: #f3f5f1; - --for-bg-surface: #ffffff; - --for-bg-elevated: #edf1eb; - --for-bg-inverse: #101513; + --for-bg-default: #ffffff; + --for-bg-subtle: #f2f3f0; + --for-bg-danger-default: #feebeb; + --for-bg-alpha-white-30: rgb(255 255 255 / 0.3); + --for-bg-alpha-white-60: rgb(255 255 255 / 0.6); + --for-bg-alpha-black-10: rgb(0 0 0 / 0.1); + --for-bg-modal: rgb(0 0 0 / 0.5); /* Semantic Color: Stroke */ - --for-stroke-subtle: #d5ddd7; - --for-stroke-default: #b4c0b8; - --for-stroke-strong: #8a968f; - --for-stroke-focus: #f44250; + --for-stroke-default: rgb(0 0 0 / 0.05); /* Semantic Color: Button */ - --for-button-primary-bg: #121212; - --for-button-primary-fg: #ffffff; - --for-button-secondary-bg: #e7ece8; - --for-button-secondary-fg: #161c18; - --for-button-destructive-bg: #b42335; - --for-button-destructive-fg: #ffffff; - --for-button-disabled-bg: #d3d9d4; - --for-button-disabled-fg: #8c9790; + --for-button-primary-frame-default: #454545; + --for-button-primary-frame-hover: #1a1a1a; + --for-button-primary-frame-disabled: #b4b4b4; + --for-button-primary-text-invert: #ffffff; + --for-button-primary-text-disabled: #858585; + + --for-button-secondary-frame-default: #ffffff; + --for-button-secondary-frame-hover: #1a1a1a; + --for-button-secondary-frame-disabled: #f0f0f0; + --for-button-secondary-stroke-default: #454545; + --for-button-secondary-stroke-hover: #1a1a1a; + --for-button-secondary-stroke-disabled: #b4b4b4; + --for-button-secondary-text-default: #454545; + --for-button-secondary-text-hover: #1a1a1a; + --for-button-secondary-text-disabled: #b4b4b4; + + /* Tertiary values are taken from screenshot labels; hover frame was not shown */ + --for-button-tertiary-frame-default: #757b7f; + --for-button-tertiary-frame-disabled: #aeaeae; + --for-button-tertiary-text-default: #e0e0e0; + --for-button-tertiary-text-hover: #ffffff; + --for-button-tertiary-text-disabled: #cfcfcf; + + --for-button-danger-frame-default: #eb3d3d; + --for-button-danger-frame-hover: #8b1d1d; + --for-button-danger-frame-pressed: #8b1d1d; + --for-button-danger-frame-disabled: #fbbdbf; + --for-button-danger-text-default: #eb3d3d; + --for-button-danger-text-hover: #8b1d1d; + --for-button-danger-text-pressed: #8b1d1d; + --for-button-danger-text-disabled: #fbbdbf; + --for-button-danger-text-invert: #ffffff; /* Semantic Color: Visual Accent */ - --for-accent-brand: #f44250; - --for-accent-success: #2f9e60; - --for-accent-warning: #f59e0b; - --for-accent-danger: #d92d20; + --for-visual-natural-1: #e4edf7; + --for-visual-natural-2: #d1dce8; + --for-visual-natural-3: #9fb2c4; + --for-visual-natural-4: #72818f; + --for-visual-natural-5: #414a52; + --for-visual-green-1: #a5fee1; + --for-visual-green-2: #3ff4b7; + --for-visual-green-3: #00e998; + --for-visual-green-4: #0ecb8c; + --for-visual-green-5: #009e69; + --for-visual-red-1: #ffdadd; + --for-visual-red-2: #ff7885; + --for-visual-red-3: #eb3d3d; + --for-visual-red-4: #9b2727; + --for-visual-red-5: #5c1414; /* Semantic spacing tokens (source of truth) */ --for-space-0: 0px; @@ -52,63 +90,28 @@ --for-space-40: 40px; /* shadcn/ui compatible tokens mapped from FoR semantics */ - --background: var(--for-bg-canvas); - --foreground: var(--for-text-primary); - --card: var(--for-bg-surface); - --card-foreground: var(--for-text-primary); - --popover: var(--for-bg-elevated); - --popover-foreground: var(--for-text-primary); - --primary: var(--for-button-primary-bg); - --primary-foreground: var(--for-button-primary-fg); - --secondary: var(--for-button-secondary-bg); - --secondary-foreground: var(--for-button-secondary-fg); - --muted: var(--for-bg-elevated); - --muted-foreground: var(--for-text-secondary); - --accent: var(--for-accent-brand); - --accent-foreground: var(--for-text-inverse); - --destructive: var(--for-button-destructive-bg); - --destructive-foreground: var(--for-button-destructive-fg); + --background: var(--for-bg-subtle); + --foreground: var(--for-text-default); + --card: var(--for-bg-default); + --card-foreground: var(--for-text-default); + --popover: var(--for-bg-default); + --popover-foreground: var(--for-text-default); + --primary: var(--for-button-primary-frame-default); + --primary-foreground: var(--for-button-primary-text-invert); + --secondary: var(--for-button-secondary-frame-default); + --secondary-foreground: var(--for-button-secondary-text-default); + --muted: var(--for-bg-subtle); + --muted-foreground: var(--for-text-subtle); + --accent: var(--for-visual-green-3); + --accent-foreground: var(--for-text-default); + --destructive: var(--for-button-danger-frame-default); + --destructive-foreground: var(--for-button-danger-text-invert); --border: var(--for-stroke-default); - --input: var(--for-stroke-subtle); - --ring: var(--for-stroke-focus); + --input: var(--for-stroke-default); + --ring: var(--for-button-primary-frame-hover); --radius: 12px; } -@media (prefers-color-scheme: dark) { - :root { - color-scheme: dark; - - --for-text-primary: #ecf2ed; - --for-text-secondary: #b7c2ba; - --for-text-tertiary: #8d9b92; - --for-text-inverse: #121714; - - --for-bg-canvas: #101512; - --for-bg-surface: #171d19; - --for-bg-elevated: #1d241f; - --for-bg-inverse: #f3f6f4; - - --for-stroke-subtle: #28312b; - --for-stroke-default: #36433b; - --for-stroke-strong: #6a776f; - --for-stroke-focus: #ff6f7a; - - --for-button-primary-bg: #f3f6f4; - --for-button-primary-fg: #101512; - --for-button-secondary-bg: #2b342e; - --for-button-secondary-fg: #ecf2ed; - --for-button-destructive-bg: #e35b6b; - --for-button-destructive-fg: #101512; - --for-button-disabled-bg: #29322c; - --for-button-disabled-fg: #657168; - - --for-accent-brand: #ff7681; - --for-accent-success: #48c07f; - --for-accent-warning: #f6b344; - --for-accent-danger: #f37a72; - } -} - @theme inline { /* Typography */ --font-ui: @@ -120,20 +123,43 @@ --text-ui-10--line-height: 1.3; --text-ui-12: 12px; --text-ui-12--line-height: 1.3; - --text-ui-14: 14px; - --text-ui-14--line-height: 1.3; + --text-ui-13: 13px; + --text-ui-13--line-height: 1.3; --text-ui-16: 16px; --text-ui-16--line-height: 1.3; - --text-ui-18: 18px; - --text-ui-18--line-height: 1.3; --text-ui-20: 20px; --text-ui-20--line-height: 1.3; - --text-ui-24: 24px; - --text-ui-24--line-height: 1.3; - --text-ui-28: 28px; - --text-ui-28--line-height: 1.3; - --text-ui-32: 32px; - --text-ui-32--line-height: 1.3; + + --text-content-display-l: 28px; + --text-content-display-l--line-height: 1.3; + --text-content-display-m: 24px; + --text-content-display-m--line-height: 1.3; + --text-content-display-s: 20px; + --text-content-display-s--line-height: 1.3; + + --text-content-headline-l: 20px; + --text-content-headline-l--line-height: 1.3; + --text-content-headline-m: 17px; + --text-content-headline-m--line-height: 1.3; + --text-content-headline-s: 16px; + --text-content-headline-s--line-height: 1.3; + + --text-content-number-l: 32px; + --text-content-number-l--line-height: 1.3; + --text-content-number-m: 24px; + --text-content-number-m--line-height: 1.3; + --text-content-number-s: 16px; + --text-content-number-s--line-height: 1.3; + + --text-content-body-l: 15px; + --text-content-body-l--line-height: 1.6; + --text-content-body-m: 14px; + --text-content-body-m--line-height: 1.6; + --text-content-body-s: 13px; + --text-content-body-s--line-height: 1.6; + + --text-content-caption: 12px; + --text-content-caption--line-height: 1.6; /* shadcn/ui color namespace */ --color-background: var(--background); @@ -155,12 +181,72 @@ --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); - --color-button-disabled-bg: var(--for-button-disabled-bg); - --color-button-disabled-fg: var(--for-button-disabled-fg); - --color-accent-brand: var(--for-accent-brand); - --color-accent-success: var(--for-accent-success); - --color-accent-warning: var(--for-accent-warning); - --color-accent-danger: var(--for-accent-danger); + + --color-text-danger-default: var(--for-text-danger-default); + --color-text-danger-subtle: var(--for-text-danger-subtle); + --color-text-danger-strong: var(--for-text-danger-strong); + + --color-button-primary-frame: var(--for-button-primary-frame-default); + --color-button-primary-frame-hover: var(--for-button-primary-frame-hover); + --color-button-primary-frame-disabled: var( + --for-button-primary-frame-disabled + ); + --color-button-primary-text: var(--for-button-primary-text-invert); + --color-button-primary-text-disabled: var(--for-button-primary-text-disabled); + + --color-button-secondary-frame: var(--for-button-secondary-frame-default); + --color-button-secondary-frame-hover: var(--for-button-secondary-frame-hover); + --color-button-secondary-frame-disabled: var( + --for-button-secondary-frame-disabled + ); + --color-button-secondary-stroke: var(--for-button-secondary-stroke-default); + --color-button-secondary-stroke-hover: var( + --for-button-secondary-stroke-hover + ); + --color-button-secondary-stroke-disabled: var( + --for-button-secondary-stroke-disabled + ); + --color-button-secondary-text: var(--for-button-secondary-text-default); + --color-button-secondary-text-hover: var(--for-button-secondary-text-hover); + --color-button-secondary-text-disabled: var( + --for-button-secondary-text-disabled + ); + + --color-button-tertiary-frame: var(--for-button-tertiary-frame-default); + --color-button-tertiary-frame-disabled: var( + --for-button-tertiary-frame-disabled + ); + --color-button-tertiary-text: var(--for-button-tertiary-text-default); + --color-button-tertiary-text-hover: var(--for-button-tertiary-text-hover); + --color-button-tertiary-text-disabled: var( + --for-button-tertiary-text-disabled + ); + + --color-button-danger-frame: var(--for-button-danger-frame-default); + --color-button-danger-frame-hover: var(--for-button-danger-frame-hover); + --color-button-danger-frame-pressed: var(--for-button-danger-frame-pressed); + --color-button-danger-frame-disabled: var(--for-button-danger-frame-disabled); + --color-button-danger-text: var(--for-button-danger-text-default); + --color-button-danger-text-hover: var(--for-button-danger-text-hover); + --color-button-danger-text-pressed: var(--for-button-danger-text-pressed); + --color-button-danger-text-disabled: var(--for-button-danger-text-disabled); + --color-button-danger-text-invert: var(--for-button-danger-text-invert); + + --color-visual-natural-1: var(--for-visual-natural-1); + --color-visual-natural-2: var(--for-visual-natural-2); + --color-visual-natural-3: var(--for-visual-natural-3); + --color-visual-natural-4: var(--for-visual-natural-4); + --color-visual-natural-5: var(--for-visual-natural-5); + --color-visual-green-1: var(--for-visual-green-1); + --color-visual-green-2: var(--for-visual-green-2); + --color-visual-green-3: var(--for-visual-green-3); + --color-visual-green-4: var(--for-visual-green-4); + --color-visual-green-5: var(--for-visual-green-5); + --color-visual-red-1: var(--for-visual-red-1); + --color-visual-red-2: var(--for-visual-red-2); + --color-visual-red-3: var(--for-visual-red-3); + --color-visual-red-4: var(--for-visual-red-4); + --color-visual-red-5: var(--for-visual-red-5); /* Radius */ --radius-sm: calc(var(--radius) - 6px); @@ -188,6 +274,6 @@ } body { - @apply bg-background text-foreground font-ui text-ui-14 antialiased; + @apply bg-background text-foreground font-ui text-ui-13 antialiased; } } diff --git a/packages/frontend/app/components/ui/button.tsx b/packages/frontend/app/components/ui/button.tsx index ffa8d8b..2a4c53c 100644 --- a/packages/frontend/app/components/ui/button.tsx +++ b/packages/frontend/app/components/ui/button.tsx @@ -4,17 +4,18 @@ import { cn } from "~/lib/utils"; const buttonVariantClasses = { default: - "border-transparent bg-primary text-primary-foreground hover:bg-primary/90", + "border-transparent bg-button-primary-frame text-button-primary-text hover:bg-button-primary-frame-hover active:bg-button-primary-frame-hover disabled:bg-button-primary-frame-disabled disabled:text-button-primary-text-disabled", secondary: - "border-border bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "border-transparent bg-transparent text-foreground hover:bg-muted", + "border-button-secondary-stroke bg-button-secondary-frame text-button-secondary-text hover:border-button-secondary-stroke-hover hover:bg-button-secondary-frame-hover hover:text-button-secondary-text-hover active:border-button-secondary-stroke-hover active:bg-button-secondary-frame-hover active:text-button-secondary-text-hover disabled:border-button-secondary-stroke-disabled disabled:bg-button-secondary-frame-disabled disabled:text-button-secondary-text-disabled", + ghost: + "border-transparent bg-button-tertiary-frame text-button-tertiary-text hover:text-button-tertiary-text-hover active:text-button-tertiary-text-hover disabled:bg-button-tertiary-frame-disabled disabled:text-button-tertiary-text-disabled", destructive: - "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/90", + "border-transparent bg-button-danger-frame text-button-danger-text-invert hover:bg-button-danger-frame-hover active:bg-button-danger-frame-pressed disabled:bg-button-danger-frame-disabled disabled:text-button-danger-text-disabled", } as const; const buttonSizeClasses = { default: "h-40 px-16", - sm: "h-32 px-12 text-ui-12", + sm: "h-32 px-12 text-ui-10", lg: "h-40 px-20 text-ui-16", icon: "size-40 p-0", } as const; @@ -42,7 +43,7 @@ export const Button = React.forwardRef( return (