Saffron 디자인 시스템 적용: 브랜드 컬러 + Pretendard 폰트 + 크림 배경

- CSS 변수 기반 brand-50~950 컬러 팔레트 추가 (Tailwind @theme inline)
- Pretendard Variable 폰트 로드 및 기본 폰트로 설정
- 라이트모드 배경 #FFFAF5 크림색 적용 (다크모드 기본 유지)
- 전체 컴포넌트 orange-* → brand-* 마이그레이션
- 식당 리스트 채널명에 YouTube SVG 아이콘 추가
- 디자인 컨셉 문서 추가 (docs/design-concepts.md)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-03-11 21:15:45 +09:00
parent ec8330a978
commit 50018c17fa
12 changed files with 177 additions and 60 deletions

View File

@@ -600,7 +600,7 @@ export default function Home() {
</div>
<button
onClick={handleReset}
className="p-1.5 text-gray-400 dark:text-gray-500 hover:text-orange-500 dark:hover:text-orange-400 transition-colors"
className="p-1.5 text-gray-400 dark:text-gray-500 hover:text-brand-500 dark:hover:text-brand-400 transition-colors"
title="초기화"
>
<svg viewBox="0 0 24 24" className="w-4.5 h-4.5 fill-current"><path d="M17.65 6.35A7.958 7.958 0 0012 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0112 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
@@ -635,8 +635,8 @@ export default function Home() {
onClick={handleToggleMyReviews}
className={`px-2.5 py-1 text-xs rounded-lg border transition-colors ${
showMyReviews
? "bg-orange-50 dark:bg-orange-900/30 border-orange-300 dark:border-orange-700 text-orange-600 dark:text-orange-400"
: "border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:border-orange-300 hover:text-orange-500"
? "bg-brand-50 dark:bg-brand-900/30 border-brand-300 dark:border-brand-700 text-brand-600 dark:text-brand-400"
: "border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:border-brand-300 hover:text-brand-500"
}`}
>
{showMyReviews ? "✎ 내 리뷰" : "✎ 리뷰"}
@@ -650,7 +650,7 @@ export default function Home() {
{user.avatar_url ? (
<img src={user.avatar_url} alt="" className="w-7 h-7 rounded-full border border-gray-200" />
) : (
<div className="w-7 h-7 rounded-full bg-orange-100 text-orange-700 flex items-center justify-center text-xs font-semibold border border-orange-200">
<div className="w-7 h-7 rounded-full bg-brand-100 text-brand-700 flex items-center justify-center text-xs font-semibold border border-brand-200">
{(user.nickname || user.email || "?").charAt(0).toUpperCase()}
</div>
)}
@@ -684,15 +684,15 @@ export default function Home() {
}}
className={`shrink-0 flex items-center gap-2 rounded-lg px-3 py-1.5 border transition-all text-left ${
channelFilter === ch.channel_name
? "bg-orange-50 dark:bg-orange-900/30 border-orange-300 dark:border-orange-700"
: "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 hover:border-orange-200 dark:hover:border-orange-800"
? "bg-brand-50 dark:bg-brand-900/30 border-brand-300 dark:border-brand-700"
: "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 hover:border-brand-200 dark:hover:border-brand-800"
}`}
style={{ width: "200px" }}
>
<svg className="w-4 h-4 shrink-0 text-red-500" viewBox="0 0 24 24" fill="currentColor"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
<div className="min-w-0 flex-1">
<p className={`text-xs font-semibold truncate ${
channelFilter === ch.channel_name ? "text-orange-600 dark:text-orange-400" : "dark:text-gray-200"
channelFilter === ch.channel_name ? "text-brand-600 dark:text-brand-400" : "dark:text-gray-200"
}`}>{ch.channel_name}</p>
{ch.description && <p className="text-[10px] text-gray-400 dark:text-gray-500 truncate">{ch.description}</p>}
</div>
@@ -710,7 +710,7 @@ export default function Home() {
value={cuisineFilter}
onChange={(e) => { setCuisineFilter(e.target.value); if (e.target.value) setBoundsFilterOn(false); }}
className={`bg-transparent border-none outline-none cursor-pointer pr-1 ${
cuisineFilter ? "text-orange-600 dark:text-orange-400 font-medium" : "text-gray-500 dark:text-gray-400"
cuisineFilter ? "text-brand-600 dark:text-brand-400 font-medium" : "text-gray-500 dark:text-gray-400"
}`}
>
<option value=""></option>
@@ -730,7 +730,7 @@ export default function Home() {
value={priceFilter}
onChange={(e) => { setPriceFilter(e.target.value); if (e.target.value) setBoundsFilterOn(false); }}
className={`bg-transparent border-none outline-none cursor-pointer pr-1 ${
priceFilter ? "text-orange-600 dark:text-orange-400 font-medium" : "text-gray-500 dark:text-gray-400"
priceFilter ? "text-brand-600 dark:text-brand-400 font-medium" : "text-gray-500 dark:text-gray-400"
}`}
>
<option value=""></option>
@@ -741,7 +741,7 @@ export default function Home() {
{(cuisineFilter || priceFilter) && (
<button
onClick={() => { setCuisineFilter(""); setPriceFilter(""); }}
className="text-gray-400 hover:text-orange-500 transition-colors"
className="text-gray-400 hover:text-brand-500 transition-colors"
title="음식 필터 초기화"
>
<svg viewBox="0 0 24 24" className="w-3 h-3 fill-current"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
@@ -755,7 +755,7 @@ export default function Home() {
value={countryFilter}
onChange={(e) => handleCountryChange(e.target.value)}
className={`bg-transparent border-none outline-none cursor-pointer pr-1 ${
countryFilter ? "text-orange-600 dark:text-orange-400 font-medium" : "text-gray-500 dark:text-gray-400"
countryFilter ? "text-brand-600 dark:text-brand-400 font-medium" : "text-gray-500 dark:text-gray-400"
}`}
>
<option value=""></option>
@@ -770,7 +770,7 @@ export default function Home() {
value={cityFilter}
onChange={(e) => handleCityChange(e.target.value)}
className={`bg-transparent border-none outline-none cursor-pointer pr-1 ${
cityFilter ? "text-orange-600 dark:text-orange-400 font-medium" : "text-gray-500 dark:text-gray-400"
cityFilter ? "text-brand-600 dark:text-brand-400 font-medium" : "text-gray-500 dark:text-gray-400"
}`}
>
<option value="">/</option>
@@ -787,7 +787,7 @@ export default function Home() {
value={districtFilter}
onChange={(e) => handleDistrictChange(e.target.value)}
className={`bg-transparent border-none outline-none cursor-pointer pr-1 ${
districtFilter ? "text-orange-600 dark:text-orange-400 font-medium" : "text-gray-500 dark:text-gray-400"
districtFilter ? "text-brand-600 dark:text-brand-400 font-medium" : "text-gray-500 dark:text-gray-400"
}`}
>
<option value="">/</option>
@@ -800,7 +800,7 @@ export default function Home() {
{countryFilter && (
<button
onClick={() => { setCountryFilter(""); setCityFilter(""); setDistrictFilter(""); setRegionFlyTo(null); }}
className="text-gray-400 hover:text-orange-500 transition-colors"
className="text-gray-400 hover:text-brand-500 transition-colors"
title="지역 필터 초기화"
>
<svg viewBox="0 0 24 24" className="w-3 h-3 fill-current"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
@@ -819,7 +819,7 @@ export default function Home() {
setDistrictFilter("");
setRegionFlyTo(null);
}}
className="flex items-center gap-1 rounded-lg px-2 py-1 bg-gray-50 dark:bg-gray-800/50 text-gray-500 dark:text-gray-400 hover:text-orange-500 transition-colors"
className="flex items-center gap-1 rounded-lg px-2 py-1 bg-gray-50 dark:bg-gray-800/50 text-gray-500 dark:text-gray-400 hover:text-brand-500 transition-colors"
>
<svg viewBox="0 0 24 24" className="w-3 h-3 fill-current"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
<span></span>
@@ -850,8 +850,8 @@ export default function Home() {
}}
className={`flex items-center gap-1 rounded-lg px-2 py-1 transition-colors ${
boundsFilterOn
? "bg-orange-50 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400"
: "bg-gray-50 dark:bg-gray-800/50 text-gray-500 dark:text-gray-400 hover:text-orange-500"
? "bg-brand-50 dark:bg-brand-900/30 text-brand-600 dark:text-brand-400"
: "bg-gray-50 dark:bg-gray-800/50 text-gray-500 dark:text-gray-400 hover:text-brand-500"
}`}
title="내 위치 주변 식당만 표시"
>
@@ -879,7 +879,7 @@ export default function Home() {
onClick={handleToggleMyReviews}
className={`px-2 py-0.5 text-[10px] rounded-full border transition-colors ${
showMyReviews
? "bg-orange-50 dark:bg-orange-900/30 border-orange-300 dark:border-orange-700 text-orange-600 dark:text-orange-400"
? "bg-brand-50 dark:bg-brand-900/30 border-brand-300 dark:border-brand-700 text-brand-600 dark:text-brand-400"
: "border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400"
}`}
>
@@ -892,7 +892,7 @@ export default function Home() {
{user.avatar_url ? (
<img src={user.avatar_url} alt="" className="w-7 h-7 rounded-full border border-gray-200" />
) : (
<div className="w-7 h-7 rounded-full bg-orange-100 text-orange-700 flex items-center justify-center text-xs font-semibold border border-orange-200">
<div className="w-7 h-7 rounded-full bg-brand-100 text-brand-700 flex items-center justify-center text-xs font-semibold border border-brand-200">
{(user.nickname || user.email || "?").charAt(0).toUpperCase()}
</div>
)}
@@ -920,7 +920,7 @@ export default function Home() {
}}
className={`shrink-0 rounded-xl px-3 py-2 text-left border transition-all ${
channelFilter === ch.channel_name
? "bg-orange-50 dark:bg-orange-900/30 border-orange-300 dark:border-orange-700"
? "bg-brand-50 dark:bg-brand-900/30 border-brand-300 dark:border-brand-700"
: "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700"
}`}
style={{ minWidth: "140px", maxWidth: "170px" }}
@@ -928,7 +928,7 @@ export default function Home() {
<div className="flex items-center gap-1.5">
<svg className="w-3.5 h-3.5 shrink-0 text-red-500" viewBox="0 0 24 24" fill="currentColor"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
<p className={`text-xs font-semibold truncate ${
channelFilter === ch.channel_name ? "text-orange-600 dark:text-orange-400" : "dark:text-gray-200"
channelFilter === ch.channel_name ? "text-brand-600 dark:text-brand-400" : "dark:text-gray-200"
}`}>{ch.channel_name}</p>
</div>
{ch.description && <p className="text-[10px] text-gray-500 dark:text-gray-400 truncate mt-0.5">{ch.description}</p>}
@@ -952,7 +952,7 @@ export default function Home() {
value={cuisineFilter}
onChange={(e) => { setCuisineFilter(e.target.value); if (e.target.value) setBoundsFilterOn(false); }}
className={`border dark:border-gray-700 rounded-lg px-2 py-1 bg-white dark:bg-gray-800 ${
cuisineFilter ? "text-orange-600 dark:text-orange-400 border-orange-300 dark:border-orange-700" : "text-gray-500 dark:text-gray-400"
cuisineFilter ? "text-brand-600 dark:text-brand-400 border-brand-300 dark:border-brand-700" : "text-gray-500 dark:text-gray-400"
}`}
>
<option value="">🍽 </option>
@@ -971,7 +971,7 @@ export default function Home() {
value={priceFilter}
onChange={(e) => { setPriceFilter(e.target.value); if (e.target.value) setBoundsFilterOn(false); }}
className={`border dark:border-gray-700 rounded-lg px-2 py-1 bg-white dark:bg-gray-800 ${
priceFilter ? "text-orange-600 dark:text-orange-400 border-orange-300 dark:border-orange-700" : "text-gray-500 dark:text-gray-400"
priceFilter ? "text-brand-600 dark:text-brand-400 border-brand-300 dark:border-brand-700" : "text-gray-500 dark:text-gray-400"
}`}
>
<option value="">💰 </option>
@@ -980,7 +980,7 @@ export default function Home() {
))}
</select>
{(cuisineFilter || priceFilter) && (
<button onClick={() => { setCuisineFilter(""); setPriceFilter(""); }} className="text-gray-400 hover:text-orange-500">
<button onClick={() => { setCuisineFilter(""); setPriceFilter(""); }} className="text-gray-400 hover:text-brand-500">
<svg viewBox="0 0 24 24" className="w-3.5 h-3.5 fill-current"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
)}
@@ -992,7 +992,7 @@ export default function Home() {
value={countryFilter}
onChange={(e) => handleCountryChange(e.target.value)}
className={`border dark:border-gray-700 rounded-lg px-2 py-1 bg-white dark:bg-gray-800 ${
countryFilter ? "text-orange-600 dark:text-orange-400 border-orange-300 dark:border-orange-700" : "text-gray-500 dark:text-gray-400"
countryFilter ? "text-brand-600 dark:text-brand-400 border-brand-300 dark:border-brand-700" : "text-gray-500 dark:text-gray-400"
}`}
>
<option value="">🌍 </option>
@@ -1005,7 +1005,7 @@ export default function Home() {
value={cityFilter}
onChange={(e) => handleCityChange(e.target.value)}
className={`border dark:border-gray-700 rounded-lg px-2 py-1 bg-white dark:bg-gray-800 ${
cityFilter ? "text-orange-600 dark:text-orange-400 border-orange-300 dark:border-orange-700" : "text-gray-500 dark:text-gray-400"
cityFilter ? "text-brand-600 dark:text-brand-400 border-brand-300 dark:border-brand-700" : "text-gray-500 dark:text-gray-400"
}`}
>
<option value="">/</option>
@@ -1019,7 +1019,7 @@ export default function Home() {
value={districtFilter}
onChange={(e) => handleDistrictChange(e.target.value)}
className={`border dark:border-gray-700 rounded-lg px-2 py-1 bg-white dark:bg-gray-800 ${
districtFilter ? "text-orange-600 dark:text-orange-400 border-orange-300 dark:border-orange-700" : "text-gray-500 dark:text-gray-400"
districtFilter ? "text-brand-600 dark:text-brand-400 border-brand-300 dark:border-brand-700" : "text-gray-500 dark:text-gray-400"
}`}
>
<option value="">/</option>
@@ -1029,7 +1029,7 @@ export default function Home() {
</select>
)}
{countryFilter && (
<button onClick={() => { setCountryFilter(""); setCityFilter(""); setDistrictFilter(""); setRegionFlyTo(null); }} className="text-gray-400 hover:text-orange-500">
<button onClick={() => { setCountryFilter(""); setCityFilter(""); setDistrictFilter(""); setRegionFlyTo(null); }} className="text-gray-400 hover:text-brand-500">
<svg viewBox="0 0 24 24" className="w-3.5 h-3.5 fill-current"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
)}
@@ -1056,7 +1056,7 @@ export default function Home() {
}}
className={`flex items-center gap-0.5 rounded-lg px-2 py-1 border transition-colors ${
boundsFilterOn
? "bg-orange-50 dark:bg-orange-900/30 border-orange-300 dark:border-orange-700 text-orange-600 dark:text-orange-400"
? "bg-brand-50 dark:bg-brand-900/30 border-brand-300 dark:border-brand-700 text-brand-600 dark:text-brand-400"
: "border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400"
}`}
>
@@ -1135,7 +1135,7 @@ export default function Home() {
activeChannel={channelFilter || undefined}
/>
<div className="absolute top-2 left-2 bg-white/90 dark:bg-gray-900/90 backdrop-blur-sm rounded-lg px-3 py-1.5 shadow-sm z-10">
<span className="text-xs font-medium text-orange-600 dark:text-orange-400">
<span className="text-xs font-medium text-brand-600 dark:text-brand-400">
{filteredRestaurants.length}
</span>
</div>
@@ -1167,7 +1167,7 @@ export default function Home() {
{user.avatar_url ? (
<img src={user.avatar_url} alt="" className="w-12 h-12 rounded-full border border-gray-200" />
) : (
<div className="w-12 h-12 rounded-full bg-orange-100 text-orange-700 flex items-center justify-center text-lg font-semibold border border-orange-200">
<div className="w-12 h-12 rounded-full bg-brand-100 text-brand-700 flex items-center justify-center text-lg font-semibold border border-brand-200">
{(user.nickname || user.email || "?").charAt(0).toUpperCase()}
</div>
)}
@@ -1259,7 +1259,7 @@ export default function Home() {
onClick={() => handleMobileTab(tab.key)}
className={`flex-1 flex flex-col items-center justify-center gap-0.5 py-2 transition-colors ${
mobileTab === tab.key
? "text-orange-600 dark:text-orange-400"
? "text-brand-600 dark:text-brand-400"
: "text-gray-400 dark:text-gray-500"
}`}
>
@@ -1276,10 +1276,10 @@ export default function Home() {
<img
src="/icon.jpg"
alt="SDJ Labs"
className="w-6 h-6 rounded-full border-2 border-orange-200 shadow-sm group-hover:scale-110 group-hover:rotate-12 transition-all duration-300"
className="w-6 h-6 rounded-full border-2 border-brand-200 shadow-sm group-hover:scale-110 group-hover:rotate-12 transition-all duration-300"
/>
<span className="absolute -top-0.5 -right-0.5 w-2 h-2 bg-orange-300 rounded-full animate-ping opacity-75" />
<span className="absolute -top-0.5 -right-0.5 w-2 h-2 bg-orange-400 rounded-full" />
<span className="absolute -top-0.5 -right-0.5 w-2 h-2 bg-brand-300 rounded-full animate-ping opacity-75" />
<span className="absolute -top-0.5 -right-0.5 w-2 h-2 bg-brand-400 rounded-full" />
</div>
<span className="font-medium tracking-wide group-hover:text-gray-600 transition-colors">
SDJ Labs Co., Ltd.