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:
85
frontend/docs/design-concepts.md
Normal file
85
frontend/docs/design-concepts.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Tasteby Design Concept 후보
|
||||
|
||||
> Oracle의 Redwood처럼, Tasteby만의 디자인 언어를 정의하기 위한 컨셉 후보안.
|
||||
|
||||
---
|
||||
|
||||
## 1. Saffron (사프란) 🟠
|
||||
|
||||
따뜻한 금빛 오렌지. 고급스러운 미식 큐레이션 느낌.
|
||||
|
||||
| 역할 | 색상 | Hex |
|
||||
|------|------|-----|
|
||||
| Primary | 깊은 오렌지 | `#E8720C` |
|
||||
| Primary Light | 밝은 오렌지 | `#F59E3F` |
|
||||
| Primary Dark | 진한 오렌지 | `#C45A00` |
|
||||
| Accent | 골드 | `#F5A623` |
|
||||
| Accent Light | 라이트 골드 | `#FFD080` |
|
||||
| Background | 크림 화이트 | `#FFFAF5` |
|
||||
| Surface | 웜 그레이 | `#F7F3EF` |
|
||||
| Text Primary | 다크 브라운 | `#2C1810` |
|
||||
| Text Secondary | 미디엄 브라운 | `#7A6555` |
|
||||
|
||||
**키워드**: 프리미엄, 미식, 큐레이션, 따뜻함, 신뢰
|
||||
**어울리는 폰트**: Pretendard, Noto Sans KR (깔끔 + 웜톤 배경)
|
||||
|
||||
---
|
||||
|
||||
## 2. Gochujang (고추장) 🔴
|
||||
|
||||
한국 음식 DNA. 약간 붉은 오렌지 톤으로 대담하고 강렬.
|
||||
|
||||
| 역할 | 색상 | Hex |
|
||||
|------|------|-----|
|
||||
| Primary | 고추장 레드 | `#D94F30` |
|
||||
| Primary Light | 밝은 레드 | `#EF7B5A` |
|
||||
| Primary Dark | 진한 레드 | `#B53518` |
|
||||
| Accent | 따뜻한 오렌지 | `#FF8C42` |
|
||||
| Accent Light | 라이트 피치 | `#FFB88C` |
|
||||
| Background | 소프트 화이트 | `#FFFBF8` |
|
||||
| Surface | 웜 베이지 | `#F5F0EB` |
|
||||
| Text Primary | 차콜 | `#1A1A1A` |
|
||||
| Text Secondary | 다크 그레이 | `#666052` |
|
||||
|
||||
**키워드**: 한국, 활기, 식욕, 대담, 강렬
|
||||
**어울리는 폰트**: Spoqa Han Sans Neo, Pretendard (모던 + 힘있는)
|
||||
|
||||
---
|
||||
|
||||
## 3. Citrus (시트러스) 🍊
|
||||
|
||||
밝고 상큼한 비비드 오렌지. 현대적이고 친근한 느낌.
|
||||
|
||||
| 역할 | 색상 | Hex |
|
||||
|------|------|-----|
|
||||
| Primary | 비비드 오렌지 | `#FF6B2B` |
|
||||
| Primary Light | 라이트 오렌지 | `#FF9A6C` |
|
||||
| Primary Dark | 딥 오렌지 | `#E04D10` |
|
||||
| Accent | 피치 | `#FFB347` |
|
||||
| Accent Light | 소프트 피치 | `#FFD9A0` |
|
||||
| Background | 퓨어 화이트 | `#FFFFFF` |
|
||||
| Surface | 쿨 그레이 | `#F5F5F7` |
|
||||
| Text Primary | 뉴트럴 블랙 | `#171717` |
|
||||
| Text Secondary | 미디엄 그레이 | `#6B7280` |
|
||||
|
||||
**키워드**: 캐주얼, 트렌디, 활발, 친근, 상큼
|
||||
**어울리는 폰트**: Geist (현재 사용 중), Inter
|
||||
|
||||
---
|
||||
|
||||
## 현재 상태 (Before)
|
||||
|
||||
- Tailwind 기본 `orange` 팔레트 사용 (커스텀 없음)
|
||||
- 폰트: Geist (Google Fonts)
|
||||
- 다크모드: `prefers-color-scheme` 기반 자동 전환
|
||||
- 브랜드 컬러 정의 없음 — 컴포넌트마다 `orange-400~700` 개별 적용
|
||||
|
||||
## 적용 계획
|
||||
|
||||
1. 컨셉 선택
|
||||
2. CSS 변수로 디자인 토큰 정의 (`globals.css`)
|
||||
3. Tailwind v4 `@theme` 에 커스텀 컬러 등록
|
||||
4. 컴포넌트별 하드코딩된 orange → 시맨틱 토큰으로 교체
|
||||
5. 다크모드 팔레트 정의
|
||||
6. 폰트 교체 (필요시)
|
||||
7. 로고/아이콘 톤 맞춤
|
||||
@@ -808,7 +808,7 @@ function VideosPanel({ isAdmin }: { isAdmin: boolean }) {
|
||||
<button
|
||||
onClick={() => startBulkStream("transcript")}
|
||||
disabled={bulkTranscripting || bulkExtracting}
|
||||
className="bg-orange-600 text-white px-4 py-2 rounded text-sm hover:bg-orange-700 disabled:opacity-50"
|
||||
className="bg-brand-600 text-white px-4 py-2 rounded text-sm hover:bg-brand-700 disabled:opacity-50"
|
||||
>
|
||||
{bulkTranscripting ? "자막 수집 중..." : "벌크 자막 수집"}
|
||||
</button>
|
||||
@@ -836,7 +836,7 @@ function VideosPanel({ isAdmin }: { isAdmin: boolean }) {
|
||||
<button
|
||||
onClick={startRemapFoods}
|
||||
disabled={remappingFoods || bulkExtracting || bulkTranscripting || rebuildingVectors || remappingCuisine}
|
||||
className="bg-orange-600 text-white px-4 py-2 rounded text-sm hover:bg-orange-700 disabled:opacity-50"
|
||||
className="bg-brand-600 text-white px-4 py-2 rounded text-sm hover:bg-brand-700 disabled:opacity-50"
|
||||
>
|
||||
{remappingFoods ? "메뉴태그 재생성 중..." : "메뉴태그 재생성"}
|
||||
</button>
|
||||
@@ -849,7 +849,7 @@ function VideosPanel({ isAdmin }: { isAdmin: boolean }) {
|
||||
<button
|
||||
onClick={() => startBulkStream("transcript", Array.from(selected))}
|
||||
disabled={bulkTranscripting || bulkExtracting}
|
||||
className="bg-orange-500 text-white px-4 py-2 rounded text-sm hover:bg-orange-600 disabled:opacity-50"
|
||||
className="bg-brand-500 text-white px-4 py-2 rounded text-sm hover:bg-brand-600 disabled:opacity-50"
|
||||
>
|
||||
선택 자막 수집 ({selected.size})
|
||||
</button>
|
||||
@@ -1073,7 +1073,7 @@ function VideosPanel({ isAdmin }: { isAdmin: boolean }) {
|
||||
</h4>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2 mb-2">
|
||||
<div
|
||||
className="bg-orange-500 h-2 rounded-full transition-all"
|
||||
className="bg-brand-500 h-2 rounded-full transition-all"
|
||||
style={{ width: `${foodsProgress.total ? (foodsProgress.current / foodsProgress.total) * 100 : 0}%` }}
|
||||
/>
|
||||
</div>
|
||||
@@ -1515,7 +1515,7 @@ function VideosPanel({ isAdmin }: { isAdmin: boolean }) {
|
||||
{r.foods_mentioned.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mt-2">
|
||||
{r.foods_mentioned.map((f, j) => (
|
||||
<span key={j} className="px-1.5 py-0.5 bg-orange-50 text-orange-700 rounded text-xs">{f}</span>
|
||||
<span key={j} className="px-1.5 py-0.5 bg-brand-50 text-brand-700 rounded text-xs">{f}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -1769,7 +1769,7 @@ function RestaurantsPanel({ isAdmin }: { isAdmin: boolean }) {
|
||||
finally { setBulkTabling(false); load(); }
|
||||
}}
|
||||
disabled={bulkTabling}
|
||||
className="px-3 py-1.5 text-xs bg-orange-500 text-white rounded hover:bg-orange-600 disabled:opacity-50"
|
||||
className="px-3 py-1.5 text-xs bg-brand-500 text-white rounded hover:bg-brand-600 disabled:opacity-50"
|
||||
>
|
||||
{bulkTabling ? `테이블링 검색 중 (${bulkTablingProgress.current}/${bulkTablingProgress.total})` : "벌크 테이블링 연결"}
|
||||
</button>
|
||||
@@ -1825,13 +1825,13 @@ function RestaurantsPanel({ isAdmin }: { isAdmin: boolean }) {
|
||||
</span>
|
||||
</div>
|
||||
{bulkTabling && bulkTablingProgress.name && (
|
||||
<div className="bg-orange-50 rounded p-3 mb-4 text-sm">
|
||||
<div className="bg-brand-50 rounded p-3 mb-4 text-sm">
|
||||
<div className="flex justify-between mb-1">
|
||||
<span>{bulkTablingProgress.current}/{bulkTablingProgress.total} - {bulkTablingProgress.name}</span>
|
||||
<span className="text-xs text-gray-500">연결: {bulkTablingProgress.linked} / 미발견: {bulkTablingProgress.notFound}</span>
|
||||
</div>
|
||||
<div className="w-full bg-orange-200 rounded-full h-1.5">
|
||||
<div className="bg-orange-500 h-1.5 rounded-full transition-all" style={{ width: `${(bulkTablingProgress.current / bulkTablingProgress.total) * 100}%` }} />
|
||||
<div className="w-full bg-brand-200 rounded-full h-1.5">
|
||||
<div className="bg-brand-500 h-1.5 rounded-full transition-all" style={{ width: `${(bulkTablingProgress.current / bulkTablingProgress.total) * 100}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1987,7 +1987,7 @@ function RestaurantsPanel({ isAdmin }: { isAdmin: boolean }) {
|
||||
finally { setTablingSearching(false); }
|
||||
}}
|
||||
disabled={tablingSearching}
|
||||
className="px-2 py-0.5 text-[11px] bg-orange-500 text-white rounded hover:bg-orange-600 disabled:opacity-50"
|
||||
className="px-2 py-0.5 text-[11px] bg-brand-500 text-white rounded hover:bg-brand-600 disabled:opacity-50"
|
||||
>
|
||||
{tablingSearching ? "검색 중..." : "테이블링 검색"}
|
||||
</button>
|
||||
|
||||
@@ -1,15 +1,37 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--background: #FFFAF5;
|
||||
--foreground: #171717;
|
||||
--brand-50: #FFF8F0;
|
||||
--brand-100: #FFEDD5;
|
||||
--brand-200: #FFD6A5;
|
||||
--brand-300: #FFBC72;
|
||||
--brand-400: #F5A623;
|
||||
--brand-500: #F59E3F;
|
||||
--brand-600: #E8720C;
|
||||
--brand-700: #C45A00;
|
||||
--brand-800: #9A4500;
|
||||
--brand-900: #6B3000;
|
||||
--brand-950: #3D1A00;
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist);
|
||||
--color-brand-50: var(--brand-50);
|
||||
--color-brand-100: var(--brand-100);
|
||||
--color-brand-200: var(--brand-200);
|
||||
--color-brand-300: var(--brand-300);
|
||||
--color-brand-400: var(--brand-400);
|
||||
--color-brand-500: var(--brand-500);
|
||||
--color-brand-600: var(--brand-600);
|
||||
--color-brand-700: var(--brand-700);
|
||||
--color-brand-800: var(--brand-800);
|
||||
--color-brand-900: var(--brand-900);
|
||||
--color-brand-950: var(--brand-950);
|
||||
--font-sans: var(--font-pretendard), var(--font-geist), system-ui, sans-serif;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist } from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
import "./globals.css";
|
||||
import { Providers } from "./providers";
|
||||
|
||||
@@ -8,6 +9,14 @@ const geist = Geist({
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const pretendard = localFont({
|
||||
src: [
|
||||
{ path: "../fonts/PretendardVariable.woff2", style: "normal" },
|
||||
],
|
||||
variable: "--font-pretendard",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Tasteby - YouTube Restaurant Map",
|
||||
description: "YouTube food channel restaurant map service",
|
||||
@@ -20,7 +29,7 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="ko" className="dark:bg-gray-950" suppressHydrationWarning>
|
||||
<body className={`${geist.variable} font-sans antialiased`}>
|
||||
<body className={`${pretendard.variable} ${geist.variable} font-sans antialiased`}>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function LoginMenu({ onGoogleSuccess }: LoginMenuProps) {
|
||||
<>
|
||||
<button
|
||||
onClick={() => setOpen(true)}
|
||||
className="px-3 py-1.5 text-sm font-medium text-gray-600 dark:text-gray-300 hover:text-orange-600 dark:hover:text-orange-400 border border-gray-300 dark:border-gray-600 hover:border-orange-400 dark:hover:border-orange-500 rounded-lg transition-colors"
|
||||
className="px-3 py-1.5 text-sm font-medium text-gray-600 dark:text-gray-300 hover:text-brand-600 dark:hover:text-brand-400 border border-gray-300 dark:border-gray-600 hover:border-brand-400 dark:hover:border-brand-500 rounded-lg transition-colors"
|
||||
>
|
||||
로그인
|
||||
</button>
|
||||
|
||||
@@ -231,7 +231,7 @@ export default function MapView({ restaurants, selected, onSelectRestaurant, onB
|
||||
{onMyLocation && (
|
||||
<button
|
||||
onClick={onMyLocation}
|
||||
className="absolute top-2 right-2 w-9 h-9 bg-white dark:bg-gray-900 rounded-lg shadow-md flex items-center justify-center text-gray-600 dark:text-gray-300 hover:text-orange-500 dark:hover:text-orange-400 transition-colors z-10"
|
||||
className="absolute top-2 right-2 w-9 h-9 bg-white dark:bg-gray-900 rounded-lg shadow-md flex items-center justify-center text-gray-600 dark:text-gray-300 hover:text-brand-500 dark:hover:text-brand-400 transition-colors z-10"
|
||||
title="내 위치"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" className="w-5 h-5 fill-current">
|
||||
|
||||
@@ -117,7 +117,7 @@ export default function RestaurantDetail({
|
||||
{restaurant.phone && (
|
||||
<p>
|
||||
<span className="text-gray-500 dark:text-gray-400">전화:</span>{" "}
|
||||
<a href={`tel:${restaurant.phone}`} className="text-orange-600 dark:text-orange-400 hover:underline">
|
||||
<a href={`tel:${restaurant.phone}`} className="text-brand-600 dark:text-brand-400 hover:underline">
|
||||
{restaurant.phone}
|
||||
</a>
|
||||
</p>
|
||||
@@ -128,7 +128,7 @@ export default function RestaurantDetail({
|
||||
href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(restaurant.name + (restaurant.address ? " " + restaurant.address : restaurant.region ? " " + restaurant.region.replace(/\|/g, " ") : ""))}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-orange-600 dark:text-orange-400 hover:underline text-xs"
|
||||
className="text-brand-600 dark:text-brand-400 hover:underline text-xs"
|
||||
>
|
||||
Google Maps에서 보기
|
||||
</a>
|
||||
@@ -223,7 +223,7 @@ export default function RestaurantDetail({
|
||||
{v.foods_mentioned.map((f, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="px-2 py-0.5 bg-orange-50 dark:bg-orange-900/30 text-orange-700 dark:text-orange-400 rounded text-xs"
|
||||
className="px-2 py-0.5 bg-brand-50 dark:bg-brand-900/30 text-brand-700 dark:text-brand-400 rounded text-xs"
|
||||
>
|
||||
{f}
|
||||
</span>
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function RestaurantList({
|
||||
onClick={() => onSelect(r)}
|
||||
className={`w-full text-left p-3 rounded-xl shadow-sm border transition-all hover:shadow-md hover:-translate-y-0.5 ${
|
||||
selectedId === r.id
|
||||
? "bg-orange-50 dark:bg-orange-900/20 border-orange-300 dark:border-orange-700 shadow-orange-100 dark:shadow-orange-900/10"
|
||||
? "bg-brand-50 dark:bg-brand-900/20 border-brand-300 dark:border-brand-700 shadow-brand-100 dark:shadow-brand-900/10"
|
||||
: "bg-white dark:bg-gray-900 border-gray-100 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
@@ -70,7 +70,7 @@ export default function RestaurantList({
|
||||
{r.foods_mentioned.slice(0, 5).map((f, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="px-1.5 py-0.5 bg-orange-50 dark:bg-orange-900/30 text-orange-700 dark:text-orange-400 rounded text-[10px]"
|
||||
className="px-1.5 py-0.5 bg-brand-50 dark:bg-brand-900/30 text-brand-700 dark:text-brand-400 rounded text-[10px]"
|
||||
>
|
||||
{f}
|
||||
</span>
|
||||
@@ -85,8 +85,9 @@ export default function RestaurantList({
|
||||
{r.channels.map((ch) => (
|
||||
<span
|
||||
key={ch}
|
||||
className="px-1.5 py-0.5 bg-orange-50 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400 rounded-full text-[10px] font-medium"
|
||||
className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-brand-50 dark:bg-brand-900/30 text-brand-600 dark:text-brand-400 rounded-full text-[10px] font-medium"
|
||||
>
|
||||
<svg className="w-2.5 h-2.5 shrink-0 text-red-400" 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>
|
||||
{ch}
|
||||
</span>
|
||||
))}
|
||||
|
||||
@@ -124,7 +124,7 @@ function ReviewForm({
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="px-3 py-1 bg-orange-500 dark:bg-orange-600 text-white text-sm rounded hover:bg-orange-600 dark:hover:bg-orange-500 disabled:opacity-50"
|
||||
className="px-3 py-1 bg-brand-500 dark:bg-brand-600 text-white text-sm rounded hover:bg-brand-600 dark:hover:bg-brand-500 disabled:opacity-50"
|
||||
>
|
||||
{submitting ? "저장 중..." : submitLabel}
|
||||
</button>
|
||||
@@ -225,7 +225,7 @@ export default function ReviewSection({ restaurantId }: ReviewSectionProps) {
|
||||
{user && !myReview && !showForm && (
|
||||
<button
|
||||
onClick={() => setShowForm(true)}
|
||||
className="mb-3 px-3 py-1 bg-orange-500 dark:bg-orange-600 text-white text-sm rounded hover:bg-orange-600 dark:hover:bg-orange-500"
|
||||
className="mb-3 px-3 py-1 bg-brand-500 dark:bg-brand-600 text-white text-sm rounded hover:bg-brand-600 dark:hover:bg-brand-500"
|
||||
>
|
||||
리뷰 작성
|
||||
</button>
|
||||
|
||||
@@ -36,11 +36,11 @@ export default function SearchBar({ onSearch, isLoading }: SearchBarProps) {
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="식당, 지역, 음식 검색..."
|
||||
className="w-full pl-9 pr-3 py-2 bg-gray-100 dark:bg-gray-800 border border-transparent focus:border-orange-400 focus:bg-white dark:focus:bg-gray-900 rounded-xl text-sm outline-none transition-all dark:text-gray-200 dark:placeholder-gray-500"
|
||||
className="w-full pl-9 pr-3 py-2 bg-gray-100 dark:bg-gray-800 border border-transparent focus:border-brand-400 focus:bg-white dark:focus:bg-gray-900 rounded-xl text-sm outline-none transition-all dark:text-gray-200 dark:placeholder-gray-500"
|
||||
/>
|
||||
{isLoading && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
||||
<div className="w-4 h-4 border-2 border-orange-400 border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-4 h-4 border-2 border-brand-400 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
|
||||
BIN
frontend/src/fonts/PretendardVariable.woff2
Normal file
BIN
frontend/src/fonts/PretendardVariable.woff2
Normal file
Binary file not shown.
Reference in New Issue
Block a user