diff --git a/app/layout.tsx b/app/layout.tsx
index d67d803..a8bcb13 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
+import Blur from "@/components/common/Blur";
const pretendard = localFont({
src: "./fonts/PretendardVariable.woff2",
@@ -38,9 +39,10 @@ export default function RootLayout({
return (
-
+
+
{children}
diff --git a/app/page.tsx b/app/page.tsx
index b4c3e0b..718c073 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,63 +1,9 @@
+import Blur from "@/components/common/Blur";
+
export default function Home() {
return (
-
-
- {/* 토큰 테스트 섹션 */}
-
-
- 🎨 Design Token Test
-
-
- {/* 1. Typography */}
-
- 1. Typography
-
-
Display 24px (Bold 700)
-
Title 20px (SemiBold 600)
-
Body 18px (Medium 500)
-
Body 16px (Regular 400)
-
- Caption 14px (Disabled Color)
-
-
-
-
- {/* 2. Colors & Backgrounds */}
-
- 2. Colors & Backgrounds
-
-
- Pink Secondary
-
-
- Orange Primary
-
-
- Surface Base
-
-
- Disabled Area
-
-
-
-
- {/* 3. Buttons */}
-
- 3. Buttons
-
-
-
-
-
-
-
-
-
+
+
+
);
}
diff --git a/app/tokens.css b/app/tokens.css
index e8566c8..3fec428 100644
--- a/app/tokens.css
+++ b/app/tokens.css
@@ -1,260 +1,417 @@
/* Auto-generated from token.json */
-/* Run: node scripts/generate-tokens.js to regenerate */
+/* Run: pnpm run token to regenerate */
+/* Total unique tokens: 132 */
@layer base {
:root {
- --color-gray-900: #1a1a1a;
+ --background-app-base: var(--color-surface-base);
+ --background-app-blur-bottom-left: var(--color-brand-secondary-pink);
+ --background-app-blur-bottom-right: var(--color-brand-primary-orange);
+ --background-app-opacity: var(--opacity-full);
+ --background-border-radius: var(--radius-none);
+ --background-glow-blur-radius: 280;
+ --background: var(--default);
+ --bg: {colors.indigo.200};
+ --black: #000000;
+ --body: Roboto;
+ --bodybold: Bold;
+ --bodyregular: Regular;
+ --border-width-default: 1;
+ --border-width-thick: 2;
+ --border-width-thin: 0.8;
+ --borderradius: var(--lg);
+ --borderwidth: var(--sm);
+ --button-background-bold: var(--color-brand-black);
+ --button-background-disabled: var(--color-background-disabled);
+ --button-background-gradient-1-end: var(--color-brand-primary-orange);
+ --button-background-gradient-1-start: var(--color-brand-primary-flame);
+ --button-border-color: var(--color-border-light);
+ --button-border-radius: var(--radius-md);
+ --button-border-width: var(--border-width-thin);
+ --button-opacity-default: var(--opacity-full);
+ --button-primary-text-default: var(--color-text-white);
+ --button-primary-text-disabled: var(--color-text-disabled);
+ --button-slate-default: var(--color-gray-0-a30);
+ --color-background-disabled: var(--color-gray-300-a40);
+ --color-border-light: var(--color-gray-0-a30);
+ --color-brand-black: var(--color-gray-900);
+ --color-brand-primary-flame: var(--color-flame-700);
+ --color-brand-primary-orange: var(--color-orange-700);
+ --color-brand-primary-pink: var(--color-pink-700);
+ --color-brand-secondary-pink: var(--color-pink-100);
+ --color-flame-100: #ffc4cd;
+ --color-flame-300: #ff8a9b;
+ --color-flame-500: #ff667c;
+ --color-flame-50: #ffe5e9;
+ --color-flame-700: #ff4d61;
+ --color-flame-900: #ed1134;
+ --color-gray-0-a30: #ffffff4d;
--color-gray-0: #ffffff;
- --color-gray-500: #808080;
- --color-gray-600: #666666;
- --color-gray-300: #b3b3b3;
- --color-gray-700: #4d4d4d;
--color-gray-100: #e5e5e5;
+ --color-gray-200: #cccccc;
+ --color-gray-300-a40: #b3b3b366;
+ --color-gray-300: #b3b3b3;
--color-gray-400: #999999;
+ --color-gray-500: #808080;
--color-gray-50: #f5f5f5;
- --color-gray-200: #cccccc;
- --color-gray-800: #333333;
+ --color-gray-600: #666666;
--color-gray-64: #efefef;
+ --color-gray-700: #4d4d4d;
+ --color-gray-800: #333333;
+ --color-gray-900: #1a1a1a;
+ --color-orange-100: #ffc7b9;
+ --color-orange-300: #ffa188;
+ --color-orange-500: #ff8a6f;
+ --color-orange-50: #ffe8e2;
+ --color-orange-700: #ff775e;
+ --color-orange-900: #dd4527;
+ --color-pink-100: #fbcde7;
--color-pink-300: #f8a4cb;
--color-pink-500: #f387be;
- --color-pink-700: #f57db2;
--color-pink-50: #fdeaf5;
+ --color-pink-700: #f57db2;
--color-pink-900: #e3468b;
- --color-pink-100: #fbcde7;
- --color-flame-300: #ff8a9b;
- --color-flame-700: #ff4d61;
- --color-flame-500: #ff667c;
- --color-flame-50: #ffe5e9;
- --color-flame-900: #ed1134;
- --color-flame-100: #ffc4cd;
- --color-orange-500: #ff8a6f;
- --color-orange-700: #ff775e;
- --color-orange-100: #ffc7b9;
- --color-orange-300: #ffa188;
- --color-orange-900: #dd4527;
- --color-orange-50: #ffe8e2;
- --color-gray-0-a30: #ffffff4d;
- --color-gray-300-a40: #b3b3b366;
--color-surface-base: var(--color-gray-64);
- --color-brand-secondary-pink: var(--color-pink-100);
- --color-brand-primary-orange: var(--color-orange-700);
- --color-brand-primary-pink: var(--color-pink-700);
- --color-brand-primary-flame: var(--color-flame-700);
- --color-border-light: var(--color-gray-0-a30);
- --color-text-white: var(--color-gray-0);
--color-text-disabled: var(--color-gray-300);
- --color-background-disabled: var(--color-gray-300-a40);
- --color-brand-black: var(--color-gray-900);
- --radius-none: 0px;
- --radius-xs: 8px;
- --radius-sm: 12px;
- --radius-md: 16px;
- --radius-lg: 24px;
- --radius-full: 99px;
- --border-thin: 0.8px;
- --border-default: 1px;
- --border-thick: 2px;
- --opacity-full: 100%;
- --opacity-medium: 50%;
- --opacity-subtle: 20%;
- --opacity-subtle-2: 40%;
+ --color-text-white: var(--color-gray-0);
+ --decreased: -5%;
+ --default: 0;
+ --flame100: #ffc4cd;
+ --flame300: #ff8a9b;
+ --flame500: #ff667c;
+ --flame50: #ffe5e9;
+ --flame700: #ff4d61;
+ --flame900: #ed1134;
+ --font-size-10: 10;
+ --font-size-12: 12;
+ --font-size-14: 14;
+ --font-size-16: 16;
+ --font-size-18: 18;
+ --font-size-20: 20;
+ --font-size-24: 24;
+ --h1: 32;
+ --h2: 26;
+ --h3: roundTo({fontSizes.body}*1.25^3);
+ --h4: roundTo({fontSizes.body}*1.25^2);
+ --h5: roundTo({fontSizes.body}*1.25^1);
+ --h6: var(--body);
+ --heading: Inter;
+ --headingbold: Bold;
+ --headingregular: Regular;
+ --high: 90%;
+ --increased: 150%;
+ --lg: var(--scale);
+ --low: 10%;
+ --md: var(--scale);
+ --multi-value: var(--xl);
+ --muted: {colors.gray.700};
+ --onaccent: var(--white);
+ --opacity-full: 100;
+ --opacity-medium: 50;
+ --opacity-subtle-2: 40;
+ --opacity-subtle: 20;
+ --orange100: #ffc7b9;
+ --orange300: #ffa188;
+ --orange500: #ff8a6f;
+ --orange50: #ffe8e2;
+ --orange700: #ff775e;
+ --orange900: #dd4527;
+ --padding: var(--md);
+ --pink100: #fbcde7;
+ --pink300: #f8a4cb;
+ --pink500: #f387be;
+ --pink50: #fdeaf5;
+ --pink700: #f57db2;
+ --pink900: #e3468b;
+ --radius-full: 99;
+ --radius-lg: 24;
+ --radius-md: 16;
+ --radius-none: 0;
+ --radius-sm: 12;
+ --radius-xs: 8;
+ --scale: 2;
+ --sm: var(--scale);
+ --subtle: {colors.gray.500};
+ --text: var(--onaccent);
+ --white: #ffffff;
+ --xl: var(--scale);
+ --xs: 4;
}
}
@layer utilities {
- /* Text utilities: text-{size}-{weight} */
-.text-16-400 {
- font-size: 16px;
- font-weight: 400;
- line-height: 1.5;
-}
+ /* Font utilities */
+ .text-16-400 {
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 1.5;
+ }
-.text-16-500 {
- font-size: 16px;
- font-weight: 500;
- line-height: 1.5;
-}
+ .text-16-500 {
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 1.5;
+ }
-.text-16-600 {
- font-size: 16px;
- font-weight: 600;
- line-height: 1.5;
-}
+ .text-16-600 {
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 1.5;
+ }
-.text-16-700 {
- font-size: 16px;
- font-weight: 700;
- line-height: 1.5;
-}
+ .text-16-700 {
+ font-size: 16px;
+ font-weight: 700;
+ line-height: 1.5;
+ }
-.text-20-400 {
- font-size: 20px;
- font-weight: 400;
- line-height: 1.5;
-}
+ .text-20-400 {
+ font-size: 20px;
+ font-weight: 400;
+ line-height: 1.5;
+ }
-.text-20-500 {
- font-size: 20px;
- font-weight: 500;
- line-height: 1.5;
-}
+ .text-20-500 {
+ font-size: 20px;
+ font-weight: 500;
+ line-height: 1.5;
+ }
-.text-20-600 {
- font-size: 20px;
- font-weight: 600;
- line-height: 1.5;
-}
+ .text-20-600 {
+ font-size: 20px;
+ font-weight: 600;
+ line-height: 1.5;
+ }
-.text-20-700 {
- font-size: 20px;
- font-weight: 700;
- line-height: 1.5;
-}
+ .text-20-700 {
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 1.5;
+ }
-.text-24-400 {
- font-size: 24px;
- font-weight: 400;
- line-height: 1.5;
-}
+ .text-24-400 {
+ font-size: 24px;
+ font-weight: 400;
+ line-height: 1.5;
+ }
-.text-24-500 {
- font-size: 24px;
- font-weight: 500;
- line-height: 1.5;
-}
+ .text-24-500 {
+ font-size: 24px;
+ font-weight: 500;
+ line-height: 1.5;
+ }
-.text-24-600 {
- font-size: 24px;
- font-weight: 600;
- line-height: 1.5;
-}
+ .text-24-600 {
+ font-size: 24px;
+ font-weight: 600;
+ line-height: 1.5;
+ }
-.text-24-700 {
- font-size: 24px;
- font-weight: 700;
- line-height: 1.5;
-}
+ .text-24-700 {
+ font-size: 24px;
+ font-weight: 700;
+ line-height: 1.5;
+ }
-.text-12-400 {
- font-size: 12px;
- font-weight: 400;
- line-height: 1.5;
-}
+ .text-12-400 {
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 1.5;
+ }
-.text-12-500 {
- font-size: 12px;
- font-weight: 500;
- line-height: 1.5;
-}
+ .text-12-500 {
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 1.5;
+ }
-.text-12-600 {
- font-size: 12px;
- font-weight: 600;
- line-height: 1.5;
-}
+ .text-12-600 {
+ font-size: 12px;
+ font-weight: 600;
+ line-height: 1.5;
+ }
-.text-12-700 {
- font-size: 12px;
- font-weight: 700;
- line-height: 1.5;
-}
+ .text-12-700 {
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 1.5;
+ }
-.text-14-400 {
- font-size: 14px;
- font-weight: 400;
- line-height: 1.5;
-}
+ .text-14-400 {
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.5;
+ }
-.text-14-500 {
- font-size: 14px;
- font-weight: 500;
- line-height: 1.5;
-}
+ .text-14-500 {
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 1.5;
+ }
-.text-14-600 {
- font-size: 14px;
- font-weight: 600;
- line-height: 1.5;
-}
+ .text-14-600 {
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1.5;
+ }
-.text-14-700 {
- font-size: 14px;
- font-weight: 700;
- line-height: 1.5;
-}
+ .text-14-700 {
+ font-size: 14px;
+ font-weight: 700;
+ line-height: 1.5;
+ }
-.text-10-400 {
- font-size: 10px;
- font-weight: 400;
- line-height: 1.5;
-}
+ .text-10-400 {
+ font-size: 10px;
+ font-weight: 400;
+ line-height: 1.5;
+ }
-.text-10-500 {
- font-size: 10px;
- font-weight: 500;
- line-height: 1.5;
-}
+ .text-10-500 {
+ font-size: 10px;
+ font-weight: 500;
+ line-height: 1.5;
+ }
-.text-10-600 {
- font-size: 10px;
- font-weight: 600;
- line-height: 1.5;
-}
+ .text-10-600 {
+ font-size: 10px;
+ font-weight: 600;
+ line-height: 1.5;
+ }
-.text-10-700 {
- font-size: 10px;
- font-weight: 700;
- line-height: 1.5;
-}
+ .text-10-700 {
+ font-size: 10px;
+ font-weight: 700;
+ line-height: 1.5;
+ }
-.text-18-400 {
- font-size: 18px;
- font-weight: 400;
- line-height: 1.5;
-}
+ .text-18-400 {
+ font-size: 18px;
+ font-weight: 400;
+ line-height: 1.5;
+ }
-.text-18-500 {
- font-size: 18px;
- font-weight: 500;
- line-height: 1.5;
-}
+ .text-18-500 {
+ font-size: 18px;
+ font-weight: 500;
+ line-height: 1.5;
+ }
-.text-18-600 {
- font-size: 18px;
- font-weight: 600;
- line-height: 1.5;
-}
+ .text-18-600 {
+ font-size: 18px;
+ font-weight: 600;
+ line-height: 1.5;
+ }
-.text-18-700 {
- font-size: 18px;
- font-weight: 700;
- line-height: 1.5;
-}
+ .text-18-700 {
+ font-size: 18px;
+ font-weight: 700;
+ line-height: 1.5;
+ }
- /* Background utilities */
- .bg-surface-base { background-color: var(--color-surface-base); }
- .bg-brand-secondary-pink { background-color: var(--color-brand-secondary-pink); }
- .bg-brand-primary-orange { background-color: var(--color-brand-primary-orange); }
- .bg-disabled { background-color: var(--color-background-disabled); }
-
- /* Button utilities */
+ /* Background color utilities */
+ .bg-background-app-base { background-color: var(--background-app-base); }
+ .bg-background-app-blur-bottom-left { background-color: var(--background-app-blur-bottom-left); }
+ .bg-background-app-blur-bottom-right { background-color: var(--background-app-blur-bottom-right); }
+ .bg-button-background-bold { background-color: var(--button-background-bold); }
+ .bg-button-background-disabled { background-color: var(--button-background-disabled); }
+ .bg-button-background-gradient-1-end { background-color: var(--button-background-gradient-1-end); }
+ .bg-button-background-gradient-1-start { background-color: var(--button-background-gradient-1-start); }
+ .bg-color-background-disabled { background-color: var(--color-background-disabled); }
+ .bg-color-border-light { background-color: var(--color-border-light); }
+ .bg-color-brand-black { background-color: var(--color-brand-black); }
+ .bg-color-brand-primary-flame { background-color: var(--color-brand-primary-flame); }
+ .bg-color-brand-primary-orange { background-color: var(--color-brand-primary-orange); }
+ .bg-color-brand-primary-pink { background-color: var(--color-brand-primary-pink); }
+ .bg-color-brand-secondary-pink { background-color: var(--color-brand-secondary-pink); }
+ .bg-color-flame-100 { background-color: var(--color-flame-100); }
+ .bg-color-flame-300 { background-color: var(--color-flame-300); }
+ .bg-color-flame-50 { background-color: var(--color-flame-50); }
+ .bg-color-flame-500 { background-color: var(--color-flame-500); }
+ .bg-color-flame-700 { background-color: var(--color-flame-700); }
+ .bg-color-flame-900 { background-color: var(--color-flame-900); }
+ .bg-color-gray-0 { background-color: var(--color-gray-0); }
+ .bg-color-gray-0-a30 { background-color: var(--color-gray-0-a30); }
+ .bg-color-gray-100 { background-color: var(--color-gray-100); }
+ .bg-color-gray-200 { background-color: var(--color-gray-200); }
+ .bg-color-gray-300 { background-color: var(--color-gray-300); }
+ .bg-color-gray-300-a40 { background-color: var(--color-gray-300-a40); }
+ .bg-color-gray-400 { background-color: var(--color-gray-400); }
+ .bg-color-gray-50 { background-color: var(--color-gray-50); }
+ .bg-color-gray-500 { background-color: var(--color-gray-500); }
+ .bg-color-gray-600 { background-color: var(--color-gray-600); }
+ .bg-color-gray-64 { background-color: var(--color-gray-64); }
+ .bg-color-gray-700 { background-color: var(--color-gray-700); }
+ .bg-color-gray-800 { background-color: var(--color-gray-800); }
+ .bg-color-gray-900 { background-color: var(--color-gray-900); }
+ .bg-color-orange-100 { background-color: var(--color-orange-100); }
+ .bg-color-orange-300 { background-color: var(--color-orange-300); }
+ .bg-color-orange-50 { background-color: var(--color-orange-50); }
+ .bg-color-orange-500 { background-color: var(--color-orange-500); }
+ .bg-color-orange-700 { background-color: var(--color-orange-700); }
+ .bg-color-orange-900 { background-color: var(--color-orange-900); }
+ .bg-color-pink-100 { background-color: var(--color-pink-100); }
+ .bg-color-pink-300 { background-color: var(--color-pink-300); }
+ .bg-color-pink-50 { background-color: var(--color-pink-50); }
+ .bg-color-pink-500 { background-color: var(--color-pink-500); }
+ .bg-color-pink-700 { background-color: var(--color-pink-700); }
+ .bg-color-pink-900 { background-color: var(--color-pink-900); }
+ .bg-color-surface-base { background-color: var(--color-surface-base); }
+ .bg-color-text-disabled { background-color: var(--color-text-disabled); }
+ .bg-color-text-white { background-color: var(--color-text-white); }
+
+ /* Text color utilities */
+ .text-button-primary-text-default { color: var(--button-primary-text-default); }
+ .text-button-primary-text-disabled { color: var(--button-primary-text-disabled); }
+ .text-color-background-disabled { color: var(--color-background-disabled); }
+ .text-color-border-light { color: var(--color-border-light); }
+ .text-color-brand-black { color: var(--color-brand-black); }
+ .text-color-brand-primary-flame { color: var(--color-brand-primary-flame); }
+ .text-color-brand-primary-orange { color: var(--color-brand-primary-orange); }
+ .text-color-brand-primary-pink { color: var(--color-brand-primary-pink); }
+ .text-color-brand-secondary-pink { color: var(--color-brand-secondary-pink); }
+ .text-color-flame-100 { color: var(--color-flame-100); }
+ .text-color-flame-300 { color: var(--color-flame-300); }
+ .text-color-flame-50 { color: var(--color-flame-50); }
+ .text-color-flame-500 { color: var(--color-flame-500); }
+ .text-color-flame-700 { color: var(--color-flame-700); }
+ .text-color-flame-900 { color: var(--color-flame-900); }
+ .text-color-gray-0 { color: var(--color-gray-0); }
+ .text-color-gray-0-a30 { color: var(--color-gray-0-a30); }
+ .text-color-gray-100 { color: var(--color-gray-100); }
+ .text-color-gray-200 { color: var(--color-gray-200); }
+ .text-color-gray-300 { color: var(--color-gray-300); }
+ .text-color-gray-300-a40 { color: var(--color-gray-300-a40); }
+ .text-color-gray-400 { color: var(--color-gray-400); }
+ .text-color-gray-50 { color: var(--color-gray-50); }
+ .text-color-gray-500 { color: var(--color-gray-500); }
+ .text-color-gray-600 { color: var(--color-gray-600); }
+ .text-color-gray-64 { color: var(--color-gray-64); }
+ .text-color-gray-700 { color: var(--color-gray-700); }
+ .text-color-gray-800 { color: var(--color-gray-800); }
+ .text-color-gray-900 { color: var(--color-gray-900); }
+ .text-color-orange-100 { color: var(--color-orange-100); }
+ .text-color-orange-300 { color: var(--color-orange-300); }
+ .text-color-orange-50 { color: var(--color-orange-50); }
+ .text-color-orange-500 { color: var(--color-orange-500); }
+ .text-color-orange-700 { color: var(--color-orange-700); }
+ .text-color-orange-900 { color: var(--color-orange-900); }
+ .text-color-pink-100 { color: var(--color-pink-100); }
+ .text-color-pink-300 { color: var(--color-pink-300); }
+ .text-color-pink-50 { color: var(--color-pink-50); }
+ .text-color-pink-500 { color: var(--color-pink-500); }
+ .text-color-pink-700 { color: var(--color-pink-700); }
+ .text-color-pink-900 { color: var(--color-pink-900); }
+ .text-color-surface-base { color: var(--color-surface-base); }
+ .text-color-text-disabled { color: var(--color-text-disabled); }
+ .text-color-text-white { color: var(--color-text-white); }
+
+ /* Border utilities */
+ .border-button-border-color { border-color: var(--button-border-color); }
+ .border-color-border-light { border-color: var(--color-border-light); }
+
+ /* Custom gradient buttons */
.bg-button-primary {
background: linear-gradient(135deg, var(--color-brand-primary-flame), var(--color-brand-primary-orange));
}
-
- .bg-button-disabled {
- background-color: var(--color-background-disabled);
- }
-
- .bg-button-slate {
- background-color: var(--color-gray-0-a30);
- }
-
- /* Border utilities */
- .border-light { border-color: var(--color-border-light); }
-
- /* Text color utilities */
- .text-white { color: var(--color-text-white); }
- .text-disabled { color: var(--color-text-disabled); }
- .text-brand-black { color: var(--color-brand-black); }
}
diff --git a/components/common/Blur.tsx b/components/common/Blur.tsx
new file mode 100644
index 0000000..5a1fd4f
--- /dev/null
+++ b/components/common/Blur.tsx
@@ -0,0 +1,14 @@
+import React from "react";
+
+const Blur = () => {
+ return (
+
+ {/* orange blur - right */}
+
+ {/* pink blur - left */}
+
+
+ );
+};
+
+export default Blur;
diff --git a/package.json b/package.json
index ff07740..b770268 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,8 @@
"lint": "eslint",
"format": "prettier --write .",
"format:check": "prettier --check .",
- "prepare": "husky"
+ "prepare": "husky",
+ "token": "node scripts/generate-tokens.js"
},
"dependencies": {
"@tanstack/react-query": "^5.90.20",
diff --git a/scripts/generate-tokens.js b/scripts/generate-tokens.js
index 188f534..a9604da 100644
--- a/scripts/generate-tokens.js
+++ b/scripts/generate-tokens.js
@@ -9,93 +9,71 @@ const __dirname = path.dirname(__filename);
const tokensPath = path.join(__dirname, "../app/token.json");
const tokens = JSON.parse(fs.readFileSync(tokensPath, "utf-8"));
-// Semantic tokens에서 실제 색상값 추출
-const semanticMode1 = tokens["Sementic/Mode 1"] || {};
-const systemMode1 = tokens["System/Mode 1"] || {};
-
-// Grayscale, Colors, Transparent 값 추출
-const grayscale = semanticMode1.Grayscale || {};
-const colors = semanticMode1.Colors || {};
-const transparent = semanticMode1.Transparent || {};
-
-// CSS 변수 생성
-let cssVariables = [];
-
-// 키 정제 함수 (공백을 하이픈으로 변경)
+// 키 정제 함수 - 더 엄격하게
function sanitizeKey(key) {
- return key.replace(/\s+/g, "-").toLowerCase();
-}
-
-// Grayscale 색상
-Object.entries(grayscale).forEach(([key, value]) => {
- if (key.startsWith("color-gray-")) {
- cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
- }
-});
-
-// Colors (Pink, Flame, Orange)
-if (colors.Pink) {
- Object.entries(colors.Pink).forEach(([key, value]) => {
- if (key.startsWith("color-pink-")) {
- cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
- }
- });
-}
-if (colors.Flame) {
- Object.entries(colors.Flame).forEach(([key, value]) => {
- if (key.startsWith("color-flame-")) {
- cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
- }
- });
-}
-if (colors.Orange) {
- Object.entries(colors.Orange).forEach(([key, value]) => {
- if (key.startsWith("color-orange-")) {
- cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
- }
- });
-}
-
-// Transparent
-Object.entries(transparent).forEach(([key, value]) => {
- cssVariables.push(` --${sanitizeKey(key)}: ${value.$value};`);
-});
-
-// System tokens
-if (systemMode1.Colors) {
- Object.entries(systemMode1.Colors).forEach(([key, value]) => {
- if (value.$type === "color") {
- const finalValue = resolveValue(value.$value);
- cssVariables.push(` --${sanitizeKey(key)}: ${finalValue};`);
- }
- });
+ return key
+ .replace(/\(.*?\)/g, "") // 괄호와 내용 제거
+ .replace(/[^\w-]/g, "-") // 특수문자를 하이픈으로
+ .replace(/^-+|-+$/g, "") // 앞뒤 하이픈 제거
+ .replace(/-+/g, "-") // 연속 하이픈을 하나로
+ .toLowerCase();
}
-// Radius
-if (systemMode1.Radius) {
- Object.entries(systemMode1.Radius).forEach(([key, value]) => {
- const varName = key.replace("radius-", "");
- cssVariables.push(` --radius-${sanitizeKey(varName)}: ${value.$value}px;`);
- });
+// 유효한 CSS 변수명인지 확인
+function isValidCSSVarName(name) {
+ // 숫자로만 구성되거나, 숫자로 시작하면 안됨
+ return !/^\d+$/.test(name) && !/^-?\d/.test(name) && name.length > 0;
}
-// Border
-if (systemMode1.Border) {
- Object.entries(systemMode1.Border).forEach(([key, value]) => {
- const varName = key.replace("border-width-", "");
- cssVariables.push(` --border-${sanitizeKey(varName)}: ${value.$value}px;`);
- });
+// 유효한 CSS 값인지 확인
+function isValidCSSValue(value) {
+ if (typeof value === "number") return true;
+ if (typeof value === "string" && value.trim().length > 0) {
+ // [object Object] 같은 잘못된 값 제외
+ if (value.includes("[object")) return false;
+ return true;
+ }
+ return false;
}
-// Opacity
-if (systemMode1.Opacity) {
- Object.entries(systemMode1.Opacity).forEach(([key, value]) => {
- const varName = key.replace("opacity-", "");
- cssVariables.push(` --opacity-${sanitizeKey(varName)}: ${value.$value}%;`);
- });
+// 토큰 저장소 (중복 방지를 위해 Map 사용)
+const tokenMap = new Map(); // key -> { type, value, path }
+
+// 재귀적으로 token.json 탐색
+function traverseTokens(obj, path = []) {
+ for (const [key, value] of Object.entries(obj)) {
+ if (value && typeof value === "object") {
+ // $type과 $value가 있으면 토큰임
+ if (value.$type && value.$value !== undefined) {
+ const tokenName = sanitizeKey(key);
+
+ // 유효성 검사
+ if (!isValidCSSVarName(tokenName)) continue;
+ if (!isValidCSSValue(value.$value)) continue;
+
+ // 경로를 포함한 고유 키 생성 (중복 방지)
+ const fullPath = [...path, key].map(sanitizeKey).join("-");
+ const uniqueKey = fullPath || tokenName;
+
+ // 이미 존재하지 않으면 추가
+ if (!tokenMap.has(uniqueKey)) {
+ tokenMap.set(uniqueKey, {
+ name: tokenName,
+ type: value.$type,
+ value: value.$value,
+ path: [...path, key],
+ fullPath: uniqueKey,
+ });
+ }
+ } else {
+ // 재귀적으로 탐색
+ traverseTokens(value, [...path, key]);
+ }
+ }
+ }
}
-// 토큰 참조 해석
+// 토큰 참조 해석 (범용)
function resolveValue(value, depth = 0) {
if (depth > 10 || typeof value !== "string") return value;
@@ -103,64 +81,112 @@ function resolveValue(value, depth = 0) {
if (!match) return value;
const ref = match[1];
-
- // Grayscale 참조
- if (ref.startsWith("Grayscale.")) {
- const key = ref.replace("Grayscale.", "");
- return `var(--${key})`;
+ const parts = ref.split(".");
+ const lastPart = parts[parts.length - 1];
+ const tokenName = sanitizeKey(lastPart);
+
+ // tokenMap에서 찾기
+ for (const [key, token] of tokenMap.entries()) {
+ if (token.name === tokenName) {
+ return `var(--${token.name})`;
+ }
}
- // Colors 참조
- if (ref.startsWith("Colors.Pink.")) {
- const key = ref.replace("Colors.Pink.", "");
- return `var(--${key})`;
- }
- if (ref.startsWith("Colors.Flame.")) {
- const key = ref.replace("Colors.Flame.", "");
- return `var(--${key})`;
- }
- if (ref.startsWith("Colors.Orange.")) {
- const key = ref.replace("Colors.Orange.", "");
- return `var(--${key})`;
+ // 못 찾으면 그대로 반환
+ return value;
+}
+
+// 1단계: 모든 토큰 수집
+traverseTokens(tokens);
+const allTokens = Array.from(tokenMap.values());
+
+console.log(`\n🔍 Collected ${allTokens.length} unique tokens`);
+
+// 2단계: CSS 변수 생성 (중복 제거 및 정렬)
+const uniqueVars = new Map();
+
+allTokens.forEach((token) => {
+ let cssValue = token.value;
+
+ // 참조 해석
+ if (typeof cssValue === "string" && cssValue.includes("{")) {
+ cssValue = resolveValue(cssValue);
}
- // Transparent 참조
- if (ref.startsWith("Transparent.")) {
- const key = ref.replace("Transparent.", "");
- return `var(--${key})`;
+ // 타입에 따라 단위 추가
+ if (token.type === "dimension" && typeof cssValue === "number") {
+ cssValue = `${cssValue}px`;
+ } else if (token.type === "spacing" && typeof cssValue === "number") {
+ cssValue = `${cssValue}px`;
}
- // System/Mode 1의 Colors 참조
- if (ref.startsWith("Colors.")) {
- const key = ref.replace("Colors.", "");
- return `var(--${key})`;
+ // 중복 방지: 같은 이름이 있으면 더 구체적인 경로를 우선
+ if (!uniqueVars.has(token.name)) {
+ uniqueVars.set(token.name, cssValue);
}
+});
- return value;
-}
+const cssVariables = Array.from(uniqueVars.entries())
+ .map(([name, value]) => ` --${name}: ${value};`)
+ .sort(); // 알파벳 순 정렬
+
+// 3단계: Tailwind 유틸리티 클래스 생성
+const colorTokens = allTokens.filter((t) => t.type === "color");
+const fontTokens = allTokens.filter((t) => t.name.startsWith("font-size-"));
+
+// 배경색 유틸리티 (중복 제거)
+const bgUtilitiesSet = new Set();
+colorTokens
+ .filter((t) => t.name.includes("color-") || t.name.includes("background-"))
+ .forEach((t) => {
+ bgUtilitiesSet.add(
+ ` .bg-${t.name} { background-color: var(--${t.name}); }`,
+ );
+ });
+const bgUtilities = Array.from(bgUtilitiesSet).sort().join("\n");
+
+// 텍스트 색상 유틸리티 (중복 제거)
+const textColorUtilitiesSet = new Set();
+colorTokens
+ .filter((t) => t.name.includes("color-") || t.name.includes("text-"))
+ .forEach((t) => {
+ textColorUtilitiesSet.add(` .text-${t.name} { color: var(--${t.name}); }`);
+ });
+const textColorUtilities = Array.from(textColorUtilitiesSet).sort().join("\n");
-// Font Size - text-{size}-{weight} 형태로 생성
-const fontSizes = systemMode1?.Font || {};
+// 폰트 사이즈 유틸리티 (text-{size}-{weight})
const weights = ["400", "500", "600", "700"];
-let tailwindUtilities = [];
-
-Object.entries(fontSizes).forEach(([key, value]) => {
- if (key.startsWith("font-size-")) {
- const size = value.$value;
+const fontUtilitiesSet = new Set();
+fontTokens.forEach((t) => {
+ const size = t.value;
+ if (typeof size === "number") {
weights.forEach((weight) => {
- const className = `.text-${size}-${weight}`;
- tailwindUtilities.push(`${className} {
- font-size: ${size}px;
- font-weight: ${weight};
- line-height: 1.5;
-}`);
+ fontUtilitiesSet.add(` .text-${size}-${weight} {
+ font-size: ${size}px;
+ font-weight: ${weight};
+ line-height: 1.5;
+ }`);
});
}
});
+const fontUtilities = Array.from(fontUtilitiesSet);
+
+// Border 유틸리티 (중복 제거)
+const borderUtilitiesSet = new Set();
+allTokens
+ .filter((t) => t.name.includes("border") && t.type === "color")
+ .forEach((t) => {
+ const className = t.name.replace(/^border-/, "");
+ borderUtilitiesSet.add(
+ ` .border-${className} { border-color: var(--${t.name}); }`,
+ );
+ });
+const borderUtilities = Array.from(borderUtilitiesSet).sort().join("\n");
-// CSS 파일 생성
+// 4단계: CSS 파일 생성
const cssContent = `/* Auto-generated from token.json */
-/* Run: node scripts/generate-tokens.js to regenerate */
+/* Run: pnpm run token to regenerate */
+/* Total unique tokens: ${uniqueVars.size} */
@layer base {
:root {
@@ -169,35 +195,22 @@ ${cssVariables.join("\n")}
}
@layer utilities {
- /* Text utilities: text-{size}-{weight} */
-${tailwindUtilities.join("\n\n")}
-
- /* Background utilities */
- .bg-surface-base { background-color: var(--color-surface-base); }
- .bg-brand-secondary-pink { background-color: var(--color-brand-secondary-pink); }
- .bg-brand-primary-orange { background-color: var(--color-brand-primary-orange); }
- .bg-disabled { background-color: var(--color-background-disabled); }
-
- /* Button utilities */
+ /* Font utilities */
+${fontUtilities.join("\n\n")}
+
+ /* Background color utilities */
+${bgUtilities}
+
+ /* Text color utilities */
+${textColorUtilities}
+
+ /* Border utilities */
+${borderUtilities}
+
+ /* Custom gradient buttons */
.bg-button-primary {
background: linear-gradient(135deg, var(--color-brand-primary-flame), var(--color-brand-primary-orange));
}
-
- .bg-button-disabled {
- background-color: var(--color-background-disabled);
- }
-
- .bg-button-slate {
- background-color: var(--color-gray-0-a30);
- }
-
- /* Border utilities */
- .border-light { border-color: var(--color-border-light); }
-
- /* Text color utilities */
- .text-white { color: var(--color-text-white); }
- .text-disabled { color: var(--color-text-disabled); }
- .text-brand-black { color: var(--color-brand-black); }
}
`;
@@ -207,8 +220,23 @@ fs.writeFileSync(outputPath, cssContent, "utf-8");
console.log("✅ Tokens generated successfully!");
console.log(`📝 Output: ${outputPath}`);
+console.log(`📊 Total unique tokens: ${uniqueVars.size}`);
+console.log(
+ ` - Colors: ${colorTokens.length}, Fonts: ${fontTokens.length}, Others: ${
+ allTokens.length - colorTokens.length - fontTokens.length
+ }`,
+);
+console.log("\n✨ Improvements:");
+console.log(" - Removed duplicate variable names");
+console.log(" - Filtered out invalid CSS values ([object Object])");
+console.log(
+ " - Fixed variable names (removed parentheses, numbers-only names)",
+);
+console.log(" - Sorted variables alphabetically");
console.log("\nNext steps:");
console.log("1. Import tokens.css in your globals.css:");
console.log(' @import "./tokens.css";');
console.log("\n2. Use utilities in your components:");
-console.log('
Hello
');
+console.log(
+ '
Hello
',
+);