From 4f669523a6be0933b76d562b88372d742c5729d2 Mon Sep 17 00:00:00 2001 From: Jaeho Date: Sat, 9 May 2026 17:57:53 +0900 Subject: [PATCH 01/11] =?UTF-8?q?chore=20:=20path=20=EB=A7=A4=ED=95=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/tsconfig.app.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json index af516fcc..b2a52c6b 100644 --- a/frontend/tsconfig.app.json +++ b/frontend/tsconfig.app.json @@ -22,7 +22,21 @@ "noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noUncheckedSideEffectImports": true, + + /* Path mapping */ + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@/shared/*": ["src/shared/*"], + "@/entities/*": ["src/domain/*"], + "@/features/*": ["src/features/*"], + "@/pages/*": ["src/pages/*"], + "@/app/*": ["src/app/*"], + }, + + + }, "include": ["src"] } From af92f69a076bef372c36253a61c0661b2433f72b Mon Sep 17 00:00:00 2001 From: Jaeho Date: Sat, 9 May 2026 22:06:07 +0900 Subject: [PATCH 02/11] =?UTF-8?q?chore=20:=20react=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 372 +++++++++++++++++++++++++++++++------ frontend/package.json | 4 +- 2 files changed, 321 insertions(+), 55 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 24661ab0..753d3dbb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,8 +8,10 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@tailwindcss/vite": "^4.3.0", "react": "^19.2.4", - "react-dom": "^19.2.4" + "react-dom": "^19.2.4", + "tailwindcss": "^4.3.0" }, "devDependencies": { "@eslint/js": "^9.39.4", @@ -272,7 +274,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -284,7 +285,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -295,7 +295,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -515,7 +514,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -526,7 +524,6 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -537,7 +534,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -547,14 +543,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -565,7 +559,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -582,7 +575,6 @@ "version": "0.122.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -595,7 +587,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -612,7 +603,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -629,7 +619,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -646,7 +635,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -663,7 +651,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -680,7 +667,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -697,7 +683,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -714,7 +699,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -731,7 +715,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -748,7 +731,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -765,7 +747,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -782,7 +763,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -799,7 +779,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -816,7 +795,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -833,7 +811,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -850,11 +827,267 @@ "dev": true, "license": "MIT" }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -879,7 +1112,7 @@ "version": "24.12.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -1487,7 +1720,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -1500,6 +1732,19 @@ "dev": true, "license": "ISC" }, + "node_modules/enhanced-resolve": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.2.tgz", + "integrity": "sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1748,7 +1993,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -1817,7 +2061,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -1864,6 +2107,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1958,6 +2207,15 @@ "dev": true, "license": "ISC" }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2053,7 +2311,6 @@ "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -2086,7 +2343,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2107,7 +2363,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2128,7 +2383,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2149,7 +2403,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2170,7 +2423,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2191,7 +2443,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2212,7 +2463,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2233,7 +2483,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2254,7 +2503,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2275,7 +2523,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2296,7 +2543,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2343,6 +2589,15 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -2367,7 +2622,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -2483,14 +2737,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2503,7 +2755,6 @@ "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2599,7 +2850,6 @@ "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", - "dev": true, "license": "MIT", "dependencies": { "@oxc-project/types": "=0.122.0", @@ -2633,7 +2883,6 @@ "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", - "dev": true, "license": "MIT" }, "node_modules/scheduler": { @@ -2679,7 +2928,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -2711,11 +2959,29 @@ "node": ">=8" } }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -2745,7 +3011,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD", "optional": true }, @@ -2804,7 +3069,7 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -2852,7 +3117,6 @@ "version": "8.0.3", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", - "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", diff --git a/frontend/package.json b/frontend/package.json index 9f2acf6a..3f52e429 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,8 +10,10 @@ "preview": "vite preview" }, "dependencies": { + "@tailwindcss/vite": "^4.3.0", "react": "^19.2.4", - "react-dom": "^19.2.4" + "react-dom": "^19.2.4", + "tailwindcss": "^4.3.0" }, "devDependencies": { "@eslint/js": "^9.39.4", From 488e0e3f4032b377971483fbedffc37baf0046f4 Mon Sep 17 00:00:00 2001 From: Jaeho Date: Sat, 9 May 2026 22:06:34 +0900 Subject: [PATCH 03/11] =?UTF-8?q?chore=20:=20vite=20config=20tailwind=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/vite.config.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 8b0f57b9..bb1d7b51 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,7 +1,14 @@ import { defineConfig } from 'vite' +import path from 'node:path' import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, }) From cfaf2893864436d69d033f76d8b44e230ca5c08d Mon Sep 17 00:00:00 2001 From: Jaeho Date: Sat, 9 May 2026 22:07:53 +0900 Subject: [PATCH 04/11] =?UTF-8?q?chore:=20FE=20=EB=A9=94=ED=83=80=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0,=20=ED=8F=B0=ED=8A=B8=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/index.html | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/index.html b/frontend/index.html index 0fca6f04..c932c402 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,18 @@ - frontend + + + + + + StackUp
From 6cb034caeddb4d644a3db1a345563cab361e8dd2 Mon Sep 17 00:00:00 2001 From: Jaeho Date: Sat, 9 May 2026 22:08:55 +0900 Subject: [PATCH 05/11] =?UTF-8?q?chore:=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EC=A7=84=EC=9E=85=EC=A0=90=20=EA=B5=AC=EC=A1=B0=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/styles/index.css | 3 + frontend/src/app/styles/index.ts | 1 + frontend/src/index.css | 111 ------------------------------ 3 files changed, 4 insertions(+), 111 deletions(-) create mode 100644 frontend/src/app/styles/index.css delete mode 100644 frontend/src/index.css diff --git a/frontend/src/app/styles/index.css b/frontend/src/app/styles/index.css new file mode 100644 index 00000000..1ae42601 --- /dev/null +++ b/frontend/src/app/styles/index.css @@ -0,0 +1,3 @@ +@import 'tailwindcss'; +@import './tokens.css'; +@import './global.css'; diff --git a/frontend/src/app/styles/index.ts b/frontend/src/app/styles/index.ts index e69de29b..6a9a4b13 100644 --- a/frontend/src/app/styles/index.ts +++ b/frontend/src/app/styles/index.ts @@ -0,0 +1 @@ +import './index.css'; diff --git a/frontend/src/index.css b/frontend/src/index.css deleted file mode 100644 index 5fb33130..00000000 --- a/frontend/src/index.css +++ /dev/null @@ -1,111 +0,0 @@ -:root { - --text: #6b6375; - --text-h: #08060d; - --bg: #fff; - --border: #e5e4e7; - --code-bg: #f4f3ec; - --accent: #aa3bff; - --accent-bg: rgba(170, 59, 255, 0.1); - --accent-border: rgba(170, 59, 255, 0.5); - --social-bg: rgba(244, 243, 236, 0.5); - --shadow: - rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; - - --sans: system-ui, 'Segoe UI', Roboto, sans-serif; - --heading: system-ui, 'Segoe UI', Roboto, sans-serif; - --mono: ui-monospace, Consolas, monospace; - - font: 18px/145% var(--sans); - letter-spacing: 0.18px; - color-scheme: light dark; - color: var(--text); - background: var(--bg); - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - @media (max-width: 1024px) { - font-size: 16px; - } -} - -@media (prefers-color-scheme: dark) { - :root { - --text: #9ca3af; - --text-h: #f3f4f6; - --bg: #16171d; - --border: #2e303a; - --code-bg: #1f2028; - --accent: #c084fc; - --accent-bg: rgba(192, 132, 252, 0.15); - --accent-border: rgba(192, 132, 252, 0.5); - --social-bg: rgba(47, 48, 58, 0.5); - --shadow: - rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; - } - - #social .button-icon { - filter: invert(1) brightness(2); - } -} - -#root { - width: 1126px; - max-width: 100%; - margin: 0 auto; - text-align: center; - border-inline: 1px solid var(--border); - min-height: 100svh; - display: flex; - flex-direction: column; - box-sizing: border-box; -} - -body { - margin: 0; -} - -h1, -h2 { - font-family: var(--heading); - font-weight: 500; - color: var(--text-h); -} - -h1 { - font-size: 56px; - letter-spacing: -1.68px; - margin: 32px 0; - @media (max-width: 1024px) { - font-size: 36px; - margin: 20px 0; - } -} -h2 { - font-size: 24px; - line-height: 118%; - letter-spacing: -0.24px; - margin: 0 0 8px; - @media (max-width: 1024px) { - font-size: 20px; - } -} -p { - margin: 0; -} - -code, -.counter { - font-family: var(--mono); - display: inline-flex; - border-radius: 4px; - color: var(--text-h); -} - -code { - font-size: 15px; - line-height: 135%; - padding: 4px 8px; - background: var(--code-bg); -} From 0bc3d51fe6cad916456780110027ccbddb5942c7 Mon Sep 17 00:00:00 2001 From: Jaeho Date: Sat, 9 May 2026 22:16:36 +0900 Subject: [PATCH 06/11] =?UTF-8?q?chore:=20=EA=B8=80=EB=A1=9C=EB=B2=8C=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/styles/global.css | 60 ++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 frontend/src/app/styles/global.css diff --git a/frontend/src/app/styles/global.css b/frontend/src/app/styles/global.css new file mode 100644 index 00000000..8d56d56d --- /dev/null +++ b/frontend/src/app/styles/global.css @@ -0,0 +1,60 @@ +/** + * Global base styles. + * - html/body 기본 + * - 접근성 기본 (focus-visible, prefers-reduced-motion) + * + * Google Fonts (Fira Sans Extra Condensed, Geist, Geist Mono, Inter) 는 + * `index.html` 의 `` 로 로드 (성능상 CSS @import url 보다 권장). + */ + +@layer base { + html { + font-family: var(--font-sans); + color: var(--color-fg); + background-color: var(--color-bg); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + font-synthesis: none; + } + + body { + margin: 0; + min-height: 100svh; + } + + h1, h2, h3 { + font-family: var(--font-heading); + color: var(--color-fg-strong); + text-transform: uppercase; + margin: 0; + } + + h4, h5, h6 { + font-family: var(--font-subheading); + color: var(--color-fg-strong); + margin: 0; + } + + p { + margin: 0; + } + + code, kbd, pre, samp { + font-family: var(--font-mono); + } + + :focus-visible { + outline: 2px solid var(--color-primary); + outline-offset: 2px; + } + + @media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + } +} From 820af095cacdd77417637a92f8fa809f6601a990 Mon Sep 17 00:00:00 2001 From: Jaeho Date: Sat, 9 May 2026 22:16:55 +0900 Subject: [PATCH 07/11] =?UTF-8?q?chore:=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20CSS=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/styles/tokens.css | 184 +++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 frontend/src/app/styles/tokens.css diff --git a/frontend/src/app/styles/tokens.css b/frontend/src/app/styles/tokens.css new file mode 100644 index 00000000..6141ab49 --- /dev/null +++ b/frontend/src/app/styles/tokens.css @@ -0,0 +1,184 @@ +/** + * StackUp Design Tokens — Tailwind CSS v4 `@theme` + * + * 토큰 추가/변경 시 `docs/design-system.md` 도 함께 갱신. + * 컴포넌트는 토큰만 참조하고 색상·간격·타이포그래피 하드코딩 금지. + */ + +@theme { + /* ========================================================= + * 1. Color — Base + * ========================================================= */ + --color-white: #ffffff; + --color-black: #000000; + --color-background: #e9e8e7; + + /* ========================================================= + * 2. Color — Sage / Neutral Scale (Main Theme) + * bg-sage-500, text-sage-800, border-sage-200 ... + * ========================================================= */ + --color-sage-50: #e8e7e1; + --color-sage-100: #d4cfcb; + --color-sage-200: #c9ccc8; + --color-sage-300: #b4bdaf; + --color-sage-400: #a0a89d; + --color-sage-500: #626e5c; + --color-sage-600: #3e4739; + --color-sage-700: #2b3625; + --color-sage-800: #1f271b; + --color-sage-900: #181e15; + --color-sage-950: #141a11; + + /* ========================================================= + * 3. Color — Semantic Aliases + * bg-primary, text-fg, text-fg-muted, border-border ... + * ========================================================= */ + --color-bg: var(--color-background); + --color-surface: var(--color-sage-50); + --color-surface-raised: var(--color-white); + --color-border: var(--color-sage-100); + --color-border-strong: var(--color-sage-200); + + --color-fg: var(--color-sage-950); + --color-fg-strong: var(--color-sage-800); + --color-fg-muted: var(--color-sage-400); + --color-fg-subtle: var(--color-sage-300); + --color-fg-disabled: var(--color-sage-200); + --color-fg-on-primary: var(--color-white); + + --color-primary: var(--color-sage-500); + --color-primary-hover: var(--color-sage-600); + --color-primary-pressed: var(--color-sage-700); + + /* ========================================================= + * 4. Typography — Font Families + * font-heading, font-subheading, font-sans + * ========================================================= */ + --font-heading: + 'Fira Sans Extra Condensed', 'Pretendard Variable', system-ui, sans-serif; + --font-display: + 'Fira Sans Extra Condensed', 'Pretendard Variable', system-ui, sans-serif; + --font-subheading: + 'Geist', 'Pretendard Variable', system-ui, sans-serif; + --font-sans: + 'Inter', 'Pretendard Variable', system-ui, sans-serif; + --font-body: + 'Inter', 'Pretendard Variable', system-ui, sans-serif; + --font-mono: + 'Geist Mono', ui-monospace, 'JetBrains Mono', Consolas, monospace; + + /* ========================================================= + * 5. Typography — Text Scale + * text-display, text-h1 ... text-caption + * (각 토큰은 size + line-height + letter-spacing + weight 페어) + * + * 반응형: 모바일 (< lg) 에서는 *-mobile 변형을 사용. + * ex)

+ * ========================================================= */ + + /* Display (H1) — 100px 데스크탑 / 48px 모바일 */ + --text-display: 100px; + --text-display--line-height: 1; + --text-display--letter-spacing: -1px; + --text-display--font-weight: 700; + + --text-display-mobile: 48px; + --text-display-mobile--line-height: 1.05; + --text-display-mobile--letter-spacing: -1px; + --text-display-mobile--font-weight: 700; + + /* H2 (Section) */ + --text-h2: 56px; + --text-h2--line-height: 1.05; + --text-h2--letter-spacing: -0.2px; + --text-h2--font-weight: 700; + + --text-h2-mobile: 42px; + --text-h2-mobile--line-height: 1.1; + --text-h2-mobile--letter-spacing: -0.2px; + --text-h2-mobile--font-weight: 700; + + /* H3 (Card / Block) */ + --text-h3: 38px; + --text-h3--line-height: 1.1; + --text-h3--letter-spacing: -0.2px; + --text-h3--font-weight: 700; + + /* H4 */ + --text-h4: 32px; + --text-h4--line-height: 1.2; + --text-h4--letter-spacing: -0.8px; + --text-h4--font-weight: 700; + + /* H5 */ + --text-h5: 24px; + --text-h5--line-height: 1.3; + --text-h5--letter-spacing: -0.2px; + --text-h5--font-weight: 700; + + /* H6 */ + --text-h6: 20px; + --text-h6--line-height: 1.4; + --text-h6--letter-spacing: -0.1px; + --text-h6--font-weight: 700; + + /* Rich text (큰 본문 / 강조) */ + --text-rich: 20px; + --text-rich--line-height: 1.5; + --text-rich--font-weight: 500; + + /* Body 1 (기본 본문) */ + --text-body: 16px; + --text-body--line-height: 1.2; + --text-body--letter-spacing: -0.01em; + --text-body--font-weight: 500; + + /* Body 2 / Button (작은 본문 / 버튼) */ + --text-button: 14px; + --text-button--line-height: 1.2; + --text-button--font-weight: 600; + + /* Caption */ + --text-caption: 12px; + --text-caption--line-height: 1.4; + --text-caption--font-weight: 400; + + /* ========================================================= + * 6. Radius + * ========================================================= */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; + --radius-2xl: 24px; + --radius-pill: 9999px; + + /* ========================================================= + * 7. Shadow / Elevation + * ========================================================= */ + --shadow-sm: 0 1px 2px rgba(31, 39, 27, 0.06); + --shadow-md: + 0 4px 6px -1px rgba(31, 39, 27, 0.10), + 0 2px 4px -2px rgba(31, 39, 27, 0.06); + --shadow-lg: + 0 10px 15px -3px rgba(31, 39, 27, 0.10), + 0 4px 6px -4px rgba(31, 39, 27, 0.05); + --shadow-focus-ring: 0 0 0 3px rgba(98, 110, 92, 0.45); + + /* ========================================================= + * 8. Motion + * ========================================================= */ + --ease-standard: cubic-bezier(0.4, 0, 0.2, 1); + --ease-decelerate: cubic-bezier(0, 0, 0.2, 1); + --ease-accelerate: cubic-bezier(0.4, 0, 1, 1); + + --duration-fast: 120ms; + --duration-normal: 200ms; + --duration-slow: 320ms; + + /* ========================================================= + * 9. Breakpoints (Tailwind v4 default 유지 + 확장 시 여기에) + * Tailwind v4 는 sm/md/lg/xl/2xl 을 기본 제공. + * 필요 시 --breakpoint-* 로 재정의. + * ========================================================= */ +} From 438ec2e289961efbff75c6a786052571538ac654 Mon Sep 17 00:00:00 2001 From: Jaeho Date: Sat, 9 May 2026 22:17:46 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat=20:=20App=EC=97=90=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EB=8D=B0=EB=AA=A8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.css | 184 -------- frontend/src/App.tsx | 961 +++++++++++++++++++++++++++++++++++++----- frontend/src/main.tsx | 2 +- 3 files changed, 853 insertions(+), 294 deletions(-) delete mode 100644 frontend/src/App.css diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index f90339d8..00000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,184 +0,0 @@ -.counter { - font-size: 16px; - padding: 5px 10px; - border-radius: 5px; - color: var(--accent); - background: var(--accent-bg); - border: 2px solid transparent; - transition: border-color 0.3s; - margin-bottom: 24px; - - &:hover { - border-color: var(--accent-border); - } - &:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; - } -} - -.hero { - position: relative; - - .base, - .framework, - .vite { - inset-inline: 0; - margin: 0 auto; - } - - .base { - width: 170px; - position: relative; - z-index: 0; - } - - .framework, - .vite { - position: absolute; - } - - .framework { - z-index: 1; - top: 34px; - height: 28px; - transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) - scale(1.4); - } - - .vite { - z-index: 0; - top: 107px; - height: 26px; - width: auto; - transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) - scale(0.8); - } -} - -#center { - display: flex; - flex-direction: column; - gap: 25px; - place-content: center; - place-items: center; - flex-grow: 1; - - @media (max-width: 1024px) { - padding: 32px 20px 24px; - gap: 18px; - } -} - -#next-steps { - display: flex; - border-top: 1px solid var(--border); - text-align: left; - - & > div { - flex: 1 1 0; - padding: 32px; - @media (max-width: 1024px) { - padding: 24px 20px; - } - } - - .icon { - margin-bottom: 16px; - width: 22px; - height: 22px; - } - - @media (max-width: 1024px) { - flex-direction: column; - text-align: center; - } -} - -#docs { - border-right: 1px solid var(--border); - - @media (max-width: 1024px) { - border-right: none; - border-bottom: 1px solid var(--border); - } -} - -#next-steps ul { - list-style: none; - padding: 0; - display: flex; - gap: 8px; - margin: 32px 0 0; - - .logo { - height: 18px; - } - - a { - color: var(--text-h); - font-size: 16px; - border-radius: 6px; - background: var(--social-bg); - display: flex; - padding: 6px 12px; - align-items: center; - gap: 8px; - text-decoration: none; - transition: box-shadow 0.3s; - - &:hover { - box-shadow: var(--shadow); - } - .button-icon { - height: 18px; - width: 18px; - } - } - - @media (max-width: 1024px) { - margin-top: 20px; - flex-wrap: wrap; - justify-content: center; - - li { - flex: 1 1 calc(50% - 8px); - } - - a { - width: 100%; - justify-content: center; - box-sizing: border-box; - } - } -} - -#spacer { - height: 88px; - border-top: 1px solid var(--border); - @media (max-width: 1024px) { - height: 48px; - } -} - -.ticks { - position: relative; - width: 100%; - - &::before, - &::after { - content: ''; - position: absolute; - top: -4.5px; - border: 5px solid transparent; - } - - &::before { - left: 0; - border-left-color: var(--border); - } - &::after { - right: 0; - border-right-color: var(--border); - } -} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 46a59920..dc1ff51d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,121 +1,864 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from './assets/vite.svg' -import heroImg from './assets/hero.png' -import './App.css' - -function App() { - const [count, setCount] = useState(0) - - return ( - <> -
-
- - React logo - Vite logo +import { useState, type ReactNode, type CSSProperties } from 'react' + +type SectionId = + | 'overview' + | 'colors' + | 'typography' + | 'radius' + | 'shadow' + | 'spacing' + | 'motion' + +const NAV: { id: SectionId; label: string }[] = [ + { id: 'overview', label: 'Overview' }, + { id: 'colors', label: 'Colors' }, + { id: 'typography', label: 'Typography' }, + { id: 'radius', label: 'Radius' }, + { id: 'shadow', label: 'Shadow' }, + { id: 'spacing', label: 'Spacing' }, + { id: 'motion', label: 'Motion' }, +] + +const PRETENDARD: CSSProperties = { + fontFamily: "'Pretendard Variable', 'Pretendard', system-ui, sans-serif", +} + +const JETBRAINS: CSSProperties = { + fontFamily: "'JetBrains Mono', ui-monospace, monospace", +} + +// ========================================================= +// Color Data +// ========================================================= +type ColorToken = { token: string; hex: string; alias?: string } + +const SAGE_SCALE: ColorToken[] = [ + { token: 'sage-50', hex: '#e8e7e1' }, + { token: 'sage-100', hex: '#d4cfcb' }, + { token: 'sage-200', hex: '#c9ccc8' }, + { token: 'sage-300', hex: '#b4bdaf' }, + { token: 'sage-400', hex: '#a0a89d' }, + { token: 'sage-500', hex: '#626e5c' }, + { token: 'sage-600', hex: '#3e4739' }, + { token: 'sage-700', hex: '#2b3625' }, + { token: 'sage-800', hex: '#1f271b' }, + { token: 'sage-900', hex: '#181e15' }, + { token: 'sage-950', hex: '#141a11' }, +] + +const NEW_BASE: ColorToken[] = [ + { token: 'background', hex: '#e9e8e7' }, + { token: 'white', hex: '#ffffff' }, + { token: 'black', hex: '#000000' }, +] + +const NEW_SEMANTIC: ColorToken[] = [ + { token: 'primary', hex: '#626e5c', alias: 'sage-500' }, + { token: 'primary-hover', hex: '#3e4739', alias: 'sage-600' }, + { token: 'primary-pressed', hex: '#2b3625', alias: 'sage-700' }, + { token: 'fg', hex: '#141a11', alias: 'sage-950' }, + { token: 'fg-strong', hex: '#1f271b', alias: 'sage-800' }, + { token: 'fg-muted', hex: '#a0a89d', alias: 'sage-400' }, + { token: 'surface', hex: '#e8e7e1', alias: 'sage-50' }, + { token: 'border', hex: '#d4cfcb', alias: 'sage-100' }, +] + +const OLD_BRAND: ColorToken[] = [ + { token: 'brand-primary', hex: '#2563EB' }, + { token: 'brand-primary-hover', hex: '#1D4ED8' }, + { token: 'brand-primary-pressed', hex: '#1E40AF' }, + { token: 'brand-secondary', hex: '#10B981' }, +] + +const OLD_SEMANTIC: ColorToken[] = [ + { token: 'success', hex: '#10B981' }, + { token: 'warning', hex: '#F59E0B' }, + { token: 'danger', hex: '#EF4444' }, + { token: 'info', hex: '#3B82F6' }, +] + +const OLD_NEUTRAL: ColorToken[] = [ + { token: 'bg', hex: '#FFFFFF' }, + { token: 'surface', hex: '#F9FAFB' }, + { token: 'border', hex: '#E5E7EB' }, + { token: 'text-primary', hex: '#111827' }, + { token: 'text-secondary', hex: '#6B7280' }, +] + +const OLD_DOMAIN: ColorToken[] = [ + { token: 'job-frontend', hex: '#22D3EE' }, + { token: 'job-backend', hex: '#A78BFA' }, + { token: 'job-infra', hex: '#FB7185' }, + { token: 'job-dba', hex: '#FACC15' }, + { token: 'type-personality', hex: '#34D399' }, + { token: 'type-technical', hex: '#60A5FA' }, + { token: 'type-live-coding', hex: '#F472B6' }, + { token: 'type-integrated', hex: '#A78BFA' }, +] + +// ========================================================= +// Type Data +// ========================================================= +type NewType = { + token: string + family: string + size: string + weight: number + className: string +} +type OldType = { + token: string + family: string + size: string + weight: number + style: CSSProperties +} + +const NEW_TYPE: NewType[] = [ + { + token: 'text-display', + family: 'Fira Sans Extra Condensed', + size: '100 / 1.0', + weight: 700, + className: 'font-display text-display-mobile lg:text-display uppercase', + }, + { + token: 'text-h2', + family: 'Fira Sans Extra Condensed', + size: '56 / 1.05', + weight: 700, + className: 'font-heading text-h2-mobile lg:text-h2 uppercase', + }, + { + token: 'text-h3', + family: 'Fira Sans Extra Condensed', + size: '38 / 1.1', + weight: 700, + className: 'font-heading text-h3 uppercase', + }, + { + token: 'text-h4', + family: 'Geist', + size: '32 / 1.2', + weight: 700, + className: 'font-subheading text-h4', + }, + { + token: 'text-h5', + family: 'Geist', + size: '24 / 1.3', + weight: 700, + className: 'font-subheading text-h5', + }, + { + token: 'text-h6', + family: 'Geist', + size: '20 / 1.4', + weight: 700, + className: 'font-subheading text-h6', + }, + { + token: 'text-rich', + family: 'Geist', + size: '20 / 1.5', + weight: 500, + className: 'font-subheading text-rich', + }, + { + token: 'text-body', + family: 'Inter', + size: '16 / 1.2', + weight: 500, + className: 'font-sans text-body', + }, + { + token: 'text-button', + family: 'Inter', + size: '14 / 1.2', + weight: 600, + className: 'font-sans text-button', + }, + { + token: 'text-caption', + family: 'Geist', + size: '12 / 1.4', + weight: 400, + className: 'font-subheading text-caption', + }, +] + +const OLD_TYPE: OldType[] = [ + { + token: '--type-display', + family: 'Pretendard', + size: '48 / 56', + weight: 700, + style: { ...PRETENDARD, fontSize: 48, lineHeight: '56px', fontWeight: 700 }, + }, + { + token: '--type-h1', + family: 'Pretendard', + size: '32 / 40', + weight: 700, + style: { ...PRETENDARD, fontSize: 32, lineHeight: '40px', fontWeight: 700 }, + }, + { + token: '--type-h2', + family: 'Pretendard', + size: '24 / 32', + weight: 700, + style: { ...PRETENDARD, fontSize: 24, lineHeight: '32px', fontWeight: 700 }, + }, + { + token: '--type-h3', + family: 'Pretendard', + size: '20 / 28', + weight: 600, + style: { ...PRETENDARD, fontSize: 20, lineHeight: '28px', fontWeight: 600 }, + }, + { + token: '--type-h4', + family: 'Pretendard', + size: '18 / 26', + weight: 600, + style: { ...PRETENDARD, fontSize: 18, lineHeight: '26px', fontWeight: 600 }, + }, + { + token: '--type-body-lg', + family: 'Pretendard', + size: '16 / 24', + weight: 400, + style: { ...PRETENDARD, fontSize: 16, lineHeight: '24px', fontWeight: 400 }, + }, + { + token: '--type-body-md', + family: 'Pretendard', + size: '14 / 20', + weight: 400, + style: { ...PRETENDARD, fontSize: 14, lineHeight: '20px', fontWeight: 400 }, + }, + { + token: '--type-body-sm', + family: 'Pretendard', + size: '12 / 18', + weight: 400, + style: { ...PRETENDARD, fontSize: 12, lineHeight: '18px', fontWeight: 400 }, + }, + { + token: '--type-code-md', + family: 'JetBrains Mono', + size: '14 / 20', + weight: 400, + style: { ...JETBRAINS, fontSize: 14, lineHeight: '20px', fontWeight: 400 }, + }, +] + +// ========================================================= +// Helpers +// ========================================================= +function luminance(hex: string): number { + const h = hex.replace('#', '') + const r = parseInt(h.substring(0, 2), 16) / 255 + const g = parseInt(h.substring(2, 4), 16) / 255 + const b = parseInt(h.substring(4, 6), 16) / 255 + const lin = (v: number) => + v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4) + return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b) +} + +function readableFg(hex: string): string { + return luminance(hex) > 0.55 ? '#1f271b' : '#ffffff' +} + +// ========================================================= +// Layout Components +// ========================================================= +function Sidebar({ + active, + onSelect, +}: { + active: SectionId + onSelect: (id: SectionId) => void +}) { + return ( + + ) +} + +function PageHeader() { + return ( +
+
+ frontend / app/styles +
+

+ Design Tokens +

+

+ 새로 정의한 Tailwind v4 디자인 시스템과{' '} + + docs/design-system.md + {' '} + 의 기존 스펙을 토큰 단위로 비교한다. +

+
+ ) +} + +function Section({ + id, + label, + title, + description, + children, +}: { + id: SectionId + label: string + title: string + description: string + children: ReactNode +}) { + return ( +
+
+
+ {label} +
+

+ {title} +

+

{description}

+
+ {children} +
+ ) +} + +function CompareRow({ children }: { children: ReactNode }) { + return ( +
{children}
+ ) +} + +function Panel({ + tone, + label, + sublabel, + children, +}: { + tone: 'new' | 'old' + label: string + sublabel?: string + children: ReactNode +}) { + return ( +
+
+ {label} + {sublabel && ( + + {sublabel} + + )} +
+
{children}
+
+ ) +} + +function MutedLabel({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ) +} + +function Swatch({ + token, + hex, + note, +}: { + token: string + hex: string + note?: string +}) { + return ( +
+
+ {hex.toUpperCase()} +
+
+
+ {token} +
+ {note && ( +
{note}
+ )} +
+
+ ) +} + +function KeyValueRow({ k, v }: { k: string; v: string }) { + return ( +
+
{k}
+
{v}
+
+ ) +} + +// ========================================================= +// Sections +// ========================================================= +function OverviewSection() { + return ( +
+ + +
+ + + + + + +
+
+ +
+ + + + + + +
+
+
+
+ ) +} + +function ColorsSection() { + return ( +
+
+
+

+ Sage Scale (NEW · 11단계) +

+
+ {SAGE_SCALE.map((c) => ( + + ))} +
+ + + + Base +
+ {NEW_BASE.map((c) => ( + + ))} +
+ Semantic Aliases +
+ {NEW_SEMANTIC.map((c) => ( + + ))} +
+
+ + Brand +
+ {OLD_BRAND.map((c) => ( + + ))} +
+ Semantic +
+ {OLD_SEMANTIC.map((c) => ( + + ))} +
+ Neutral +
+ {OLD_NEUTRAL.map((c) => ( + + ))} +
+
+
+
-

Get started

-

- Edit src/App.tsx and save to test HMR +

+ Domain Colors{' '} + + (OLD only — 새 시스템 매핑 미결) + +

+

+ 직군 / 면접 유형 컬러는 docs/design-system.md §2.1 에만 존재. Sage + 모노크로매틱과 어떻게 매핑할지 별도 결정 필요.

+
+ {OLD_DOMAIN.map((c) => ( + + ))} +
-
+
+ ) +} + +function TypographySection() { + const SAMPLE = 'StackUp · 빠른 면접, 깊은 피드백' + return ( +
+ + - Count is {count} - -
- -
- -
-
- -

Documentation

-

Your questions, answered

-
-
- -

Connect with us

-

Join the Vite community

-
+ ) +} + +function RadiusSection() { + const items = [ + { token: 'sm', value: '4px', cls: 'rounded-sm' }, + { token: 'md', value: '8px', cls: 'rounded-md' }, + { token: 'lg', value: '12px', cls: 'rounded-lg' }, + { token: 'xl', value: '16px', cls: 'rounded-xl' }, + { token: '2xl', value: '24px', cls: 'rounded-2xl' }, + { token: 'pill', value: '9999px', cls: 'rounded-pill' }, + ] + return ( +
+ + +
+ {items.map((r) => ( +
+
+
{r.cls}
+
+ {r.value} +
+
+ ))} +
+ + +
+ + + + + +
+
+ 동일 5단계. 새 시스템에 2xl{' '} + (24px) 추가됨. +
+
+ +
+ ) +} + +function ShadowSample({ name, cls }: { name: string; cls: string }) { + return ( +
+
+
{name}
+
+ ) +} + +function ShadowSection() { + return ( +
+ + +
+ + + + +
+
+ +
+ + + + +
+
+
+
+ ) +} + +function SpacingSection() { + const scale = [1, 2, 3, 4, 5, 6, 8, 10, 12, 16] + return ( +
+ +
+ {scale.map((n) => ( +
+
+ space-{n} +
+
+
+ {n * 4}px +
+
+ ))}
-
+ +
+ ) +} + +function MotionDemo() { + const [hover, setHover] = useState(false) + return ( +
setHover(true)} + onMouseLeave={() => setHover(false)} + > +
+
+ hover to play (200ms · ease-standard) +
+
+ ) +} -
-
- +function MotionSection() { + return ( +
+ + +
+ + + + + + +
+
+ Live Preview + +
+
+ +
+ + + + + + +
+
+ 값 동일. 키 명만 --easing-* →{' '} + --ease-* 로 단순화. +
+
+
+
) } -export default App +// ========================================================= +// App +// ========================================================= +export default function App() { + const [active, setActive] = useState('overview') + + return ( +
+
+ +
+
+ + + + + + + + +
+
+
+
+ ) +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index bef5202a..037249fa 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,6 +1,6 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' -import './index.css' +import '@/app/styles' import App from './App.tsx' createRoot(document.getElementById('root')!).render( From 19c4924456308e9637010b6e1166a624e68c36cd Mon Sep 17 00:00:00 2001 From: Jaeho Date: Sat, 9 May 2026 22:38:17 +0900 Subject: [PATCH 09/11] =?UTF-8?q?docs:=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EB=AC=B8=EC=84=9C=20=EA=B0=9C?= =?UTF-8?q?=ED=8E=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/design-system.md | 554 ++++++++++++++++++++++++++---------------- 1 file changed, 339 insertions(+), 215 deletions(-) diff --git a/docs/design-system.md b/docs/design-system.md index 3e73f35e..152703a3 100644 --- a/docs/design-system.md +++ b/docs/design-system.md @@ -1,292 +1,416 @@ # 디자인 시스템 -> StackUp 프론트엔드의 시각·행동 규약. Figma 토큰과 1:1로 동기화된다. -> 구현 위치: `frontend/src/app/styles/`, `frontend/src/shared/ui/` +> StackUp 프론트엔드 시각·행동 규약. **Tailwind CSS v4 기반 단일 출처(SSOT).** +> 구현 — [`frontend/src/app/styles/`](../frontend/src/app/styles/) (`tokens.css`, `global.css`, `index.css`) +> 데모 페이지 — [`frontend/src/App.tsx`](../frontend/src/App.tsx) (`npm run dev` → `http://localhost:5173`) +> 컴포넌트 — [`frontend/src/shared/ui/`](../frontend/src/shared/ui/) (도메인 비종속), [`frontend/src/features/*/ui/`](../frontend/src/features/) (도메인 종속) --- ## 1. 디자인 원칙 -1. **신뢰감 우선** — 면접 도구이므로 가벼운 톤 지양. 진중하고 차분한 시각 언어. -2. **집중 환경 보호** — 면접 화면은 노이즈 최소화. 핵심 UI만 노출, 부가 정보는 hover/click으로 점진적 공개. -3. **상태 가시성** — 분석 진행, 세션 진행 등 비동기 상태는 항상 명시적으로 보여준다. -4. **접근성 (WCAG 2.1 AA)** — 키보드 only 조작 가능, 명도 대비 4.5:1 이상. -5. **모바일 웹뷰 대응** — 데스크탑 우선이지만 mobile breakpoint에서도 깨지지 않게. +1. **신뢰감 우선** — 면접 도구이므로 가벼운 톤 지양. 진중·차분한 시각 언어. +2. **집중 환경 보호** — 면접 화면은 노이즈 최소화. 핵심 UI만 노출, 부가 정보는 hover/click 으로 점진적 공개. +3. **상태 가시성** — 분석 진행, 세션 상태, SSE 이벤트 등 비동기 상태는 항상 명시적으로 표현. +4. **모노크로매틱 + 의미 컬러** — Sage 단일 베이스로 톤 일관성을 유지하고, Status / Domain 만 muted jewel tone 으로 식별성 부여. +5. **접근성 (WCAG 2.1 AA)** — 키보드 only 조작, 명도 대비 4.5:1 이상, focus ring 명확. +6. **모바일 웹뷰 대응** — 데스크탑 우선이지만 mobile breakpoint(`< lg`)에서도 깨지지 않게. --- -## 2. 디자인 토큰 - -### 2.1 컬러 - -#### Brand -| 토큰 | Light | Dark | 용도 | -|------|-------|------|------| -| `--color-brand-primary` | `#2563EB` | `#3B82F6` | CTA, 활성 상태 | -| `--color-brand-primary-hover` | `#1D4ED8` | `#60A5FA` | hover | -| `--color-brand-primary-pressed` | `#1E40AF` | `#2563EB` | active | -| `--color-brand-secondary` | `#10B981` | `#34D399` | 성공·완료 | - -#### Semantic -| 토큰 | Light | Dark | 용도 | -|------|-------|------|------| -| `--color-success` | `#10B981` | `#34D399` | 성공 메시지, ANALYZED 뱃지 | -| `--color-warning` | `#F59E0B` | `#FBBF24` | 경고, PROCESSING | -| `--color-danger` | `#EF4444` | `#F87171` | 에러, FAILED, 삭제 | -| `--color-info` | `#3B82F6` | `#60A5FA` | 정보 토스트 | - -#### Neutral -| 토큰 | 16진 | 용도 | -|------|------|------| -| `--color-bg` | `#FFFFFF` / `#0B0F19` | 페이지 배경 | -| `--color-surface` | `#F9FAFB` / `#111827` | 카드, 모달 | -| `--color-surface-raised` | `#FFFFFF` / `#1F2937` | 모달, 팝오버 | -| `--color-border` | `#E5E7EB` / `#374151` | 보더 | -| `--color-text-primary` | `#111827` / `#F9FAFB` | 본문 | -| `--color-text-secondary` | `#6B7280` / `#9CA3AF` | 부가 정보 | -| `--color-text-disabled` | `#D1D5DB` / `#4B5563` | 비활성 | - -#### 도메인 컬러 (직군 / 면접 유형) -| 토큰 | 색상 | -|------|------| -| `--color-job-frontend` | `#22D3EE` (cyan) | -| `--color-job-backend` | `#A78BFA` (violet) | -| `--color-job-infra` | `#FB7185` (rose) | -| `--color-job-dba` | `#FACC15` (amber) | -| `--color-type-personality` | `#34D399` | -| `--color-type-technical` | `#60A5FA` | -| `--color-type-live-coding` | `#F472B6` | -| `--color-type-integrated` | `#A78BFA` | - -### 2.2 타이포그래피 - -폰트 스택: -```css ---font-sans: 'Pretendard Variable', 'Pretendard', -apple-system, BlinkMacSystemFont, system-ui, sans-serif; ---font-mono: 'JetBrains Mono', 'D2Coding', 'Menlo', monospace; -``` +## 2. 디자인 토큰 — 단일 출처 -| 토큰 | 크기 / 라인높이 / 굵기 | 용도 | -|------|------------------------|------| -| `--type-display` | 48 / 56 / 700 | 랜딩 히어로 | -| `--type-h1` | 32 / 40 / 700 | 페이지 제목 | -| `--type-h2` | 24 / 32 / 700 | 섹션 제목 | -| `--type-h3` | 20 / 28 / 600 | 카드 제목 | -| `--type-h4` | 18 / 26 / 600 | 서브 제목 | -| `--type-body-lg` | 16 / 24 / 400 | 본문 (기본) | -| `--type-body-md` | 14 / 20 / 400 | 보조 본문 | -| `--type-body-sm` | 12 / 18 / 400 | 캡션, 메타 | -| `--type-code-md` | 14 / 20 / 400 (mono) | 코드 | +모든 토큰은 [`frontend/src/app/styles/tokens.css`](../frontend/src/app/styles/tokens.css) 의 `@theme` 블록에만 정의. 컴포넌트는 토큰만 참조하고 **하드코딩 금지** (`bg-[#626e5c]` ✗ → `bg-primary` ✓). -### 2.3 간격 (Spacing) — 4px grid +### 2.1 컬러 — 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 | -| `--space-16` | 64px | - -### 2.4 라디우스 +| 토큰 | Hex | Tailwind | 용도 | +|---|---|---|---| +| `--color-white` | `#ffffff` | `bg-white`, `text-white` | 순백 | +| `--color-black` | `#000000` | `bg-black` | 순흑 | +| `--color-background` | `#e9e8e7` | `bg-background` (= `bg-bg`) | 앱 기본 배경 | + +### 2.2 컬러 — Sage Scale (메인 테마, 11 단계) + +| 토큰 | Hex | 권장 용도 | +|---|---|---| +| `sage-50` | `#e8e7e1` | 가장 밝은 컴포넌트 배경 (= `surface`) | +| `sage-100` | `#d4cfcb` | 분리선 / 보더 (= `border`) | +| `sage-200` | `#c9ccc8` | 비활성 텍스트 / 보조 배경 (= `border-strong`, `fg-disabled`) | +| `sage-300` | `#b4bdaf` | 보조 텍스트 (= `fg-subtle`) | +| `sage-400` | `#a0a89d` | 보조 텍스트 강조 (= `fg-muted`) | +| `sage-500` | `#626e5c` | **Primary**, 활성 / 포커스 | +| `sage-600` | `#3e4739` | Primary hover | +| `sage-700` | `#2b3625` | Primary pressed / 강조 컴포넌트 | +| `sage-800` | `#1f271b` | 주요 헤딩 (= `fg-strong`) | +| `sage-900` | `#181e15` | 본문 (대안) | +| `sage-950` | `#141a11` | 가장 어두운 텍스트 (= `fg`, 기본) | -| 토큰 | 값 | 용도 | -|------|----|------| -| `--radius-sm` | 4px | 뱃지 | -| `--radius-md` | 8px | 버튼, 인풋 | -| `--radius-lg` | 12px | 카드 | -| `--radius-xl` | 16px | 모달 | -| `--radius-pill` | 9999px | 칩, 아바타 | +Tailwind 사용: `bg-sage-{n}`, `text-sage-{n}`, `border-sage-{n}`. -### 2.5 그림자 (Elevation) +### 2.3 컬러 — Semantic Aliases -| 토큰 | 값 | -|------|----| -| `--shadow-sm` | `0 1px 2px rgba(0,0,0,0.06)` | -| `--shadow-md` | `0 4px 6px -1px rgba(0,0,0,0.10), 0 2px 4px -2px rgba(0,0,0,0.06)` | -| `--shadow-lg` | `0 10px 15px -3px rgba(0,0,0,0.10), 0 4px 6px -4px rgba(0,0,0,0.05)` | -| `--shadow-focus-ring` | `0 0 0 3px rgba(37,99,235,0.45)` | +`sage-*` 직접 참조보다 의미 별칭 사용을 권장 (다크 모드 도입 시 자동 대응). + +| 토큰 | 매핑 | Tailwind | +|---|---|---| +| `--color-bg` | `background` | `bg-bg` | +| `--color-surface` | `sage-50` | `bg-surface` | +| `--color-surface-raised` | `white` | `bg-surface-raised` | +| `--color-border` | `sage-100` | `border-border` | +| `--color-border-strong` | `sage-200` | `border-border-strong` | +| `--color-fg` | `sage-950` | `text-fg` | +| `--color-fg-strong` | `sage-800` | `text-fg-strong` | +| `--color-fg-muted` | `sage-400` | `text-fg-muted` | +| `--color-fg-subtle` | `sage-300` | `text-fg-subtle` | +| `--color-fg-disabled` | `sage-200` | `text-fg-disabled` | +| `--color-fg-on-primary` | `white` | `text-fg-on-primary` | +| `--color-primary` | `sage-500` | `bg-primary`, `text-primary` | +| `--color-primary-hover` | `sage-600` | `bg-primary-hover` | +| `--color-primary-pressed` | `sage-700` | `bg-primary-pressed` | + +### 2.4 컬러 — Status + +상태 시각화. Sage 톤과 조화되는 muted jewel tones. + +| 카테고리 | -50 (연한 배경) | -500 (기본) | -700 (강조) | 단축 | +|---|---|---|---|---| +| Success | `#e8efe1` | `#5b7c47` | `#3f5731` | `bg-success`, `text-success` | +| Warning | `#f4e8d4` | `#b88840` | `#8a6529` | `bg-warning`, `text-warning` | +| Danger | `#f4e0d8` | `#a8503c` | `#803a2a` | `bg-danger`, `text-danger` | +| Info | `#dde4ea` | `#4d6878` | `#36475a` | `bg-info`, `text-info` | + +**조합 규칙**: +- 솔리드 뱃지: `bg-success text-white` +- 연한 뱃지: `bg-success-50 text-success-700` +- 인라인 텍스트 강조: `text-success-700` + +### 2.5 컬러 — Domain (Jobs / Interview Types) + +StackUp 도메인 한정 컬러. `features/*/ui` 의 도메인 뱃지에서만 사용 (`shared/ui` 에서 직접 참조 금지). + +#### 직군 -### 2.6 모션 +| 직군 | Hex | Tailwind | +|---|---|---| +| Frontend | `#5e8a98` (teal) | `bg-job-frontend` | +| Backend | `#7d6c93` (plum) | `bg-job-backend` | +| Infra | `#b06c70` (rose) | `bg-job-infra` | +| DBA | `#b89c5e` (gold) | `bg-job-dba` | + +#### 면접 유형 + +| 유형 | Hex | Tailwind | +|---|---|---| +| Personality | `#6f9978` (mint) | `bg-type-personality` | +| Technical | `#6c8294` (slate) | `bg-type-technical` | +| Live Coding | `#a87385` (mauve) | `bg-type-live-coding` | +| Integrated | `#8a7896` (violet)| `bg-type-integrated` | + +### 2.6 타이포그래피 — 폰트 패밀리 (3-Tier) + +| 토큰 | 폰트 | 용도 | Tailwind | +|---|---|---|---| +| `--font-heading` / `--font-display` | Fira Sans Extra Condensed | H1~H3 (Uppercase, 임팩트) | `font-heading`, `font-display` | +| `--font-subheading` | Geist | H4~H6, Caption (모던) | `font-subheading` | +| `--font-sans` / `--font-body` | Inter | 본문, 버튼 (가독성) | `font-sans`, `font-body` | +| `--font-mono` | Geist Mono | 코드 / 숫자 모노 | `font-mono` | + +폰트 로딩은 [`frontend/index.html`](../frontend/index.html) 의 `` (Google Fonts). + +### 2.7 타이포그래피 — 텍스트 스케일 + +각 토큰은 `font-size + line-height + letter-spacing + font-weight` 페어. 반응형은 `*-mobile` 변형. + +| Tailwind | Size / LH | Weight | 폰트 카테고리 | 용도 | +|---|---|---|---|---| +| `text-display` (`text-display-mobile`) | 100/1.0 (48/1.05) | 700 | Heading | 랜딩 / 페이지 타이틀 | +| `text-h2` (`text-h2-mobile`) | 56/1.05 (42/1.1) | 700 | Heading | 섹션 제목 | +| `text-h3` | 38/1.1 | 700 | Heading | 카드 / 블록 제목 | +| `text-h4` | 32/1.2 | 700 | Subheading | 큰 서브 헤딩 | +| `text-h5` | 24/1.3 | 700 | Subheading | 중간 서브 헤딩 | +| `text-h6` | 20/1.4 | 700 | Subheading | 작은 서브 헤딩 | +| `text-rich` | 20/1.5 | 500 | Subheading | 큰 본문 / 강조 | +| `text-body` | 16/1.2 | 500 | Sans | 기본 본문 | +| `text-button` | 14/1.2 | 600 | Sans | 버튼 / 작은 본문 | +| `text-caption` | 12/1.4 | 400 | Subheading | 캡션 / 메타 | + +> **반응형 헤딩 사용법**: `

` + +> **계층 가이드**: 한 페이지에 `text-display` 1개, `text-h2`/`text-h3` 는 섹션 단위. 카드 안 헤더는 `text-h5`/`text-h6` 우선. + +### 2.8 간격 (Spacing — 4px Grid) + +Tailwind v4 기본 `--spacing: 0.25rem` (= 4px) 사용. `p-4` = `16px`. + +| 토큰 | 값 | +|---|---| +| `space-1` | 4px | +| `space-2` | 8px | +| `space-3` | 12px | +| `space-4` | 16px | +| `space-5` | 20px | +| `space-6` | 24px | +| `space-8` | 32px | +| `space-10` | 40px | +| `space-12` | 48px | +| `space-16` | 64px | + +권장 — 위 10개 값 우선. 임의 값(`p-[13px]`)은 디자인 의도가 분명할 때만. + +### 2.9 라디우스 + +| 토큰 | 값 | Tailwind | 용도 | +|---|---|---|---| +| `--radius-sm` | 4px | `rounded-sm` | 뱃지 | +| `--radius-md` | 8px | `rounded-md` | 버튼, 인풋 | +| `--radius-lg` | 12px | `rounded-lg` | 카드 | +| `--radius-xl` | 16px | `rounded-xl` | 모달 | +| `--radius-2xl` | 24px | `rounded-2xl` | 큰 모달 / Hero | +| `--radius-pill` | 9999px | `rounded-pill` | 칩, 아바타, 토글 | + +### 2.10 그림자 / Elevation (Sage-tint) + +| 토큰 | Tailwind | 값 | +|---|---|---| +| `--shadow-sm` | `shadow-sm` | `0 1px 2px rgba(31,39,27,.06)` | +| `--shadow-md` | `shadow-md` | `0 4px 6px -1px rgba(31,39,27,.10), 0 2px 4px -2px rgba(31,39,27,.06)` | +| `--shadow-lg` | `shadow-lg` | `0 10px 15px -3px rgba(31,39,27,.10), 0 4px 6px -4px rgba(31,39,27,.05)` | +| `--shadow-focus-ring` | `shadow-focus-ring` | `0 0 0 3px rgba(98,110,92,.45)` | + +### 2.11 모션 + +| 토큰 | 값 | Tailwind | 용도 | +|---|---|---|---| +| `--duration-fast` | 120ms | `duration-fast` | hover, focus | +| `--duration-normal` | 200ms | `duration-normal` | 기본 transition | +| `--duration-slow` | 320ms | `duration-slow` | 모달 enter / exit | +| `--ease-standard` | `cubic-bezier(0.4,0,0.2,1)` | `ease-standard` | 기본 | +| `--ease-decelerate` | `cubic-bezier(0,0,0.2,1)` | `ease-decelerate` | enter | +| `--ease-accelerate` | `cubic-bezier(0.4,0,1,1)` | `ease-accelerate` | exit | + +> `prefers-reduced-motion: reduce` 사용자에게는 모든 transition / animation 0.01ms 강제 (`global.css` 에서 처리). + +### 2.12 Z-Index + +레이어 충돌 방지용 정수 스케일. Tailwind 자동 utility 미생성 (`--z-*` 는 namespace 가 아님) → CSS var 직접 사용. | 토큰 | 값 | 용도 | -|------|----|------| -| `--duration-fast` | 120ms | hover, focus | -| `--duration-normal` | 200ms | 기본 transition | -| `--duration-slow` | 320ms | 모달 enter/exit | -| `--easing-standard` | `cubic-bezier(0.4, 0, 0.2, 1)` | 기본 | -| `--easing-decelerate` | `cubic-bezier(0, 0, 0.2, 1)` | enter | -| `--easing-accelerate` | `cubic-bezier(0.4, 0, 1, 1)` | exit | +|---|---|---| +| `--z-base` | 0 | 기본 | +| `--z-raised` | 10 | 카드 hover, 떠오름 | +| `--z-dropdown` | 1000 | Select, Menu | +| `--z-sticky` | 1100 | Sticky TopNav | +| `--z-modal-backdrop` | 1200 | 모달 dim | +| `--z-modal` | 1300 | 모달 본체 | +| `--z-popover` | 1400 | Popover | +| `--z-tooltip` | 1500 | Tooltip | +| `--z-toast` | 1600 | Toast (가장 위) | + +```tsx +
...
+// or +
...
+``` + +### 2.13 컨테이너 / Breakpoint + +| 토큰 | 값 | Tailwind | 용도 | +|---|---|---|---| +| `--container-readable` | 65ch | `max-w-readable` | 본문 / 프로즈 카드 | +| `--container-content` | 1280px | `max-w-content` | TopNav 안쪽 메인 영역 | +| `--container-app` | 1440px | `max-w-app` | 앱 전체 최대 폭 | -> `prefers-reduced-motion` 사용자에게는 모든 transition을 0ms로 강제. +**Breakpoints** — Tailwind v4 default 사용: +- `sm: 640px`, `md: 768px`, `lg: 1024px`, `xl: 1280px`, `2xl: 1536px`. -### 2.7 Breakpoint +### 2.14 아이콘 -| 토큰 | min-width | 용도 | -|------|-----------|------| -| `--bp-sm` | 640px | 모바일 가로 | -| `--bp-md` | 768px | 태블릿 | -| `--bp-lg` | 1024px | 데스크탑 | -| `--bp-xl` | 1280px | 대형 모니터 | +- 라이브러리 — **Lucide Icons** (`lucide-react` 도입 예정, 트리쉐이킹 지원). +- 크기 — `16 / 20 / 24 px` (line-height 와 정렬). +- 색상 — `currentColor` (텍스트 색 상속). Tailwind: `text-fg`, `text-fg-muted` 적용. +- 의미 있는 아이콘은 `aria-label` 필수, 장식용은 `aria-hidden="true"`. --- ## 3. 컴포넌트 인벤토리 -> 위치: `frontend/src/shared/ui/{ComponentName}/`. 각 컴포넌트는 `index.ts`로 export. +위치: [`frontend/src/shared/ui/{Name}/`](../frontend/src/shared/ui/) (도메인 비종속) 또는 [`frontend/src/features/*/ui/`](../frontend/src/features/) (도메인 종속). 각 컴포넌트는 `index.ts` 로 public API. ### Foundation -- `Button` — variant: `primary | secondary | ghost | danger`, size: `sm | md | lg`, state: `loading | disabled` -- `IconButton` — 아이콘 전용, aria-label 필수 -- `Link` — 인라인/블록, 외부 링크는 자동으로 `target="_blank" rel="noopener"` -- `Input` — text/email/url, prefix·suffix slot, 에러 메시지 슬롯 -- `Textarea` — auto-grow 옵션 -- `Select` — 단일 선택 (네이티브 select 래핑) -- `Combobox` — 검색·자동완성 (Repository 등록 등) -- `Checkbox`, `Radio`, `Switch` -- `FileUploader` — Drag & Drop, 진행률, 파일 검증 메시지 +- `Button` — variant: `primary | secondary | ghost | danger` · size: `sm | md | lg` · state: `loading | disabled`. +- `IconButton` — 아이콘 전용, `aria-label` 필수. +- `Link` — 인라인 / 블록, 외부 링크는 자동 `target="_blank" rel="noopener"`. +- `Input`, `Textarea`, `Select`, `Combobox` (검색·자동완성). +- `Checkbox`, `Radio`, `Switch`. +- `FileUploader` — Drag & Drop, 진행률, 파일 검증 메시지. ### Display -- `Badge` — 상태 표시 (READY/IN_PROGRESS/COMPLETED/FAILED 색상 매핑) -- `Tag` / `Chip` — 기술 스택 등 다중 라벨 -- `Avatar` — GitHub avatar -- `Progress` — 선형 / 원형 -- `Skeleton` — 로딩 placeholder -- `EmptyState` — 빈 목록 안내 (아이콘 + 설명 + CTA) -- `Card` — `header / body / footer` slot +- `Badge` — 상태 매핑 (§4 참조). +- `Tag` / `Chip` — 기술 스택 등 다중 라벨. +- `Avatar` — GitHub avatar. +- `Progress` — 선형 / 원형. +- `Skeleton` — 로딩 placeholder (4-state §5). +- `EmptyState` — 빈 목록 안내, 아이콘 + 설명 + CTA (4-state §5). +- `Card` — `header / body / footer` slot. ### Feedback -- `Toast` — 4종 (success/info/warning/error), 우상단 stack, 4초 자동 dismiss -- `Modal` — `sm / md / lg / fullscreen`, focus trap 필수 -- `Drawer` — 우측 슬라이드, 세션 설정 등 -- `Popover`, `Tooltip` — 키보드 접근 가능 -- `ConfirmDialog` — 파괴적 액션 (삭제, 회원 탈퇴) 전용 -- `Alert` — 페이지 내 inline 경고 +- `Toast` — 4종 (success / info / warning / error), 우상단 stack, 4초 자동 dismiss, `z-toast`. +- `Modal` — `sm / md / lg / fullscreen`, focus trap 필수, `z-modal` + `z-modal-backdrop`. +- `Drawer` — 우측 슬라이드, 세션 설정 등. +- `Popover`, `Tooltip` — 키보드 접근 가능. +- `ConfirmDialog` — 파괴적 액션(삭제, 회원 탈퇴) 전용. +- `Alert` — 페이지 inline 경고. ### Navigation -- `TopNav` — 로고, 글로벌 액션, Avatar 드롭다운 -- `SideNav` — Workspace 좌측 메뉴 -- `Tabs` — `Underline | Pills` -- `Breadcrumb` -- `Pagination` +- `TopNav` — 로고, 글로벌 액션, Avatar 드롭다운, 64px height, sticky, `z-sticky`. +- `SideNav` — Workspace 좌측 메뉴, 240px width. +- `Tabs` — `Underline | Pills`. +- `Breadcrumb`, `Pagination`. + +### Domain-aware (`features/*/ui` 에 위치) +- `JobCategoryBadge` — 직군별 컬러 (§2.5). +- `InterviewTypeBadge` — 면접 유형별 컬러 (§2.5). +- `SessionStatusBadge` — 상태 매핑 (§4). +- `AnalysisStateIndicator` — `QUEUED / PROCESSING / COMPLETED / FAILED` 4단계 progress. + +> **shared vs features 구분** — 도메인 의존이 있으면 `features`, 디자인 토큰만 참조하면 `shared`. + +--- + +## 4. 상태 ↔ 컬러 매핑 -### Domain-aware (`shared/ui` 가 아닌 `features/*/ui`로 위치) -- `JobCategoryBadge` — 직군별 컬러 -- `InterviewTypeBadge` — 면접 유형별 컬러 -- `SessionStatusBadge` -- `AnalysisStateIndicator` — QUEUED/PROCESSING/COMPLETED/FAILED 4단계 progress +| 도메인 상태 | 시각 컬러 | 토큰 | 컴포넌트 예 | +|---|---|---|---| +| `READY` / `PENDING` / `QUEUED` | neutral | `text-fg-muted` (sage-400) | 회색 Badge | +| `IN_PROGRESS` / `PROCESSING` / `ANALYZING` | warning | `bg-warning-50 text-warning-700` | 노란 Badge + spinner | +| `COMPLETED` / `ANALYZED` / `ACTIVE` | success | `bg-success-50 text-success-700` | 초록 Badge | +| `INTERRUPTED` | warning | `bg-warning-50 text-warning-700` | 노란 Badge (느낌표 아이콘) | +| `FAILED` | danger | `bg-danger-50 text-danger-700` | 빨간 Badge + 재시도 | +| `CANCELLED` / `ARCHIVED` | disabled | `text-fg-disabled` | 회색 strikethrough | -> **shared vs features 구분**: 도메인 의존성이 있으면 features로. 디자인 토큰만 사용하면 shared로. +**구현 컨벤션** — `frontend/src/shared/ui/StatusBadge` 단일 컴포넌트가 모든 매핑을 캡슐화. 새 상태 추가 시 이 컴포넌트만 수정. --- -## 4. 상태별 색상 매핑 (Status Color Map) +## 5. 4-State UI 패턴 (비동기) + +서버 데이터의 4가지 상태별 UI 분기. [`frontend/src/shared/lib/AsyncBoundary`](../frontend/src/shared/lib/AsyncBoundary/) 로 일원화. -| 상태 | 색상 | 컴포넌트 예시 | -|------|------|----------------| -| `READY` / `PENDING` / `QUEUED` | neutral / `text-secondary` | 회색 Badge | -| `IN_PROGRESS` / `PROCESSING` / `ANALYZING` | warning | 노란 Badge + spinner | -| `COMPLETED` / `ANALYZED` / `ACTIVE` | success | 초록 Badge | -| `INTERRUPTED` | warning | 노란 Badge (느낌표) | -| `FAILED` | danger | 빨간 Badge + 재시도 버튼 | -| `CANCELLED` / `ARCHIVED` | text-disabled | 회색 strikethrough | +| 상태 | UI | 컴포넌트 | +|---|---|---| +| **Loading (Pending)** | shape-mimicking placeholder | `Skeleton` | +| **Empty** | 아이콘 + 설명 + CTA | `EmptyState` | +| **Error (Rejected)** | 메시지 + 재시도 버튼 | `Alert` (또는 `rejectedFallback`) | +| **Success** | 정상 컨텐츠 | (각 페이지 자체) | + +```tsx +} + rejectedFallback={({ error, reset }) => ( + + )} +> + + +``` -**구현 컨벤션**: `frontend/src/shared/ui/StatusBadge` 단일 컴포넌트가 모든 매핑을 캡슐화. 새 상태 추가 시 이 컴포넌트만 수정. +상세: [`frontend/src/shared/CLAUDE.md`](../frontend/src/shared/CLAUDE.md), [`docs/ui-patterns.md`](./ui-patterns.md). --- -## 5. 페이지 레이아웃 그리드 +## 6. 페이지 레이아웃 ``` ┌────────────────────────────────────────────────────┐ -│ TopNav (height: 64px, sticky) │ +│ TopNav (height: 64px, sticky, z-sticky) │ ├──────────┬─────────────────────────────────────────┤ │ │ │ -│ SideNav │ Main Content │ -│ (240px) │ - max-width: 1280px │ -│ │ - padding: 24px (mobile: 16px) │ +│ SideNav │ Main Content │ +│ (240px) │ - max-w-content (1280px) │ +│ │ - px-6 lg:px-12, py-12 │ │ │ │ └──────────┴─────────────────────────────────────────┘ ``` 특수 페이지: -- **Login**: SideNav 없음, 중앙 정렬 카드 -- **Interview**: SideNav 숨김(focus mode), 좌측 면접관, 우측 답변 영역, 하단 컨트롤 -- **Feedback Report**: TopNav만, 인쇄 친화적 레이아웃 (`@media print`) +- **Login** — SideNav 없음, 중앙 정렬 카드. +- **Interview** — focus mode (TopNav 숨김 옵션), 좌측 면접관, 우측 답변 영역, 하단 컨트롤. +- **Feedback Report** — TopNav 만, 인쇄 친화 (`@media print`). --- -## 6. 다크 모드 +## 7. 다크 모드 (Phase 2 예정) -- `` 로 토글 -- `prefers-color-scheme` 자동 감지 + 사용자 명시 선택 우선 -- 토큰만 갱신 (컴포넌트 코드 수정 불필요) -- 면접 진행 중에는 다크 모드 강제 (눈 피로 감소) — Phase 2에서 검토 +- `` 토글. +- `prefers-color-scheme` 자동 감지 + 사용자 명시 선택 우선. +- **토큰만 갱신** (컴포넌트 코드 수정 X) — `[data-theme='dark'] { --color-bg: ...; ... }`. +- 면접 진행 중 다크 모드 강제 옵션 검토 (눈 피로 감소). + +다크 토큰 정의는 Phase 2 진입 시 [`frontend/src/app/styles/tokens.css`](../frontend/src/app/styles/tokens.css) 에 추가. --- -## 7. 아이콘 +## 8. 토큰 → Tailwind 매핑 빠른 참조 + +| CSS Variable | Tailwind Utility | +|---|---| +| `--color-sage-{50..950}` | `bg-sage-{n}`, `text-sage-{n}`, `border-sage-{n}` | +| `--color-primary` / `--color-fg` / `--color-bg` | `bg-primary` / `text-fg` / `bg-bg` | +| `--color-success` / `--color-success-50` / `--color-success-700` | `bg-success` / `bg-success-50` / `text-success-700` | +| `--color-job-frontend` | `bg-job-frontend`, `text-job-frontend` | +| `--font-heading` / `--font-sans` / `--font-mono` | `font-heading` / `font-sans` / `font-mono` | +| `--text-display` (+ paired LH / letter / weight) | `text-display` | +| `--radius-md` / `--radius-pill` | `rounded-md` / `rounded-pill` | +| `--shadow-md` / `--shadow-focus-ring` | `shadow-md` / `shadow-focus-ring` | +| `--duration-normal` / `--ease-standard` | `duration-normal` / `ease-standard` | +| `--container-content` / `--container-readable` | `max-w-content` / `max-w-readable` | +| `--z-modal` / `--z-toast` | `z-[var(--z-modal)]` (namespace 미생성) | -- 라이브러리: **Lucide Icons** (트리쉐이킹 지원) -- 크기: 16/20/24px (line height와 정렬) -- 색상: `currentColor` (텍스트 색상 상속) -- 의미 있는 아이콘은 `aria-label` 필수, 장식용은 `aria-hidden="true"` +**원칙** — 컴포넌트는 utility class 만 사용. `bg-[#626e5c]` 같은 직접 hex 사용 금지. --- -## 8. 토큰 → 코드 매핑 - -`frontend/src/app/styles/tokens.css`: -```css -:root { - /* 위 표의 모든 토큰을 CSS 변수로 정의 */ - --color-brand-primary: #2563EB; - --space-4: 16px; - /* ... */ -} - -[data-theme='dark'] { - --color-brand-primary: #3B82F6; - /* ... 다크모드 오버라이드 */ -} -``` +## 9. 접근성 체크리스트 (모든 컴포넌트) -컴포넌트는 토큰만 참조: -```tsx -// 좋음 -