From d46acc43e36dd74d7686bb0674f9f7f42cd5a8d7 Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 11 Jun 2026 00:59:36 +0900 Subject: [PATCH 01/19] =?UTF-8?q?docs(spec):=20=EC=82=AC=EC=9D=B4=ED=8A=B8?= =?UTF-8?q?=20=EB=A6=AC=EB=89=B4=EC=96=BC=20=EC=84=A4=EA=B3=84=20=E2=80=94?= =?UTF-8?q?=20=EC=99=B8=EC=A3=BC+=EC=86=8C=ED=94=84=ED=8A=B8=EC=9B=A8?= =?UTF-8?q?=EC=96=B4=20=ED=8C=90=EB=A7=A4=202=EC=B6=95=20=EC=9E=AC?= =?UTF-8?q?=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 계좌이체 중심 결제, 기존 서비스 숨김(admin 토글), 고객 포털, 풀 리디자인 - orders 단일 소스 구매 식별, pack 인프라 범용 제품 시스템 확장 Co-Authored-By: Claude Opus 4.8 (1M context) --- ...ite-renewal-outsourcing-products-design.md | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-11-site-renewal-outsourcing-products-design.md diff --git a/docs/superpowers/specs/2026-06-11-site-renewal-outsourcing-products-design.md b/docs/superpowers/specs/2026-06-11-site-renewal-outsourcing-products-design.md new file mode 100644 index 0000000..eb5441b --- /dev/null +++ b/docs/superpowers/specs/2026-06-11-site-renewal-outsourcing-products-design.md @@ -0,0 +1,157 @@ +# 쟁승메이드 사이트 리뉴얼 — 외주 의뢰 + 완성 소프트웨어 판매 중심 재구성 + +- **작성일**: 2026-06-11 +- **상태**: CEO 승인 완료 +- **목표**: 사이트 정체성을 "외주 개발 의뢰 수주 + 완성 소프트웨어 판매" 2축으로 재정립. + 전문 B2B 에이전시 수준의 UI/UX 풀 리디자인, 외주 의뢰 플로우 고도화(고객 포털 포함), + NAS 기반 결제 후 다운로드 구조의 오류 제거. + +--- + +## 0. 확정된 핵심 결정 (CEO) + +| 항목 | 결정 | +|------|------| +| 결제 | **계좌이체 중심**으로 정리. PG(PortOne)는 코드만 보존, 유료라 추후 연결 | +| 기존 서비스 (사주·음악 팩·로또·설문/gyeol·웹사이트 샘플 등) | **숨김** — 관리자만 토글·접근 가능 | +| 외주 플로우 | **고객 포털까지** — 상태 추적 + 견적 열람/수락/거절 | +| 디자인 | **풀 리디자인** — B2B 개발 에이전시 톤 (Pretendard, slate+딥블루) | +| 제품 판매 | **범용 제품 시스템** — 음악 팩 전용 pack 인프라를 일반 제품으로 확장 | + +## 1. 아키텍처 결정 (대안 비교 결과) + +### 1-1. 외주 의뢰 데이터 모델 — `contact_requests` 확장 (채택) +- 기존 테이블에 상태 머신 + `public_token` 컬럼 추가, `quotes.contact_request_id` FK로 연결. +- 대안(projects 테이블 신설)은 이중 마이그레이션·전면 재작성 부담으로 기각. +- **이유**: NAS self-host 전환 진행 중(클라우드/셀프호스트 양쪽 마이그레이션 필요) → 변경 폭 최소가 안전. + +### 1-2. 제품 파일 시스템 — `products` 확장 + `pack_files.product_id` 연결 (채택) +- `products`에 설명·`pay_method`·`is_hidden`(또는 is_active 재활용)·상세 콘텐츠 컬럼 추가. +- `pack_files`에 `product_id` FK 추가, 기존 음악 팩 파일은 음악 제품에 연결. +- 대안(신규 product_files 테이블)은 web-backend(packs-lab) 수정이 필요해 기각. +- **이유**: DSM 링크 발급·HMAC·admin 업로드 인프라(web-backend) **무수정** 재사용. + +### 1-3. 구매 식별 — `orders` 테이블 단일 소스 (채택) +- 현재 `contact_requests.service` 문자열 파싱("구매 신청: AI 음악 마스터 팩 · 프로") 의존 → 제거. +- 모든 구매는 `orders(product_id, status, method)` 기준. 기존 구매자는 이관 스크립트로 orders 생성. + +## 2. 정보 구조 (IA) + +``` +/ 메인 — Hero(외주+소프트웨어 2축), 서비스 소개, 개발 프로세스, + 포트폴리오 하이라이트, 제품 진열, 신뢰 요소(7년차 경력·실적), CTA +/outsourcing 외주 의뢰 — 포트폴리오, 진행 프로세스 안내, 단계형 의뢰 폼 +/products 완성 소프트웨어 카탈로그 (is_active 제품만) +/products/[id] 제품 상세 + 계좌이체 구매 +/track/[token] 비회원 의뢰 상태 추적 (이메일 링크로 접근) +/login 리디자인 (Supabase Auth 유지: 이메일+Google OAuth) +/mypage 리디자인 — 탭: 프로필 · 내 의뢰 · 내 제품/다운로드 · 주문 내역 +/quote/[token] 공개 견적서 (유지, 수락/거절 동기화 보강) +/legal/* 유지 (톤만 리디자인) +/admin/* 주문 관리(신설) + 제품 관리(packs 일반화) + 의뢰·견적 통합 뷰 + 숨김 서비스 토글 +``` + +### 숨김 처리 대상 +`/work/saju*`, `/music/*`, `/gyeol`, `/packages`, 로또 관련 노출 전부. +- `service_settings` 토글 기반: 비활성 시 일반 사용자에게 404(notFound), **admin 세션(admin_token 쿠키)이면 접근 허용**. +- 데이터(saju_records, 구독 등)는 보존 — 토글 재활성 시 복귀. +- 기존 `/work/freelance` → `/outsourcing` redirect 추가 (next.config redirects). + +### 외주로 통합 +- 웹사이트 제작(`/work/website`)은 외주 개발의 한 유형으로 `/outsourcing`에 통합. + 샘플 포트폴리오(`/work/website/samples/*`)는 포트폴리오 자료로 재사용 (숨김 아님). + +## 3. 디자인 시스템 (풀 리디자인) + +- **레이아웃**: 사이드바 대시보드형 → **상단 네비 + 풋터의 기업 사이트형**. 모바일은 햄버거 드로어. +- **타이포**: Jua 제거 → **Pretendard** (next/font local 또는 CDN). +- **컬러**: slate 뉴트럴 베이스 + 딥블루 포인트 1색. 카드/섹션/버튼/뱃지 공통 컴포넌트 정비. +- **품질 가드**: 구현 시 designer·soft-skill 스킬 적용 — generic AI 패턴(과한 그래디언트, 이모지 남발, 보라색 남용) 차단. 이미지 없이 타이포·여백·SVG로 완성도. +- admin 영역도 동일 토큰 적용하되 기능 우선의 밀도 높은 레이아웃. + +## 4. 제품 구매 → 다운로드 흐름 + +``` +제품 상세(/products/[id]) → [구매하기] → 로그인 확인(미로그인 → /login?next=) +→ 계좌이체 신청 모달 (케이뱅크 100-116-337157 박재오 안내, 약관 동의) +→ orders 생성 (product_id, amount, status='pending', metadata.method='bank_transfer') +→ 고객: 접수 확인 메일(Resend) / 관리자: 신규 주문 알림 메일 +→ /admin/orders: 입금 확인 [완료] 원클릭 → status='paid' +→ 고객: "다운로드가 활성화되었습니다" 메일 +→ /mypage '내 제품': orders(status='paid') 기준 제품별 파일 목록 +→ 다운로드 클릭 → /api/packs/sign-link (검증을 orders 기반으로 교체) +→ web-backend HMAC → DSM 공유 링크 (4시간 만료, 만료 시 재클릭으로 재발급) +``` + +- `PurchaseAgreementModal`을 범용 제품용으로 일반화. +- `PaymentButton`(PortOne)은 보존하되 `products.pay_method='bank_transfer'`일 때 미노출. + 추후 PG 계약 시 제품별 플래그만 변경하면 카드 결제 활성. +- admin 팩 업로드 UI(/admin/packs)는 제품 관리(/admin/products) 안으로 통합 — 제품 선택 → 파일 업로드. +- tier(starter/pro/master) 개념은 음악 팩 하위 호환용으로 유지하되, 신규 제품은 product_id 직접 매칭. + +## 5. 외주 의뢰 플로우 (고객 포털 포함) + +### 상태 머신 (contact_requests.status) +`pending(접수) → reviewing(검토중) → quoted(견적발송) → accepted(수주)/on_hold(보류) → in_progress(진행중) → completed(완료)` +(+ `cancelled`) + +### 흐름 +``` +[고객] /outsourcing 단계형 의뢰 폼 + 단계: ① 프로젝트 유형 → ② 예산/희망 일정 → ③ 상세 요구사항 → ④ 연락처(로그인 시 자동 채움) +→ contact_requests 생성 (public_token 발급) + 접수 확인 메일(추적 링크 /track/[token] 포함) + + 관리자 알림 메일 (기존 Resend 흐름 유지) + +[관리자] /admin 의뢰 상세 (contacts + quotes 통합 뷰) +→ 상태 변경 드롭다운 (상태 머신 순서 강제) +→ 견적서 작성 시 contact_request_id 자동 연결 +→ [견적 발송] 버튼 → quotes.status='sent' + 고객 메일 자동 발송(견적 링크 포함) + + contact_requests.status='quoted' 동기화 + +[고객] /track/[token] (비회원) 또는 /mypage '내 의뢰' (회원) +→ 상태 타임라인 표시 + 연결된 견적서 열람 +→ 견적 수락/거절 버튼 → quotes.status='accepted'/'rejected' + + contact_requests.status 동기화('accepted'/'on_hold') + 관리자 알림 메일 +``` + +- rate limit·XSS 방지 등 기존 `/api/contact` 보안 로직 유지. +- 견적서 작성 시 DB 자동 등록 원칙 유지 ([[feedback_quotes_admin]]). + +## 6. DB 마이그레이션 (클라우드 + self-host 양쪽 적용) + +신규 마이그레이션 SQL 파일 1~2개로 작성 (`supabase/migrations/2026-06-11-*.sql`): + +1. `products`: `description_long TEXT`, `pay_method TEXT DEFAULT 'bank_transfer'`, `features JSONB`, `sort_order INT` 추가 +2. `pack_files`: `product_id TEXT REFERENCES products(id)` 추가 + 기존 음악 팩 파일 product_id 백필 +3. `contact_requests`: `public_token TEXT UNIQUE`, `budget TEXT`, `timeline TEXT`, `project_type TEXT` 추가 + + status CHECK 갱신 (새 상태 머신) +4. `quotes`: `contact_request_id UUID REFERENCES contact_requests(id)` 추가 +5. 기존 음악 팩 구매자(contact_requests 'completed' + "구매 신청:" 패턴) → orders 이관 스크립트 +6. RLS: `/track/[token]`용 조회는 서버(admin client)에서만 — 테이블 직접 노출 없음 + +**운영 주의**: 현 운영=Vercel+클라우드 Supabase, NAS self-host로 전환 직전(Phase 6 ③ DNS 전환 대기). +마이그레이션은 **양쪽 DB에 동일 순서로 적용**하고, 이관 스크립트는 멱등하게 작성. + +## 7. 구현 단계 + +| Phase | 내용 | 검증 | +|-------|------|------| +| **1. 디자인 시스템 + IA** | 디자인 토큰·공통 컴포넌트, 상단 네비 레이아웃 전환, 메인·/outsourcing 리디자인, 숨김 서비스 처리, /login·/mypage 리디자인 | 전 페이지 렌더·반응형·숨김 토글 확인 | +| **2. 제품 판매 시스템** | DB 마이그레이션, /products 카탈로그·상세, 계좌이체 구매 플로우, /admin/orders·/admin/products, sign-link 검증 교체, 기존 구매자 이관 | 회원 구매→입금확인→다운로드 E2E | +| **3. 외주 고객 포털** | 단계형 폼, contact↔quote 연결, 상태 머신, /track/[token], 자동 이메일, admin 통합 뷰 | 비회원 의뢰→견적→수락 E2E | + +## 8. E2E 검증 시나리오 (각 Phase 종료 시 수동) + +- **A. 외주 (비회원)**: 의뢰 폼 제출 → 접수 메일 수신 → /track 접속 → admin 견적 발송 → 메일 링크로 견적 열람 → 수락 → admin 알림 확인 +- **B. 제품 구매 (회원)**: 회원가입/로그인 → 제품 구매 신청 → 접수 메일 → admin 입금 확인 → 다운로드 활성 메일 → mypage 다운로드 → DSM 링크 파일 수신 +- **C. 숨김 서비스**: 일반 사용자 404 확인 / admin 세션 접근 확인 / 토글 재활성 복귀 확인 +- **D. 회귀**: 기존 음악 팩 구매자 mypage 다운로드 정상 동작 (이관 후) + +## 9. 의도적 제외 (이번 범위 아님) + +- PG(카드) 결제 활성화 — 플래그 구조만 준비 +- 관리자 RBAC(권한 분화) +- 구독 정기 결제 +- ZIP 일괄 다운로드, 다운로드 횟수 제한 +- 사주 SaaS 도메인 분리 (별도 트랙) +- web-backend(packs-lab) 코드 수정 From a496c2244bae3e2ce09364ad4c76901c0307e0e2 Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 11 Jun 2026 01:06:48 +0900 Subject: [PATCH 02/19] =?UTF-8?q?docs(plan):=20=EB=A6=AC=EB=89=B4=EC=96=BC?= =?UTF-8?q?=20Phase=201=20=EA=B5=AC=ED=98=84=20=EA=B3=84=ED=9A=8D=20?= =?UTF-8?q?=E2=80=94=20=EB=94=94=EC=9E=90=EC=9D=B8=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=C2=B7=EC=88=A8=EA=B9=80=20=EA=B0=80=EB=93=9C=C2=B7=EB=A9=94?= =?UTF-8?q?=EC=9D=B8/=EC=99=B8=EC=A3=BC/=EB=A1=9C=EA=B7=B8=EC=9D=B8/?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=ED=8E=98=EC=9D=B4=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-06-11-renewal-phase1-design-ia.md | 446 ++++++++++++++++++ 1 file changed, 446 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-11-renewal-phase1-design-ia.md diff --git a/docs/superpowers/plans/2026-06-11-renewal-phase1-design-ia.md b/docs/superpowers/plans/2026-06-11-renewal-phase1-design-ia.md new file mode 100644 index 0000000..2d094b8 --- /dev/null +++ b/docs/superpowers/plans/2026-06-11-renewal-phase1-design-ia.md @@ -0,0 +1,446 @@ +# 사이트 리뉴얼 Phase 1 — 디자인 시스템 + IA 구현 계획 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. +> **UI 페이지 태스크(5~8)는 구현 시 반드시 `designer` + `soft-skill` 스킬을 먼저 로드**하여 generic AI 패턴(과한 그래디언트·글래스모피즘·보라색 남용)을 차단할 것. + +**Goal:** 쟁승메이드를 "외주 개발 + 완성 소프트웨어 판매" 2축의 전문 B2B 에이전시 사이트로 풀 리디자인하고, 기존 서비스(사주·음악·로또·설문)를 admin 전용 숨김 처리한다. + +**Architecture:** 기존 다크 글래스 `--kx-*` 토큰 체계를 라이트 slate+딥블루 전문 토큰으로 전면 교체. Jua → Pretendard. 숨김은 `service_settings` 토글 + 서버 레이아웃 가드(admin_token 쿠키 예외). 페이지는 기존 라우트 구조 위에서 교체하며 web-backend·결제 로직은 건드리지 않는다. + +**Tech Stack:** Next.js 16 App Router, TypeScript, Tailwind CSS v4, Supabase (Auth/DB), Pretendard(npm) + +**Spec:** `docs/superpowers/specs/2026-06-11-site-renewal-outsourcing-products-design.md` + +**검증 방식 노트:** 이 저장소에 테스트 인프라가 없고 Phase 1은 UI·라우팅 중심이므로 TDD 대신 `npm run build` + dev 서버 수동 E2E 체크리스트(Task 10)로 검증한다. 로직 단위 테스트는 Phase 2(주문/다운로드 검증 로직)에서 vitest 도입과 함께 시작. + +--- + +## 사전 확인 사항 (현재 코드 상태) + +- 레이아웃: `app/layout.tsx` (Jua 폰트, 음악 팩 중심 metadata/jsonLd) → 교체 대상 +- 셸: `app/components/DashboardShell.tsx` (standalone 분기) → 유지, `app/components/PublicShell.tsx`(TopNav+푸터) → 리뉴얼 +- 네비: `app/components/TopNav.tsx` — 링크가 SaaS제품/AI음악/커스텀외주 → 교체 +- 토큰: `app/globals.css` `--kx-*` 다크 테마 (#060e20 배경, 보라 #cc97ff 프라이머리) → 교체 +- 숨김 토글 인프라: `service_settings` 테이블 + `app/api/admin/services/route.ts`(GET/PATCH, DEFAULT_SERVICES) + `app/admin/services/page.tsx` 존재 → 재사용 +- admin 인증: `lib/admin-auth.ts`의 `verifyAdminTokenNode(token)` + `admin_token` 쿠키 +- 리다이렉트: `next.config.ts` redirects() 존재 → 추가만 + +--- + +### Task 1: 디자인 토큰 + Pretendard 폰트 교체 + +**Files:** +- Modify: `package.json` (pretendard 추가) +- Modify: `app/globals.css` (토큰 교체) +- Modify: `app/layout.tsx` (Jua 제거, Pretendard 적용) + +- [ ] **Step 1: pretendard 설치** + +```bash +npm install pretendard +``` + +- [ ] **Step 2: `app/globals.css` 토큰 교체** + +기존 `--kx-*` 변수 블록(40~51행 부근)을 **유지한 채 아래 신규 토큰을 추가**하고, 기존 kx 변수의 값만 새 라이트 테마로 재매핑한다 (kx 변수를 참조하는 기존 페이지가 많아 즉시 삭제하면 깨짐 — Phase 1 완료 후 잔여 참조 제거): + +```css +:root { + /* === JSM Professional tokens (2026-06 renewal) === */ + --jsm-bg: #f8fafc; /* slate-50 본문 배경 */ + --jsm-surface: #ffffff; /* 카드 */ + --jsm-surface-alt: #f1f5f9; /* slate-100 섹션 교차 배경 */ + --jsm-ink: #0f172a; /* slate-900 본문 텍스트 */ + --jsm-ink-soft: #475569; /* slate-600 보조 텍스트 */ + --jsm-ink-faint: #94a3b8; /* slate-400 캡션 */ + --jsm-line: #e2e8f0; /* slate-200 보더 */ + --jsm-navy: #0b1f3a; /* 딥네이비 — 푸터/다크 섹션 */ + --jsm-accent: #1d4ed8; /* blue-700 포인트 (단일 포인트 컬러) */ + --jsm-accent-hover: #1e40af; /* blue-800 */ + --jsm-accent-soft: #dbeafe; /* blue-100 뱃지 배경 */ + + /* 기존 kx 변수 재매핑 (잔여 참조 호환용) */ + --kx-surface: var(--jsm-bg); + --kx-surface-low: var(--jsm-surface-alt); + --kx-surface-mid: var(--jsm-surface); + --kx-surface-high: var(--jsm-surface); + --kx-surface-bright: var(--jsm-surface-alt); + --kx-on-surface: var(--jsm-ink); + --kx-on-variant: var(--jsm-ink-soft); + --kx-primary: var(--jsm-accent); + --kx-primary-dim: var(--jsm-accent-hover); + --kx-secondary: var(--jsm-accent); + --kx-secondary-dim: var(--jsm-accent-hover); + --kx-outline: var(--jsm-line); +} +``` + +`body` 폰트 스택을 `var(--font-pretendard), Pretendard Variable, Pretendard, -apple-system, sans-serif`로 변경. `.kx-display`, `.kx-gradient-text`, `.kx-btn-primary` 등 기존 유틸 클래스는 새 토큰 기반으로 값만 정제 (gradient-text는 단색 `--jsm-ink`로, btn-primary는 `--jsm-accent` 솔리드로). + +- [ ] **Step 3: `app/layout.tsx`에서 Jua 제거 + Pretendard import** + +```tsx +// 제거: import { Jua } from "next/font/google"; 및 jua 정의/사용 +import "pretendard/dist/web/variable/pretendardvariable-dynamic-subset.css"; +// → (또는 className 제거) +``` + +- [ ] **Step 4: 빌드 확인** + +Run: `npm run build` +Expected: 성공 (스타일 회귀는 Task 4~8에서 페이지별 정리) + +- [ ] **Step 5: Commit** + +```bash +git add package.json package-lock.json app/globals.css app/layout.tsx +git commit -m "feat(design): JSM 전문 토큰 체계 + Pretendard 도입, kx 토큰 재매핑" +``` + +--- + +### Task 2: 서비스 숨김 가드 라이브러리 + +**Files:** +- Create: `lib/service-visibility.ts` +- Create: `supabase/migrations/2026-06-11-hide-legacy-services.sql` +- Modify: `app/api/admin/services/route.ts` (DEFAULT_SERVICES 갱신) + +- [ ] **Step 1: 마이그레이션 SQL 작성** (클라우드 + self-host 양쪽 적용 — 멱등) + +```sql +-- 2026-06-11 리뉴얼: 레거시 서비스 숨김 토글 시드 +-- service_settings: 신규 id 추가 (이미 있으면 무시) +INSERT INTO service_settings (id, name, description, is_active, order_index) +VALUES + ('saju', 'AI 사주 분석', '사주 입력 및 AI 해석 (레거시)', false, 101), + ('music', 'AI 음악 팩', '음악 가이드 패키지·샘플·스튜디오', false, 102), + ('gyeol', 'CONTOUR 설문', '/gyeol PMF 설문', false, 103), + ('packages', 'SaaS 제품 허브(구)', '구 /packages 페이지', false, 104), + ('lotto', '로또 추천', '로또 번호 추천 노출', false, 105) +ON CONFLICT (id) DO NOTHING; +``` + +- [ ] **Step 2: `lib/service-visibility.ts` 작성** + +```typescript +import { cookies } from 'next/headers'; +import { createAdminClient } from '@/lib/supabase/admin'; +import { verifyAdminTokenNode } from '@/lib/admin-auth'; + +/** 숨김 가능 서비스 id (service_settings.id와 일치) */ +export type HideableService = 'saju' | 'music' | 'gyeol' | 'packages' | 'lotto'; + +/** + * 서비스 노출 여부. admin_token 세션이면 항상 true. + * service_settings 조회 실패(테이블 미생성 등) 시 안전하게 숨김(false). + */ +export async function isServiceVisible(id: HideableService): Promise { + const cookieStore = await cookies(); + const token = cookieStore.get('admin_token')?.value; + if (token && verifyAdminTokenNode(token)) return true; + + try { + const supabase = createAdminClient(); + const { data, error } = await supabase + .from('service_settings') + .select('is_active') + .eq('id', id) + .maybeSingle(); + if (error || !data) return false; + return data.is_active === true; + } catch { + return false; + } +} +``` + +- [ ] **Step 3: `app/api/admin/services/route.ts`의 `DEFAULT_SERVICES`를 신규 id 체계로 갱신** + +```typescript +const DEFAULT_SERVICES = [ + { id: 'saju', name: 'AI 사주 분석', description: '사주 입력 및 AI 해석 (레거시)', is_active: false, order_index: 101 }, + { id: 'music', name: 'AI 음악 팩', description: '음악 가이드 패키지·샘플·스튜디오', is_active: false, order_index: 102 }, + { id: 'gyeol', name: 'CONTOUR 설문', description: '/gyeol PMF 설문', is_active: false, order_index: 103 }, + { id: 'packages', name: 'SaaS 제품 허브(구)', description: '구 /packages 페이지', is_active: false, order_index: 104 }, + { id: 'lotto', name: '로또 추천', description: '로또 번호 추천 노출', is_active: false, order_index: 105 }, +]; +``` + +- [ ] **Step 4: 마이그레이션 적용 (운영 DB 2곳)** + +Run (CEO 또는 세션에서): 클라우드 Supabase SQL Editor + NAS self-host(`supa.jaengseung-made.com`) SQL Editor 양쪽에 Step 1 SQL 실행. +Expected: 5 rows inserted (또는 0 — 재실행 시). + +- [ ] **Step 5: Commit** + +```bash +git add lib/service-visibility.ts supabase/migrations/2026-06-11-hide-legacy-services.sql app/api/admin/services/route.ts +git commit -m "feat(visibility): service_settings 기반 서비스 숨김 가드 + 레거시 서비스 시드" +``` + +--- + +### Task 3: 레거시 라우트에 숨김 가드 적용 + +**Files:** +- Create: `app/work/saju/layout.tsx` +- Create: `app/music/layout.tsx` +- Create: `app/gyeol/layout.tsx` (기존에 있으면 Modify — 가드만 추가) +- Create: `app/packages/layout.tsx` + +- [ ] **Step 1: 서버 레이아웃 가드 4개 작성** (모두 동일 패턴, id만 다름) + +`app/work/saju/layout.tsx`: + +```tsx +import { notFound } from 'next/navigation'; +import { isServiceVisible } from '@/lib/service-visibility'; + +export default async function SajuLayout({ children }: { children: React.ReactNode }) { + if (!(await isServiceVisible('saju'))) notFound(); + return <>{children}; +} +``` + +`app/music/layout.tsx` → `isServiceVisible('music')`, +`app/gyeol/layout.tsx` → `isServiceVisible('gyeol')` (기존 layout 있으면 함수 본문에 가드 추가), +`app/packages/layout.tsx` → `isServiceVisible('packages')`. + +주의: 해당 디렉토리에 기존 `layout.tsx`가 이미 있으면 새로 만들지 말고 기존 파일 상단에 가드 삽입. 클라이언트 레이아웃이면 서버 래퍼 레이아웃을 한 단계 위에 둘 수 없으므로 page 단위로 가드 서버 컴포넌트 래핑. + +- [ ] **Step 2: dev 서버에서 동작 확인** + +Run: `npm run dev` 후 +- 비로그인 브라우저(시크릿): `/work/saju`, `/music/packs`, `/gyeol`, `/packages` → 404 +- `/admin/login` 로그인 후 동일 URL → 정상 렌더 +Expected: 위와 같음 + +- [ ] **Step 3: Commit** + +```bash +git add app/work/saju/layout.tsx app/music/layout.tsx app/gyeol/layout.tsx app/packages/layout.tsx +git commit -m "feat(visibility): 사주·음악·설문·패키지 라우트 숨김 가드 적용" +``` + +--- + +### Task 4: TopNav + 푸터(PublicShell) 리뉴얼 + +**Files:** +- Modify: `app/components/TopNav.tsx` +- Modify: `app/components/PublicShell.tsx` + +- [ ] **Step 1: TopNav 링크·CTA 교체** + +```typescript +const LINKS = [ + { href: '/outsourcing', label: '외주 개발' }, + { href: '/products', label: '소프트웨어' }, +]; +// CTA 버튼: "Try now"(→/music) 제거 → "프로젝트 문의" (→/outsourcing#contact) +``` + +스타일: 라이트 배경 기준으로 재작성 — 스크롤 시 흰 배경 + `--jsm-line` 하단 보더 + 약한 그림자(현재의 다크 글래스 blur 제거). 로고 "JSM"은 gradient-text 제거, `--jsm-ink` 단색 + "쟁승메이드" 병기. 로그인/마이페이지/로그아웃 분기 로직은 그대로 유지(Supabase auth 구독 코드 무수정). + +- [ ] **Step 2: PublicShell 푸터 링크 그룹 교체** + +``` +서비스: 외주 개발(/outsourcing) · 소프트웨어(/products) +회사: 문의하기(mailto) · 진행 프로세스(/outsourcing#process) +Legal: 이용약관 · 개인정보처리방침 · 환불 정책 (유지) +``` + +숨김 서비스 링크(SaaS 제품/AI 음악/AI 사주) 전부 제거. 푸터 배경은 `--jsm-navy`, 사업자 정보 라인 유지. `KakaoFloatButton` 유지. + +- [ ] **Step 3: dev 서버에서 네비·푸터 렌더 확인** (모바일 드로어 포함) + +- [ ] **Step 4: Commit** + +```bash +git add app/components/TopNav.tsx app/components/PublicShell.tsx +git commit -m "feat(nav): 외주·소프트웨어 2축 네비게이션 + 푸터 리뉴얼" +``` + +--- + +### Task 5: 메인 페이지 리디자인 (`app/page.tsx`) + 메타데이터 + +**Files:** +- Modify: `app/page.tsx` (전면 교체) +- Modify: `app/layout.tsx` (metadata + jsonLd 교체) + +> **구현 전 designer + soft-skill 스킬 로드 필수.** + +- [ ] **Step 1: `app/layout.tsx` metadata/jsonLd 교체** + +- title default: `"외주 개발 · 완성 소프트웨어 | 쟁승메이드"` +- description: `"7년차 대기업 백엔드 개발자가 직접 설계하고 만듭니다. 맞춤 소프트웨어 외주 개발과 검증된 완성 소프트웨어를 제공하는 쟁승메이드."` +- keywords: 외주 개발, 소프트웨어 개발, 웹사이트 제작, 업무 자동화, 백엔드 개발자, 프리랜서 개발자 +- jsonLd: OfferCatalog에서 음악 팩·사주 Offer 제거, 외주 개발·웹사이트 제작 Service만 유지 (소프트웨어 제품 Offer는 Phase 2에서 동적 생성) + +- [ ] **Step 2: 메인 페이지 섹션 구성으로 전면 재작성** (서버 컴포넌트, 이미지 없이 타이포·여백·SVG) + +| 섹션 | 내용 (실제 카피) | +|------|------| +| Hero | 헤드라인: "필요한 소프트웨어, 만들어 드리거나 — 이미 만들어 두었습니다." 서브: "7년차 대기업 백엔드 개발자가 직접 설계·개발·운영합니다. 맞춤 외주 개발과 검증된 완성 소프트웨어 중 선택하세요." CTA 2개: [프로젝트 문의하기 → /outsourcing#contact] [소프트웨어 보기 → /products] | +| 2축 서비스 | 카드 2장 — ① 외주 개발: "기획부터 배포·운영까지. 웹/API/자동화/봇" → /outsourcing ② 완성 소프트웨어: "결제 후 바로 다운로드. 직접 운영하며 검증한 도구" → /products | +| 개발 프로세스 | 4단계 타임라인: 01 무료 상담·요구 정리 → 02 견적·범위 확정 → 03 개발·중간 공유 → 04 납품·배포 지원 (각 1줄 설명) | +| 신뢰 요소 | 숫자 스탯: "7년차 대기업 백엔드" · "직접 운영 중인 서비스 15+" · "기획→배포 원스톱" + 기술 스택 라벨(Python·Java·Spring·Next.js·AI 연동) | +| 포트폴리오 하이라이트 | 실제 운영 사례 3장: 주식 자동매매 시스템(텔레그램 연동) / 부동산 청약 자동 수집·매칭 / AI 콘텐츠 자동화 파이프라인 — 각 "직접 개발·운영 중" 뱃지, [더 보기 → /outsourcing#portfolio] | +| 소프트웨어 진열 | Phase 1은 정적: "출시 준비 중인 제품" 카드 + [입고 알림 → /outsourcing#contact]. Phase 2에서 products 테이블 동적 진열로 교체 예정 주석 명시 | +| 최종 CTA | 네이비 풀폭 밴드: "프로젝트, 이야기부터 시작하세요" + [무료 상담 신청] | + +- [ ] **Step 3: 빌드 + 렌더 확인** + +Run: `npm run build && npm run dev` → `/` 데스크톱·모바일 확인 + +- [ ] **Step 4: Commit** + +```bash +git add app/page.tsx app/layout.tsx +git commit -m "feat(home): 외주+소프트웨어 2축 메인 페이지 풀 리디자인" +``` + +--- + +### Task 6: `/outsourcing` 페이지 신설 + 리다이렉트 + +**Files:** +- Create: `app/outsourcing/page.tsx` +- Modify: `next.config.ts` (redirects 추가) +- 참고(복사 소스): `app/work/freelance/page.tsx`, `app/components/ContactForm.tsx` + +> **구현 전 designer + soft-skill 스킬 로드 필수.** 단계형 폼은 Phase 3 — 이번엔 기존 `ContactForm` 재사용. + +- [ ] **Step 1: `app/outsourcing/page.tsx` 작성** — 섹션 구성: + +1. Hero: "맞춤 소프트웨어 외주 개발" + "기획 정리가 안 됐어도 괜찮습니다. 상담에서 함께 정리합니다." +2. 제공 분야 카드 6: 웹 서비스 / 웹사이트 제작 / 업무 자동화(RPA·엑셀·크롤링) / API·백엔드 / 텔레그램·디스코드 봇 / AI 연동 개발 +3. `#process` 진행 프로세스: 상담 → 견적(영업일 2일 내) → 계약·착수 → 중간 공유(주 1회 이상) → 납품·검수 → 무상 하자보수 30일 +4. `#portfolio` 포트폴리오: 기존 `/work/freelance`의 실사례 + `/work/website/samples/*` 샘플 링크 카드 재사용 +5. 자주 묻는 질문 4개: 견적 기준 / 수정 횟수 / 소스코드 제공 / 유지보수 +6. `#contact` 의뢰 폼: 기존 `ContactForm` 컴포넌트 그대로 임베드 (새 토큰으로 스타일만 정제) + +- [ ] **Step 2: `next.config.ts` redirects 추가** + +```typescript +{ source: '/work/freelance', destination: '/outsourcing', permanent: true }, +{ source: '/work', destination: '/outsourcing', permanent: true }, +{ source: '/work/website', destination: '/outsourcing', permanent: true }, +// 주의: /work/saju*, /work/website/samples/* 는 redirect 하지 않음 (admin 접근용 잔존) +``` + +기존 redirects 중 destination이 `/work`·`/work/freelance`·`/work/website`인 항목은 새 destination(`/outsourcing`)으로 갱신해 redirect 체인 방지. + +- [ ] **Step 3: dev 확인** — `/outsourcing` 렌더 + `/work/freelance` → `/outsourcing` 301 + 폼 제출 1회 테스트(접수 메일 수신) + +- [ ] **Step 4: Commit** + +```bash +git add app/outsourcing/page.tsx next.config.ts +git commit -m "feat(outsourcing): 외주 의뢰 페이지 신설 + work 라우트 리다이렉트" +``` + +---### Task 7: `/login` 리디자인 + +**Files:** +- Modify: `app/login/page.tsx` + +- [ ] **Step 1: 비주얼 리디자인** — Supabase 로직(signUp/signInWithPassword/signInWithOAuth, 에러 처리, redirect) **무수정**, 마크업·스타일만 교체: + - 중앙 카드형 (max-w-sm, 흰 카드, `--jsm-line` 보더), 좌측 또는 상단에 "쟁승메이드" 워드마크 + - Google 버튼 → 이메일 폼 순서, 가입/로그인 토글 유지 + - 다크 배경·글래스 효과 제거, `--jsm-bg` 배경 + +- [ ] **Step 2: dev 확인** — 이메일 로그인 + Google OAuth 각 1회 + +- [ ] **Step 3: Commit** + +```bash +git add app/login/page.tsx +git commit -m "feat(login): 로그인 페이지 전문 톤 리디자인 (로직 무수정)" +``` + +--- + +### Task 8: `/mypage` 리디자인 + 탭 재구성 + +**Files:** +- Modify: `app/mypage/page.tsx` (1,048줄 — 탭 단위로 작업) + +- [ ] **Step 1: 탭 구조 변경** — 기존 7탭 → 4탭: + +| 새 탭 | 기존 소스 | 처리 | +|------|----------|------| +| 프로필 | 프로필 탭 | 유지·리스타일 | +| 내 의뢰 | 의뢰 탭 | 유지·리스타일 (Phase 3에서 타임라인 고도화) | +| 내 제품 | 팩 탭 | 명칭 변경·리스타일 — **다운로드 로직(sign-link 호출) 무수정** | +| 주문 내역 | 결제+주문 탭 통합 | 병합·리스타일 | + +사주·구독 탭: 렌더 분기에서 제외(코드는 주석이 아닌 조건 `false` 처리 또는 제거 — 데이터 로직은 남겨도 무방). 탭 상태/데이터 페칭 구조는 유지. + +- [ ] **Step 2: 비주얼 리디자인** — 새 토큰 적용, 카드·테이블 정제. 다운로드 버튼/상태 뱃지 명확화("입금 확인 후 활성화됩니다" 안내 문구 포함). + +- [ ] **Step 3: dev 확인** — 로그인 → 4탭 전환 → 기존 구매 계정으로 다운로드 버튼 동작(또는 테스트 데이터) 확인 + +- [ ] **Step 4: Commit** + +```bash +git add app/mypage/page.tsx +git commit -m "feat(mypage): 4탭 재구성 + 전문 톤 리디자인 (다운로드 로직 무수정)" +``` + +--- + +### Task 9: 잔여 kx 참조·레거시 노출 정리 + +**Files:** +- Modify: `app/payment/success/page.tsx`, `app/payment/fail/page.tsx`, `app/quote/[token]/page.tsx`, `app/legal/*/page.tsx` — 새 토큰 기준 톤 정제 (구조 변경 없음) +- Modify: `app/components/KakaoFloatButton.tsx` 등 공용 컴포넌트의 다크 전제 스타일 + +- [ ] **Step 1: 잔여 다크 전제 스타일 검색·정리** + +Run: `grep -rn "kx-gradient-text\|GlassFilter\|LiquidGlass" app/ --include="*.tsx"` +→ 노출 페이지(숨김 라우트 제외)에서 발견된 사용처를 새 토큰으로 정리. `app/layout.tsx`의 `` 제거. + +- [ ] **Step 2: 빌드 확인** — `npm run build` 성공 + +- [ ] **Step 3: Commit** + +```bash +git add -A +git commit -m "refactor(design): 잔여 글래스·그래디언트 스타일 정리" +``` + +--- + +### Task 10: Phase 1 E2E 검증 (스펙 §8 시나리오 C + 회귀) + +- [ ] **Step 1: 숨김 검증 (시나리오 C)** + - 시크릿 창: `/work/saju`, `/music/packs`, `/music/studio`, `/gyeol`, `/packages` → 전부 404 + - admin 로그인 창: 동일 URL 전부 정상 렌더 + - `/admin/services`에서 'music' 토글 ON → 시크릿 창 `/music/packs` 정상 렌더 → 다시 OFF → 404 + +- [ ] **Step 2: 핵심 동선 회귀** + - `/` → 네비·Hero CTA → `/outsourcing` → 폼 제출 → 접수 메일 수신 + `/admin/contacts`에 기록 + - 회원 로그인 → `/mypage` 4탭 정상 + (구매 데이터 있으면) 다운로드 링크 발급 + - `/work/freelance` → `/outsourcing` 301 확인 + - 모바일 뷰(375px): 네비 드로어·메인·외주 페이지 레이아웃 확인 + +- [ ] **Step 3: 빌드·배포 준비** + +Run: `npm run build` +Expected: 에러 0. (배포는 CEO 확인 후 git push → Vercel) + +- [ ] **Step 4: 최종 Commit + Phase 1 완료 보고** + +```bash +git add -A && git commit -m "chore: Phase 1 디자인+IA 리뉴얼 완료" +``` + +--- + +## Phase 2·3 예고 (별도 플랜으로 작성) + +- **Phase 2 — 제품 판매 시스템**: DB 마이그레이션(products 확장·pack_files.product_id·orders 정비), `/products` 카탈로그·상세, 계좌이체 구매 모달 일반화, `/admin/orders`·`/admin/products`, sign-link 검증 orders 기반 교체, 기존 구매자 이관. vitest 도입. +- **Phase 3 — 외주 고객 포털**: 단계형 의뢰 폼, contact↔quote FK, 상태 머신, `/track/[token]`, 자동 이메일(Resend), admin 의뢰·견적 통합 뷰. From e14e527e288860821d0e08b7192dcda289c714b8 Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 11 Jun 2026 01:15:11 +0900 Subject: [PATCH 03/19] =?UTF-8?q?feat(design):=20JSM=20=EC=A0=84=EB=AC=B8?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=EC=B2=B4=EA=B3=84=20+=20Pretendard=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85,=20kx=20=ED=86=A0=ED=81=B0=20=EC=9E=AC?= =?UTF-8?q?=EB=A7=A4=ED=95=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- app/globals.css | 79 +++++++++++++++++++++++++++-------------------- app/layout.tsx | 11 ++----- package-lock.json | 7 +++++ package.json | 1 + 4 files changed, 55 insertions(+), 43 deletions(-) diff --git a/app/globals.css b/app/globals.css index 9360889..3f91653 100644 --- a/app/globals.css +++ b/app/globals.css @@ -36,19 +36,32 @@ --card-bg: #ffffff; --border: #dbe8ff; - /* ─── Kinetic Ether Tokens (다크 테마 섹션 전용) ─── */ - --kx-surface: #060e20; - --kx-surface-low: #091328; - --kx-surface-mid: #0f1930; - --kx-surface-high: #141f38; - --kx-surface-bright: #1f2b49; - --kx-on-surface: #dee5ff; - --kx-on-variant: #a3aac4; - --kx-primary: #cc97ff; - --kx-primary-dim: #9c48ea; - --kx-secondary: #53ddfc; - --kx-secondary-dim: #40ceed; - --kx-outline: rgba(64, 72, 93, 0.15); + /* === JSM Professional tokens (2026-06 renewal) === */ + --jsm-bg: #f8fafc; /* slate-50 본문 배경 */ + --jsm-surface: #ffffff; /* 카드 */ + --jsm-surface-alt: #f1f5f9; /* slate-100 섹션 교차 배경 */ + --jsm-ink: #0f172a; /* slate-900 본문 텍스트 */ + --jsm-ink-soft: #475569; /* slate-600 보조 텍스트 */ + --jsm-ink-faint: #94a3b8; /* slate-400 캡션 */ + --jsm-line: #e2e8f0; /* slate-200 보더 */ + --jsm-navy: #0b1f3a; /* 딥네이비 — 푸터/다크 섹션 */ + --jsm-accent: #1d4ed8; /* blue-700 포인트 (단일 포인트 컬러) */ + --jsm-accent-hover: #1e40af; /* blue-800 */ + --jsm-accent-soft: #dbeafe; /* blue-100 뱃지 배경 */ + + /* 기존 kx 변수 재매핑 (잔여 참조 호환용) */ + --kx-surface: var(--jsm-bg); + --kx-surface-low: var(--jsm-surface-alt); + --kx-surface-mid: var(--jsm-surface); + --kx-surface-high: var(--jsm-surface); + --kx-surface-bright: var(--jsm-surface-alt); + --kx-on-surface: var(--jsm-ink); + --kx-on-variant: var(--jsm-ink-soft); + --kx-primary: var(--jsm-accent); + --kx-primary-dim: var(--jsm-accent-hover); + --kx-secondary: var(--jsm-accent); + --kx-secondary-dim: var(--jsm-accent-hover); + --kx-outline: var(--jsm-line); } @theme inline { @@ -56,8 +69,8 @@ --color-foreground: var(--foreground); --color-primary: var(--primary); --color-secondary: var(--secondary); - --font-sans: var(--font-jua), 'Jua', -apple-system, system-ui, sans-serif; - --font-mono: var(--font-jua), 'Jua', ui-monospace, monospace; + --font-sans: var(--font-pretendard, 'Pretendard Variable'), Pretendard, -apple-system, BlinkMacSystemFont, system-ui, sans-serif; + --font-mono: var(--font-pretendard, 'Pretendard Variable'), Pretendard, ui-monospace, monospace; } * { @@ -69,16 +82,16 @@ html { } body { - background: var(--background); - color: var(--foreground); - font-family: var(--font-jua), 'Jua', -apple-system, system-ui, sans-serif; + background: var(--jsm-bg); + color: var(--jsm-ink); + font-family: var(--font-pretendard, 'Pretendard Variable'), Pretendard, -apple-system, BlinkMacSystemFont, system-ui, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -/* font-mono utility → Jua 통일 */ +/* font-mono utility → Pretendard 통일 */ .font-mono, code, pre, kbd, samp { - font-family: var(--font-jua), 'Jua', ui-monospace, monospace; + font-family: var(--font-pretendard, 'Pretendard Variable'), Pretendard, ui-monospace, monospace; } /* Dashboard layout */ @@ -86,7 +99,7 @@ body { display: flex; height: 100dvh; overflow: hidden; - background: var(--background); + background: var(--jsm-bg); } .main-content { @@ -107,18 +120,19 @@ body { .kx-section { background: var(--kx-surface); color: var(--kx-on-surface); - font-family: var(--font-jua), 'Jua', system-ui, sans-serif; + font-family: var(--font-pretendard, 'Pretendard Variable'), Pretendard, system-ui, sans-serif; } .kx-section p, .kx-section li, .kx-section span:not(.kx-label) { color: var(--kx-on-variant); } .kx-display { - font-family: var(--font-jua), 'Jua', system-ui, sans-serif; - letter-spacing: -0.01em; + font-family: var(--font-pretendard, 'Pretendard Variable'), Pretendard, system-ui, sans-serif; + font-weight: 700; + letter-spacing: -0.02em; color: inherit; } .kx-label { - font-family: var(--font-jua), 'Jua', system-ui, sans-serif; + font-family: var(--font-pretendard, 'Pretendard Variable'), Pretendard, system-ui, sans-serif; font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.08em; @@ -147,15 +161,14 @@ body { 0 0 80px 0 rgba(83, 221, 252, 0.08); } .kx-btn-primary { - background: linear-gradient(135deg, #cc97ff 0%, #c284ff 100%); - color: #0b0113; + background: var(--jsm-accent); + color: #ffffff; font-weight: 700; - box-shadow: 0 0 20px 0 rgba(168, 85, 247, 0.4); - transition: transform 0.15s ease, box-shadow 0.15s ease; + transition: background 0.15s ease, transform 0.15s ease; } .kx-btn-primary:hover { + background: var(--jsm-accent-hover); transform: translateY(-1px); - box-shadow: 0 0 28px 0 rgba(168, 85, 247, 0.55); } .kx-btn-ghost { color: var(--kx-secondary); @@ -166,10 +179,8 @@ body { background: var(--kx-surface-bright); } .kx-gradient-text { - background: linear-gradient(135deg, #cc97ff 0%, #53ddfc 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; + color: var(--jsm-ink); + -webkit-text-fill-color: var(--jsm-ink); } .kx-orb { position: absolute; diff --git a/app/layout.tsx b/app/layout.tsx index 74cb6ec..4b91bdf 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,17 +1,10 @@ import type { Metadata } from "next"; import Script from "next/script"; -import { Jua } from "next/font/google"; +import "pretendard/dist/web/variable/pretendardvariable-dynamic-subset.css"; import "./globals.css"; import DashboardShell from "./components/DashboardShell"; import { GlassFilter } from "./components/LiquidGlass"; -const jua = Jua({ - weight: "400", - subsets: ["latin"], - variable: "--font-jua", - display: "swap", -}); - export const metadata: Metadata = { title: { default: "AI 음악·뮤비 팩 ₩39,000~ | 쟁승메이드", @@ -133,7 +126,7 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - + - {children} diff --git a/app/legal/privacy/page.tsx b/app/legal/privacy/page.tsx index 7b3b84b..868a898 100644 --- a/app/legal/privacy/page.tsx +++ b/app/legal/privacy/page.tsx @@ -8,7 +8,7 @@ export const metadata: Metadata = { export default function PrivacyPage() { return (
-

개인정보처리방침

+

개인정보처리방침

diff --git a/app/legal/refund/page.tsx b/app/legal/refund/page.tsx index 3b319e3..3b7cb4b 100644 --- a/app/legal/refund/page.tsx +++ b/app/legal/refund/page.tsx @@ -8,7 +8,7 @@ export const metadata: Metadata = { export default function RefundPage() { return (

-

환불 정책

+

환불 정책

diff --git a/app/legal/terms/page.tsx b/app/legal/terms/page.tsx index daf0343..8c15a1c 100644 --- a/app/legal/terms/page.tsx +++ b/app/legal/terms/page.tsx @@ -8,7 +8,7 @@ export const metadata: Metadata = { export default function TermsPage() { return (

-

이용약관

+

이용약관

diff --git a/app/payment/fail/page.tsx b/app/payment/fail/page.tsx index b47ef19..a417324 100644 --- a/app/payment/fail/page.tsx +++ b/app/payment/fail/page.tsx @@ -19,7 +19,7 @@ function FailContent() {
{code === 'USER_CANCEL' || code === 'PAY_PROCESS_CANCELED' ? '결제 취소' : '결제 실패'}
-

+

{code === 'USER_CANCEL' || code === 'PAY_PROCESS_CANCELED' ? '결제를 취소하셨습니다' : '결제에 실패했습니다'}

{message}

@@ -45,9 +45,9 @@ export default function PaymentFailPage() { return (
-
+
-
+
쟁승메이드 결제 diff --git a/app/payment/success/page.tsx b/app/payment/success/page.tsx index 12f062a..c747a14 100644 --- a/app/payment/success/page.tsx +++ b/app/payment/success/page.tsx @@ -18,7 +18,7 @@ function SuccessContent() {
결제 완료
-

결제가 완료되었습니다!

+

결제가 완료되었습니다!

{paymentId && (

주문번호: {paymentId}

)} @@ -47,9 +47,9 @@ export default function PaymentSuccessPage() { return (
-
+
-
+
쟁승메이드 결제 diff --git a/app/portfolio/[token]/page.tsx b/app/portfolio/[token]/page.tsx index a7245fa..062c0b4 100644 --- a/app/portfolio/[token]/page.tsx +++ b/app/portfolio/[token]/page.tsx @@ -23,69 +23,78 @@ export default async function PortfolioGateway({ params }: Props) { const expires = new Date(payload.exp).toLocaleDateString('ko-KR'); return ( -
-
+
+ {/* 헤더 배너 — jsm-navy 사용 (푸터/다크 섹션 전용 토큰) */} +
+
+
+ 쟁 +
+ 쟁승메이드 + + Private · {payload.memo || 'Confidential'} + +
+
+ +
-
- - - Private Portfolio · {payload.memo || 'Confidential'} +
+ + + 개인 공유 포트폴리오
-

+

박재오
- - 외주 개발 포트폴리오 - + 외주 개발 포트폴리오

-

+

현직 실무 엔지니어 · 계약서 우선 · 납기 패널티 보장 · 소스코드 100% 인도. - 본 페이지는 {expires}까지 유효한 개별 공유 링크입니다. +

+

+ 이 링크는 {expires}까지 유효합니다

-

+

Freelance

-

외주 개발 · 전체 소개

-

+

외주 개발 · 전체 소개

+

계약 프로세스, 납기 패널티, 포트폴리오 사례, 견적 문의.

- + 자세히 보기 → -

+

Website

-

홈페이지·쇼핑몰 제작

-

+

홈페이지·쇼핑몰 제작

+

Next.js 기반 반응형 웹, SEO 기본, 3개월 유지보수 포함.

- + 자세히 보기 →
-
+
© 쟁승메이드 · 010-3907-1392 · bgg8988@gmail.com
diff --git a/app/quote/[token]/page.tsx b/app/quote/[token]/page.tsx index 1b38d01..0935845 100644 --- a/app/quote/[token]/page.tsx +++ b/app/quote/[token]/page.tsx @@ -20,8 +20,8 @@ interface Quote { } const CATEGORY_COLORS: Record = { - 기획: '#60a5fa', 디자인: '#f472b6', 개발: '#34d399', 인프라: '#fb923c', - 유지보수: '#a78bfa', 기타: '#94a3b8', + 기획: '#2563eb', 디자인: '#db2777', 개발: '#059669', 인프라: '#ea580c', + 유지보수: '#7c3aed', 기타: '#64748b', }; export default function QuotePage() { @@ -100,10 +100,10 @@ export default function QuotePage() { if (loading) { return ( -
+
-
-

견적서를 불러오는 중...

+
+

견적서를 불러오는 중...

@@ -112,29 +112,29 @@ export default function QuotePage() { if (notFound || !quote) { return ( -
+
🔍
-

견적서를 찾을 수 없습니다

-

링크가 만료되었거나 잘못된 주소입니다

+

견적서를 찾을 수 없습니다

+

링크가 만료되었거나 잘못된 주소입니다

); } if (submitted) { return ( -
+
🎉
-

견적을 수락해 주셨습니다!

-

+

견적을 수락해 주셨습니다!

+

담당자가 확인 후 빠른 시일 내에 연락드리겠습니다.
선택하신 내용을 기반으로 계약을 진행합니다.

-
-
최종 견적 금액
-
{grandTotal.toLocaleString()}원
+
+
최종 견적 금액
+
{grandTotal.toLocaleString()}원
{maintenanceTotal > 0 && ( -
+ 유지보수 {maintenanceTotal.toLocaleString()}원/월
+
+ 유지보수 {maintenanceTotal.toLocaleString()}원/월
)}
@@ -149,13 +149,12 @@ export default function QuotePage() { ].filter((t) => t.show !== false); return ( -
+
{/* 헤더 */} -
+
{/* 브랜드 */}
-
+
쟁승메이드
-
jaengseung-made.com
+
jaengseung-made.com
- + 공식 견적서