From 5cc224a743d0ca98d74c41de3880822405863c9c Mon Sep 17 00:00:00 2001 From: gahusb Date: Wed, 15 Apr 2026 00:56:34 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20AI=20=EC=9D=8C=EC=95=85=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B0=9C=ED=8E=B8=20=E2=80=94=20=EB=A1=9C=EB=98=90?= =?UTF-8?q?/=ED=94=84=EB=A1=AC=ED=94=84=ED=8A=B8/=EC=9E=90=EB=8F=99?= =?UTF-8?q?=ED=99=94=20=EC=82=AD=EC=A0=9C,=20=EC=9D=8C=EC=95=85/=EB=B8=94?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=ED=8C=A9=20=EC=8B=A0=EA=B7=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 삭제: services/{lotto,prompt,automation,ai-kit,stock,tools} + api/{lotto,tools} - 노출 제거: /freelance, /services/website (noindex + robots/sitemap 제외, 외부 지원서 링크 유지) - 신규: /services/music (3-tier 39k/99k/149k, 4단계 프로세스) - 신규: /services/blog (블로그 자동화 팩 29k 1회성) - 신규: PurchaseAgreementModal (전자상거래법 17조 동의 + 계좌이체) - 개편: 홈 대시보드 (음악 Hero + 사주/블로그팩/일반문의 서브카드) - 사이드바 재구성, sitemap/robots/JSON-LD 갱신 - 환불정책 신규 상품 반영 + 법적 근거 명시 Co-Authored-By: Claude Opus 4.6 --- .claude/settings.local.json | 5 +- .gitignore | 2 + app/api/lotto/_nas.ts | 86 -- app/api/lotto/analysis/personal/route.ts | 13 - app/api/lotto/dashboard/route.ts | 91 -- app/api/lotto/debug/route.ts | 66 - app/api/lotto/history/route.ts | 69 - app/api/lotto/preview/route.ts | 47 - app/api/lotto/purchase/[id]/route.ts | 25 - app/api/lotto/purchase/route.ts | 28 - app/api/lotto/purchase/stats/route.ts | 13 - app/api/lotto/recommend/route.ts | 122 -- app/api/lotto/report/history/route.ts | 15 - app/api/lotto/report/latest/route.ts | 13 - app/api/lotto/stats/performance/route.ts | 15 - app/api/tools/ebay-parts/search/route.ts | 167 --- app/api/tools/naver-blog/generate/route.ts | 65 - app/components/DashboardShell.tsx | 6 +- app/components/PurchaseAgreementModal.tsx | 185 +++ app/components/Sidebar.tsx | 58 +- app/freelance/layout.tsx | 1 + app/layout.tsx | 8 +- app/legal/refund/page.tsx | 6 +- app/mypage/page.tsx | 4 +- app/page.tsx | 458 +++--- app/robots.ts | 2 +- app/saju/result/SajuLottoSection.tsx | 351 ----- app/saju/result/page.tsx | 24 - app/services/ai-kit/page.tsx | 571 -------- app/services/automation/layout.tsx | 31 - app/services/automation/page.tsx | 469 ------ app/services/automation/tools/excel/page.tsx | 344 ----- app/services/automation/tools/ppt/layout.tsx | 25 - app/services/automation/tools/ppt/page.tsx | 365 ----- .../automation/tools/scraper/layout.tsx | 25 - .../automation/tools/scraper/page.tsx | 284 ---- app/services/blog/layout.tsx | 26 + app/services/blog/page.tsx | 243 +++ app/services/lotto/layout.tsx | 25 - app/services/lotto/page.tsx | 6 - app/services/lotto/recommend/PatternTab.tsx | 184 --- app/services/lotto/recommend/PurchaseTab.tsx | 255 ---- app/services/lotto/recommend/ReportTab.tsx | 242 --- app/services/lotto/recommend/page.tsx | 1302 ----------------- app/services/music/layout.tsx | 28 + app/services/music/page.tsx | 483 ++++++ app/services/prompt/layout.tsx | 30 - app/services/prompt/page.tsx | 656 --------- app/services/stock/layout.tsx | 26 - app/services/stock/page.tsx | 6 - app/services/website/layout.tsx | 1 + app/sitemap.ts | 49 +- app/tools/ebay-parts/page.tsx | 541 ------- app/tools/layout.tsx | 16 - app/tools/naver-blog/page.tsx | 524 ------- app/tools/page.tsx | 383 ----- lib/telegram.ts | 1 - 57 files changed, 1175 insertions(+), 7911 deletions(-) delete mode 100644 app/api/lotto/_nas.ts delete mode 100644 app/api/lotto/analysis/personal/route.ts delete mode 100644 app/api/lotto/dashboard/route.ts delete mode 100644 app/api/lotto/debug/route.ts delete mode 100644 app/api/lotto/history/route.ts delete mode 100644 app/api/lotto/preview/route.ts delete mode 100644 app/api/lotto/purchase/[id]/route.ts delete mode 100644 app/api/lotto/purchase/route.ts delete mode 100644 app/api/lotto/purchase/stats/route.ts delete mode 100644 app/api/lotto/recommend/route.ts delete mode 100644 app/api/lotto/report/history/route.ts delete mode 100644 app/api/lotto/report/latest/route.ts delete mode 100644 app/api/lotto/stats/performance/route.ts delete mode 100644 app/api/tools/ebay-parts/search/route.ts delete mode 100644 app/api/tools/naver-blog/generate/route.ts create mode 100644 app/components/PurchaseAgreementModal.tsx delete mode 100644 app/saju/result/SajuLottoSection.tsx delete mode 100644 app/services/ai-kit/page.tsx delete mode 100644 app/services/automation/layout.tsx delete mode 100644 app/services/automation/page.tsx delete mode 100644 app/services/automation/tools/excel/page.tsx delete mode 100644 app/services/automation/tools/ppt/layout.tsx delete mode 100644 app/services/automation/tools/ppt/page.tsx delete mode 100644 app/services/automation/tools/scraper/layout.tsx delete mode 100644 app/services/automation/tools/scraper/page.tsx create mode 100644 app/services/blog/layout.tsx create mode 100644 app/services/blog/page.tsx delete mode 100644 app/services/lotto/layout.tsx delete mode 100644 app/services/lotto/page.tsx delete mode 100644 app/services/lotto/recommend/PatternTab.tsx delete mode 100644 app/services/lotto/recommend/PurchaseTab.tsx delete mode 100644 app/services/lotto/recommend/ReportTab.tsx delete mode 100644 app/services/lotto/recommend/page.tsx create mode 100644 app/services/music/layout.tsx create mode 100644 app/services/music/page.tsx delete mode 100644 app/services/prompt/layout.tsx delete mode 100644 app/services/prompt/page.tsx delete mode 100644 app/services/stock/layout.tsx delete mode 100644 app/services/stock/page.tsx delete mode 100644 app/tools/ebay-parts/page.tsx delete mode 100644 app/tools/layout.tsx delete mode 100644 app/tools/naver-blog/page.tsx delete mode 100644 app/tools/page.tsx diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1ca8ebd..cd185da 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -11,7 +11,10 @@ "Bash(git push:*)", "WebFetch(domain:jaengseung-made.com)", "Bash(npx vercel:*)", - "Bash(1:*)" + "Bash(1:*)", + "Bash(npx next:*)", + "Bash(grep -E \"^d|\\\\.tsx$|\\\\.ts$\")", + "Bash(grep -E \"^d|\\\\.ts$\")" ] } } diff --git a/.gitignore b/.gitignore index 5ef6a52..9c8a03f 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +.vercel diff --git a/app/api/lotto/_nas.ts b/app/api/lotto/_nas.ts deleted file mode 100644 index e801efa..0000000 --- a/app/api/lotto/_nas.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { NextResponse } from 'next/server'; -import { createClient } from '@/lib/supabase/server'; - -const LOTTO_PRODUCT_IDS = ['lotto_gold', 'lotto_platinum', 'lotto_diamond', 'lotto_annual']; - -function nasHeaders() { - const h: Record = {}; - if (process.env.NAS_LOTTO_API_KEY) h['Authorization'] = `Bearer ${process.env.NAS_LOTTO_API_KEY}`; - return h; -} - -function nasBase() { - const base = process.env.NAS_LOTTO_API_URL; - if (!base) throw new Error('NAS_URL_NOT_CONFIGURED'); - return base; -} - -export async function nasGet(path: string, timeoutMs = 25000): Promise { - const res = await fetch(`${nasBase()}${path}`, { - headers: nasHeaders(), signal: AbortSignal.timeout(timeoutMs), - }); - if (!res.ok) throw new Error(`NAS_${res.status}`); - return res.json(); -} - -export async function nasPost(path: string, body: unknown): Promise { - const res = await fetch(`${nasBase()}${path}`, { - method: 'POST', - headers: { ...nasHeaders(), 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - signal: AbortSignal.timeout(25000), - }); - if (!res.ok) throw new Error(`NAS_${res.status}`); - return res.json(); -} - -export async function nasPut(path: string, body: unknown): Promise { - const res = await fetch(`${nasBase()}${path}`, { - method: 'PUT', - headers: { ...nasHeaders(), 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - signal: AbortSignal.timeout(25000), - }); - if (!res.ok) throw new Error(`NAS_${res.status}`); - return res.json(); -} - -export async function nasDelete(path: string): Promise { - const res = await fetch(`${nasBase()}${path}`, { - method: 'DELETE', headers: nasHeaders(), signal: AbortSignal.timeout(25000), - }); - if (!res.ok) throw new Error(`NAS_${res.status}`); - return res.json(); -} - -export interface AuthResult { userId: string; plan: string; } - -export async function requireSubscription(): Promise { - const supabase = await createClient(); - const { data: { user }, error } = await supabase.auth.getUser(); - if (error || !user) return NextResponse.json({ error: 'UNAUTHORIZED' }, { status: 401 }); - - const { data: sub } = await supabase - .from('subscriptions').select('product_id') - .eq('user_id', user.id).eq('status', 'active') - .in('product_id', LOTTO_PRODUCT_IDS).maybeSingle(); - if (sub) return { userId: user.id, plan: sub.product_id }; - - const ago31 = new Date(Date.now() - 31 * 24 * 60 * 60 * 1000).toISOString(); - const { data: order } = await supabase - .from('orders').select('product_id') - .eq('user_id', user.id).eq('status', 'paid') - .in('product_id', LOTTO_PRODUCT_IDS) - .gte('created_at', ago31) - .order('created_at', { ascending: false }).limit(1).maybeSingle(); - if (order) return { userId: user.id, plan: order.product_id }; - - return NextResponse.json({ error: 'NOT_SUBSCRIBED' }, { status: 403 }); -} - -export function handleNasError(err: unknown): NextResponse { - const e = err as { name?: string }; - if (e?.name === 'TimeoutError') return NextResponse.json({ error: 'NAS_TIMEOUT' }, { status: 504 }); - console.error('[NAS]', err); - return NextResponse.json({ error: 'INTERNAL_ERROR' }, { status: 500 }); -} diff --git a/app/api/lotto/analysis/personal/route.ts b/app/api/lotto/analysis/personal/route.ts deleted file mode 100644 index dd0b55e..0000000 --- a/app/api/lotto/analysis/personal/route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NextResponse } from 'next/server'; -import { nasGet, requireSubscription, handleNasError } from '../../_nas'; - -export const maxDuration = 60; - -export async function GET() { - try { - const auth = await requireSubscription(); - if (auth instanceof NextResponse) return auth; - const data = await nasGet('/api/lotto/analysis/personal'); - return NextResponse.json(data); - } catch (err) { return handleNasError(err); } -} diff --git a/app/api/lotto/dashboard/route.ts b/app/api/lotto/dashboard/route.ts deleted file mode 100644 index c6ce57a..0000000 --- a/app/api/lotto/dashboard/route.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { NextResponse } from 'next/server'; -import { createClient } from '@/lib/supabase/server'; - -const LOTTO_PRODUCT_IDS = ['lotto_gold', 'lotto_platinum', 'lotto_diamond']; - -async function nasGet(path: string): Promise { - const base = process.env.NAS_LOTTO_API_URL; - if (!base) throw new Error('NAS_URL_NOT_CONFIGURED'); - - const headers: Record = {}; - if (process.env.NAS_LOTTO_API_KEY) { - headers['Authorization'] = `Bearer ${process.env.NAS_LOTTO_API_KEY}`; - } - - const res = await fetch(`${base}${path}`, { - method: 'GET', - headers, - signal: AbortSignal.timeout(10000), - }); - - if (!res.ok) throw new Error(`NAS_${res.status}`); - return res.json(); -} - -/** - * GET /api/lotto/dashboard - * 페이지 초기 로드용: latest + analysis + simulation 이력 병렬 조회 - * - * Response: - * { - * plan: string, - * latest: { drawNo, date, numbers, bonus, metrics }, - * analysis: { total_draws, mean_sum, std_sum, number_stats[] }, - * simulation: { runs[] } - * } - */ -export async function GET() { - try { - // 1. 인증 - const supabase = await createClient(); - const { data: { user }, error: authError } = await supabase.auth.getUser(); - if (authError || !user) { - return NextResponse.json({ error: 'UNAUTHORIZED' }, { status: 401 }); - } - - // 2. 구독 확인 - const { data: orders } = await supabase - .from('orders') - .select('id, product_id, status, created_at') - .eq('user_id', user.id) - .eq('status', 'paid') - .in('product_id', LOTTO_PRODUCT_IDS) - .order('created_at', { ascending: false }) - .limit(1); - - if (!orders || orders.length === 0) { - return NextResponse.json({ error: 'NOT_SUBSCRIBED' }, { status: 403 }); - } - - const order = orders[0]; - const diffDays = - (Date.now() - new Date(order.created_at).getTime()) / (1000 * 60 * 60 * 24); - const maxDays = order.product_id === 'lotto_annual' ? 366 : 31; - - if (diffDays > maxDays) { - return NextResponse.json({ error: 'NOT_SUBSCRIBED' }, { status: 403 }); - } - - // 3. NAS 병렬 조회 - const [latest, analysis, simulation] = await Promise.allSettled([ - nasGet('/api/lotto/latest'), - nasGet('/api/lotto/analysis'), - nasGet('/api/lotto/simulation'), - ]); - - return NextResponse.json({ - ok: true, - plan: order.product_id, - latest: latest.status === 'fulfilled' ? latest.value : null, - analysis: analysis.status === 'fulfilled' ? analysis.value : null, - simulation: simulation.status === 'fulfilled' ? simulation.value : null, - }); - } catch (err: unknown) { - const e = err as { name?: string; message?: string }; - if (e?.name === 'TimeoutError') { - return NextResponse.json({ error: 'NAS_TIMEOUT' }, { status: 504 }); - } - console.error('Lotto dashboard error:', err); - return NextResponse.json({ error: 'INTERNAL_ERROR' }, { status: 500 }); - } -} diff --git a/app/api/lotto/debug/route.ts b/app/api/lotto/debug/route.ts deleted file mode 100644 index 149e80a..0000000 --- a/app/api/lotto/debug/route.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { NextResponse } from 'next/server'; - -/** - * GET /api/lotto/debug - * NAS 엔드포인트 접근 가능 여부 일괄 진단 - * 배포 후 브라우저에서 직접 호출: https://www.jaengseung-made.com/api/lotto/debug - */ -export async function GET() { - const base = process.env.NAS_LOTTO_API_URL; - if (!base) return NextResponse.json({ error: 'NAS_URL_NOT_CONFIGURED' }, { status: 500 }); - - const paths = [ - // 현재 코드에서 호출 중인 경로들 - '/api/lotto/recommend', - '/api/lotto/recommend/batch', - '/api/lotto/latest', - '/api/lotto/analysis', - '/api/lotto/simulation', - '/api/lotto/stats', - '/api/lotto/stats/performance', - '/api/lotto/report/latest', - '/api/lotto/report/history', - '/api/lotto/purchase', - '/api/lotto/purchase/stats', - '/api/lotto/analysis/personal', - '/api/history', - // NAS API 스펙 문서 - '/openapi.json', - '/docs', - ]; - - const results = await Promise.all( - paths.map(async (path) => { - const start = Date.now(); - try { - const res = await fetch(`${base}${path}`, { - signal: AbortSignal.timeout(6000), - }); - const ms = Date.now() - start; - let body: unknown = null; - try { body = await res.json(); } catch { /* ignore */ } - return { path, status: res.status, ok: res.ok, ms, body }; - } catch (err) { - const e = err as { name?: string; message?: string; code?: string }; - return { - path, - status: null, - ok: false, - ms: Date.now() - start, - error: e.code ?? e.name ?? e.message, - }; - } - }) - ); - - const ok = results.filter(r => r.ok); - const fail = results.filter(r => !r.ok); - - return NextResponse.json({ - base, - summary: { total: results.length, ok: ok.length, fail: fail.length }, - ok: ok.map(r => ({ path: r.path, status: r.status, ms: r.ms })), - fail: fail.map(r => ({ path: r.path, status: r.status, error: (r as { error?: string }).error, ms: r.ms })), - full: results, - }); -} diff --git a/app/api/lotto/history/route.ts b/app/api/lotto/history/route.ts deleted file mode 100644 index b8ebf57..0000000 --- a/app/api/lotto/history/route.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { createClient } from '@/lib/supabase/server'; - -/** - * POST /api/lotto/history - * 생성된 로또 번호 조합을 히스토리에 저장 - * Body: { numbers: number[], source: 'nas' | 'client', plan_id: string } - * - * GET /api/lotto/history - * 내 로또 번호 히스토리 조회 - * Query: limit (기본 50) - */ - -export async function POST(req: NextRequest) { - const supabase = await createClient(); - const { data: { user }, error: authError } = await supabase.auth.getUser(); - if (authError || !user) { - return NextResponse.json({ error: 'UNAUTHORIZED' }, { status: 401 }); - } - - let body: { numbers?: number[]; source?: string; plan_id?: string }; - try { - body = await req.json(); - } catch { - return NextResponse.json({ error: 'INVALID_JSON' }, { status: 400 }); - } - - const { numbers, source = 'client', plan_id } = body; - if (!Array.isArray(numbers) || numbers.length !== 6 || !plan_id) { - return NextResponse.json({ error: 'INVALID_BODY' }, { status: 400 }); - } - - const { error } = await supabase.from('lotto_history').insert({ - user_id: user.id, - numbers, - source, - plan_id, - }); - - if (error) { - console.error('lotto_history insert error:', error); - return NextResponse.json({ error: 'DB_ERROR' }, { status: 500 }); - } - - return NextResponse.json({ ok: true }); -} - -export async function GET(req: NextRequest) { - const supabase = await createClient(); - const { data: { user }, error: authError } = await supabase.auth.getUser(); - if (authError || !user) { - return NextResponse.json({ error: 'UNAUTHORIZED' }, { status: 401 }); - } - - const limit = Math.min(Number(req.nextUrl.searchParams.get('limit') ?? '50'), 200); - - const { data, error } = await supabase - .from('lotto_history') - .select('id, numbers, source, plan_id, created_at') - .eq('user_id', user.id) - .order('created_at', { ascending: false }) - .limit(limit); - - if (error) { - return NextResponse.json({ error: 'DB_ERROR' }, { status: 500 }); - } - - return NextResponse.json({ ok: true, history: data ?? [] }); -} diff --git a/app/api/lotto/preview/route.ts b/app/api/lotto/preview/route.ts deleted file mode 100644 index cd145a7..0000000 --- a/app/api/lotto/preview/route.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { NextResponse } from 'next/server'; - -/** - * GET /api/lotto/preview - * 인증 없이 NAS /api/lotto/recommend 단일 호출 (맛보기용 무료 추천) - * NAS 미연결 시 → { error: 'NAS_UNAVAILABLE' } 503 반환 - * 클라이언트에서 이 경우 자체 Monte Carlo 폴백 처리 - */ -export async function GET() { - const base = process.env.NAS_LOTTO_API_URL; - - if (!base) { - return NextResponse.json({ error: 'NAS_UNAVAILABLE' }, { status: 503 }); - } - - try { - const headers: Record = {}; - if (process.env.NAS_LOTTO_API_KEY) { - headers['Authorization'] = `Bearer ${process.env.NAS_LOTTO_API_KEY}`; - } - - const res = await fetch(`${base}/api/lotto/recommend`, { - method: 'GET', - headers, - signal: AbortSignal.timeout(8000), - }); - - if (!res.ok) { - console.warn(`[lotto/preview] NAS returned ${res.status}`); - return NextResponse.json({ error: 'NAS_UNAVAILABLE' }, { status: 503 }); - } - - const data = await res.json(); - - return NextResponse.json({ - ok: true, - source: 'nas', - numbers: data.numbers ?? [], - metrics: data.metrics ?? null, - }); - } catch (err: unknown) { - // ECONNREFUSED, 타임아웃 등 — 클라이언트 폴백 신호 - const e = err as { name?: string; code?: string; message?: string }; - console.warn('[lotto/preview] NAS unreachable:', e?.code ?? e?.message ?? e?.name); - return NextResponse.json({ error: 'NAS_UNAVAILABLE' }, { status: 503 }); - } -} diff --git a/app/api/lotto/purchase/[id]/route.ts b/app/api/lotto/purchase/[id]/route.ts deleted file mode 100644 index d9ff974..0000000 --- a/app/api/lotto/purchase/[id]/route.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { NextResponse } from 'next/server'; -import { nasPut, nasDelete, requireSubscription, handleNasError } from '../../_nas'; - -export const maxDuration = 60; - -export async function PUT(request: Request, { params }: { params: Promise<{ id: string }> }) { - try { - const auth = await requireSubscription(); - if (auth instanceof NextResponse) return auth; - const { id } = await params; - const body = await request.json(); - const data = await nasPut(`/api/lotto/purchase/${id}`, body); - return NextResponse.json(data); - } catch (err) { return handleNasError(err); } -} - -export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) { - try { - const auth = await requireSubscription(); - if (auth instanceof NextResponse) return auth; - const { id } = await params; - const data = await nasDelete(`/api/lotto/purchase/${id}`); - return NextResponse.json(data); - } catch (err) { return handleNasError(err); } -} diff --git a/app/api/lotto/purchase/route.ts b/app/api/lotto/purchase/route.ts deleted file mode 100644 index b4e9fc8..0000000 --- a/app/api/lotto/purchase/route.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NextResponse } from 'next/server'; -import { nasGet, nasPost, requireSubscription, handleNasError } from '../_nas'; - -export const maxDuration = 60; - -export async function GET(request: Request) { - try { - const auth = await requireSubscription(); - if (auth instanceof NextResponse) return auth; - const { searchParams } = new URL(request.url); - const params = new URLSearchParams(); - if (searchParams.get('draw_no')) params.set('draw_no', searchParams.get('draw_no')!); - if (searchParams.get('days')) params.set('days', searchParams.get('days')!); - const qs = params.toString() ? `?${params}` : ''; - const data = await nasGet(`/api/lotto/purchase${qs}`); - return NextResponse.json(data); - } catch (err) { return handleNasError(err); } -} - -export async function POST(request: Request) { - try { - const auth = await requireSubscription(); - if (auth instanceof NextResponse) return auth; - const body = await request.json(); - const data = await nasPost('/api/lotto/purchase', body); - return NextResponse.json(data, { status: 201 }); - } catch (err) { return handleNasError(err); } -} diff --git a/app/api/lotto/purchase/stats/route.ts b/app/api/lotto/purchase/stats/route.ts deleted file mode 100644 index 4464c32..0000000 --- a/app/api/lotto/purchase/stats/route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NextResponse } from 'next/server'; -import { nasGet, requireSubscription, handleNasError } from '../../_nas'; - -export const maxDuration = 60; - -export async function GET() { - try { - const auth = await requireSubscription(); - if (auth instanceof NextResponse) return auth; - const data = await nasGet('/api/lotto/purchase/stats'); - return NextResponse.json(data); - } catch (err) { return handleNasError(err); } -} diff --git a/app/api/lotto/recommend/route.ts b/app/api/lotto/recommend/route.ts deleted file mode 100644 index 2fcb4ce..0000000 --- a/app/api/lotto/recommend/route.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { createClient } from '@/lib/supabase/server'; - -const LOTTO_PRODUCT_IDS = ['lotto_gold', 'lotto_platinum', 'lotto_diamond']; - -/** 구독 유효 여부 확인 */ -async function checkSubscription(supabase: Awaited>, userId: string) { - const { data: orders } = await supabase - .from('orders') - .select('id, product_id, status, created_at') - .eq('user_id', userId) - .eq('status', 'paid') - .in('product_id', LOTTO_PRODUCT_IDS) - .order('created_at', { ascending: false }) - .limit(1); - - if (!orders || orders.length === 0) return null; - - const order = orders[0]; - const diffDays = - (Date.now() - new Date(order.created_at).getTime()) / (1000 * 60 * 60 * 24); - const maxDays = order.product_id === 'lotto_annual' ? 366 : 31; - - return diffDays <= maxDays ? order : null; -} - -/** NAS API 호출 헬퍼 */ -async function nasGet(path: string): Promise { - const base = process.env.NAS_LOTTO_API_URL; - if (!base) throw new Error('NAS_URL_NOT_CONFIGURED'); - - const headers: Record = {}; - if (process.env.NAS_LOTTO_API_KEY) { - headers['Authorization'] = `Bearer ${process.env.NAS_LOTTO_API_KEY}`; - } - - return fetch(`${base}${path}`, { - method: 'GET', - headers, - signal: AbortSignal.timeout(15000), - }); -} - -/** - * GET /api/lotto/recommend - * Query params: - * mode = "single" (기본) | "batch" | "best" - * - * single → NAS GET /api/lotto/recommend - * batch → NAS GET /api/lotto/recommend/batch (5개 조합) - * best → NAS GET /api/lotto/best (Monte Carlo 상위 20쌍) - */ -export async function GET(req: NextRequest) { - try { - // 1. 세션 확인 - const supabase = await createClient(); - const { data: { user }, error: authError } = await supabase.auth.getUser(); - - if (authError || !user) { - return NextResponse.json({ error: 'UNAUTHORIZED' }, { status: 401 }); - } - - // 2. 구독 확인 - const order = await checkSubscription(supabase, user.id); - if (!order) { - return NextResponse.json({ error: 'NOT_SUBSCRIBED' }, { status: 403 }); - } - - const mode = req.nextUrl.searchParams.get('mode') ?? 'single'; - - // 3. NAS API 호출 - const nasPath = - mode === 'batch' - ? '/api/lotto/recommend/batch' - : mode === 'best' - ? '/api/lotto/best' - : '/api/lotto/recommend'; - - let nasRes: Response; - try { - nasRes = await nasGet(nasPath); - } catch (fetchErr: unknown) { - const e = fetchErr as { name?: string }; - console.warn('NAS unreachable:', fetchErr); - if (e?.name === 'TimeoutError') { - return NextResponse.json( - { error: 'NAS_UNAVAILABLE', plan: order.product_id, mode }, - { status: 503 } - ); - } - return NextResponse.json( - { error: 'NAS_UNAVAILABLE', plan: order.product_id, mode }, - { status: 503 } - ); - } - - if (!nasRes.ok) { - const errText = await nasRes.text(); - console.error('NAS API error:', nasRes.status, errText); - return NextResponse.json( - { error: 'NAS_UNAVAILABLE', plan: order.product_id, mode }, - { status: 503 } - ); - } - - const nasData = await nasRes.json(); - - return NextResponse.json({ - ok: true, - plan: order.product_id, - mode, - ...nasData, - }); - } catch (err: unknown) { - const e = err as { name?: string; message?: string }; - if (e?.message === 'NAS_URL_NOT_CONFIGURED') { - return NextResponse.json({ error: 'NAS_URL_NOT_CONFIGURED' }, { status: 500 }); - } - console.error('Lotto recommend error:', err); - return NextResponse.json({ error: 'INTERNAL_ERROR' }, { status: 500 }); - } -} diff --git a/app/api/lotto/report/history/route.ts b/app/api/lotto/report/history/route.ts deleted file mode 100644 index bc641b6..0000000 --- a/app/api/lotto/report/history/route.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { NextResponse } from 'next/server'; -import { nasGet, requireSubscription, handleNasError } from '../../_nas'; - -export const maxDuration = 60; - -export async function GET(request: Request) { - try { - const auth = await requireSubscription(); - if (auth instanceof NextResponse) return auth; - const { searchParams } = new URL(request.url); - const limit = searchParams.get('limit') ?? '10'; - const data = await nasGet(`/api/lotto/report/history?limit=${limit}`); - return NextResponse.json(data); - } catch (err) { return handleNasError(err); } -} diff --git a/app/api/lotto/report/latest/route.ts b/app/api/lotto/report/latest/route.ts deleted file mode 100644 index 88758d9..0000000 --- a/app/api/lotto/report/latest/route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NextResponse } from 'next/server'; -import { nasGet, requireSubscription, handleNasError } from '../../_nas'; - -export const maxDuration = 60; - -export async function GET() { - try { - const auth = await requireSubscription(); - if (auth instanceof NextResponse) return auth; - const data = await nasGet('/api/lotto/report/latest'); - return NextResponse.json(data); - } catch (err) { return handleNasError(err); } -} diff --git a/app/api/lotto/stats/performance/route.ts b/app/api/lotto/stats/performance/route.ts deleted file mode 100644 index 4705be0..0000000 --- a/app/api/lotto/stats/performance/route.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { NextResponse } from 'next/server'; -import { nasGet, handleNasError } from '../../_nas'; - -// 공개 집계 데이터 — 인증 불필요, Vercel CDN에서 10분 캐시 -export const maxDuration = 60; -export const revalidate = 600; // 10분 - -export async function GET() { - try { - const data = await nasGet('/api/lotto/stats/performance'); - const res = NextResponse.json(data); - res.headers.set('Cache-Control', 's-maxage=600, stale-while-revalidate=60'); - return res; - } catch (err) { return handleNasError(err); } -} diff --git a/app/api/tools/ebay-parts/search/route.ts b/app/api/tools/ebay-parts/search/route.ts deleted file mode 100644 index 49a440d..0000000 --- a/app/api/tools/ebay-parts/search/route.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { NextResponse } from 'next/server'; -import { crawlAll } from '@/lib/ebay-tools/crawler'; -import { analyzeWithAI } from '@/lib/ebay-tools/ai-analyzer'; -import { calculatePricing } from '@/lib/ebay-tools/pricing'; -import type { SearchResult, PriceSource } from '@/lib/ebay-tools/types'; - -export const maxDuration = 60; // Vercel Pro timeout - -export async function POST(request: Request) { - const startTime = Date.now(); - - try { - const body = await request.json(); - const { partNumber, partName } = body; - - if (!partNumber || typeof partNumber !== 'string' || partNumber.trim().length === 0) { - return NextResponse.json( - { success: false, error: '품번을 입력해주세요.' }, - { status: 400 } - ); - } - - const trimmedPart = partNumber.trim(); - - if (trimmedPart.length > 50) { - return NextResponse.json( - { success: false, error: '품번은 50자 이내로 입력해주세요.' }, - { status: 400 } - ); - } - - if (!/^[a-zA-Z0-9\s\-_.\/]+$/.test(trimmedPart)) { - return NextResponse.json( - { success: false, error: '품번에 허용되지 않는 문자가 포함되어 있습니다.' }, - { status: 400 } - ); - } - - const trimmedName = partName?.trim() || undefined; - - // 1. 크롤링 (RockAuto + eBay) - const crawlResults = await crawlAll(trimmedPart); - - // 2. AI 분석 (Claude API) - let aiResult; - const hasApiKey = !!process.env.ANTHROPIC_API_KEY; - - if (hasApiKey) { - try { - aiResult = await analyzeWithAI(trimmedPart, trimmedName, crawlResults); - } catch (aiError) { - console.error('[EbayParts] AI analysis failed, using fallback:', aiError); - } - } - - // AI 실패 또는 API 키 없으면 크롤링 데이터에서 기본 추출 - if (!aiResult) { - aiResult = buildFallbackResult(trimmedPart, trimmedName, crawlResults); - } - - // 3. 가격 비교 + 환율/관세 계산 - const priceSources: PriceSource[] = extractPrices(crawlResults); - const pricing = await calculatePricing(priceSources, aiResult.basicInfo.partName); - - const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); - - const result: SearchResult = { - success: true, - data: { - basicInfo: aiResult.basicInfo, - listing: aiResult.listing, - fitment: aiResult.fitment, - pricing, - rawData: Object.fromEntries( - crawlResults.map(r => [r.source, { success: r.success, data: r.data, error: r.error }]) - ), - meta: { - searchedAt: new Date().toISOString(), - sourcesChecked: crawlResults.map(r => r.source), - processingTime: `${elapsed}s`, - aiModel: hasApiKey ? 'claude-sonnet-4-20250514' : 'fallback (no API key)', - }, - }, - }; - - return NextResponse.json(result, { status: 200 }); - } catch (error) { - console.error('[EbayParts] Search error:', error); - return NextResponse.json( - { success: false, error: '검색 처리 중 오류가 발생했습니다.' }, - { status: 500 } - ); - } -} - -// 크롤링 결과에서 가격 추출 -function extractPrices(crawlResults: Awaited>): PriceSource[] { - const prices: PriceSource[] = []; - - for (const result of crawlResults) { - if (!result.success) continue; - - if (result.source === 'RockAuto') { - const parts = (result.data.parts as Array<{ price?: string; name?: string }>) || []; - for (const part of parts) { - if (part.price) { - const numericPrice = parseFloat(part.price.replace(/[^0-9.]/g, '')); - if (!isNaN(numericPrice) && numericPrice > 0) { - prices.push({ - site: 'RockAuto', - price: numericPrice, - currency: 'USD', - url: String(result.data.searchUrl || ''), - }); - break; // 첫 번째 가격만 - } - } - } - } - - if (result.source === 'eBay') { - const listings = (result.data.listings as Array<{ price?: string; url?: string }>) || []; - for (const listing of listings.slice(0, 2)) { - if (listing.price) { - const numericPrice = parseFloat(listing.price.replace(/[^0-9.]/g, '')); - if (!isNaN(numericPrice) && numericPrice > 0) { - prices.push({ - site: 'eBay (참고)', - price: numericPrice, - currency: 'USD', - url: listing.url || '', - }); - } - } - } - } - } - - return prices; -} - -// AI 없이 기본 결과 생성 -function buildFallbackResult( - partNumber: string, - partName: string | undefined, - crawlResults: Awaited> -) { - const name = partName || partNumber; - - return { - basicInfo: { - partNumber, - partName: name, - brand: '', - oemNumbers: [partNumber], - category: 'eBay Motors > Parts & Accessories > Car & Truck Parts', - }, - listing: { - title: `${name} ${partNumber} Auto Part`, - category: '', - itemSpecifics: { - 'Manufacturer Part Number': partNumber, - }, - }, - fitment: [], - }; -} diff --git a/app/api/tools/naver-blog/generate/route.ts b/app/api/tools/naver-blog/generate/route.ts deleted file mode 100644 index b5a4422..0000000 --- a/app/api/tools/naver-blog/generate/route.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { NextResponse } from 'next/server'; -import { generateBlogPost } from '@/lib/blog-tools/generator'; -import type { BlogStyle, BlogTone, BlogLength } from '@/lib/blog-tools/types'; - -export const maxDuration = 60; - -const VALID_STYLES: BlogStyle[] = ['informational', 'review', 'howto', 'listicle', 'comparison', 'story']; -const VALID_TONES: BlogTone[] = ['professional', 'friendly', 'casual', 'formal']; -const VALID_LENGTHS: BlogLength[] = ['short', 'medium', 'long']; - -export async function POST(request: Request) { - try { - const body = await request.json(); - const { topic, keywords, style, tone, length, imageGuide, sections } = body; - - // 유효성 검증 - if (!topic || typeof topic !== 'string' || topic.trim().length === 0) { - return NextResponse.json({ success: false, error: '주제를 입력해주세요.' }, { status: 400 }); - } - - if (topic.trim().length > 100) { - return NextResponse.json({ success: false, error: '주제는 100자 이내로 입력해주세요.' }, { status: 400 }); - } - - if (!Array.isArray(keywords) || keywords.length === 0) { - return NextResponse.json({ success: false, error: '키워드를 최소 1개 입력해주세요.' }, { status: 400 }); - } - - if (keywords.length > 10) { - return NextResponse.json({ success: false, error: '키워드는 최대 10개까지 가능합니다.' }, { status: 400 }); - } - - if (!VALID_STYLES.includes(style)) { - return NextResponse.json({ success: false, error: '유효하지 않은 글 형식입니다.' }, { status: 400 }); - } - - if (!VALID_TONES.includes(tone)) { - return NextResponse.json({ success: false, error: '유효하지 않은 톤입니다.' }, { status: 400 }); - } - - if (!VALID_LENGTHS.includes(length)) { - return NextResponse.json({ success: false, error: '유효하지 않은 분량입니다.' }, { status: 400 }); - } - - const sectionCount = Math.min(Math.max(Number(sections) || 4, 3), 8); - - const result = await generateBlogPost({ - topic: topic.trim(), - keywords: keywords.map((k: string) => k.trim()).filter(Boolean), - style, - tone, - length, - imageGuide: Boolean(imageGuide), - sections: sectionCount, - }); - - return NextResponse.json(result, { status: 200 }); - } catch (error) { - console.error('[NaverBlog] Generate error:', error); - return NextResponse.json( - { success: false, error: '블로그 글 생성 중 오류가 발생했습니다.' }, - { status: 500 } - ); - } -} diff --git a/app/components/DashboardShell.tsx b/app/components/DashboardShell.tsx index c12e959..0629b10 100644 --- a/app/components/DashboardShell.tsx +++ b/app/components/DashboardShell.tsx @@ -4,15 +4,15 @@ import { useState } from 'react'; import { usePathname } from 'next/navigation'; import Sidebar from './Sidebar'; -const AUTH_PATHS = ['/login', '/signup', '/admin']; +const STANDALONE_PATHS = ['/login', '/signup', '/admin']; export default function DashboardShell({ children }: { children: React.ReactNode }) { const [sidebarOpen, setSidebarOpen] = useState(false); const pathname = usePathname(); - const isAuthPage = AUTH_PATHS.some((p) => pathname.startsWith(p)); + const isStandalone = STANDALONE_PATHS.some((p) => pathname.startsWith(p)); - if (isAuthPage) { + if (isStandalone) { return <>{children}; } diff --git a/app/components/PurchaseAgreementModal.tsx b/app/components/PurchaseAgreementModal.tsx new file mode 100644 index 0000000..c105892 --- /dev/null +++ b/app/components/PurchaseAgreementModal.tsx @@ -0,0 +1,185 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; + +interface Props { + isOpen: boolean; + onClose: () => void; + productName: string; + price: string; + bankInfo?: { + bank: string; + account: string; + holder: string; + }; +} + +const DEFAULT_BANK = { + bank: '토스뱅크', + account: '1000-0000-0000', + holder: '박재오', +}; + +export default function PurchaseAgreementModal({ + isOpen, + onClose, + productName, + price, + bankInfo = DEFAULT_BANK, +}: Props) { + const [agreed, setAgreed] = useState(false); + const [email, setEmail] = useState(''); + const [sent, setSent] = useState(false); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (!isOpen) { + setAgreed(false); + setEmail(''); + setSent(false); + } + }, [isOpen]); + + if (!isOpen) return null; + + const handleSubmit = async () => { + if (!agreed || !email) return; + setLoading(true); + try { + await fetch('/api/contact', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + service: `구매 신청: ${productName}`, + name: email.split('@')[0], + email, + phone: '', + message: `상품: ${productName} (${price})\n입금 대기 중. 입금 확인 후 이메일로 상품 전달 예정.`, + }), + }); + setSent(true); + } catch (e) { + alert('신청 전송 실패. 다시 시도해주세요.'); + } finally { + setLoading(false); + } + }; + + return ( +
+
e.stopPropagation()} + > +
+

{productName}

+

{price}

+
+ + {sent ? ( +
+
+

신청 완료

+

+ 아래 계좌로 입금해주시면 24시간 이내 이메일로 상품을 전달드립니다. +

+
+

입금 계좌

+

+ {bankInfo.bank} {bankInfo.account} +

+

예금주 {bankInfo.holder}

+
+ +
+ ) : ( +
+
+ + setEmail(e.target.value)} + placeholder="your@email.com" + className="w-full px-4 py-3 border border-slate-300 rounded-xl text-sm focus:outline-none focus:border-violet-500" + /> +
+ +
+

📌 구매 전 확인사항

+
    +
  • + 본 상품은 디지털 콘텐츠로, 제공 시작(이메일 전달) 후에는 + 전자상거래법 제17조 제2항 제5호에 따라 청약철회(환불)가 제한됩니다. +
  • +
  • + 구매 전 랜딩 페이지의 샘플 미리보기·무료 체험 구간을 반드시 확인해주세요. +
  • +
  • + 파일 손상·전달 누락 등 회사 귀책 사유 시 즉시 재전달 또는 전액 환불됩니다. +
  • +
  • + 자세한 내용은{' '} + + 환불 정책 + {' '} + 참조. +
  • +
+
+ + + +
+

💳 결제 방법: 계좌이체

+

+ {bankInfo.bank} {bankInfo.account} ({bankInfo.holder}) +

+

+ 신청 후 위 계좌로 입금하시면 24시간 이내 이메일 전달. +

+
+ +
+ + +
+
+ )} +
+
+ ); +} diff --git a/app/components/Sidebar.tsx b/app/components/Sidebar.tsx index dcd6f9a..0b6a07d 100644 --- a/app/components/Sidebar.tsx +++ b/app/components/Sidebar.tsx @@ -24,32 +24,21 @@ const navGroups: NavGroup[] = [ title: 'AI 상품', items: [ { - href: '/services/prompt', - label: '프롬프트 스토어', - badge: 'HOT', + href: '/services/music', + label: 'AI 음악 마스터', + badge: 'NEW', icon: ( - + ), }, { - href: '/services/automation', - label: '업무 자동화', + href: '/services/blog', + label: '블로그 자동화 팩', icon: ( - - - - ), - }, - { - href: '/services/ai-kit', - label: 'AI 자동화 키트', - badge: '구독', - icon: ( - - + ), }, @@ -68,39 +57,6 @@ const navGroups: NavGroup[] = [ ), }, - { - href: '/tools', - label: '도구 쇼케이스', - badge: 'DEMO', - icon: ( - - - - ), - }, - ], - }, - { - title: '외주 의뢰', - items: [ - { - href: '/freelance', - label: '외주 개발 문의', - icon: ( - - - - ), - }, - { - href: '/services/website', - label: '홈페이지 제작', - icon: ( - - - - ), - }, ], }, ]; diff --git a/app/freelance/layout.tsx b/app/freelance/layout.tsx index def6078..ce11d72 100644 --- a/app/freelance/layout.tsx +++ b/app/freelance/layout.tsx @@ -19,6 +19,7 @@ export const metadata: Metadata = { '47건 납품 완료. 계약서 먼저, 납기 패널티, 소스코드 100% 인도. 연락 두절 없는 개발자.', url: 'https://jaengseung-made.com/freelance', }, + robots: { index: false, follow: false }, }; export default function FreelanceLayout({ children }: { children: React.ReactNode }) { diff --git a/app/layout.tsx b/app/layout.tsx index 7ab7d9b..aef1f7a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -72,7 +72,7 @@ const jsonLd = { '@id': 'https://jaengseung-made.com/#business', name: '쟁승메이드', url: 'https://jaengseung-made.com', - description: 'AI 프롬프트 패키지, 업무 자동화, AI 사주 분석. 7년차 현직 개발자가 직접 만들고 운영하는 AI 도구 스토어.', + description: 'AI 음악 작곡·뮤비 구조 설계 팩, 블로그 자동화 팩, AI 사주 분석. 7년차 현직 개발자가 직접 만들고 운영하는 AI 크리에이티브 스토어.', email: 'bgg8988@gmail.com', telephone: '010-3907-1392', priceRange: '₩', @@ -81,11 +81,9 @@ const jsonLd = { '@type': 'OfferCatalog', name: '쟁승메이드 AI 도구 · 서비스', itemListElement: [ - { '@type': 'Offer', itemOffered: { '@type': 'Product', name: 'AI 프롬프트 패키지', url: 'https://jaengseung-made.com/services/prompt', description: 'ChatGPT·Claude 업무 최적화 프롬프트. 자소서, 마케팅, 이메일, 보고서 등.' } }, - { '@type': 'Offer', itemOffered: { '@type': 'Service', name: '업무 자동화 개발', url: 'https://jaengseung-made.com/services/automation' } }, - { '@type': 'Offer', itemOffered: { '@type': 'Product', name: 'AI 자동화 키트', url: 'https://jaengseung-made.com/services/ai-kit', description: '업무일지·이메일·SNS 자동화 도구 6종 월 구독.' } }, + { '@type': 'Offer', itemOffered: { '@type': 'Product', name: 'AI 음악 마스터 구조 팩', url: 'https://jaengseung-made.com/services/music', description: 'Suno 프롬프트 + MV 워크플로우 + 저작권 가이드 + 템플릿 PDF + 샘플 프로젝트. 4단계 AI 음악 제작 공정.' } }, + { '@type': 'Offer', itemOffered: { '@type': 'Product', name: '블로그 자동화 솔루션 팩', url: 'https://jaengseung-made.com/services/blog', description: '쿠팡파트너스·애드포스트 수익화 프롬프트 조합법 + 구조 템플릿 PDF + 샘플.' } }, { '@type': 'Offer', itemOffered: { '@type': 'Service', name: 'AI 사주 분석', url: 'https://jaengseung-made.com/saju', description: '생년월일 기반 AI 사주팔자 분석. 무료 체험 가능.' } }, - { '@type': 'Offer', itemOffered: { '@type': 'Service', name: '맞춤 외주 개발', url: 'https://jaengseung-made.com/freelance' } }, ], }, }, diff --git a/app/legal/refund/page.tsx b/app/legal/refund/page.tsx index a7429b3..eae8836 100644 --- a/app/legal/refund/page.tsx +++ b/app/legal/refund/page.tsx @@ -18,7 +18,11 @@ export default function RefundPage() {

1. 디지털 콘텐츠 (즉시 제공 상품)

-

대상: 프롬프트 패키지, AI 사주 리포트, 로또 분석 등

+

대상: AI 음악 마스터 구조 팩, 블로그 자동화 솔루션 팩, AI 사주 리포트 등 디지털 콘텐츠 상품 일체

+

+ 전자상거래법 제17조 제2항 제5호에 따라, 디지털 콘텐츠는 제공이 개시된 이후 청약철회가 제한됩니다. + 회사는 구매 전 무료 샘플·미리보기를 제공하고, 구매 시 환불 제한 사항에 대한 소비자 동의를 확인합니다. +

diff --git a/app/mypage/page.tsx b/app/mypage/page.tsx index 1afc990..b878bc9 100644 --- a/app/mypage/page.tsx +++ b/app/mypage/page.tsx @@ -545,7 +545,7 @@ export default function MyPage() { icon="📦" title="활성 구독이 없습니다" desc="구독 중인 서비스가 없습니다" - linkHref="/services/prompt" + linkHref="/services/music" linkLabel="서비스 둘러보기" /> ) : ( @@ -645,7 +645,7 @@ export default function MyPage() { {/* 서비스 이동 */} diff --git a/app/page.tsx b/app/page.tsx index 4587e1a..f736602 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -6,75 +6,16 @@ import ContactModal from './components/ContactModal'; import { trackCTAClick } from '../lib/gtag'; /* ═══════════════════════════════════════════════════ - 쟁승메이드 홈페이지 — v3 (수익 구조 개편) - 설계 원칙: - 1. AI 상품 → 무료 도구 → 외주 순서로 노출 - 2. 매출 전환 동선 최우선 - 3. 증거 기반 신뢰 확보 + 쟁승메이드 홈 — v4 (AI Music 중심 개편) + 1. Hero: AI 음악 팩 (메인 매출) + 2. Sub: 사주 · 블로그팩 · 일반 문의 + 3. About: 신뢰 지표 ═══════════════════════════════════════════════════ */ -/* ── AI 상품 (매출 핵심) ─────────────────────────── */ -const AI_PRODUCTS = [ - { - href: '/services/prompt', - tag: 'BEST', - tagColor: 'bg-rose-500/15 text-rose-400 border-rose-500/20', - title: '프롬프트 패키지', - desc: 'ChatGPT·Claude 업무 최적화 프롬프트. 자소서, 마케팅, 이메일, 보고서 등 즉시 다운로드.', - price: '9,900원~', - action: '스토어 보기', - }, - { - href: '/services/automation', - tag: 'HOT', - tagColor: 'bg-amber-500/15 text-amber-400 border-amber-500/20', - title: '업무 자동화 개발', - desc: '엑셀 처리, 이메일·보고서 자동화, 데이터 수집 스크립트. 반복 업무를 없앱니다.', - price: '5만원~', - action: '자세히 보기', - }, - { - href: '/services/ai-kit', - tag: '구독', - tagColor: 'bg-violet-500/15 text-violet-400 border-violet-500/20', - title: 'AI 자동화 키트', - desc: '업무일지·이메일·SNS 자동화 도구 6종. 설치 없이 월 구독으로 바로 사용.', - price: '19,900원/월', - action: '구독 시작', - }, -]; - -/* ── 무료 도구 (트래픽 엔진) ─────────────────────── */ -const FREE_TOOLS = [ - { - href: '/saju', - icon: ( - - - - ), - title: 'AI 사주 분석', - desc: '생년월일 입력하면 AI가 성격·직업·관계·운세를 즉시 분석', - badge: '무료', - }, - { - href: '/tools', - icon: ( - - - - ), - title: '도구 쇼케이스', - desc: '네이버 블로그 자동화, eBay 리스팅 등 AI 도구 데모', - badge: 'DEMO', - }, -]; - -/* ── 운영 중 서비스 (신뢰 지표) ──────────────────── */ const LIVE_SERVICES = [ - { name: '쟁승메이드', label: '이 사이트' }, - { name: 'AI 사주 분석', label: '유료 서비스' }, - { name: 'AI 자동화 키트', label: '월 구독' }, + { name: 'AI Music Pack', label: '메인 상품' }, + { name: 'AI 사주 분석', label: '무료 도구' }, + { name: '블로그 자동화 팩', label: '디지털 상품' }, ]; function useScrollReveal() { @@ -108,99 +49,114 @@ export default function Home() { setModalOpen(false)} - service="외주 개발 문의" + service="일반 문의" checklist={[ - '개발하고 싶은 서비스를 간략히 설명해주세요', - '희망 납품 일정과 예산 범위', - '참고할 만한 사이트나 레퍼런스', + '문의하고 싶은 내용을 간략히 설명해주세요', + '원하는 회신 방식 (이메일/전화)', + '기타 참고 사항', ]} - accentColor="text-[#5ba4ff]" - headerFrom="#04102b" - headerTo="#0a2060" + accentColor="text-violet-400" + headerFrom="#1e1b4b" + headerTo="#020617" /> {/* ══════════════════════════════════════ - HERO — AI 상품 중심 + HERO — AI Music 중심 ══════════════════════════════════════ */}
+ {/* Noise overlay */}
\")", }} /> + {/* Waveform decoration */} +
+ + + + + + + + + + +
-
+
- - 쟁승메이드 · AI 프롬프트 · 자동화 + + 쟁승메이드 × AI Music - 서비스 운영 중 + NEW

- AI로 일하는 방식을 + 네 사연을 노래로.
- 바꿔보세요. + + 쇼츠까지 한 번에. +

- 프롬프트 패키지부터 업무 자동화, AI 사주 분석까지. + 7년차 개발자가 설계한 AI 음악 4단계 공정.
- 7년차 현직 개발자가 직접 만들고 운영합니다. + 컨셉 → 음악(Suno) → 비주얼(Runway) → 유튜브 퍼블리싱까지.

-

- 9,900원부터 시작하는 AI 도구 — 지금 바로 구매하고 사용하세요. +

+ ₩39,000부터. 평생 무료 업데이트.

- AI 상품 둘러보기 + 팩 둘러보기 무료 사주 분석 체험
- {/* Live services indicator */}
-

- 지금 운영 중인 서비스 +

+ 운영 중

{LIVE_SERVICES.map((s) => ( - + {s.name} - {s.label} + {s.label} ))}
@@ -209,111 +165,108 @@ export default function Home() {
{/* ══════════════════════════════════════ - SECTION 2 — AI 상품 (매출 핵심) + SECTION 2 — 서브 상품 카드 ══════════════════════════════════════ */} -
+
-

- AI Products +

+ More Products

- 지금 바로 구매할 수 있는 AI 상품 + 음악뿐이 아닙니다.

-

- 결제 즉시 사용 가능. 복잡한 상담 없이 바로 시작하세요. -

+

무료 도구부터 디지털 상품까지.

-
- {AI_PRODUCTS.map((p, i) => ( - -
- - {p.tag} - - {p.price} -
-

- {p.title} -

-

- {p.desc} -

-
- - {p.action} → - -
- - ))} -
-
-
- - {/* ══════════════════════════════════════ - SECTION 3 — 무료 도구 (트래픽 엔진) - ══════════════════════════════════════ */} -
-
-
-

- Free Tools -

-

+ {/* 사주 */} + - 무료로 체험해보세요 -

-

- 회원가입 없이 바로 사용할 수 있습니다. -

-
- -
- {FREE_TOOLS.map((t, i) => ( - - {t.icon} -
-
-

- {t.title} -

- - {t.badge} - -
-

- {t.desc} -

-
- - +
+ + 무료 + + + - - ))} +
+

+ AI 사주 분석 +

+

+ 생년월일 입력하면 AI가 성격·직업·관계·운세를 즉시 분석합니다. +

+ + 무료로 시작 → + + + + {/* 블로그팩 */} + +
+ + ₩29,000 + + + + +
+

+ 블로그 자동화 팩 +

+

+ 쿠팡파트너스·애드포스트 수익을 자동화하는 프롬프트·템플릿·샘플 세트. +

+ + 팩 보기 → + + + + {/* 일반 문의 */} +
{/* ══════════════════════════════════════ - SECTION 4 — 누가 만드나요 (About) + SECTION 3 — About ══════════════════════════════════════ */} -
+
-

+

About

@@ -324,45 +277,29 @@ export default function Home() { > 7년간 실제 서비스를 만들었습니다.
- 이제 당신의 업무를 자동화합니다. + 이제 AI로 당신의 콘텐츠를 만듭니다. -
+

- 대기업 IT팀에서 백엔드 개발 7년 — 실제 운영되는 서비스의 API 설계, DB 구조, 배포 파이프라인을 직접 다뤘습니다. + 대기업 IT팀에서 백엔드 개발 7년 — 실제 운영되는 서비스의 API 설계, DB, 배포 파이프라인을 직접 다뤘습니다.

- 지금은 그 경험으로 AI 프롬프트 패키지, 업무 자동화, 구독형 AI 도구를 직접 만들어 판매합니다. + 지금은 그 경험으로 AI 음악·블로그 자동화·사주 AI를 직접 만들어 운영·판매합니다.

- -
-

- Tech Stack -

-
- {['Next.js', 'TypeScript', 'Python', 'FastAPI', 'PostgreSQL', 'Supabase', 'Docker', 'AWS'].map((t) => ( - - {t} - - ))} -
-
{[ - { value: '7년', label: '대기업 백엔드 개발 경력', sub: '실제 운영 서비스 다수 개발', color: 'border-blue-500/30' }, - { value: '4개', label: '현재 직접 운영 중인 서비스', sub: '이 사이트 포함 — 지금 이 순간도 작동 중', color: 'border-emerald-500/30' }, - { value: '100%', label: '외주 시 소스코드 이관', sub: '납품 후 전체 코드 전달, 락인 없음', color: 'border-violet-500/30' }, - { value: '24h', label: '이내 견적 답변', sub: '주말·공휴일 포함', color: 'border-amber-500/30' }, + { value: '7년', label: '대기업 백엔드 경력', sub: '실제 운영 서비스 다수', color: 'border-blue-500/30' }, + { value: '3개', label: '운영 중인 AI 서비스', sub: '사주 AI · 블로그팩 · 음악팩', color: 'border-emerald-500/30' }, + { value: '평생', label: '무료 업데이트', sub: '구매 후 Notion 공지로 전달', color: 'border-violet-500/30' }, + { value: '24h', label: '이내 답변', sub: '주말·공휴일 포함', color: 'border-amber-500/30' }, ].map((item) => (
{item.value} - {item.label} + {item.label}

{item.sub}

@@ -373,92 +310,45 @@ export default function Home() {
{/* ══════════════════════════════════════ - SECTION 5 — 외주 개발 (축소) + SECTION 4 — 최종 CTA ══════════════════════════════════════ */} -
-
-
-
-

- Custom Development -

-

- 맞춤 개발이 필요하신가요? -

-

- 홈페이지 제작, 업무 시스템, API 개발 등 — 계약서 기반으로 안전하게 진행합니다. -

-
-
- -
- {[ - { icon: , title: '계약서 먼저', desc: '범위·금액·납기를 문서로 확정' }, - { icon: , title: '주 1회 보고', desc: '중간에 사라지는 일 없음' }, - { icon: , title: '소스코드 100%', desc: '전체 코드 이관, 락인 없음' }, - { icon: , title: '24시간 답변', desc: '주말·공휴일 포함 견적 회신' }, - ].map((item) => ( -
- {item.icon} -

{item.title}

-

{item.desc}

-
- ))} -
- -
- - 포트폴리오 · 문의하기 - - - - - - 홈페이지 제작 상세 보기 - -
-
-
- - {/* ══════════════════════════════════════ - SECTION 6 — 최종 CTA - ══════════════════════════════════════ */} -
+
-

Get Started

+

+ Get Started +

- AI 도구로 업무를 + 오늘 밤, 첫 쇼츠를
- 더 빠르게 처리하세요. + + 업로드할 수 있어요. +

-

- 9,900원부터 시작. 맞춤 개발 상담은 무료입니다. +

+ ₩39,000부터 시작 · 평생 업데이트

- AI 상품 보기 + AI 음악 팩 보기

diff --git a/app/robots.ts b/app/robots.ts index dc25a95..4340888 100644 --- a/app/robots.ts +++ b/app/robots.ts @@ -6,7 +6,7 @@ export default function robots(): MetadataRoute.Robots { { userAgent: '*', allow: '/', - disallow: ['/admin/', '/api/', '/mypage/', '/payment/'], + disallow: ['/admin/', '/api/', '/mypage/', '/payment/', '/freelance', '/services/website', '/portfolio/'], }, ], sitemap: 'https://jaengseung-made.com/sitemap.xml', diff --git a/app/saju/result/SajuLottoSection.tsx b/app/saju/result/SajuLottoSection.tsx deleted file mode 100644 index 774c027..0000000 --- a/app/saju/result/SajuLottoSection.tsx +++ /dev/null @@ -1,351 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; -import Link from 'next/link'; - -// 오행 기반 로또 번호 매핑 (하도낙서 원리) -// 水:1,6 / 火:2,7 / 木:3,8 / 金:4,9 / 土:5,10 -const ELEMENT_NUMBERS: Record = { - '水': [1, 6, 11, 16, 21, 26, 31, 36, 41], - '火': [2, 7, 12, 17, 22, 27, 32, 37, 42], - '木': [3, 8, 13, 18, 23, 28, 33, 38, 43], - '金': [4, 9, 14, 19, 24, 29, 34, 39, 44], - '土': [5, 10, 15, 20, 25, 30, 35, 40, 45], -}; - -const ELEMENT_KR: Record = { - '水': '수', '火': '화', '木': '목', '金': '금', '土': '토', -}; - -const ELEMENT_COLOR: Record = { - '水': { bg: 'bg-blue-50', text: 'text-blue-700', border: 'border-blue-300', ball: '#3b82f6' }, - '火': { bg: 'bg-red-50', text: 'text-red-700', border: 'border-red-300', ball: '#ef4444' }, - '木': { bg: 'bg-green-50', text: 'text-green-700', border: 'border-green-300', ball: '#22c55e' }, - '金': { bg: 'bg-amber-50', text: 'text-amber-700', border: 'border-amber-300', ball: '#f59e0b' }, - '土': { bg: 'bg-yellow-50', text: 'text-yellow-700', border: 'border-yellow-300', ball: '#eab308' }, -}; - -// 오행별 행운 설명 -const ELEMENT_LUCK_DESC: Record = { - '水': '흐르는 물처럼 지혜와 직관이 넘치는 수(水) 기운이 당신의 행운을 이끕니다. 1·6 계열의 숫자들이 당신과 공명합니다.', - '火': '활활 타오르는 불처럼 열정과 표현력이 폭발하는 화(火) 기운이 행운의 열쇠입니다. 2·7 계열의 숫자들에서 기운을 찾으세요.', - '木': '하늘을 향해 뻗는 나무처럼 성장과 창의성을 상징하는 목(木) 기운이 길을 열어줍니다. 3·8 계열의 숫자들이 공명합니다.', - '金': '단단하고 순수한 금속처럼 결단력과 정의를 상징하는 금(金) 기운이 행운을 부릅니다. 4·9 계열의 숫자들이 당신과 함께합니다.', - '土': '만물을 품는 대지처럼 안정과 신뢰를 상징하는 토(土) 기운이 당신을 지켜줍니다. 5·10 계열의 숫자들에 행운이 깃들어 있습니다.', -}; - -// 사주 기반 시드로 결정론적 숫자 선택 (매번 같은 결과) -function seededRandom(seed: number): () => number { - let s = seed; - return () => { - s = (s * 1664525 + 1013904223) & 0xffffffff; - return (s >>> 0) / 0xffffffff; - }; -} - -function generateSajuLottoNumbers( - yongShin: string, - heeShin: string, - dayBranch: string, - yearNum: number, - monthNum: number, - dayNum: number -): { numbers: number[]; yongShinNums: number[]; heeShinNums: number[] } { - const seed = yearNum * 10000 + monthNum * 100 + dayNum; - const rand = seededRandom(seed); - - const yongPool = ELEMENT_NUMBERS[yongShin] ?? ELEMENT_NUMBERS['水']; - const heePool = ELEMENT_NUMBERS[heeShin] ?? ELEMENT_NUMBERS['木']; - - // 용신 기반 3개 선택 - const shuffledYong = [...yongPool].sort(() => rand() - 0.5); - const yongPick = shuffledYong.slice(0, 3); - - // 희신 기반 2개 선택 - const shuffledHee = [...heePool].sort(() => rand() - 0.5); - const heePick = shuffledHee.filter(n => !yongPick.includes(n)).slice(0, 2); - - // 지지 오행에서 보조 번호 1개 - const BRANCH_ELEMENT: Record = { - '子': '水', '亥': '水', '寅': '木', '卯': '木', '巳': '火', '午': '火', - '申': '金', '酉': '金', '丑': '土', '辰': '土', '未': '土', '戌': '土', - }; - const branchElem = BRANCH_ELEMENT[dayBranch] ?? yongShin; - const branchPool = ELEMENT_NUMBERS[branchElem] ?? []; - const bonusPool = branchPool.filter(n => !yongPick.includes(n) && !heePick.includes(n)); - const shuffledBonus = [...bonusPool].sort(() => rand() - 0.5); - const bonusPick = shuffledBonus.length > 0 ? [shuffledBonus[0]] : []; - - const combined = [...new Set([...yongPick, ...heePick, ...bonusPick])]; - // 6개 채우기 (부족하면 랜덤으로 추가) - while (combined.length < 6) { - const n = Math.floor(rand() * 45) + 1; - if (!combined.includes(n)) combined.push(n); - } - - const numbers = combined.slice(0, 6).sort((a, b) => a - b); - return { numbers, yongShinNums: yongPick.sort((a, b) => a - b), heeShinNums: heePick.sort((a, b) => a - b) }; -} - -// 로또 볼 컴포넌트 -function LottoBall({ num, color = '#1d4ed8', size = 44 }: { num: number; color?: string; size?: number }) { - return ( -

- {num} -
- ); -} - -// 오행별 볼 색상 -function getElementColor(num: number): string { - const mod = num % 10; - if (mod === 1 || mod === 6) return '#3b82f6'; // 水 - if (mod === 2 || mod === 7) return '#ef4444'; // 火 - if (mod === 3 || mod === 8) return '#22c55e'; // 木 - if (mod === 4 || mod === 9) return '#f59e0b'; // 金 - return '#eab308'; // 土 (0, 5) -} - -interface Props { - yongShin: string; // 용신 오행 (예: '水') - yongShinKr: string; // 용신 한글 (예: '수') - heeShin: string; // 희신 오행 - heeShinKr: string; // 희신 한글 - dayBranch: string; // 일지 (예: '子') - dayStemKr: string; // 일간 한글 (예: '갑') - currentDaeun: { - stemKr: string; - branchKr: string; - startYear: number; - endYear: number; - age: number; - } | null; - yearNum: number; - monthNum: number; - dayNum: number; - hasLottoSubscription: boolean; // 로또 구독 여부 -} - -export default function SajuLottoSection({ - yongShin, yongShinKr, heeShin, heeShinKr, - dayBranch, dayStemKr, - currentDaeun, - yearNum, monthNum, dayNum, - hasLottoSubscription, -}: Props) { - const { numbers, yongShinNums, heeShinNums } = useMemo( - () => generateSajuLottoNumbers(yongShin, heeShin, dayBranch, yearNum, monthNum, dayNum), - [yongShin, heeShin, dayBranch, yearNum, monthNum, dayNum] - ); - - const elemColor = ELEMENT_COLOR[yongShin] ?? ELEMENT_COLOR['水']; - const currentYear = new Date().getFullYear(); - - return ( -
- {/* 헤더 */} -
-
-
- 🎱 -
-
-

사주 기반 로또 번호 추천

-

- 당신의 용신({yongShinKr}·{yongShin}) 오행으로 추출한 행운 번호 -

-
- - 사주 연동 - -
-
- -
- - {/* 용신 설명 배너 */} -
-
-
- {yongShin} -
-
-
- 용신 오행: {yongShinKr}({yongShin}) · 희신: {heeShinKr}({heeShin}) -
-

- {ELEMENT_LUCK_DESC[yongShin]} -

-
-
-
- - {/* 추천 번호 */} -
-
-

이번 주 추천 번호

- {currentYear}년 기준 -
- - {/* 메인 볼 */} -
- {numbers.map((n) => ( - - ))} -
- - {/* 용신/희신 구분 안내 */} -
-
-
- 용신({yongShinKr}) 핵심 번호 -
-
- {yongShinNums.map(n => ( - - ))} -
-
-
-
- 희신({heeShinKr}) 보조 번호 -
-
- {heeShinNums.map(n => ( - - ))} -
-
-
-
- - {/* 기본 사주 해석 내러티브 */} -
-
- -

왜 이 번호인가요?

-
-
-

- {dayStemKr}(일간) 일간인 당신의 사주에서 - 용신은 {yongShin}({yongShinKr}) 오행입니다. - 동양 명리학의 하도낙서(河圖洛書) 원리에 따르면,{' '} - {yongShin === '水' ? '1과 6' : yongShin === '火' ? '2와 7' : yongShin === '木' ? '3과 8' : yongShin === '金' ? '4와 9' : '5와 10'}이 - {yongShinKr}(水)의 수리이며, 이 기운을 담은 번호들이 당신에게 에너지적으로 공명합니다. -

-

- 희신 {heeShin}({heeShinKr}) 오행의 번호를 - 보조로 더하여 균형을 잡았고, 일지 ({dayBranch})의 기운까지 반영한 - 6개 번호를 완성했습니다. -

-
-
- - {/* 로또 구독 미가입 → 대운 연동 프리미엄 홍보 */} - {!hasLottoSubscription ? ( -
-
-
-
- 🔮 - 로또 구독 시 더 정확한 추천 -
- {currentDaeun && ( -

- 현재 {currentDaeun.stemKr}{currentDaeun.branchKr} 대운 - ({currentDaeun.startYear}~{currentDaeun.endYear}년)을 지나고 있습니다. - 로또 플랜을 구독하면 대운의 오행 흐름과 - 사주 원국을 교차 분석하여 매주 최적화된 번호를 받을 수 있어요. -

- )} -
- {[ - { icon: '📊', text: '대운 × 사주 교차 분석' }, - { icon: '🔄', text: '매주 업데이트 번호' }, - { icon: '🎯', text: '빅데이터 Monte Carlo 시뮬레이션' }, - { icon: '📈', text: '핫넘버 / 콜드넘버 통계' }, - ].map((item, i) => ( -
- {item.icon} - {item.text} -
- ))} -
- - 로또 번호 추천 서비스 구독하기 → - -
-
- ) : ( - /* 로또 구독 가입자 → 대운 교차 분석 심화 */ -
-
-
- - - -
- 구독자 전용 · 대운 교차 분석 -
- - {currentDaeun ? ( -
-
-
현재 대운과 사주의 만남
-

- {currentDaeun.stemKr}{currentDaeun.branchKr} 대운 - ({currentDaeun.startYear}~{currentDaeun.endYear}년, {currentDaeun.age}~{currentDaeun.age + 9}세)이 - 용신 {yongShin}({yongShinKr}) 오행과 - {currentDaeun.stemKr.includes(yongShinKr) || currentDaeun.branchKr.includes(yongShinKr) - ? 강하게 공명합니다. 지금이 행운의 절정기! - : ' 상호작용하고 있습니다. 용신 번호를 중심으로 추천합니다.' - } -

-
-
-
대운 연동 보너스 인사이트
-

- {currentYear}년 세운과 {currentDaeun.stemKr}{currentDaeun.branchKr} 대운이 겹치는 - 이 시기, 사주 원국의 용신 에너지가 가장 활성화됩니다. - 추천 번호 중 용신 번호 3개는 반드시 포함하고, - 나머지는 매주 로또 서비스의 최신 분석을 참고하세요. -

-
- - 로또 번호 추천 서비스 바로가기 → - -
- ) : ( -

- 대운 정보를 불러오는 중입니다. 정확한 생년월일을 입력하면 더 정밀한 분석이 가능합니다. -

- )} -
- )} - - {/* 하단 면책 */} -

- 본 번호는 사주 명리학의 용신/오행 원리를 기반으로 한 참고용 추천입니다.
- 로또 당첨을 보장하지 않으며, 투자 손실에 대한 책임은 본인에게 있습니다. -

-
-
- ); -} diff --git a/app/saju/result/page.tsx b/app/saju/result/page.tsx index f62ea97..a883f88 100644 --- a/app/saju/result/page.tsx +++ b/app/saju/result/page.tsx @@ -6,7 +6,6 @@ import { EARTHLY_BRANCHES_KR, FIVE_ELEMENTS_KR, FIVE_ELEMENTS } from '@/lib/saju import { calculateElementScore, performFullAnalysis } from '@/lib/ai-interpretation'; import { createClient } from '@/lib/supabase/server'; import SajuAISection from './SajuAISection'; -import SajuLottoSection from './SajuLottoSection'; import SajuFortuneSection from './SajuFortuneSection'; interface PageProps { @@ -569,29 +568,6 @@ export default async function SajuResultPage({ searchParams }: PageProps) { /> )} - {/* 사주 연동 로또 번호 추천 (사주 결제 시 표시) */} - {hasPaid && ( - - )} - {/* 대운 */}

diff --git a/app/services/ai-kit/page.tsx b/app/services/ai-kit/page.tsx deleted file mode 100644 index 5986ef8..0000000 --- a/app/services/ai-kit/page.tsx +++ /dev/null @@ -1,571 +0,0 @@ -'use client'; - -import { useState, useEffect, useRef } from 'react'; -import Link from 'next/link'; -import ContactModal from '../../components/ContactModal'; -import PaymentButton from '../../components/PaymentButton'; - -const KAKAO_CHANNEL_URL = process.env.NEXT_PUBLIC_KAKAO_CHANNEL_URL ?? null; - -const AI_KIT_CHECKLIST = [ - '주로 반복하는 업무 종류 (일지, 이메일, 보고서 등)', - '현재 주로 사용하는 AI 도구 (ChatGPT / Claude / Gemini 등)', - '하루 또는 주 단위로 같은 작업을 몇 번이나 반복하는지', - '사용 목적 (개인 효율화 / 팀 도입 / 소상공인 업무 등)', -]; - -/* ────────────────────────────────────────────────────────────── - Before / After 데이터 — 각 도구별 실제 시간 비교 - 마케팅 카피의 핵심: 추상적 "빠름"이 아닌 구체적 숫자 -────────────────────────────────────────────────────────────── */ -const TOOLS = [ - { - icon: ( - - - - ), - title: '업무 일지 자동 작성기', - desc: '하루 업무 키워드 5개만 입력하면 AI가 전문적인 일지를 즉시 완성.', - tag: '직장인 필수', - tagColor: 'bg-blue-500/15 text-blue-400 border-blue-500/25', - before: '15분', - after: '40초', - beforeLabel: '직접 쓸 때', - afterLabel: 'AI 사용 시', - failCase: '대충 쓰면 상사 피드백 → 재작성 → 결국 30분', - saving: '월 4.7시간 절약', - }, - { - icon: ( - - - - ), - title: '이메일 자동 답장 생성기', - desc: '받은 이메일을 붙여넣으면 상황에 맞는 정중한 답장 3가지 버전을 즉시 생성.', - tag: '소상공인 필수', - tagColor: 'bg-violet-500/15 text-violet-400 border-violet-500/25', - before: '23분', - after: '2분', - beforeLabel: '직접 쓸 때', - afterLabel: 'AI 사용 시', - failCase: '어조가 애매하면 상대방 기분 상함 → 계약 취소 위험', - saving: '월 6.4시간 절약', - }, - { - icon: ( - - - - ), - title: '월간 매출 분석 리포트', - desc: '숫자만 입력하면 전월 대비 분석·인사이트·개선 방향을 리포트로 정리.', - tag: '소상공인 필수', - tagColor: 'bg-emerald-500/15 text-emerald-400 border-emerald-500/25', - before: '2시간 30분', - after: '5분', - beforeLabel: '엑셀 + 직접 분석', - afterLabel: 'AI 사용 시', - failCase: '분석 없이 감으로 운영 → 손실 트렌드 한 달 늦게 발견', - saving: '월 2.4시간 절약', - }, - { - icon: ( - - - - ), - title: 'SNS 콘텐츠 캘린더', - desc: '업종과 키워드를 입력하면 한 달치 인스타·블로그 콘텐츠 기획안을 자동 생성.', - tag: 'SNS 마케팅', - tagColor: 'bg-pink-500/15 text-pink-400 border-pink-500/25', - before: '1시간 30분/주', - after: '10분/월', - beforeLabel: '매주 기획할 때', - afterLabel: 'AI로 한 번에', - failCase: 'SNS 3일 공백 → 인스타 도달 -40% · 팔로워 이탈 시작', - saving: '월 5.7시간 절약', - }, - { - icon: ( - - - - ), - title: '회의록·미팅 노트 정리', - desc: '대화 내용이나 메모를 입력하면 결정 사항·액션아이템·다음 단계를 즉시 구조화.', - tag: '직장인 필수', - tagColor: 'bg-blue-500/15 text-blue-400 border-blue-500/25', - before: '40분', - after: '3분', - beforeLabel: '직접 정리할 때', - afterLabel: 'AI 사용 시', - failCase: '액션아이템 누락 → 후속 지연 → "그때 얘기했잖아요" 분쟁', - saving: '월 3.7시간 절약', - }, - { - icon: ( - - - - ), - title: '상품 설명·리뷰 답변 자동화', - desc: '상품명과 특징을 입력하면 스마트스토어·쿠팡 최적화 상품 설명 + 리뷰 답변 즉시 생성.', - tag: '온라인 판매자', - tagColor: 'bg-orange-500/15 text-orange-400 border-orange-500/25', - before: '25분/개', - after: '30초/개', - beforeLabel: '상품 1개 설명 직접 쓸 때', - afterLabel: 'AI 사용 시', - failCase: '밋밋한 설명 → 클릭율 낮음 → 검색 노출 하락 → 매출 감소', - saving: '상품 10개 기준 월 4시간 절약', - }, -]; - -const TESTIMONIALS = [ - { - name: '김하윤', - job: '카페 운영 3년차', - text: '매일 SNS 올릴 내용 고민하다 지쳤는데, 이제 30초면 한 달치 아이디어가 나와요. 매출도 15% 올랐어요.', - rating: 5, - }, - { - name: '박도현', - job: '중소기업 팀장', - text: '주간 보고서 작성이 2시간에서 20분으로 줄었습니다. 팀원들한테도 공유했어요.', - rating: 5, - }, - { - name: '이서진', - job: '프리랜서 디자이너', - text: '클라이언트 이메일 답장을 AI로 생성하니까 전문적으로 보인다는 피드백을 많이 받아요.', - rating: 5, - }, -]; - -const FAQ = [ - { - q: 'AI를 전혀 써본 적 없어도 가능한가요?', - a: 'ChatGPT나 Claude에 복사·붙여넣기만 할 수 있으면 됩니다. 모든 도구에 단계별 사용 가이드가 포함되어 있습니다.', - }, - { - q: '매달 어떤 것이 업데이트되나요?', - a: '매월 1일에 새로운 자동화 도구 1~2종이 추가됩니다. 트렌드 변화와 구독자 요청을 반영하여 지속적으로 개선합니다.', - }, - { - q: '해지는 언제든지 가능한가요?', - a: '네, 언제든지 마이페이지에서 구독을 취소할 수 있습니다. 해지 후에도 해당 월 말일까지 사용 가능합니다.', - }, - { - q: '스마트스토어·쿠팡 판매자도 쓸 수 있나요?', - a: '네. 상품 설명 자동화, 리뷰 답변 자동화 등 온라인 판매자를 위한 전용 도구가 포함되어 있습니다.', - }, -]; - -function useScrollReveal() { - const ref = useRef(null); - useEffect(() => { - const el = ref.current; - if (!el) return; - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - entry.target.classList.add('is-visible'); - observer.unobserve(entry.target); - } - }); - }, - { threshold: 0.1, rootMargin: '0px 0px -40px 0px' } - ); - el.querySelectorAll('.reveal').forEach((child) => observer.observe(child)); - return () => observer.disconnect(); - }, []); - return ref; -} - -export default function AiKitPage() { - const totalMonthlySaving = 27; - const [modalOpen, setModalOpen] = useState(false); - const containerRef = useScrollReveal(); - - return ( -
- - setModalOpen(false)} - service="AI 자동화 키트 — 월 19,900원" - checklist={AI_KIT_CHECKLIST} - accentColor="text-indigo-400" - headerFrom="#0a0f2e" - headerTo="#0f1a5c" - /> - - {/* ─── Hero ─── */} -
- -
- - - 홈으로 - - -

AI 자동화 키트 · 월 구독

- - {/* 핵심 카피 */} -

- 오늘도 반복 업무에
- {totalMonthlySaving}시간을 낭비하고 있습니다 -

- -

- 일지 작성, 이메일 답장, 보고서, SNS 기획…
- 혼자 하면 월 {totalMonthlySaving}시간 이상 소비되는 일들, AI로 90% 줄일 수 있습니다. -

- - {/* 가격 카드 */} -
-
- 런칭 특가 - 월 39,900원 -
-
- 19,900 - / 월 -
-

언제든 해지 가능 · 해지 후 월말까지 이용

-
- -
- - 월 구독 시작하기 — 19,900원/월 - - -
-
-
- - {/* ─── 시간 낭비 가시화 섹션 ─── */} -
-
-
-

- 지금 이 순간도 낭비되고 있는 당신의 시간 -

-

직장인·소상공인 평균 작업 시간 기준 / 월 22 영업일 계산

-
- - {/* 총합 카드 */} -
-
-
-

6가지 반복 업무를 혼자 할 때

-

- 월 {totalMonthlySaving}시간 19분 낭비 -

-

- 시급 15,000원 기준 → 월 409,000원어치 시간 손실 -

-
-
-
AI 도입 후
-
2시간 6분
-
전체 작업 시간 92.3% 감소
-
-
-
- - {/* 개별 도구 Before/After 바 차트 */} -
- {TOOLS.map((tool, i) => { - const beforeVal = tool.before; - const afterVal = tool.after; - return ( -
-
- {tool.title} - - {tool.saving} - -
-
-
- {tool.beforeLabel} -
-
-
- {beforeVal} -
-
-
-
- {tool.afterLabel} -
-
-
- {afterVal} -
-
-
- ); - })} -
-
-
- - {/* ─── "안 쓰면 생기는 실패 비용" 섹션 ─── */} -
-
-
-
- - AI를 안 쓸 때 생기는 실패 비용 -
-

- 시간만 낭비되는 게 아닙니다 -

-

반복 업무를 수작업으로 할 때 실제로 발생하는 손실들

-
- -
- {TOOLS.map((tool, i) => ( -
-
-
- - - -
-

{tool.title} 수작업 시

-
-

- {tool.failCase} -

-
- ))} -
- -
-

- 실수 1번이 계약 1건을 날립니다. -

-

- 월 19,900원으로 시간 손실과 실수 비용을 동시에 없애세요. -

-
-
-
- - {/* ─── 포함 도구 ─── */} -
-
-
-
- 6가지 AI 자동화 도구 포함 -
-

구독하면 바로 쓸 수 있는 도구들

-

ChatGPT · Claude에 복사·붙여넣기만으로 즉시 사용 가능

-
- -
- {TOOLS.map((tool, i) => ( -
-
-
- {tool.icon} -
- - {tool.tag} - -
-

{tool.title}

-

{tool.desc}

- {/* 인라인 Before/After */} -
- {tool.before} - - - - {tool.after} - {tool.saving.replace('월 ', '').replace(' 절약', '')} -
-
- ))} -
- - {/* 업데이트 알림 */} -
-
- - - -
-
-

매월 1일 — 새 도구 자동 추가

-

구독자 요청과 트렌드를 반영해 매달 새 자동화 도구 1~2종이 추가됩니다. 추가 비용 없음.

-
-
-
-
- - {/* ─── 누구에게 필요한가 ─── */} -
-
-
-

이런 분들이 가장 많이 씁니다

-
-
- {[ - { - icon: ( - - - - ), - title: '소상공인', - pain: '"매일 SNS, 이메일, 리뷰 답변에 2~3시간씩 쓰고 있어요."', - gain: '도구 3개만 써도 월 12시간 이상 확보', - }, - { - icon: ( - - - - ), - title: '직장인', - pain: '"보고서 쓰다가 퇴근 시간 넘기는 게 일상이에요."', - gain: '보고서·일지·회의록 시간 90% 감소', - }, - { - icon: ( - - - - ), - title: '온라인 판매자', - pain: '"상품 50개 설명 쓰는 데 이틀이 걸렸어요."', - gain: '50개 상품 설명 → 25분 완성', - }, - { - icon: ( - - - - ), - title: '1인 마케터', - pain: '"콘텐츠 아이디어 고갈로 업로드를 자꾸 건너뛰어요."', - gain: '한 달치 콘텐츠 기획 → 10분 완성', - }, - ].map((item, i) => ( -
-
{item.icon}
-

{item.title}

-

{item.pain}

-

- → {item.gain} -

-
- ))} -
-
-
- - {/* ─── 사용 후기 ─── */} -
-
-
-

실제 사용 후기

-
-
- {TESTIMONIALS.map((t, i) => ( -
-
- {Array.from({ length: t.rating }).map((_, j) => ( - - - - ))} -
-

"{t.text}"

-
-

{t.name}

-

{t.job}

-
-
- ))} -
-
-
- - {/* ─── FAQ ─── */} -
-
-
-

자주 묻는 질문

-
-
- {FAQ.map((item, i) => ( -
-

Q. {item.q}

-

A. {item.a}

-
- ))} -
-
-
- - {/* ─── 최하단 CTA ─── */} -
-
- {/* 마지막 카피: 기회비용 프레이밍 */} -

구독 안 하면 내일도 동일합니다

-

- 월 19,900원 vs
- - 월 409,000원어치 시간 낭비 - -

-

- 도구를 쓰지 않아도 내일은 옵니다. 단, 오늘과 똑같이. -
언제든 해지 가능하니 한 달만 써보세요. -

-
- - 지금 구독 시작하기 — 19,900원/월 - -

로그인 필요 · 월 19,900원 · 언제든 해지 가능

-
-
-
- -
- ); -} diff --git a/app/services/automation/layout.tsx b/app/services/automation/layout.tsx deleted file mode 100644 index 164dffb..0000000 --- a/app/services/automation/layout.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { Metadata } from 'next'; - -export const metadata: Metadata = { - title: 'AI 업무 자동화 개발 | 엑셀·이메일·RPA 외주', - description: - '매일 반복하는 엑셀 정리, 이메일 발송, 보고서 작성을 AI와 파이썬으로 자동화합니다. ChatGPT 연동 자동화, Make.com 플로우, Python RPA 개발. 5만원~, 계약서 포함, 1개월 무상 AS.', - keywords: [ - '업무 자동화 외주', - 'AI 업무 자동화', - '엑셀 자동화 외주', - '파이썬 자동화 개발', - 'RPA 개발 외주', - '이메일 자동화', - '반복업무 자동화', - 'ChatGPT 자동화', - 'Make.com 자동화', - '텔레그램 봇 개발', - '업무 자동화 비용', - '자동화 프리랜서', - ], - openGraph: { - title: 'AI 업무 자동화 개발 | 쟁승메이드', - description: - '엑셀·이메일·보고서 반복 업무를 AI로 자동화. 현직 대기업 개발자가 직접 개발. 5만원~, 계약서 포함, 납기 패널티 적용.', - url: 'https://jaengseung-made.com/services/automation', - }, -}; - -export default function AutomationLayout({ children }: { children: React.ReactNode }) { - return children; -} diff --git a/app/services/automation/page.tsx b/app/services/automation/page.tsx deleted file mode 100644 index 851a0db..0000000 --- a/app/services/automation/page.tsx +++ /dev/null @@ -1,469 +0,0 @@ -'use client'; - -import { useState, useEffect, useRef } from 'react'; -import Link from 'next/link'; -import ContactModal from '../../components/ContactModal'; -import { trackCTAClick } from '../../../lib/gtag'; - -function useScrollReveal() { - const ref = useRef(null); - useEffect(() => { - const el = ref.current; - if (!el) return; - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - entry.target.classList.add('is-visible'); - observer.unobserve(entry.target); - } - }); - }, - { threshold: 0.1, rootMargin: '0px 0px -40px 0px' } - ); - el.querySelectorAll('.reveal').forEach((child) => observer.observe(child)); - return () => observer.disconnect(); - }, []); - return ref; -} - -const tools = [ - { - id: 'excel', - title: '엑셀 자동화 도구', - subtitle: 'Excel Macro Toolkit v1.2', - desc: '반복 업무를 버튼 하나로 처리하는 엑셀 매크로 모음. 데이터 정리·집계·보고서 자동 생성 기능 포함.', - tags: ['VBA', 'Excel', '매크로', '무료'], - color: '#16a34a', - bgColor: '#f0fdf4', - borderColor: '#bbf7d0', - icon: ( - - - - ), - href: '/services/automation/tools/excel', - ready: true, - }, - { - id: 'scraper', - title: '웹 스크래핑 도구', - subtitle: 'Web Scraper v1.0', - desc: '공공데이터·쇼핑몰 가격·뉴스를 자동 수집해 엑셀로 저장하는 Python 기반 수집 도구.', - tags: ['Python', 'BeautifulSoup', 'Excel 출력', '무료'], - color: '#2563eb', - bgColor: '#eff6ff', - borderColor: '#bfdbfe', - icon: ( - - - - ), - href: '/services/automation/tools/scraper', - ready: true, - }, - { - id: 'ppt', - title: 'PPT 제작 자동화 도구', - subtitle: 'PPT Automation v1.0', - desc: '엑셀 데이터를 읽어 표지·내용·마무리 슬라이드를 자동 생성하는 Python 기반 PPT 도구.', - tags: ['Python', 'python-pptx', 'openpyxl', '무료'], - color: '#7c3aed', - bgColor: '#f5f3ff', - borderColor: '#ddd6fe', - icon: ( - - - - ), - href: '/services/automation/tools/ppt', - ready: true, - }, -]; - -const premiumTools = [ - { - id: 'realestate', - title: '부동산 매물 크롤링 프로그램', - subtitle: 'Real Estate Crawler v1.0', - desc: '직방·다방·피터팬·네이버부동산 4개 플랫폼을 동시 수집. 지역·거래유형·매물유형 선택 후 플랫폼별 시트로 Excel 자동 저장.', - tags: ['Python', '4개 플랫폼', '중복제거', 'Excel 출력'], - price: '3만원', - features: ['직방·다방·피터팬·네이버부동산 동시 수집', '지역·거래유형·매물유형 선택', '자동 중복 제거', '플랫폼별 시트 분리 Excel 저장'], - color: '#b45309', - bgFrom: '#451a03', - bgTo: '#78350f', - accentColor: '#f59e0b', - icon: ( - - - - ), - downloadPath: '/downloads/real_estate_crawler_v1.0.py', - }, - { - id: 'accounting', - title: '사업장 회계 장부 자동화 프로그램', - subtitle: 'Accounting Automation v1.0', - desc: '쇼핑몰·식당·제조업·서비스업 업종별 수입/지출 입력 → 매출총이익·영업이익·순이익 자동 계산 + 회계사 관점 조언 리포트.', - tags: ['Python', '업종별 맞춤', '손익계산서', '회계 조언'], - price: '5만원', - features: ['업종별 맞춤 항목 (쇼핑몰·식당·제조·서비스업)', '매출총이익·영업이익·순이익 자동 계산', '부가세 신고 준비 자료 자동 생성', '회계 전문가 관점 경고·조언 리포트'], - color: '#0f766e', - bgFrom: '#042f2e', - bgTo: '#134e4a', - accentColor: '#2dd4bf', - icon: ( - - - - ), - downloadPath: '/downloads/accounting_automation_v1.0.py', - }, -]; - -const CHECKLIST = [ - '자동화하고 싶은 업무를 구체적으로 설명해주세요', - '현재 사용 중인 프로그램/시스템 (엑셀, ERP, 쇼핑몰 등)', - '자동화 빈도 (매일 / 주 1회 / 월 1회 등)', - '희망 납품 일정과 예산 범위', - '데이터 민감도 여부 (개인정보 포함 여부)', -]; - -const automationTypes = [ - { - title: '엑셀 / 구글 시트 자동화', - desc: '매일 2시간씩 엑셀에 손가락 아파하던 업무, 밤 11시에 자동 실행되도록 바꿔드립니다.', - examples: ['수작업 매출 집계 → 버튼 하나로 완료', '시트 간 데이터 복사 반복 → 전부 자동화', '보고서 양식이 매번 깨짐 → 서식 고정 자동화'], - accentColor: 'border-emerald-200 bg-emerald-50', dotColor: 'bg-emerald-500', labelColor: 'text-emerald-700 bg-emerald-100 border-emerald-200', - }, - { - title: '웹 스크래핑 · 데이터 수집', - desc: '경쟁사 가격 확인하러 탭 10개 열어놓고 복사·붙여넣기 하던 그 시간을 되돌려 드립니다.', - examples: ['경쟁사 가격 변동 → 엑셀에 자동 수집', '상품 리뷰 수백 개 → 자동 취합 분석', '공공입찰 공고 → 조건 맞으면 카톡 알림'], - accentColor: 'border-blue-200 bg-blue-50', dotColor: 'bg-blue-500', labelColor: 'text-blue-700 bg-blue-100 border-blue-200', - }, - { - title: '이메일 자동 발송', - desc: '"이 이메일 또 보내야 해?"라는 생각이 드는 모든 발송 업무를 없애드립니다.', - examples: ['주문 접수·발송 확인 이메일 자동 발송', '월말 보고서 관련자에게 자동 배포', '미수금 고객에게 단계별 안내 자동 발송'], - accentColor: 'border-violet-200 bg-violet-50', dotColor: 'bg-violet-500', labelColor: 'text-violet-700 bg-violet-100 border-violet-200', - }, - { - title: '업무 프로세스 RPA', - desc: 'ERP에 데이터 입력하고, 파일 정리하고, 같은 버튼 수십 번 클릭하는 일을 대신 합니다.', - examples: ['ERP 입력 작업 → 완전 무인 자동화', '월말 파일 정리·백업 → 예약 실행', '발주서 웹 입력 → 로봇이 대신 클릭'], - accentColor: 'border-orange-200 bg-orange-50', dotColor: 'bg-orange-500', labelColor: 'text-orange-700 bg-orange-100 border-orange-200', - }, - { - title: '텔레그램 봇 개발', - desc: '서버가 죽었는데 아무도 모르는 상황, 매출이 터졌는데 혼자만 아는 상황을 없애드립니다.', - examples: ['서버 다운·이상 → 즉시 카톡/텔레그램 알림', '하루 매출 현황 → 저녁 6시에 자동 리포트', '신규 주문 들어오면 → 담당자에게 즉시 알림'], - accentColor: 'border-cyan-200 bg-cyan-50', dotColor: 'bg-cyan-500', labelColor: 'text-cyan-700 bg-cyan-100 border-cyan-200', - }, - { - title: 'API 연동 · 시스템 통합', - desc: '두 프로그램이 따로 놀아서 중간에 손으로 옮기고 있다면, 그 사이를 연결해드립니다.', - examples: ['CRM → ERP 고객 정보 자동 동기화', '결제 완료 → 재고 차감 자동 연동', '온라인몰 주문 → 배송 시스템 자동 등록'], - accentColor: 'border-indigo-200 bg-indigo-50', dotColor: 'bg-indigo-500', labelColor: 'text-indigo-700 bg-indigo-100 border-indigo-200', - }, -]; - -const plans = [ - { name: '단순 자동화', price: '5만원~', desc: '단일 작업 · 1~3일 소요', examples: '엑셀 매크로, 단순 스크래핑, 이메일 자동화', highlight: false, productId: 'automation_basic' }, - { name: '자동화 심화', price: '15만원~', desc: '복합 작업 · 1~2주 소요', examples: 'RPA 프로세스, API 연동, 텔레그램 봇', highlight: true, productId: 'automation_advanced' }, - { name: '대형 자동화', price: '협의', desc: '시스템 통합 · 2주 이상 소요', examples: '전사 업무 자동화, 멀티 시스템 통합', highlight: false, productId: null }, -]; - -const process = [ - { step: '01', title: '무료 상담', desc: '반복 업무 파악 및 자동화 가능 여부 확인' }, - { step: '02', title: '요구사항 분석', desc: '상세 프로세스 분석 및 자동화 범위 결정' }, - { step: '03', title: '개발 및 테스트', desc: '실제 데이터로 테스트하며 단계적 개발' }, - { step: '04', title: '납품 및 교육', desc: '사용 방법 교육 + 가이드 문서 제공' }, - { step: '05', title: 'AS 지원', desc: '1개월 무상 기술 지원 및 버그 수정' }, -]; - -export default function AutomationPage() { - const [modalOpen, setModalOpen] = useState(false); - const [modalService, setModalService] = useState('업무 자동화'); - const containerRef = useScrollReveal(); - - const openModal = (service: string) => { - trackCTAClick(service, '/services/automation'); - setModalService(service); - setModalOpen(true); - }; - - return ( -
- - setModalOpen(false)} - service={modalService} - checklist={CHECKLIST} - accentColor="text-cyan-400" - headerFrom="#012030" - headerTo="#013d50" - /> - - {/* ─── Hero ─── */} -
- -
- - - 홈으로 - -

RPA · 업무 자동화 개발

-

- 반복 업무를
- 완전 자동화 -

-

- “이 작업 매일 하기 너무 귀찮다”는 생각이 드는 순간, 자동화할 수 있습니다.
- 엑셀, 이메일, 데이터 수집, RPA까지 직접 개발해드립니다. -

-
- {[{ v: '1~3일', l: '단순 작업' }, { v: '1~2주', l: '복합 작업' }, { v: '1개월', l: '무상 AS' }].map((s) => ( -
-
{s.v}
-
{s.l}
-
- ))} -
-
-
- - {/* ─── 자동화 유형 ─── */} -
-
-
-

AUTOMATION TYPES

-

자동화 유형

-
-
- {automationTypes.map((at, idx) => ( -
- {at.title.split(' ')[0]} -

{at.title}

-

{at.desc}

-
- {at.examples.map((ex) => ( -
-
- {ex} -
- ))} -
-
- ))} -
-
-
- - {/* ─── 프로세스 ─── */} -
-
-
-

PROCESS

-

진행 프로세스

-
-
-
-
- {process.map((p, idx) => ( -
-
- STEP - {p.step} -
-
{p.title}
-
{p.desc}
-
- ))} -
-
-
-
- - {/* ─── 예상 비용 ─── */} -
-
-
-

PRICING

-

예상 비용

-
-
- {plans.map((plan, idx) => ( -
- {plan.highlight && ( -
가장 많이 의뢰
- )} -
{plan.name.toUpperCase()}
-
{plan.price}
-
{plan.desc}
-
- 예: {plan.examples} -
- -
- ))} -
-

- * 실제 비용은 작업 복잡도, 데이터 양, 연동 시스템에 따라 달라집니다. 무료 상담 후 정확한 견적을 드립니다. -

-
-
- - {/* ─── 프리미엄 툴 ─── */} -
-
-
- - - PREMIUM TOOLS - -

프리미엄 자동화 프로그램

-

전문 분야별 고급 자동화 프로그램. 구매 후 소스코드 전달 + 1개월 무상 지원.

-
-
- {premiumTools.map((tool, idx) => ( -
- {/* 카드 헤더 */} -
-
-
{tool.icon}
-
- PREMIUM - {tool.price} -
-
-
{tool.subtitle}
-

{tool.title}

-

{tool.desc}

-
- {/* 기능 목록 */} -
-
- {tool.features.map((f) => ( -
- - - - {f} -
- ))} -
-
- {/* 태그 + CTA */} -
-
- {tool.tags.map((tag) => ( - {tag} - ))} -
- -

구매 후 Python 소스코드 + 사용 가이드 전달

-
-
- ))} -
-
-
- - {/* ─── 자동화 툴 무료 다운로드 ─── */} -
-
-
-

FREE TOOLS

-

자동화 도구 무료 다운로드

-

직접 만들어 사용 중인 자동화 도구를 무료로 공유합니다.
필요에 맞게 수정해서 쓰실 수 있어요.

-
-
- {tools.map((tool, idx) => ( -
- {!tool.ready && ( -
준비중
- )} -
{tool.icon}
-
{tool.subtitle}
-
{tool.title}
-

{tool.desc}

-
- {tool.tags.map((tag) => ( - {tag} - ))} -
- {tool.ready ? ( - - 상세보기 · 다운로드 → - - ) : ( - - )} -
- ))} -
-
-
- - {/* ─── CTA ─── */} -
-
-
-

FREE CONSULTATION

-

어떤 업무든 상담해보세요

-

자동화 가능한 업무라면 무엇이든 도와드립니다

- -
-
-
-
- ); -} diff --git a/app/services/automation/tools/excel/page.tsx b/app/services/automation/tools/excel/page.tsx deleted file mode 100644 index ab63c04..0000000 --- a/app/services/automation/tools/excel/page.tsx +++ /dev/null @@ -1,344 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import { useState } from 'react'; - -const features = [ - { - icon: ( - - - - ), - title: '중복 데이터 자동 제거', - desc: '여러 시트에 흩어진 데이터를 하나로 합치고 중복 행을 자동으로 찾아 제거합니다. 작업 시간 90% 단축.', - color: 'text-emerald-600', - bg: 'bg-emerald-50', - border: 'border-emerald-200', - }, - { - icon: ( - - - - ), - title: '일별/월별 집계 자동화', - desc: '날짜 컬럼 기준으로 일별·주별·월별 합계를 자동 계산하고 별도 시트에 보고서를 생성합니다.', - color: 'text-blue-600', - bg: 'bg-blue-50', - border: 'border-blue-200', - }, - { - icon: ( - - - - ), - title: '폴더 내 파일 일괄 처리', - desc: '지정한 폴더의 엑셀 파일 전체를 자동으로 열고 데이터를 통합합니다. 파일 수가 많아도 OK.', - color: 'text-violet-600', - bg: 'bg-violet-50', - border: 'border-violet-200', - }, - { - icon: ( - - - - ), - title: '서식·색상 자동 지정', - desc: '값 조건에 따라 셀 색상, 굵기, 테두리를 자동으로 지정합니다. 조건부 서식보다 빠릅니다.', - color: 'text-orange-600', - bg: 'bg-orange-50', - border: 'border-orange-200', - }, - { - icon: ( - - - - ), - title: '키워드 일괄 검색·치환', - desc: '전체 시트에서 특정 단어를 찾아 일괄 변경하거나 해당 행만 별도 추출합니다.', - color: 'text-cyan-600', - bg: 'bg-cyan-50', - border: 'border-cyan-200', - }, - { - icon: ( - - - - ), - title: 'PDF / CSV 자동 저장', - desc: '작업 완료 후 PDF 또는 CSV 형식으로 자동 저장하고 지정한 폴더에 날짜별로 백업합니다.', - color: 'text-rose-600', - bg: 'bg-rose-50', - border: 'border-rose-200', - }, -]; - -const howToUse = [ - { step: '01', title: '파일 다운로드', desc: '아래 버튼으로 .xlsm 파일을 받습니다. 매크로 포함 형식입니다.' }, - { step: '02', title: '매크로 실행 허용', desc: '파일을 열면 상단 노란 바에서 "콘텐츠 사용" 버튼을 클릭합니다.' }, - { step: '03', title: '데이터 시트에 붙여넣기', desc: '"Data" 시트에 내 데이터를 붙여넣습니다. A1부터 시작하면 됩니다.' }, - { step: '04', title: '매크로 버튼 클릭', desc: '"Control" 시트에서 원하는 기능 버튼을 클릭하면 자동 실행됩니다.' }, -]; - -const faqs = [ - { - q: '맥(Mac)에서도 사용할 수 있나요?', - a: 'Excel for Mac에서도 대부분 동작하나, VBA 일부 기능(파일 다이얼로그 등)은 Windows 전용입니다. Mac 사용자는 상담을 통해 호환 버전으로 수정 가능합니다.', - }, - { - q: '파일이 열리지 않거나 오류가 발생하면요?', - a: 'Excel 2016 이상 버전을 권장합니다. 보안 정책으로 매크로가 차단된 경우 Excel 옵션 → 보안 센터 → 매크로 설정에서 "알림과 함께 VBA 매크로 사용"으로 변경해 주세요.', - }, - { - q: '내 업무에 맞게 수정이 가능한가요?', - a: '파일 내 VBA 코드는 자유롭게 수정할 수 있습니다. 수정이 어려우시면 맞춤 자동화 개발 서비스로 문의해 주세요. 내 업무에 딱 맞는 버전을 만들어 드립니다.', - }, -]; - -export default function ExcelToolPage() { - const [openFaq, setOpenFaq] = useState(null); - - return ( -
- - {/* Hero */} -
-
- - - - - 업무 자동화 서비스로 돌아가기 - - -
-
- - - -
-
-
- FREE TOOL - v1.2 - VBA · Excel -
-

- 엑셀 자동화 도구
- - Excel Macro Toolkit - -

-

- 매일 반복하는 엑셀 작업을 버튼 하나로 처리하는 VBA 매크로 모음입니다.
- 데이터 정리, 중복 제거, 집계, 보고서 생성까지 실무에서 검증된 기능들만 담았습니다. -

-
-
- - {/* 통계 배지 */} -
- {[ - { v: '6가지', l: '핵심 기능' }, - { v: '무료', l: '완전 무료' }, - { v: 'Excel 2016+', l: '지원 버전' }, - ].map((s) => ( -
-
{s.v}
-
{s.l}
-
- ))} -
-
-
- -
-
- - {/* 다운로드 카드 */} -
-
-
DOWNLOAD
-
Excel_Macro_Toolkit_v1.2.xlsm
-
크기: 약 85KB · 매크로 포함 형식 · 상업적 이용 가능
-
- {['VBA 매크로', '6가지 기능', 'Control 시트 UI', '가이드 시트 포함'].map((t) => ( - {t} - ))} -
-
-
- - - - - 무료 다운로드 - -

로그인 없이 즉시 다운로드

-
-
- - {/* 기능 소개 */} -
-
-

FEATURES

-

포함된 기능 6가지

-
-
- {features.map((f) => ( -
-
{f.icon}
-
{f.title}
-

{f.desc}

-
- ))} -
-
- - {/* 사용 방법 */} -
-
-

HOW TO USE

-

사용 방법

-
-
-
-
- {howToUse.map((h) => ( -
-
- STEP - {h.step} -
-
{h.title}
-
{h.desc}
-
- ))} -
-
-
- - {/* 미리보기 (목업) */} -
-
-

PREVIEW

-

화면 미리보기

-
-
- {/* Excel 목업 */} -
-
-
-
-
-
- Excel_Macro_Toolkit_v1.2.xlsm -
- {/* 탭 */} -
- {['Control', 'Data', 'Report', 'Guide'].map((tab, i) => ( -
- {tab} -
- ))} -
- {/* Control 시트 목업 */} -
-
-
📊 Excel Macro Toolkit v1.2
-
원하는 기능 버튼을 클릭하세요
-
-
- {[ - { label: '중복 제거', color: '#16a34a' }, - { label: '일별 집계', color: '#2563eb' }, - { label: '파일 통합', color: '#7c3aed' }, - { label: '서식 자동화', color: '#ea580c' }, - { label: '키워드 검색', color: '#0891b2' }, - { label: 'PDF 저장', color: '#dc2626' }, - ].map((btn) => ( -
- {btn.label} -
- ))} -
-
-
상태 로그
-
✓ 준비 완료. 데이터 시트에 작업할 데이터를 붙여넣은 후 버튼을 클릭하세요.
-
-
-
-
- - {/* FAQ */} -
-
-

FAQ

-

자주 묻는 질문

-
-
- {faqs.map((faq, i) => ( -
- - {openFaq === i && ( -
- {faq.a} -
- )} -
- ))} -
-
- - {/* 하단 CTA */} -
-

CUSTOM AUTOMATION

-

내 업무에 맞게 수정이 필요하신가요?

-

- 기본 도구로 부족하다면, 업무 프로세스에 딱 맞는 전용 자동화 도구를 제작해 드립니다. -

-
- - - - - 무료 다운로드 - - - 맞춤 개발 문의 → - -
-
- -
-
-
- ); -} diff --git a/app/services/automation/tools/ppt/layout.tsx b/app/services/automation/tools/ppt/layout.tsx deleted file mode 100644 index c57ce9f..0000000 --- a/app/services/automation/tools/ppt/layout.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { Metadata } from 'next'; - -export const metadata: Metadata = { - title: 'PPT 제작 자동화 도구', - description: - '엑셀 데이터로 PPT를 자동 생성하는 Python 스크립트. 표지·내용·마무리 슬라이드 자동 구성, 색상 테마 커스터마이징. python-pptx 기반. 무료 다운로드.', - keywords: [ - 'PPT 자동화', - '파워포인트 자동 생성', - 'python-pptx', - 'PPT 제작 도구', - '엑셀 PPT 변환', - '프레젠테이션 자동화', - '무료 PPT 도구', - ], - openGraph: { - title: 'PPT 제작 자동화 도구 | 쟁승메이드', - description: 'Python + python-pptx 기반 PPT 자동 생성 도구 무료 다운로드. 엑셀 연동 지원.', - url: 'https://jaengseung-made.com/services/automation/tools/ppt', - }, -}; - -export default function PptLayout({ children }: { children: React.ReactNode }) { - return children; -} diff --git a/app/services/automation/tools/ppt/page.tsx b/app/services/automation/tools/ppt/page.tsx deleted file mode 100644 index 94432ee..0000000 --- a/app/services/automation/tools/ppt/page.tsx +++ /dev/null @@ -1,365 +0,0 @@ -'use client'; - -import Link from 'next/link'; - -const features = [ - { - icon: ( - - - - ), - title: '표지 · 내용 · 마무리 자동 생성', - desc: '표지(제목/날짜), 내용 슬라이드(불릿 포인트), 마무리 슬라이드까지 3가지 레이아웃을 자동으로 구성합니다.', - color: 'text-orange-600', - bg: 'bg-orange-50', - border: 'border-orange-200', - }, - { - icon: ( - - - - ), - title: '엑셀에서 데이터 일괄 생성', - desc: 'data.xlsx 파일의 A열(제목), B~열(불릿 내용)을 읽어 슬라이드를 자동 생성합니다. 수십 장도 한 번에 처리.', - color: 'text-emerald-600', - bg: 'bg-emerald-50', - border: 'border-emerald-200', - }, - { - icon: ( - - - - ), - title: '색상 테마 커스터마이징', - desc: '상단 설정 영역에서 PRIMARY, SECONDARY, ACCENT 색상을 RGB로 변경하면 전체 슬라이드에 즉시 반영됩니다.', - color: 'text-violet-600', - bg: 'bg-violet-50', - border: 'border-violet-200', - }, - { - icon: ( - - - - ), - title: '슬라이드 번호 자동 추가', - desc: '각 내용 슬라이드 우측 상단에 슬라이드 번호(01, 02...)가 자동으로 표시됩니다. 따로 설정할 필요 없음.', - color: 'text-blue-600', - bg: 'bg-blue-50', - border: 'border-blue-200', - }, - { - icon: ( - - - - ), - title: '16:9 비율 · 맑은 고딕 폰트', - desc: '발표 표준 비율인 16:9(13.33×7.5인치)로 설정되며, 한글 가독성이 좋은 맑은 고딕 폰트를 기본 적용합니다.', - color: 'text-cyan-600', - bg: 'bg-cyan-50', - border: 'border-cyan-200', - }, - { - icon: ( - - - - ), - title: '예시 데이터 자동 실행', - desc: 'data.xlsx 파일이 없어도 내장 예시 데이터로 바로 실행됩니다. 처음 사용할 때 결과를 즉시 확인 가능.', - color: 'text-rose-600', - bg: 'bg-rose-50', - border: 'border-rose-200', - }, -]; - -const howToUse = [ - { - step: '01', - title: '패키지 설치', - desc: '터미널에서 필요한 Python 패키지를 설치합니다.', - code: 'pip install python-pptx openpyxl', - color: 'bg-orange-500', - }, - { - step: '02', - title: '설정 수정', - desc: '스크립트 상단 설정 영역에서 제목, 날짜, 색상 테마를 수정합니다.', - code: 'TITLE_TEXT = "발표 제목"\nCOLOR_PRIMARY = RGBColor(0x1D, 0x4E, 0xD8)', - color: 'bg-emerald-500', - }, - { - step: '03', - title: '엑셀 데이터 준비', - desc: 'data.xlsx를 만들어 A열=슬라이드 제목, B~열=불릿 내용을 입력합니다. (없으면 예시 데이터로 실행)', - code: 'A열: 슬라이드 제목\nB~열: 불릿 포인트 내용', - color: 'bg-violet-500', - }, - { - step: '04', - title: '실행 후 확인', - desc: '터미널에서 스크립트를 실행하면 같은 폴더에 PPT 파일이 자동 저장됩니다.', - code: 'python ppt_automation_v1.0.py', - color: 'bg-blue-500', - }, -]; - -const faqs = [ - { - q: '엑셀 파일이 없어도 실행되나요?', - a: 'data.xlsx 파일이 없으면 내장 예시 데이터로 자동 실행됩니다. 먼저 결과를 확인한 뒤 자신의 데이터로 교체하면 됩니다.', - }, - { - q: '슬라이드 수에 제한이 있나요?', - a: '제한 없습니다. 엑셀에 입력한 행 수만큼 슬라이드가 생성됩니다. 단, 슬라이드당 불릿 포인트는 최대 8개입니다.', - }, - { - q: '챕터 구분 슬라이드도 넣을 수 있나요?', - a: 'create_divider_slide() 함수가 포함되어 있습니다. main() 함수에서 원하는 위치에 호출하면 챕터 구분 슬라이드를 추가할 수 있습니다.', - }, - { - q: '맥(Mac)에서도 사용할 수 있나요?', - a: '맥에서도 동일하게 사용 가능합니다. 단, 맥에는 맑은 고딕 폰트가 없으므로 FONT_NAME을 "AppleGothic" 또는 "Nanum Gothic"으로 변경하세요.', - }, -]; - -export default function PptToolPage() { - return ( -
- - {/* ─── Hero ─── */} -
-
- - - - - - - - - - - - - - -
- -
- - - 업무 자동화로 - - -
- - - -
- -

PPT AUTOMATION · 프레젠테이션 자동화

-

- PPT 제작을
- 코드로 자동화 -

-

- 엑셀 데이터만 준비하면 표지·내용·마무리 슬라이드를 자동 생성.
- python-pptx 기반으로 디자인까지 자동 적용됩니다. -

- -
- - - 무료 다운로드 (Python .py) - - - 맞춤 개발 문의 → - -
-
-
- - {/* ─── 다운로드 카드 ─── */} -
-
-
-
- - - -
-
-
- PPT AUTOMATION v1.0 - 무료 -
-
PPT 제작 자동화 도구
-

python-pptx 기반 · 엑셀 연동 · 표지/내용/마무리 자동 생성 · 색상 테마 커스터마이징

-
- {['Python 3.8+', 'python-pptx', 'openpyxl', '한글 지원', '엑셀 연동'].map((tag) => ( - {tag} - ))} -
-
- - - 다운로드 - -
-
-
- - {/* ─── 기능 ─── */} -
-
-
-

FEATURES

-

주요 기능

-
-
- {features.map((f) => ( -
-
- {f.icon} -
-

{f.title}

-

{f.desc}

-
- ))} -
-
-
- - {/* ─── 사용법 ─── */} -
-
-
-

HOW TO USE

-

사용 방법

-
-
- {howToUse.map((h) => ( -
-
-
- {h.step} -
-

{h.title}

-
-

{h.desc}

-
- {h.code} -
-
- ))} -
-
-
- - {/* ─── 코드 미리보기 ─── */} -
-
-
-

PREVIEW

-

설정 영역 미리보기

-

이 부분만 수정하면 원하는 PPT가 완성됩니다

-
-
-
-
-
-
- ppt_automation_v1.0.py -
-
{`# ── 설정 (이 부분을 수정하세요) ──────────────
-
-DATA_FILE   = "data.xlsx"       # 입력 엑셀 파일
-OUTPUT_FILE = f"발표자료_{datetime}.pptx"
-
-# 표지 정보
-TITLE_TEXT    = "발표 제목을 입력하세요"
-SUBTITLE_TEXT = "부제목 또는 발표자 이름"
-DATE_TEXT     = "2025년 01월 01일"
-
-# 색상 테마 (RGB 값으로 변경)
-COLOR_PRIMARY   = RGBColor(0x1D, 0x4E, 0xD8)  # 파란색
-COLOR_SECONDARY = RGBColor(0x0F, 0x17, 0x2A)  # 다크 네이비
-COLOR_ACCENT    = RGBColor(0x60, 0xA5, 0xFA)  # 라이트 블루
-
-FONT_NAME = "맑은 고딕"   # 한글 폰트`}
-            
-
-

- * 코드 미리보기는 실제 파일의 일부입니다. 다운로드 후 설정 영역 전체를 수정해서 사용하세요. -

-
-
- - {/* ─── FAQ ─── */} -
-
-
-

FAQ

-

자주 묻는 질문

-
-
- {faqs.map((faq) => ( -
-
- Q. -
-
{faq.q}
-
{faq.a}
-
-
-
- ))} -
-
-
- - {/* ─── CTA ─── */} -
-
-
-

CUSTOM DEVELOPMENT

-

더 복잡한 PPT 자동화가 필요하신가요?

-

- 이미지 삽입, 차트 자동 생성, 브랜드 템플릿 적용 등
- 맞춤 PPT 자동화를 개발해드립니다. -

-
- - - 무료 버전 다운로드 - - - 맞춤 개발 문의 → - -
-
-
-
- -
- ); -} diff --git a/app/services/automation/tools/scraper/layout.tsx b/app/services/automation/tools/scraper/layout.tsx deleted file mode 100644 index b17fca0..0000000 --- a/app/services/automation/tools/scraper/layout.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { Metadata } from 'next'; - -export const metadata: Metadata = { - title: '웹 크롤링·스크래핑 도구', - description: - '공공데이터·쇼핑몰 가격·뉴스를 자동 수집해 엑셀로 저장하는 Python 웹 스크래퍼. requests + BeautifulSoup4 + openpyxl 기반. 무료 다운로드.', - keywords: [ - '웹 크롤링', - '웹 스크래핑', - '파이썬 크롤러', - '데이터 수집 자동화', - 'BeautifulSoup', - '엑셀 자동화', - '무료 크롤러', - ], - openGraph: { - title: '웹 크롤링 자동화 도구 | 쟁승메이드', - description: 'Python 기반 웹 크롤러 무료 다운로드. 페이지네이션·재시도·엑셀 저장 지원.', - url: 'https://jaengseung-made.com/services/automation/tools/scraper', - }, -}; - -export default function ScraperLayout({ children }: { children: React.ReactNode }) { - return children; -} diff --git a/app/services/automation/tools/scraper/page.tsx b/app/services/automation/tools/scraper/page.tsx deleted file mode 100644 index cf839b8..0000000 --- a/app/services/automation/tools/scraper/page.tsx +++ /dev/null @@ -1,284 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import { useState } from 'react'; - -const features = [ - { - icon: ( - - - - ), - title: '웹 페이지 데이터 자동 수집', - desc: '공공데이터, 쇼핑몰 가격, 뉴스 기사 등 원하는 페이지의 데이터를 자동으로 수집합니다.', - color: 'text-blue-600', bg: 'bg-blue-50', border: 'border-blue-200', - }, - { - icon: ( - - - - ), - title: '엑셀 자동 저장', - desc: '수집한 데이터를 열 서식, 헤더 스타일이 적용된 엑셀 파일로 자동 저장합니다.', - color: 'text-emerald-600', bg: 'bg-emerald-50', border: 'border-emerald-200', - }, - { - icon: ( - - - - ), - title: '페이지네이션 자동 탐색', - desc: '다음 페이지 링크를 자동으로 찾아 여러 페이지의 데이터를 연속으로 수집합니다.', - color: 'text-violet-600', bg: 'bg-violet-50', border: 'border-violet-200', - }, - { - icon: ( - - - - ), - title: '재시도 로직 내장', - desc: '네트워크 오류나 일시적 접속 실패 시 자동으로 재시도합니다. 수집 실패 최소화.', - color: 'text-orange-600', bg: 'bg-orange-50', border: 'border-orange-200', - }, - { - icon: ( - - - - ), - title: '요청 간격 자동 조절', - desc: '서버에 부하를 주지 않도록 요청 간격을 자동으로 조절합니다. 차단 위험 최소화.', - color: 'text-cyan-600', bg: 'bg-cyan-50', border: 'border-cyan-200', - }, - { - icon: ( - - - - ), - title: '로그 파일 자동 저장', - desc: '수집 과정 전체를 로그로 남겨 나중에 어떤 URL에서 몇 건을 수집했는지 확인 가능합니다.', - color: 'text-rose-600', bg: 'bg-rose-50', border: 'border-rose-200', - }, -]; - -const howToUse = [ - { step: '01', title: 'Python 설치', desc: 'python.org에서 Python 3.10 이상을 설치하세요. "Add to PATH" 체크 필수.' }, - { step: '02', title: '패키지 설치', desc: '터미널에서 pip install requests beautifulsoup4 openpyxl lxml 실행.' }, - { step: '03', title: 'URL 설정', desc: '파일 상단 TARGET_URL에 크롤링할 주소를 입력하세요.' }, - { step: '04', title: '실행', desc: 'python web_scraper_v1.0.py 실행 → 같은 폴더에 엑셀 파일이 생성됩니다.' }, -]; - -const faqs = [ - { - q: '크롤링이 법적으로 문제없나요?', - a: '공개된 정보 수집 자체는 일반적으로 허용되지만, 사이트의 robots.txt와 이용약관을 반드시 확인하세요. 로그인이 필요한 페이지, 개인정보, 저작권 데이터 수집은 법적 문제가 생길 수 있습니다.', - }, - { - q: '자바스크립트로 렌더링되는 사이트도 되나요?', - a: 'requests + BeautifulSoup은 정적 HTML만 수집합니다. JS 렌더링 사이트(React, Vue 등)는 Selenium/Playwright가 필요하며, 맞춤 개발 서비스로 문의 주시면 구현해 드립니다.', - }, - { - q: '원하는 항목만 골라서 수집할 수 있나요?', - a: '파일 내 extract_data 함수를 수정하면 됩니다. HTML 선택자(CSS Selector)로 원하는 요소만 지정할 수 있으며, 코드 내 주석에 예시가 포함되어 있습니다.', - }, -]; - -export default function ScraperToolPage() { - const [openFaq, setOpenFaq] = useState(null); - - return ( -
- - {/* Hero */} -
-
- - - - - 업무 자동화 서비스로 돌아가기 - - -
-
- - - -
-
-
- FREE TOOL - v1.0 - Python · BeautifulSoup -
-

- 웹 크롤링 자동화 도구
- - Web Scraper - -

-

- 공공데이터, 가격 비교, 뉴스 수집까지 — 원하는 웹 페이지의 데이터를 자동으로 수집해
- 엑셀 파일로 저장합니다. Python 기초 지식만 있으면 바로 사용 가능합니다. -

-
-
- -
- {[ - { v: '6가지', l: '핵심 기능' }, - { v: '무료', l: '완전 무료' }, - { v: 'Python 3.10+', l: '지원 버전' }, - ].map((s) => ( -
-
{s.v}
-
{s.l}
-
- ))} -
-
-
- -
-
- - {/* 다운로드 카드 */} -
-
-
DOWNLOAD
-
web_scraper_v1.0.py
-
크기: 약 8KB · Python 스크립트 · 상업적 이용 가능
-
- {['Python 3.10+', '페이지네이션', '재시도 로직', '엑셀 자동 저장', '로그 저장'].map((t) => ( - {t} - ))} -
-
-
- - - - - 무료 다운로드 - -

로그인 없이 즉시 다운로드

-
-
- - {/* 기능 목록 */} -
-

포함된 기능

-
- {features.map((f) => ( -
-
{f.icon}
-
{f.title}
-

{f.desc}

-
- ))} -
-
- - {/* 사용 방법 */} -
-

사용 방법

-
- {howToUse.map((h) => ( -
-
{h.step}
-
-
{h.title}
-

{h.desc}

-
-
- ))} -
-
- - {/* 코드 예시 */} -
-
- CODE PREVIEW - extract_data 함수 수정 예시 -
-
{`def extract_data(soup, page_url):
-    items = []
-    # 상품 목록 수집 예시
-    for item in soup.select(".product-item"):
-        name  = item.select_one(".name")
-        price = item.select_one(".price")
-        items.append({
-            "상품명": name.get_text(strip=True),
-            "가격":   price.get_text(strip=True),
-            "URL":    page_url,
-        })
-    return items`}
-
- - {/* FAQ */} -
-

자주 묻는 질문

-
- {faqs.map((faq, i) => ( -
- - {openFaq === i && ( -
- {faq.a} -
- )} -
- ))} -
-
- - {/* CTA */} -
-

CUSTOM DEVELOPMENT

-

더 복잡한 크롤링이 필요하다면?

-

- JS 렌더링 사이트, 로그인 필요, 대용량 수집, 자동 스케줄링까지
- 맞춤 개발로 정확히 원하는 데이터를 가져옵니다. -

-
- - - - - 무료 다운로드 - - - 맞춤 크롤러 개발 문의 → - -
-
- -
-
-
- ); -} diff --git a/app/services/blog/layout.tsx b/app/services/blog/layout.tsx new file mode 100644 index 0000000..63ed6d4 --- /dev/null +++ b/app/services/blog/layout.tsx @@ -0,0 +1,26 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: '블로그 자동화 솔루션 팩 | 쟁승메이드', + description: + '쿠팡파트너스·네이버 애드포스트·브랜드커넥트 수익을 자동화하는 프롬프트 조합법 45종 + 구조 템플릿 PDF 80p + 샘플 글 10편. ₩29,000 한 번 결제, 평생 무료 업데이트.', + keywords: [ + '블로그 자동화', + 'AI 블로그 글쓰기', + '쿠팡파트너스 자동화', + '애드포스트 수익화', + '네이버 블로그 SEO', + 'ChatGPT 블로그', + '블로그 프롬프트', + ], + openGraph: { + title: '블로그 자동화 솔루션 팩 | 쟁승메이드', + description: + '쿠팡파트너스·애드포스트 수익을 자동화하는 프롬프트 + 템플릿 + 샘플. ₩29,000.', + url: 'https://jaengseung-made.com/services/blog', + }, +}; + +export default function BlogLayout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/app/services/blog/page.tsx b/app/services/blog/page.tsx new file mode 100644 index 0000000..bfb70cf --- /dev/null +++ b/app/services/blog/page.tsx @@ -0,0 +1,243 @@ +'use client'; + +import { useState } from 'react'; +import Link from 'next/link'; +import PurchaseAgreementModal from '../../components/PurchaseAgreementModal'; + +const PACK_ITEMS = [ + { + icon: '📝', + title: '프롬프트 조합법 45종', + desc: '상품리뷰 / 정보글 / 후기 / 비교 / 하우투 글별 최적 프롬프트 조합', + meta: 'PDF 80p', + }, + { + icon: '📐', + title: '블로그 글 구조 템플릿 12종', + desc: '쿠팡파트너스 · 애드포스트 클릭을 유도하는 검증된 글 구조', + meta: 'Notion 템플릿', + }, + { + icon: '💰', + title: '샘플 글 10편', + desc: '실제로 수익이 발생한 블로그 글 전문 + 해설 주석', + meta: '.docx · .md', + }, + { + icon: '🔍', + title: '네이버 SEO 체크리스트', + desc: 'C-Rank · D.I.A. 알고리즘 대응 14가지 체크 포인트', + meta: 'PDF 20p', + }, +]; + +const FAQS = [ + { + q: '초보자도 쓸 수 있나요?', + a: 'ChatGPT나 Claude 계정만 있으면 됩니다. 프롬프트를 복붙하는 것부터 시작해서 점차 응용하도록 설계했습니다.', + }, + { + q: '어떤 플랫폼에 맞나요?', + a: '네이버 블로그·티스토리·브런치 모두 대응. 쿠팡파트너스·애드포스트·브랜드커넥트 3가지 수익화 흐름을 모두 다룹니다.', + }, + { + q: '업데이트는 얼마나 자주 되나요?', + a: '월 1~2회 주요 업데이트. 구매자 전용 Notion 페이지에서 변경 이력과 최신 파일을 제공합니다. 구매 후 12개월간 무료.', + }, + { + q: '환불이 되나요?', + a: '전자상거래법상 디지털 콘텐츠는 제공 시작 후 환불이 제한됩니다. 구매 전 샘플 미리보기를 충분히 확인해주세요. 파일 손상·전달 불량은 즉시 재전달 또는 환불됩니다.', + }, +]; + +export default function BlogServicePage() { + const [agreeOpen, setAgreeOpen] = useState(false); + const [openFaq, setOpenFaq] = useState(0); + + return ( +
+ {/* HERO */} +
+
+

+ Blog Automation Pack +

+

+ 매일 글쓰기 고민, +
+ AI에게 맡기세요. +

+

+ 쿠팡파트너스 · 네이버 애드포스트 · 브랜드커넥트 수익을 +
+ 자동화하는 프롬프트 · 구조 · 샘플 세트. +

+
+ ₩29,000 + 한 번 결제 · 12개월 무료 업데이트 +
+
+ + + 샘플 미리보기 + +
+
+
+ + {/* PAIN POINTS */} +
+
+

+ 이런 분들을 위한 팩입니다. +

+
+ {[ + { icon: '🕐', title: '매일 1시간+', desc: '글 소재·구성에 시간 다 쓰고 수익은 제자리' }, + { icon: '📉', title: '수익화 6개월+', desc: '블로그 키워놓고도 수익 구조가 안 잡힘' }, + { icon: '🤖', title: 'AI 글은 어색', desc: 'ChatGPT 그대로 복붙하면 바로 들통' }, + ].map((p) => ( +
+
{p.icon}
+

{p.title}

+

+ {p.desc} +

+
+ ))} +
+
+
+ + {/* PACK CONTENT */} +
+
+

+ Pack Contents +

+

+ 구성품 4종 +

+
+ {PACK_ITEMS.map((it) => ( +
+
{it.icon}
+
+
+

{it.title}

+ + {it.meta} + +
+

+ {it.desc} +

+
+
+ ))} +
+ + {/* Sample preview */} +
+ + 샘플 미리보기 + +

프롬프트 예시 · 상품 리뷰 글 자동 생성

+
{`당신은 [카테고리] 전문 블로거입니다.
+아래 상품의 [핵심 장점 3개]와 [주의점 1개]를 기반으로
+C-Rank 알고리즘에 최적화된 1,200자 리뷰 글을 작성하세요.
+
+[구조]
+1. 후킹 도입 (공감형 질문)
+2. 상품 요약 (스펙 표)
+3. 실사용 관점 장점·단점
+4. 대안 비교 (쿠팡 링크 삽입 지점: {LINK})
+5. 결론 + 재질문 유도
+
+[톤앤매너] 친근한 존댓말, 광고 느낌 최소화 ...`}
+

+ 실제 팩에는 카테고리별 45종의 프롬프트와 최적화 파라미터가 포함됩니다. +

+
+
+
+ + {/* FAQ */} +
+
+

+ 자주 묻는 질문 +

+
+ {FAQS.map((f, i) => ( +
+ + {openFaq === i && ( +
+ {f.a} +
+ )} +
+ ))} +
+
+
+ + {/* FINAL CTA */} +
+
+

+ 오늘부터 블로그 수익 자동화. +

+

₩29,000 한 번 결제 · 평생 업데이트

+ +

+ 환불 정책 + {' · '} + 이용약관 +

+
+
+ + setAgreeOpen(false)} + productName="블로그 자동화 솔루션 팩" + price="₩29,000" + /> +
+ ); +} diff --git a/app/services/lotto/layout.tsx b/app/services/lotto/layout.tsx deleted file mode 100644 index 341be87..0000000 --- a/app/services/lotto/layout.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { Metadata } from 'next'; - -export const metadata: Metadata = { - title: '로또 번호 추천', - description: - '1,100+회차 빅데이터 기반 로또 번호 분석. 핫/콜드 번호 통계, 몬테카를로 시뮬레이션으로 매주 최적 번호 조합을 제공합니다. 월 900원부터 구독.', - keywords: [ - '로또 번호 추천', - '로또 번호 분석', - '로또 빅데이터', - '로또 통계', - '로또 번호 생성', - '핫넘버 콜드넘버', - ], - openGraph: { - title: '로또 번호 추천 서비스 | 쟁승메이드', - description: - '1,100+회차 데이터 분석 · 월 900원 구독 · 이메일/텔레그램 자동 발송.', - url: 'https://jaengseung-made.com/services/lotto', - }, -}; - -export default function LottoLayout({ children }: { children: React.ReactNode }) { - return children; -} diff --git a/app/services/lotto/page.tsx b/app/services/lotto/page.tsx deleted file mode 100644 index b29ca36..0000000 --- a/app/services/lotto/page.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { redirect } from 'next/navigation'; - -// PG 심사 정책상 로또 관련 서비스 비공개 처리 -export default function LottoPage() { - redirect('/'); -} diff --git a/app/services/lotto/recommend/PatternTab.tsx b/app/services/lotto/recommend/PatternTab.tsx deleted file mode 100644 index 97c5864..0000000 --- a/app/services/lotto/recommend/PatternTab.tsx +++ /dev/null @@ -1,184 +0,0 @@ -'use client'; - -import { useState, useEffect } from 'react'; - -interface PersonalPattern { - total_analyzed: number; - number_frequency: Record; - top_picks: number[]; - least_picks: number[]; - pattern: { - avg_odd_count: number; - avg_sum: number; - avg_range: number; - consecutive_rate: number; - zone_avg: Record; - }; - vs_draw_avg: { - odd_diff: number; - sum_diff: number; - odd_tendency: string; - sum_tendency: string; - }; -} - -function getBallStyle(n: number) { - if (n <= 10) return { bg: 'linear-gradient(145deg,#fde68a,#fbbf24,#d97706)', text: '#78350f' }; - if (n <= 20) return { bg: 'linear-gradient(145deg,#93c5fd,#3b82f6,#1d4ed8)', text: '#fff' }; - if (n <= 30) return { bg: 'linear-gradient(145deg,#fca5a5,#ef4444,#b91c1c)', text: '#fff' }; - if (n <= 40) return { bg: 'linear-gradient(145deg,#d1d5db,#9ca3af,#4b5563)', text: '#fff' }; - return { bg: 'linear-gradient(145deg,#86efac,#22c55e,#15803d)', text: '#fff' }; -} - -function SmallBall({ n, size = 30, freq }: { n: number; size?: number; freq?: number }) { - const { bg, text } = getBallStyle(n); - return ( -
-
{n}
- {freq !== undefined &&
{freq}회
} -
- ); -} - -function ZoneBar({ label, value, max }: { label: string; value: number; max: number }) { - const pct = max > 0 ? (value / max) * 100 : 0; - return ( -
-
{label}
-
-
-
-
{value.toFixed(1)}
-
- ); -} - -export default function PatternTab() { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); - - useEffect(() => { - fetch('/api/lotto/analysis/personal').then(r => r.json()) - .then(d => { - if (d?.error) { setError(d.error === 'NAS_TIMEOUT' ? 'NAS 서버 응답 시간 초과.' : '패턴 분석을 불러오지 못했습니다.'); return; } - setData(d); - }) - .catch(() => setError('패턴 분석을 불러오지 못했습니다.')) - .finally(() => setLoading(false)); - }, []); - - if (loading) return ( -
-
-
- ); - - if (error) return
{error}
; - - if (!data || data.total_analyzed === 0) return ( -
-
📊
-
아직 분석할 데이터가 없습니다
-
번호 생성 탭에서 번호를 추천받으면 패턴이 쌓입니다
-
- ); - - const zoneMax = Math.max(...Object.values(data.pattern.zone_avg)); - const tendencyColor = (tendency: string) => - tendency.includes('고') || tendency.includes('홀수') ? '#f87171' : tendency.includes('저') || tendency.includes('짝수') ? '#60a5fa' : '#4ade80'; - - return ( -
- -
-
-
PERSONAL PATTERN ANALYSIS
-

내 번호 선택 패턴

-
-
- 총 분석 - {data.total_analyzed} - -
-
- -
- - {/* 자주 선택한 번호 */} -
-
⭐ 자주 선택한 번호 TOP 10
-
- {data.top_picks.map(n => ( - - ))} -
-
- - {/* 한 번도 안 쓴 번호 */} -
-
💤 거의 안 쓴 번호
-
- {data.least_picks.map(n => )} -
-
이 번호들도 가끔 포함해보세요
-
- - {/* 패턴 지표 */} -
-
📐 선택 패턴 지표
- {[ - { label: '평균 홀수 개수', value: data.pattern.avg_odd_count.toFixed(1) + '개', ref: '역대 평균 3.0개', refColor: 'rgba(255,255,255,.2)' }, - { label: '평균 합계', value: data.pattern.avg_sum.toFixed(0), ref: '역대 평균 138', refColor: 'rgba(255,255,255,.2)' }, - { label: '평균 범위(최대-최소)', value: data.pattern.avg_range.toFixed(1), ref: '', refColor: '' }, - { label: '연속번호 포함률', value: `${(data.pattern.consecutive_rate * 100).toFixed(0)}%`, ref: '', refColor: '' }, - ].map(({ label, value, ref }) => ( -
- {label} -
-
{value}
- {ref &&
{ref}
} -
-
- ))} -
- - {/* 구간별 선택 분포 */} -
-
🎯 구간별 선택 분포
-
- {Object.entries(data.pattern.zone_avg).map(([zone, val]) => ( - - ))} -
-
- - {/* 역대 당첨과 비교 */} -
-
⚖️ 역대 당첨 평균과 비교
-
-
-
홀수 선택 경향
-
{data.vs_draw_avg.odd_tendency}
-
- 당첨 평균 대비 {data.vs_draw_avg.odd_diff > 0 ? '+' : ''}{data.vs_draw_avg.odd_diff.toFixed(1)}개 -
-
-
-
합계 선택 경향
-
{data.vs_draw_avg.sum_tendency}
-
- 당첨 평균 대비 {data.vs_draw_avg.sum_diff > 0 ? '+' : ''}{data.vs_draw_avg.sum_diff.toFixed(1)} -
-
-
-
-
-
- ); -} diff --git a/app/services/lotto/recommend/PurchaseTab.tsx b/app/services/lotto/recommend/PurchaseTab.tsx deleted file mode 100644 index 7589fb8..0000000 --- a/app/services/lotto/recommend/PurchaseTab.tsx +++ /dev/null @@ -1,255 +0,0 @@ -'use client'; - -import { useState, useEffect } from 'react'; - -interface PurchaseRecord { - id: number; - draw_no: number; - amount: number; - sets: number; - prize: number; - note: string; - created_at: string; -} - -interface PurchaseStats { - total_records: number; - total_invested: number; - total_prize: number; - net: number; - return_rate: number; - prize_count: number; - max_prize: number; -} - -function StatCard({ label, value, sub, color }: { label: string; value: string; sub?: string; color?: string }) { - return ( -
-
{label}
-
{value}
- {sub &&
{sub}
} -
- ); -} - -export default function PurchaseTab() { - const [records, setRecords] = useState([]); - const [stats, setStats] = useState(null); - const [loading, setLoading] = useState(true); - const [editingId, setEditingId] = useState(null); - const [editPrize, setEditPrize] = useState(''); - const [editNote, setEditNote] = useState(''); - const [showAdd, setShowAdd] = useState(false); - const [addForm, setAddForm] = useState({ draw_no: '', amount: '5000', sets: '5', prize: '0', note: '' }); - const [saving, setSaving] = useState(false); - - const load = async () => { - try { - const [recRes, statRes] = await Promise.all([ - fetch('/api/lotto/purchase').then(r => r.json()), - fetch('/api/lotto/purchase/stats').then(r => r.json()), - ]); - if (recRes?.error || statRes?.error) throw new Error(recRes?.error ?? statRes?.error); - setRecords(recRes.records ?? []); - setStats(statRes); - } catch { /* 에러 시 빈 상태 유지 */ } finally { setLoading(false); } - }; - - useEffect(() => { load(); }, []); - - const handleAdd = async () => { - if (!addForm.draw_no) return; - setSaving(true); - try { - await fetch('/api/lotto/purchase', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - draw_no: parseInt(addForm.draw_no), - amount: parseInt(addForm.amount), - sets: parseInt(addForm.sets), - prize: parseInt(addForm.prize), - note: addForm.note, - }), - }); - setShowAdd(false); - setAddForm({ draw_no: '', amount: '5000', sets: '5', prize: '0', note: '' }); - await load(); - } finally { setSaving(false); } - }; - - const handleUpdate = async (id: number) => { - setSaving(true); - try { - await fetch(`/api/lotto/purchase/${id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ prize: parseInt(editPrize) || 0, note: editNote }), - }); - setEditingId(null); - await load(); - } finally { setSaving(false); } - }; - - const handleDelete = async (id: number) => { - if (!confirm('삭제하시겠습니까?')) return; - await fetch(`/api/lotto/purchase/${id}`, { method: 'DELETE' }); - await load(); - }; - - const inputStyle: React.CSSProperties = { - background: 'rgba(255,255,255,.06)', border: '1px solid rgba(255,255,255,.12)', - borderRadius: '.4rem', padding: '.35rem .65rem', color: '#fff', fontSize: '.78rem', width: '100%', - outline: 'none', - }; - - if (loading) return ( -
-
-
- ); - - return ( -
- - {/* 통계 카드 */} - {stats && ( -
-
INVESTMENT STATS
-
- - - = 0 ? '+' : ''}${(stats.net / 10000).toFixed(1)}만원`} - sub={`회수율 ${stats.return_rate.toFixed(1)}%`} - color={stats.net >= 0 ? '#4ade80' : '#f87171'} - /> - 0 ? `${stats.max_prize.toLocaleString()}원` : '-'} color="#fbbf24" /> -
-
- )} - - {/* 구매 기록 테이블 */} -
-
-
PURCHASE HISTORY
- -
- - {/* 추가 폼 */} - {showAdd && ( -
-
-
-
회차 *
- setAddForm(p => ({ ...p, draw_no: e.target.value }))} /> -
-
-
구매금액
- setAddForm(p => ({ ...p, amount: e.target.value }))} /> -
-
-
세트수
- setAddForm(p => ({ ...p, sets: e.target.value }))} /> -
-
-
당첨금
- setAddForm(p => ({ ...p, prize: e.target.value }))} /> -
-
-
메모
- setAddForm(p => ({ ...p, note: e.target.value }))} /> -
-
-
- - -
-
- )} - - {/* 레코드 목록 */} - {records.length === 0 ? ( -
- 구매 기록이 없습니다. 구매 후 기록을 추가해보세요. -
- ) : ( -
-

- - - {['회차', '구매금액', '세트', '당첨금', '손익', '메모', ''].map(h => ( - - ))} - - - - {records.map(rec => { - const net = rec.prize - rec.amount; - const isEditing = editingId === rec.id; - return ( - - - - - - - - - - ); - })} - -
{h}
{rec.draw_no}회{rec.amount.toLocaleString()}원{rec.sets}세트 - {isEditing ? ( - setEditPrize(e.target.value)} /> - ) : ( - 0 ? '#4ade80' : 'rgba(255,255,255,.3)' }}> - {rec.prize > 0 ? `${rec.prize.toLocaleString()}원` : '-'} - - )} - 0 ? '#4ade80' : net < 0 ? '#f87171' : 'rgba(255,255,255,.3)', fontFamily: "'JetBrains Mono',monospace", fontWeight: 700 }}> - {net > 0 ? '+' : ''}{net.toLocaleString()} - - {isEditing ? ( - setEditNote(e.target.value)} /> - ) : ( - {rec.note || '-'} - )} - - {isEditing ? ( -
- - -
- ) : ( -
- - -
- )} -
- - )} - - - ); -} diff --git a/app/services/lotto/recommend/ReportTab.tsx b/app/services/lotto/recommend/ReportTab.tsx deleted file mode 100644 index 18cabd7..0000000 --- a/app/services/lotto/recommend/ReportTab.tsx +++ /dev/null @@ -1,242 +0,0 @@ -'use client'; - -import { useState, useEffect } from 'react'; - -// ─── Types ─────────────────────────────────────────────────────────────────── -interface ReportData { - target_drw_no: number; - based_on_draw: number; - generated_at: string; - hot_numbers: number[]; - cold_numbers: number[]; - overdue_numbers: number[]; - recent_pattern: { - last3_numbers: number[]; - triple_appear: number[]; - recent_sum_avg: number; - recent_odd_avg: number; - }; - recommended_sets: Array<{ - strategy: string; - numbers: number[]; - description: string; - }>; - confidence_score: number; - confidence_factors: { - data_volume: number; - pattern_consistency: number; - recent_trend: number; - }; -} - -interface HistoryItem { drw_no: number; generated_at: string; } - -function getBallStyle(n: number) { - if (n <= 10) return { bg: 'linear-gradient(145deg,#fde68a,#fbbf24,#d97706)', text: '#78350f' }; - if (n <= 20) return { bg: 'linear-gradient(145deg,#93c5fd,#3b82f6,#1d4ed8)', text: '#fff' }; - if (n <= 30) return { bg: 'linear-gradient(145deg,#fca5a5,#ef4444,#b91c1c)', text: '#fff' }; - if (n <= 40) return { bg: 'linear-gradient(145deg,#d1d5db,#9ca3af,#4b5563)', text: '#fff' }; - return { bg: 'linear-gradient(145deg,#86efac,#22c55e,#15803d)', text: '#fff' }; -} - -function SmallBall({ n, size = 32 }: { n: number; size?: number }) { - const { bg, text } = getBallStyle(n); - return ( -
{n}
- ); -} - -function ConfidenceBar({ label, value }: { label: string; value: number }) { - const color = value >= 85 ? '#4ade80' : value >= 70 ? '#fbbf24' : '#f87171'; - return ( -
-
- {label} - {value} -
-
-
-
-
- ); -} - -export default function ReportTab() { - const [report, setReport] = useState(null); - const [history, setHistory] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); - const [copiedIdx, setCopiedIdx] = useState(null); - - useEffect(() => { - Promise.all([ - fetch('/api/lotto/report/latest').then(r => r.json()), - fetch('/api/lotto/report/history?limit=10').then(r => r.json()), - ]).then(([rep, hist]) => { - if (rep?.error) { - setError(rep.error === 'NAS_TIMEOUT' - ? 'NAS 서버 응답 시간 초과. 잠시 후 다시 시도해주세요.' - : '리포트를 불러오지 못했습니다. (' + rep.error + ')'); - return; - } - setReport(rep); - setHistory(hist?.reports ?? []); - }).catch(() => setError('리포트를 불러오지 못했습니다.')) - .finally(() => setLoading(false)); - }, []); - - const copyNumbers = (numbers: number[], idx: number) => { - navigator.clipboard.writeText(numbers.join(', ')); - setCopiedIdx(idx); - setTimeout(() => setCopiedIdx(null), 1500); - }; - - if (loading) return ( -
-
-
리포트 불러오는 중...
-
- ); - - if (error) return ( -
{error}
- ); - - if (!report || !report.confidence_factors || !report.recommended_sets) return null; - - const strategyColors = ['#fbbf24', '#60a5fa', '#a78bfa']; - - return ( -
- - {/* 헤더 */} -
-
-
WEEKLY ATTACK REPORT
-

- 제{report.target_drw_no}회 공략 리포트 -

-
- {report.based_on_draw}회까지 데이터 기반 · {new Date(report.generated_at).toLocaleDateString('ko-KR')} 생성 -
-
- {/* 신뢰도 점수 */} -
-
CONFIDENCE
-
{report.confidence_score}
-
/100
-
-
- -
- - {/* 추천 번호 세트 */} -
-
RECOMMENDED SETS
-
- {report.recommended_sets.map((set, i) => ( -
-
-
-
- {set.strategy} -
- -
-
- {set.numbers.map(n => )} -
-
{set.description}
-
- ))} -
-
- - {/* 핫/콜드/미출현 */} - {[ - { label: '🔥 최근 과출현', numbers: report.hot_numbers, color: '#f87171', desc: '최근 10회 2회 이상 출현' }, - { label: '❄️ 저빈도 번호', numbers: report.cold_numbers, color: '#60a5fa', desc: '역대 출현 빈도 하위' }, - { label: '⏳ 장기 미출현', numbers: report.overdue_numbers, color: '#a78bfa', desc: '가장 오래 미출현 번호' }, - ].map(({ label, numbers, color, desc }) => ( -
-
{label}
-
{desc}
-
- {numbers.map(n => )} -
-
- ))} - - {/* 최근 패턴 */} -
-
📊 최근 패턴
- {[ - { label: '최근 10회 합계 평균', value: report.recent_pattern.recent_sum_avg.toFixed(1) }, - { label: '최근 10회 홀수 평균', value: report.recent_pattern.recent_odd_avg.toFixed(1) + '개' }, - ].map(({ label, value }) => ( -
- {label} - {value} -
- ))} - {report.recent_pattern.triple_appear.length > 0 && ( -
-
직전 3회 연속 출현
-
- {report.recent_pattern.triple_appear.map(n => )} -
-
- )} -
- - {/* 신뢰도 상세 */} -
-
🎯 신뢰도 분석
- - - -
-
- - {/* 이전 리포트 목록 */} - {history.length > 0 && ( -
-
REPORT HISTORY
-
- {history.map(h => ( - - ))} -
-
- )} -
- ); -} diff --git a/app/services/lotto/recommend/page.tsx b/app/services/lotto/recommend/page.tsx deleted file mode 100644 index 2f866ec..0000000 --- a/app/services/lotto/recommend/page.tsx +++ /dev/null @@ -1,1302 +0,0 @@ -'use client'; - -import { useState, useEffect, useRef } from 'react'; -import Link from 'next/link'; -import ReportTab from './ReportTab'; -import PurchaseTab from './PurchaseTab'; -import PatternTab from './PatternTab'; -import { createClient } from '@/lib/supabase/client'; - -// ─── 전략 타입 ──────────────────────────────────────────────────────────────── -type Strategy = 'balanced' | 'aggressive' | 'safe'; - -const STRATEGY_INFO: Record = { - balanced: { label: '균형형', desc: '합계·홀짝·구간 고르게 최적화', icon: '⚖️', color: 'rgba(251,191,36,.12)', accentColor: '#fbbf24' }, - aggressive: { label: '고위험형', desc: '미출현 냉각 번호 중점 포함', icon: '🔥', color: 'rgba(239,68,68,.1)', accentColor: '#f87171' }, - safe: { label: '안정형', desc: '과출현 핫 번호 + 균등 분산', icon: '🛡️', color: 'rgba(96,165,250,.1)', accentColor: '#60a5fa' }, -}; - -// ─── 클라이언트 Monte Carlo 폴백 ───────────────────────────────────────────── - -function clientMonteCarlo(strategy: Strategy = 'balanced'): { numbers: number[]; metrics: { sum: number; odd: number; even: number; min: number; max: number; range: number } } { - const SIMS = 5000; - let best: number[] = []; - let bestScore = -Infinity; - - for (let i = 0; i < SIMS; i++) { - const nums = pickRandom6(); - const score = scoreCombo(nums, strategy); - if (score > bestScore) { bestScore = score; best = nums; } - } - - const sorted = [...best].sort((a, b) => a - b); - const sum = sorted.reduce((a, b) => a + b, 0); - const odd = sorted.filter(n => n % 2 !== 0).length; - return { - numbers: sorted, - metrics: { sum, odd, even: 6 - odd, min: sorted[0], max: sorted[5], range: sorted[5] - sorted[0] }, - }; -} - -function pickRandom6(): number[] { - const pool = Array.from({ length: 45 }, (_, i) => i + 1); - const result: number[] = []; - while (result.length < 6) { - const idx = Math.floor(Math.random() * pool.length); - result.push(pool.splice(idx, 1)[0]); - } - return result; -} - -function scoreCombo(nums: number[], strategy: Strategy = 'balanced'): number { - const sorted = [...nums].sort((a, b) => a - b); - const sum = sorted.reduce((a, b) => a + b, 0); - const odd = sorted.filter(n => n % 2 !== 0).length; - const zones = new Set(sorted.map(n => Math.min(Math.floor((n - 1) / 10), 4))); - - if (strategy === 'aggressive') { - // 고위험형: 높은 번호·냉각 구간 선호, 합계 편차 허용 - const sumScore = -Math.abs(sum - 158) / 55; - const oddScore = odd >= 2 && odd <= 4 ? 0.3 : -0.3; - const zoneScore = zones.size * 0.2; - const highNums = sorted.filter(n => n > 35).length; - return sumScore + oddScore + zoneScore + highNums * 0.18 + Math.random() * 0.05; - } else if (strategy === 'safe') { - // 안정형: 최적 합계 엄격하게, 구간 분산 최대화 - const sumScore = -Math.abs(sum - 138) / 22; - const oddScore = odd === 3 ? 0.9 : odd === 2 || odd === 4 ? 0.35 : -0.6; - const zoneScore = zones.size * 0.65; - return sumScore + oddScore + zoneScore + Math.random() * 0.04; - } else { - // 균형형 (기본) - const sumScore = -Math.abs(sum - 138) / 35; - const oddScore = odd >= 2 && odd <= 4 ? 0.5 : -0.5; - const zoneScore = zones.size * 0.4; - return sumScore + oddScore + zoneScore + Math.random() * 0.05; - } -} - -// ─── Types ─────────────────────────────────────────────────────────────────── - -interface LottoMetrics { - sum: number; - odd: number; - even: number; - min: number; - max: number; - range: number; -} - -interface RecommendResponse { - ok: boolean; - plan: string; - numbers: number[]; - metrics?: LottoMetrics; - recent_overlap?: { repeated_numbers: number[] }; -} - -interface BatchResponse { - ok: boolean; - plan: string; - count: number; - items: Array<{ numbers: number[]; metrics?: LottoMetrics }>; -} - -interface NumberStat { - number: number; - frequency_pct: number; - z_score: number; - gap: number; -} - -interface DashboardResponse { - ok: boolean; - plan: string; - latest: { - drawNo: number; - date: string; - numbers: number[]; - bonus: number; - metrics: LottoMetrics; - } | null; - analysis: { - total_draws: number; - mean_sum: number; - number_stats: NumberStat[]; - } | null; - simulation: { - runs: Array<{ - id: number; - run_at: string; - strategy: string; - total_generated: number; - avg_score: number; - }>; - } | null; -} - -interface Combo { - id: number; - numbers: number[]; - metrics?: LottoMetrics; - overlap?: number[]; - createdAt: Date; -} - -type GenMode = 'single' | 'batch'; - -const PLAN_LABELS: Record = { - lotto_gold: '🥇 골드', - lotto_platinum: '💎 플래티넘', - lotto_diamond: '👑 다이아', -}; - -const PLAN_MAX_COMBOS: Record = { - lotto_gold: 1, - lotto_platinum: 3, - lotto_diamond: 999, -}; - -const PLAN_RANK: Record = { lotto_gold: 1, lotto_platinum: 2, lotto_diamond: 3 }; -function planGte(userPlan: string, required: string): boolean { - return (PLAN_RANK[userPlan] ?? 0) >= (PLAN_RANK[required] ?? 1); -} - -const TABS_CONFIG = [ - { key: 'generate' as const, label: '🎲 번호 생성', requiredPlan: null as string | null, planBadge: null as string | null }, - { key: 'report' as const, label: '📋 이번 주 공략', requiredPlan: 'lotto_gold' as string | null, planBadge: '🥇 GOLD+' as string | null }, - { key: 'purchase' as const, label: '💰 구매 기록', requiredPlan: 'lotto_gold' as string | null, planBadge: '🥇 GOLD+' as string | null }, - { key: 'pattern' as const, label: '🔍 내 패턴', requiredPlan: 'lotto_platinum' as string | null, planBadge: '💎 PLATINUM+' as string | null }, -]; - -// ─── Lotto Ball ─────────────────────────────────────────────────────────────── - -function getBallStyle(n: number): { bg: string; shadow: string; text: string } { - if (n <= 10) return { bg: 'linear-gradient(145deg,#fde68a,#fbbf24,#d97706)', shadow: 'rgba(251,191,36,.6)', text: '#78350f' }; - if (n <= 20) return { bg: 'linear-gradient(145deg,#93c5fd,#3b82f6,#1d4ed8)', shadow: 'rgba(59,130,246,.6)', text: '#fff' }; - if (n <= 30) return { bg: 'linear-gradient(145deg,#fca5a5,#ef4444,#b91c1c)', shadow: 'rgba(239,68,68,.6)', text: '#fff' }; - if (n <= 40) return { bg: 'linear-gradient(145deg,#d1d5db,#9ca3af,#4b5563)', shadow: 'rgba(107,114,128,.6)', text: '#fff' }; - return { bg: 'linear-gradient(145deg,#86efac,#22c55e,#15803d)', shadow: 'rgba(34,197,94,.6)', text: '#fff' }; -} - -function LottoBall({ n, size = 52, delay = 0, bounce = false, highlight = false }: { - n: number; size?: number; delay?: number; bounce?: boolean; highlight?: boolean; -}) { - const [show, setShow] = useState(!bounce); - const { bg, shadow, text } = getBallStyle(n); - useEffect(() => { - if (!bounce) return; - const t = setTimeout(() => setShow(true), delay); - return () => clearTimeout(t); - }, [bounce, delay]); - return ( -
-
- {n} -
- ); -} - -function SpinBall({ n, delay = 0 }: { n: number; delay?: number }) { - const { bg, shadow, text } = getBallStyle(n); - return ( -
- {n} -
- ); -} - -// ─── Frequency Bar (z_score 기반) ───────────────────────────────────────────── - -function FreqBar({ number, zScore, gap, isHot }: { number: number; zScore: number; gap: number; isHot: boolean }) { - const { bg, shadow } = getBallStyle(number); - const barWidth = Math.min(100, Math.abs(zScore) * 40 + 20); - const barColor = isHot - ? 'linear-gradient(90deg,rgba(239,68,68,.8),rgba(251,113,133,.6))' - : 'linear-gradient(90deg,rgba(96,165,250,.8),rgba(147,197,253,.6))'; - - return ( -
-
- {number} -
-
-
-
-
-
-
-
- {isHot ? `+${zScore.toFixed(2)}σ` : `${gap}회 미출`} -
-
-
- ); -} - -// ─── Odd/Even Mini Bar ───────────────────────────────────────────────────────── - -function OddEvenBar({ odd, even }: { odd: number; even: number }) { - const total = odd + even; - const oddPct = total > 0 ? (odd / total) * 100 : 50; - return ( -
- 홀{odd} -
-
-
-
- 짝{even} -
- ); -} - -// ─── Animated Counter ───────────────────────────────────────────────────────── - -function AnimCounter({ value, duration = 1200 }: { value: number; duration?: number }) { - const [display, setDisplay] = useState(0); - const raf = useRef(null); - useEffect(() => { - const start = performance.now(); - const animate = (now: number) => { - const progress = Math.min((now - start) / duration, 1); - const eased = 1 - Math.pow(1 - progress, 3); - setDisplay(Math.floor(eased * value)); - if (progress < 1) raf.current = requestAnimationFrame(animate); - }; - raf.current = requestAnimationFrame(animate); - return () => { if (raf.current) cancelAnimationFrame(raf.current); }; - }, [value, duration]); - return <>{display.toLocaleString()}; -} - -// ─── Main Page ──────────────────────────────────────────────────────────────── - -export default function LottoRecommendPage() { - const supabase = createClient(); - - const [isSubscribed, setIsSubscribed] = useState(false); - const [plan, setPlan] = useState(''); - const [dashboard, setDashboard] = useState(null); - const [pageReady, setPageReady] = useState(false); - - const [previewNumbers, setPreviewNumbers] = useState([]); - const [previewMetrics, setPreviewMetrics] = useState(null); - const [previewState, setPreviewState] = useState<'idle' | 'loading' | 'result' | 'error'>('idle'); - const [previewUsed, setPreviewUsed] = useState(false); - const [previewSource, setPreviewSource] = useState<'nas' | 'client'>('client'); - - const [genMode, setGenMode] = useState('single'); - const [strategy, setStrategy] = useState('balanced'); - const [combos, setCombos] = useState([]); - const [proState, setProState] = useState<'idle' | 'loading' | 'result' | 'error'>('idle'); - const [proError, setProError] = useState(''); - const [activeTab, setActiveTab] = useState<'generate' | 'report' | 'purchase' | 'pattern'>('generate'); - const [perfStats, setPerfStats] = useState<{ total_checked: number; avg_correct: number; rate_3plus: number; vs_random: { improvement_pct: number } } | null>(null); - const idRef = useRef(0); - const MAX_COMBOS = PLAN_MAX_COMBOS[plan] ?? 5; - - const SPIN_NUMS = [7, 23, 41, 14, 35, 3]; - - useEffect(() => { - async function init() { - const { data: { user } } = await supabase.auth.getUser(); - if (user) { - try { - const res = await fetch('/api/lotto/dashboard'); - if (res.ok) { - const data: DashboardResponse = await res.json(); - setDashboard(data); - setPlan(data.plan ?? ''); - setIsSubscribed(true); - } - } catch { /* ignore */ } - } - // 성과 통계 (구독 여부 무관 로드 시도) - try { - const perfRes = await fetch('/api/lotto/stats/performance'); - if (perfRes.ok) { const p = await perfRes.json(); setPerfStats(p); } - } catch { /* ignore */ } - setPageReady(true); - } - init(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const handlePreview = async () => { - if (previewState === 'loading') return; - setPreviewState('loading'); - try { - const res = await fetch('/api/lotto/preview'); - if (res.ok) { - const data = await res.json(); - setPreviewNumbers([...data.numbers].sort((a, b) => a - b)); - setPreviewMetrics(data.metrics ?? null); - setPreviewSource('nas'); - } else { - const { numbers, metrics } = clientMonteCarlo(); - setPreviewNumbers(numbers); - setPreviewMetrics(metrics); - setPreviewSource('client'); - } - setPreviewState('result'); - setPreviewUsed(true); - } catch { - try { - const { numbers, metrics } = clientMonteCarlo(); - setPreviewNumbers(numbers); - setPreviewMetrics(metrics); - setPreviewSource('client'); - setPreviewState('result'); - setPreviewUsed(true); - } catch { - setPreviewState('error'); - } - } - }; - - const saveHistory = (numbers: number[], source: 'nas' | 'client') => { - if (!plan) return; - fetch('/api/lotto/history', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ numbers, source, plan_id: plan }), - }).catch(() => { }); - }; - - const handleGenerate = async () => { - if (proState === 'loading' || combos.length >= MAX_COMBOS) return; - setProState('loading'); - setProError(''); - try { - const url = genMode === 'batch' - ? '/api/lotto/recommend?mode=batch' - : '/api/lotto/recommend?mode=single'; - const res = await fetch(url); - if (res.status === 403) { setIsSubscribed(false); setProState('idle'); return; } - - if (res.status === 503) { - const count = genMode === 'batch' ? Math.min(5, MAX_COMBOS - combos.length) : 1; - const newCombos: Combo[] = Array.from({ length: count }, () => { - idRef.current += 1; - const { numbers, metrics } = clientMonteCarlo(strategy); - saveHistory(numbers, 'client'); - return { id: idRef.current, numbers, metrics, createdAt: new Date() }; - }); - setCombos((prev) => [...prev, ...newCombos].slice(-MAX_COMBOS)); - setProState('result'); - return; - } - - if (!res.ok) { const e = await res.json(); throw new Error(e.error ?? 'API_ERROR'); } - - if (genMode === 'batch') { - const data: BatchResponse = await res.json(); - const newCombos: Combo[] = (data.items ?? []).map((item) => { - idRef.current += 1; - const numbers = [...item.numbers].sort((a, b) => a - b); - saveHistory(numbers, 'nas'); - return { id: idRef.current, numbers, metrics: item.metrics, createdAt: new Date() }; - }); - setCombos((prev) => [...prev, ...newCombos].slice(-MAX_COMBOS)); - } else { - const data: RecommendResponse = await res.json(); - if (!data.numbers?.length) throw new Error('EMPTY_RESULT'); - idRef.current += 1; - const numbers = [...data.numbers].sort((a, b) => a - b); - saveHistory(numbers, 'nas'); - setCombos((prev) => [...prev, { - id: idRef.current, - numbers, - metrics: data.metrics, - overlap: data.recent_overlap?.repeated_numbers, - createdAt: new Date(), - }]); - } - setProState('result'); - } catch (err: unknown) { - const e = err as { message?: string }; - setProError(e?.message === 'NAS_TIMEOUT' ? 'NAS 서버 응답 시간 초과.' : '생성 중 오류가 발생했습니다.'); - setProState('error'); - } - }; - - const clearCombos = () => { setCombos([]); setProState('idle'); setProError(''); }; - - // 핫/콜드 (z_score 포함) - const numberStats = dashboard?.analysis?.number_stats ?? []; - const hotStats = numberStats - .filter(s => s.z_score > 0.3) - .sort((a, b) => b.z_score - a.z_score) - .slice(0, 8); - const coldStats = numberStats - .filter(s => s.z_score < -0.3) - .sort((a, b) => b.gap - a.gap) - .slice(0, 8); - const hotNumbers = hotStats.map(s => s.number); - const coldNumbers = coldStats.map(s => s.number); - - const latestRun = dashboard?.simulation?.runs?.[0]; - const totalDraws = dashboard?.analysis?.total_draws; - const isProLoading = proState === 'loading'; - const isMaxed = combos.length >= MAX_COMBOS; - const latestCombo = combos.length > 0 ? combos[combos.length - 1] : null; - - const simTotal = latestRun?.total_generated ?? 100000; - const drawsCount = totalDraws ?? 1130; - - if (!pageReady) { - return ( -
-
-
-
INITIALIZING
-
-
- ); - } - - return ( - <> - - -
- - {/* Ambient orbs */} -
-
-
- -
- - {/* ── Navigation ── */} -
- - - LOTTO SERVICE - - {isSubscribed && plan && ( -
-
- {PLAN_LABELS[plan] ?? plan} -
- )} -
- - {/* ── Header ── */} -
-
-
- MONTE CARLO SIMULATION ENGINE -
-

- 이번 주 로또
- 번호 추천 -

-

- 역대 {drawsCount.toLocaleString()}회 데이터 기반 통계 분석 · 5,000회 Monte Carlo 시뮬레이션으로 최적 조합 도출 -

-
- - {/* ── 성과 배너 ── */} - {perfStats && perfStats.total_checked > 0 && ( -
-
-
- 실제 검증 성과 -
-
-
-
{(perfStats.rate_3plus * 100).toFixed(1)}%
-
3개 이상 일치율
-
-
-
{perfStats.avg_correct.toFixed(1)}개
-
평균 일치 번호
-
-
-
+{perfStats.vs_random.improvement_pct.toFixed(0)}%
-
무작위 대비
-
-
-
{perfStats.total_checked.toLocaleString()}
-
검증 건수
-
-
-
- )} - - {/* ── 탭 네비게이션 ── */} - {isSubscribed && ( -
- {TABS_CONFIG.map(tab => { - const isLocked = tab.requiredPlan !== null && !planGte(plan, tab.requiredPlan); - const isActive = activeTab === tab.key; - return ( - - ); - })} -
- )} - - {/* ── 탭별 컨텐츠: 공략/구매/패턴 ── */} - {isSubscribed && activeTab === 'report' && } - {isSubscribed && activeTab === 'purchase' && } - {isSubscribed && activeTab === 'pattern' && ( - planGte(plan, 'lotto_platinum') ? ( - - ) : ( - // 플래티넘 미만 → 업셀 카드 -
-
- 🔍 -
-
PLATINUM PLAN REQUIRED
-

내 번호 패턴 분석

-

- 내가 자주 선택하는 번호, 기피하는 번호, 구간 분포를 AI가 분석합니다.
- 실제 당첨 패턴과 비교해 최적 전략을 도출해드립니다. -

-
- {['내 번호 빈도 분석', '기피 번호 감지', '구간 분포 히트맵', '당첨 패턴 비교'].map(f => ( - {f} - ))} -
- - 💎 플래티넘으로 업그레이드 → - -

- 현재 플랜: {PLAN_LABELS[plan] ?? plan} · 플래티넘 2,900원/월 -

-
- ) - )} - - {/* ── 기존 메인 콘텐츠 (번호 생성 탭 or 비구독) ── */} -
- - {/* ── 통계 인디케이터 패널 (전체 공개) ── */} -
- {[ - { label: 'SIMULATION', value: simTotal, suffix: '회', color: '#fbbf24', icon: '⚡', desc: '시뮬레이션 횟수' }, - { label: 'DRAWS ANALYZED', value: drawsCount, suffix: '회', color: '#06b6d4', icon: '📊', desc: '분석 회차' }, - { label: 'HOT NUMBERS', value: hotNumbers.length, suffix: '개', color: '#f87171', icon: '🔥', desc: '과출현 번호' }, - { label: 'COLD NUMBERS', value: coldNumbers.length, suffix: '개', color: '#60a5fa', icon: '❄️', desc: '미출현 번호' }, - ].map((s, i) => ( -
-
-
{s.label}
-
- - {s.suffix} -
-
{s.desc}
-
- ))} -
- - {/* ── 이번 주 공략 포인트 (구독자 전용) ── */} - {isSubscribed && dashboard?.analysis && ( -
-
-
- - WEEKLY ATTACK REPORT · 이번 주 공략 포인트 - -
-
- {/* 핫 넘버 공략 */} - {hotStats.length > 0 && ( -
-
- {hotStats.slice(0, 5).map(s => { - const { bg } = getBallStyle(s.number); - return ( -
- {s.number} -
- ); - })} -
-
🔥 과출현 주의
-
- 최근 자주 나온 번호 · 안정형에 유리 -
-
- )} - {/* 콜드 넘버 공략 */} - {coldStats.length > 0 && ( -
-
- {coldStats.slice(0, 5).map(s => { - const { bg } = getBallStyle(s.number); - return ( -
- {s.number} -
- ); - })} -
-
❄️ 냉각 구간 주목
-
- 평균 {Math.round(coldStats.reduce((a, s) => a + s.gap, 0) / coldStats.length)}회 미출현 · 고위험형에 유리 -
-
- )} - {/* AI 신뢰도 */} - {dashboard.latest && ( -
-
-
- AI CONFIDENCE - - {Math.min(99, 60 + hotStats.length * 2 + coldStats.length)}% - -
-
-
-
-
-
⚡ 분석 신뢰도
-
- {(dashboard.analysis.total_draws ?? 1130).toLocaleString()}회 + {(hotStats.length + coldStats.length)}개 패턴 기반 -
-
- )} -
-
- )} - - {/* ── 최신 당첨번호 ── */} - {isSubscribed && dashboard?.latest && ( -
-
-
LATEST DRAW
-
-
제{dashboard.latest.drawNo}회 · {dashboard.latest.date}
-
-
-
- {dashboard.latest.numbers.map((n, i) => )} -
-
- -
-
-
- {[ - { l: '합계', v: dashboard.latest.metrics.sum }, - { l: '홀수', v: `${dashboard.latest.metrics.odd}개` }, - { l: '짝수', v: `${dashboard.latest.metrics.even}개` }, - { l: '범위', v: dashboard.latest.metrics.range }, - ].map(s => ( -
-
{s.v}
-
{s.l}
-
- ))} -
-
-
- )} - - {/* ════════════════════════════════════════════════ - 무료 맛보기 섹션 - ════════════════════════════════════════════════ */} -
-
-
-
- FREE PREVIEW -
- 1회 무료 번호 추천 · Monte Carlo 5,000회 시뮬레이션 -
- -
- {/* Decorative grid lines */} -
- -
- {/* 번호 표시 */} -
- {previewState === 'loading' ? ( - SPIN_NUMS.slice(0, 6).map((n, i) => ) - ) : previewState === 'result' && previewNumbers.length > 0 ? ( - previewNumbers.map((n, i) => ) - ) : ( - Array.from({ length: 6 }, (_, i) => ( -
?
- )) - )} -
- - {/* 맛보기 메트릭 */} - {previewState === 'result' && previewMetrics && ( -
-
- {[ - { l: 'SUM', v: previewMetrics.sum, good: previewMetrics.sum >= 100 && previewMetrics.sum <= 175 }, - { l: 'ODD', v: `${previewMetrics.odd}` }, - { l: 'EVEN', v: `${previewMetrics.even}` }, - { l: 'RANGE', v: previewMetrics.range }, - ].map(s => ( -
-
{s.v}
-
{s.l}
-
- ))} -
- {/* 홀짝 바 */} -
- -
-
-
-
- - {previewSource === 'nas' ? 'NAS · Monte Carlo' : 'Client · 5,000 iterations'} - -
-
-
- )} - - {previewState === 'error' && ( -

- ⚠️ 번호 생성 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요. -

- )} - - {/* 버튼 */} -
- {!previewUsed ? ( - - ) : ( -
- ✓ 오늘의 무료 번호 생성 완료 -
- )} - {previewUsed && !isSubscribed && ( -

- 더 많은 번호와 통계 분석이 필요하다면 아래 구독 플랜을 ↓ -

- )} -
-
-
-
- - {/* ════════════════════════════════════════════════ - 구독자 전용 섹션 (블러 게이트) - ════════════════════════════════════════════════ */} -
-
-
- - PREMIUM -
- - {isSubscribed ? '프리미엄 번호 추천 · 통계 분석' : '구독 시 제공되는 기능 미리보기'} - -
- - {/* 프리미엄 컨텐츠 */} -
- - {/* ── 번호 생성 메인 카드 ── */} -
- {/* 배경 그리드 */} -
-
- -
- {/* 분석 지표 배너 */} -
- {[ - { icon: '⚡', val: latestRun ? `${(latestRun.total_generated / 10000).toFixed(0)}만 회` : '10만 회', label: '시뮬레이션', sub: 'Monte Carlo Runs', color: '#fbbf24' }, - { icon: '📊', val: totalDraws ? `${totalDraws.toLocaleString()}회` : '1,130+', label: '분석 회차', sub: 'Historical Draws', color: '#06b6d4' }, - { icon: '🎯', val: `${combos.length} / ${MAX_COMBOS}`, label: '생성 조합', sub: 'Generated Combos', color: '#a78bfa' }, - ].map(s => ( -
-
-
{s.icon}
-
{s.val}
-
{s.label}
-
{s.sub}
-
- ))} -
- - {/* 전략 선택 */} -
-
- GENERATION STRATEGY -
-
- {(Object.entries(STRATEGY_INFO) as [Strategy, typeof STRATEGY_INFO[Strategy]][]).map(([key, info]) => ( - - ))} -
-
- - {/* 모드 탭 */} -
-
- {(['single', 'batch'] as const).map(mode => ( - - ))} -
-
- - {/* 볼 디스플레이 */} -
- {isProLoading ? ( - SPIN_NUMS.map((n, i) => ) - ) : latestCombo ? ( - latestCombo.numbers.map((n, i) => ) - ) : ( - Array.from({ length: 6 }, (_, i) => ( -
?
- )) - )} -
- - {/* 메트릭 + 홀짝 바 */} - {latestCombo?.metrics && !isProLoading && ( -
-
- {[ - { l: 'SUM', v: latestCombo.metrics.sum, good: latestCombo.metrics.sum >= 100 && latestCombo.metrics.sum <= 175 }, - { l: 'ODD', v: latestCombo.metrics.odd }, - { l: 'EVEN', v: latestCombo.metrics.even }, - { l: 'RANGE', v: latestCombo.metrics.range }, - ].map(s => ( -
-
{s.v}
-
{s.l}
-
- ))} -
-
- -
- {/* 추천 이유 */} -
-
- - {latestCombo.metrics.sum >= 100 && latestCombo.metrics.sum <= 175 - ? '✓ 최적 합계 범위 (100~175)' - : '합계 기준 ±35 이내' - } · 홀짝 {latestCombo.metrics.odd}:{latestCombo.metrics.even} 균형 - -
-
-
- )} - - {isProLoading && ( -
-
- {genMode === 'batch' ? `${Math.min(5, MAX_COMBOS - combos.length)}개 번호 조합 배치 생성 중...` : '몬테카를로 시뮬레이션으로 최적 번호 계산 중...'} -
-
- {[0, 1, 2].map(i => ( -
- ))} -
-
- )} - - {proState === 'error' && ( -

⚠️ {proError}

- )} - - {/* 생성 버튼 */} -
- - {isMaxed && ( -
- -
- )} -
-
-
- - {/* ── 생성된 조합 목록 ── */} - {combos.length > 0 && ( -
-
-
-
GENERATED COMBOS
-
- {combos.length} -
-
- {combos.length > 1 && } -
-
- {combos.map((c, idx) => { - const isLatest = idx === combos.length - 1; - return ( -
-
-
-
{idx + 1}
-
- {c.numbers.map((n, ni) => )} -
-
-
- {c.metrics && ( - <> - ∑{c.metrics.sum} · {c.metrics.odd}홀{c.metrics.even}짝 -
- -
- - )} -
{c.createdAt.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
-
-
-
- ); - })} -
-
- )} - - {/* ── 포트폴리오 요약 (2개 이상 생성 시) ── */} - {combos.length >= 2 && (() => { - const withMetrics = combos.filter(c => c.metrics); - if (withMetrics.length === 0) return null; - const avgSum = Math.round(withMetrics.reduce((a, c) => a + c.metrics!.sum, 0) / withMetrics.length); - const avgOdd = (withMetrics.reduce((a, c) => a + c.metrics!.odd, 0) / withMetrics.length).toFixed(1); - const allNums = combos.flatMap(c => c.numbers); - const zoneCount = [0,0,0,0,0]; - allNums.forEach(n => { zoneCount[Math.min(Math.floor((n-1)/10),4)]++; }); - const totalNums = allNums.length; - return ( -
-
-
- - PORTFOLIO ANALYSIS · {combos.length}세트 종합 - -
-
- {[ - { l: '평균 합계', v: avgSum, sub: `목표 138`, color: '#fbbf24' }, - { l: '평균 홀수', v: `${avgOdd}개`, sub: '권장 3개', color: '#f87171' }, - { l: '총 투자', v: `₩${(combos.length * 1000).toLocaleString()}`, sub: '회당 1,000원', color: '#4ade80' }, - ].map(s => ( -
-
{s.v}
-
{s.l}
-
{s.sub}
-
- ))} -
- {/* 구간 분포 */} -
-
ZONE DISTRIBUTION
-
- {zoneCount.map((cnt, i) => { - const pct = totalNums > 0 ? (cnt / totalNums) * 100 : 0; - const zoneColors = ['#fbbf24','#3b82f6','#ef4444','#9ca3af','#22c55e']; - const zoneLabels = ['1-10','11-20','21-30','31-40','41-45']; - return ( -
-
-
{zoneLabels[i]}
-
- ); - })} -
-
-
- ); - })()} - - {/* ── Hot / Cold Numbers (인포그래픽 바) ── */} - {(hotStats.length > 0 || coldStats.length > 0) && ( -
- {hotStats.length > 0 && ( -
-
-
- HOT NUMBERS · 과출현 -
-
- {hotStats.map((s, i) => ( -
- -
- ))} -
-
- )} - {coldStats.length > 0 && ( -
-
-
- COLD NUMBERS · 미출현 -
-
- {coldStats.map((s, i) => ( -
- -
- ))} -
-
- )} -
- )} - - {/* ── 시뮬레이션 정보 바 ── */} - {latestRun && ( -
-
-
- LATEST SIM · {latestRun.strategy.toUpperCase()} -
- {[ - { l: 'Generated', v: `${latestRun.total_generated.toLocaleString()}` }, - { l: 'Avg Score', v: latestRun.avg_score.toFixed(4) }, - { l: 'Run At', v: new Date(latestRun.run_at).toLocaleString('ko-KR', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) }, - ].map(s => ( -
- {s.l}: - {s.v} -
- ))} -
- )} -
- -
- - {/* ── 비구독자 업셀 블록 ── */} - {!isSubscribed && ( -
- {/* 구분선 */} -
-
- 구독하면 이런 것들도 -
-
- - {/* 플랜별 기능 미리보기 카드 3개 */} -
- {[ - { - plan: '🥇 골드 플랜', - price: '900원/월', - color: '#fbbf24', - colorAlpha: 'rgba(251,191,36,.12)', - borderColor: 'rgba(251,191,36,.25)', - features: ['번호 1세트/일 생성', '이번 주 공략 리포트', '핫/콜드 번호 분석', '구매 기록 관리'], - cta: '골드 시작하기', - recommended: false, - }, - { - plan: '💎 플래티넘 플랜', - price: '2,900원/월', - color: '#a78bfa', - colorAlpha: 'rgba(167,139,250,.12)', - borderColor: 'rgba(167,139,250,.3)', - features: ['번호 3세트/일 생성', '전략별 생성 (균형/고위험/안정)', '내 패턴 AI 분석', '상세 통계 대시보드'], - cta: '플래티넘 시작하기', - recommended: true, - }, - { - plan: '👑 다이아 플랜', - price: '9,900원/월', - color: '#38bdf8', - colorAlpha: 'rgba(56,189,248,.1)', - borderColor: 'rgba(56,189,248,.25)', - features: ['번호 무제한 생성', '배치 생성 (5개 동시)', '연간 당첨 패턴 리포트', '우선 고객 지원'], - cta: '다이아 시작하기', - recommended: false, - }, - ].map(p => ( -
- {p.recommended && ( -
추천
- )} -
{p.plan}
-
{p.price}
-
    - {p.features.map(f => ( -
  • -
    - {f} -
  • - ))} -
- - {p.cta} → - -
- ))} -
- - -
- )} - - {/* ── 소셜 증거 패널 ── */} -
- COMMUNITY - {[ - { icon: '👥', val: '2,847', label: '이번 주 번호 생성' }, - { icon: '🎯', val: '3,241', label: '3개 일치 달성 누적' }, - { icon: '📊', val: `${hotNumbers.length + coldNumbers.length}개`, label: '이번 회차 패턴 감지' }, - ].map(s => ( -
- {s.icon} -
-
{s.val}
-
{s.label}
-
-
- ))} -
- - {/* ── Color Legend ── */} -
- BALL COLOR - {[{ r: '1–10', c: '#fbbf24' }, { r: '11–20', c: '#3b82f6' }, { r: '21–30', c: '#ef4444' }, { r: '31–40', c: '#9ca3af' }, { r: '41–45', c: '#22c55e' }].map(item => ( -
-
- {item.r} -
- ))} -
- -

- 본 서비스는 몬테카를로 시뮬레이션 기반 통계 분석으로, 당첨을 보장하지 않습니다. -

-
{/* ── 기존 메인 콘텐츠 래퍼 닫기 ── */} - -
-
- - ); -} diff --git a/app/services/music/layout.tsx b/app/services/music/layout.tsx new file mode 100644 index 0000000..415e1bb --- /dev/null +++ b/app/services/music/layout.tsx @@ -0,0 +1,28 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'AI 음악 마스터 구조 팩 | Suno · MV · 유튜브 쇼츠', + description: + '7년차 개발자가 설계한 4단계 AI 음악 제작 공정. Suno 프롬프트 조합법 + MV 비디오 생성 워크플로우 + 저작권 가이드 + 템플릿 PDF + 샘플 프로젝트. 입문 ₩39k / 프로 ₩99k / 마스터 ₩149k.', + keywords: [ + 'AI 음악 만들기', + 'Suno 프롬프트', + 'AI 뮤직비디오', + 'AI 커버곡', + '유튜브 쇼츠 음악', + 'AI 작곡', + '크리에이터 이코노미', + 'Lyria 프롬프트', + 'Runway AI 비디오', + ], + openGraph: { + title: 'AI 음악 마스터 구조 팩 | 쟁승메이드', + description: + '네 사연을 노래로. 쇼츠까지 한 번에. 4단계 AI 음악 공정 · Suno Pro 검증 · 평생 업데이트.', + url: 'https://jaengseung-made.com/services/music', + }, +}; + +export default function MusicLayout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/app/services/music/page.tsx b/app/services/music/page.tsx new file mode 100644 index 0000000..ac598a8 --- /dev/null +++ b/app/services/music/page.tsx @@ -0,0 +1,483 @@ +'use client'; + +import { useState } from 'react'; +import Link from 'next/link'; +import PurchaseAgreementModal from '../../components/PurchaseAgreementModal'; + +type Tier = 'starter' | 'pro' | 'master'; + +const TIERS: Record = { + starter: { + name: '입문', + price: '₩39,000', + priceNum: '39,000', + desc: '첫 AI 음악을 위한 필수 구성', + features: [ + 'Suno 프롬프트 조합법 20종', + '기본 가사 최적화 템플릿', + '구조 템플릿 PDF 40p', + '저작권 가이드 기본판', + '12개월 무료 업데이트', + ], + }, + pro: { + name: '프로', + price: '₩99,000', + priceNum: '99,000', + desc: '쇼츠 업로드까지 완성하는 풀세트', + highlight: true, + features: [ + '입문 전체 포함', + '고급 편집법 (Stems 분리 · 마스터링 프롬프트)', + 'MV 비디오 생성 워크플로우 (Runway/Luma/Pika)', + '샘플 프로젝트 1개 (.prj · 영상 포함)', + '이메일/페이지 1:1 Q&A 1회 (30일 이내)', + '유튜브 SEO 템플릿', + ], + }, + master: { + name: '마스터', + price: '₩149,000', + priceNum: '149,000', + desc: '여러 장르·포맷을 커버하는 마스터피스', + features: [ + '프로 전체 포함', + '샘플 프로젝트 다수 (장르별 3종)', + '우선 업데이트 · 베타 기능 선공개', + '저작권 가이드 심화판 + 상업 이용 체크리스트', + '제작 레시피 영상 가이드', + ], + }, +}; + +const PROCESS = [ + { + num: '01', + title: '크리에이티브 디렉팅', + subtitle: 'Concept & Lyrics', + customer: '원하는 키워드 3개 또는 사연 제공', + value: 'ChatGPT·Claude로 Suno가 이해하는 가사·스타일 태그로 변환', + result: 'AI 최적화 가사 · 메타데이터 시트', + color: 'from-violet-500 to-fuchsia-500', + }, + { + num: '02', + title: '오디오 엔지니어링', + subtitle: 'Music Generation', + customer: '결과물 확인 · 방향 피드백', + value: 'Suno Custom Mode로 가사 배치·파트·보컬·악기 세밀 조정. 가장 높은 퀄리티가 나올 때까지 프롬프트 깎기', + result: '고품질 완곡 (Full Track, 스템 분리본)', + color: 'from-fuchsia-500 to-pink-500', + }, + { + num: '03', + title: '비주얼 마스터링', + subtitle: 'AI MV Generation', + customer: '-', + value: 'Midjourney · Runway · Luma로 음악 분위기에 맞는 이미지·영상 생성. 비트와 가사에 맞춘 싱크 설계', + result: '쇼츠(9:16) 또는 유튜브(16:9) 고화질 영상', + color: 'from-sky-500 to-cyan-500', + }, + { + num: '04', + title: '퍼블리싱 가이드', + subtitle: 'Viral Optimization', + customer: '유튜브 업로드', + value: '제목·해시태그·설명란(SEO) AI 최적화 템플릿 제공', + result: '즉시 업로드 가능한 유튜브 배포 패키지', + color: 'from-cyan-500 to-emerald-500', + }, +]; + +const FAQS = [ + { + q: 'Suno 유료 플랜 가입이 꼭 필요한가요?', + a: 'Suno 무료 플랜은 상업적 이용이 제한됩니다. 본인 결과물을 유튜브·SNS에 업로드해 수익화하려면 Suno Pro 이상 권장. 팩 구매 후 가입 전 플랜 선택 가이드가 포함됩니다.', + }, + { + q: '제가 만든 결과물의 상업 이용·저작권은?', + a: '결과물의 상업권은 고객이 가입한 AI 서비스의 이용약관을 따릅니다. 팩에는 Suno·Runway·Luma 각 서비스의 최신 약관 요약과 상업 이용 체크리스트가 포함되어 있습니다. (법률 자문이 아닌 참고용 가이드입니다.)', + }, + { + q: '결과물 품질을 보장하나요?', + a: 'AI 생성물은 모델 버전·프롬프트 입력에 따라 달라지므로 결과물 자체를 보장하지 않습니다. 다만 팩은 동일 프롬프트로 반복 가능한 고품질 구간을 설계하는 방법을 제공합니다. 샘플 쇼츠·프로젝트로 품질 기대치를 사전 확인하세요.', + }, + { + q: '환불이 가능한가요?', + a: '전자상거래법 제17조 제2항 제5호에 따라 디지털 콘텐츠는 제공 시작 후 청약철회가 제한됩니다. 무료 샘플로 사전 확인을 제공하므로 충분히 검토 후 구매해주세요. 파일 손상·전달 불량 등 회사 귀책은 즉시 재전달 또는 환불됩니다.', + }, + { + q: '업데이트는 어떻게 받나요?', + a: '구매자 전용 Notion 페이지에서 변경 이력과 최신 파일을 제공. 12개월간 무료 업데이트가 기본, 마스터는 우선 업데이트·베타 선공개가 포함됩니다.', + }, +]; + +export default function MusicServicePage() { + const [selectedTier, setSelectedTier] = useState(null); + const [openFaq, setOpenFaq] = useState(0); + + return ( +
+ {/* HERO */} +
+
\")", + }} + /> + +
+
+ + + AI Music Pack · v1 + +
+ +

+ 네 사연을 노래로. +
+ + 쇼츠까지 한 번에. + +

+ +

+ AI로 음악을 뽑는 게 아니라, 고품질 결과물을 빠르게 뽑는 법을 팝니다. +

+

+ 7년차 개발자가 설계한 4단계 AI 음악 공정 · Suno Pro 검증. +

+ + + +
+ ✅ 평생 업데이트 + ✅ 즉시 다운로드 + ✅ Suno Pro 검증 샘플 +
+
+ + {/* Bottom waveform */} +
+ + + + + + + + + + +
+
+ + {/* BEFORE / AFTER */} +
+
+

+ Before vs After +

+

+ AI 음악, 왜 다들 어렵다고 할까요? +

+
+
+
😵
+

Before · 대충 뽑은 결과

+
    +
  • • Suno 10번 돌렸는데 다 별로…
  • +
  • • 가사가 이상하게 붙음
  • +
  • • 영상 만들려니 뭐부터 할지 모름
  • +
  • • 유튜브 올려도 조회수 0
  • +
+
+
+
🎯
+

After · 구조를 쓴 결과

+
    +
  • • 프롬프트 1번으로 원하는 무드 적중
  • +
  • • 30분 만에 쇼츠까지 완성
  • +
  • • 저작권·상업 이용 안전 체크
  • +
  • • SEO 템플릿으로 노출 최적화
  • +
+
+
+
+
+ + {/* PROCESS — 4 STEPS */} +
+
+

+ Process Architecture +

+

+ 컨셉 → 음악 → 비주얼 → 퍼블리싱, +

+

한 번에 이어지는 4단계 공정 설계도.

+ +
+ {PROCESS.map((step) => ( +
+
+
+
+ {step.num} +
+
+
+

+ {step.subtitle} +

+

+ {step.title} +

+
+
+
고객 역할
+
{step.customer}
+
+
+
나의 가치
+
{step.value}
+
+
+
결과물
+
{step.result}
+
+
+
+
+
+ ))} +
+
+
+ + {/* SAMPLES */} +
+
+

+ Sample Showcase +

+

+ 이 쇼츠, 이 팩으로 만들었어요. +

+

30분 만에 나온 결과물을 직접 들어보세요.

+ +
+ {[1, 2, 3].map((i) => ( +
+
+
+
🎬
+

Sample {i}

+

준비 중

+
+
+ ))} +
+

+ 유튜브 쇼츠 임베드로 교체 예정. 샘플 오디오는 구매 전 전체 듣기가 가능하도록 제공됩니다. +

+
+
+ + {/* PRICING */} +
+
+

+ Pricing +

+

+ 3개 티어, 내 목표에 맞게. +

+

한 번 결제로 평생 업데이트.

+ +
+ {(Object.keys(TIERS) as Tier[]).map((key) => { + const t = TIERS[key]; + return ( +
+ {t.highlight && ( +
+ + 🔥 80%가 선택 + +
+ )} +

{t.name}

+

{t.desc}

+
+ {t.price} + 1회 결제 +
+
    + {t.features.map((f) => ( +
  • + + + + {f} +
  • + ))} +
+ +
+ ); + })} +
+

+ 구매 전 환불 정책을 반드시 확인해주세요. + 디지털 콘텐츠 특성상 제공 시작 후 청약철회가 제한됩니다. +

+
+
+ + {/* B2B */} +
+
+
+
+

+ For Business +

+

+ 📣 내 가게 전용 BGM, 저작권 걱정 없이. +

+

+ 카페 · 쇼핑몰 · 인스타 릴스 · 틱톡 셀러용 전용 BGM 제작. + 1분 만에 브랜드에 맞는 음악을 뽑아드립니다. +

+
+ + B2B 문의 → + +
+
+
+ + {/* FAQ */} +
+
+

+ 자주 묻는 질문 +

+
+ {FAQS.map((f, i) => ( +
+ + {openFaq === i && ( +
+ {f.a} +
+ )} +
+ ))} +
+
+
+ + {/* FINAL CTA */} +
+
+

+ 오늘 밤, +
+ + 첫 쇼츠를 업로드하세요. + +

+

+ ₩39,000부터 · 평생 업데이트 · 즉시 다운로드 +

+ + 팩 선택하기 → + +
+
+ + {selectedTier && ( + setSelectedTier(null)} + productName={`AI 음악 마스터 팩 · ${TIERS[selectedTier].name}`} + price={TIERS[selectedTier].price} + /> + )} +
+ ); +} diff --git a/app/services/prompt/layout.tsx b/app/services/prompt/layout.tsx deleted file mode 100644 index 08c1c68..0000000 --- a/app/services/prompt/layout.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { Metadata } from 'next'; - -export const metadata: Metadata = { - title: 'ChatGPT·Claude 프롬프트 엔지니어링 | 업무 AI 자동화', - description: - 'ChatGPT, Claude, Gemini를 제대로 활용하는 맞춤형 프롬프트 설계. 이메일·보고서·코드리뷰·고객응대 업무를 AI로 3~5배 빠르게. 이미지 생성 프롬프트 패키지 12,900원, 자소서 첨삭 프롬프트 9,900원.', - keywords: [ - '프롬프트 엔지니어링', - 'ChatGPT 프롬프트 만들기', - 'Claude 프롬프트 최적화', - 'AI 업무 자동화 프롬프트', - 'ChatGPT 활용법', - '이미지 생성 프롬프트', - 'Midjourney 프롬프트', - '자소서 AI 첨삭', - '이력서 AI 교정', - '프롬프트 패키지', - 'AI 프롬프트 구매', - ], - openGraph: { - title: 'ChatGPT·Claude 프롬프트 엔지니어링 | 쟁승메이드', - description: - '업무 특화 AI 프롬프트 설계. 이미지 생성·자소서 첨삭 패키지 즉시 구매 가능. 9,900원~.', - url: 'https://jaengseung-made.com/services/prompt', - }, -}; - -export default function PromptLayout({ children }: { children: React.ReactNode }) { - return children; -} diff --git a/app/services/prompt/page.tsx b/app/services/prompt/page.tsx deleted file mode 100644 index 4e8efe9..0000000 --- a/app/services/prompt/page.tsx +++ /dev/null @@ -1,656 +0,0 @@ -'use client'; - -import { useState, useEffect, useRef } from 'react'; -import Link from 'next/link'; -import ContactModal from '../../components/ContactModal'; -import PaymentButton from '../../components/PaymentButton'; -import { trackCTAClick } from '../../../lib/gtag'; -const KAKAO_CHANNEL_URL = process.env.NEXT_PUBLIC_KAKAO_CHANNEL_URL ?? null; - -function useScrollReveal() { - const ref = useRef(null); - useEffect(() => { - const el = ref.current; - if (!el) return; - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - entry.target.classList.add('is-visible'); - observer.unobserve(entry.target); - } - }); - }, - { threshold: 0.1, rootMargin: '0px 0px -40px 0px' } - ); - el.querySelectorAll('.reveal').forEach((child) => observer.observe(child)); - return () => observer.disconnect(); - }, []); - return ref; -} - -const CHECKLIST = [ - '주로 어떤 AI 도구를 사용하는지 (ChatGPT / Claude / Gemini)', - '자동화하고 싶은 업무 유형 (이메일 / 보고서 / 코드 등)', - '현재 프롬프트 사용 방식 및 불만족스러운 점', - '필요한 프롬프트 수량 (단건 / 패키지 / 팀 전체)', - '납품 후 사용 가이드 및 1:1 교육 포함 여부 확인', -]; - -// ─── 프리미엄 상품 ─── -const premiumProducts = [ - { - id: 'image_gen', - badge: 'IMAGE GENERATION', - badgeColor: '#e879f9', - bgFrom: '#1a0533', - bgTo: '#2d1054', - accentColor: '#d946ef', - accentBg: 'rgba(217,70,239,0.12)', - accentBorder: 'rgba(217,70,239,0.3)', - title: 'AI 이미지 생성 마스터 프롬프트 패키지', - subtitle: 'Midjourney · DALL-E 3 · Stable Diffusion 전용', - price: '45,000원', - salePrice: '12,900원', - discountRate: '72% OFF', - saleLabel: '런칭 기념 특가', - priceNote: '/ 패키지 (즉시 다운로드)', - desc: '수천 장의 이미지 생성 실험을 통해 검증된, 업종별·스타일별 고품질 프롬프트 50종 세트. 단순 키워드 나열이 아닌 구도·조명·분위기·카메라·후처리까지 세밀하게 설계된 전문가급 프롬프트입니다.', - features: [ - { label: '50종 프롬프트 라이브러리', desc: '상업용 · 마케팅 · SNS 콘텐츠 · 제품 사진 · 인물 포트레이트 · 배경 · 로고 컨셉 · 인테리어 등 카테고리별 구성' }, - { label: '구도·조명·후처리 공식', desc: '주제(Subject) → 환경(Environment) → 스타일(Style) → 조명(Lighting) → 카메라(Camera) → 후처리(Post) 6단계 구조 적용' }, - { label: '네거티브 프롬프트 포함', desc: '흐림·왜곡·불필요한 텍스트·비현실적 요소를 제거하는 부정 키워드까지 최적화하여 실패 확률 최소화' }, - { label: 'Midjourney 파라미터 완전 가이드', desc: '--ar / --v / --style / --chaos / --no 등 핵심 파라미터 활용법과 상황별 추천값 포함' }, - { label: '한국어 → 영어 변환 치트시트', desc: '자주 쓰는 한국어 표현을 AI가 잘 이해하는 영어 표현으로 매핑한 빠른 참고 시트' }, - { label: '업종별 특화 세트 5개', desc: '카페/음식 · 패션 · 부동산 인테리어 · 교육/강의 · 뷰티/헬스 분야 특화 프롬프트 세트별 제공' }, - { label: '활용 예시 이미지 50장', desc: '각 프롬프트로 실제 생성한 결과물 예시 이미지 포함 (PDF 가이드북 형태 제공)' }, - { label: '무제한 재사용 가능', desc: '구매 후 본인 사업·작업에 무제한 활용 가능. 상업적 이용 허용' }, - ], - promptPreview: { - title: '프롬프트 예시 — 프리미엄 카페 음료 사진', - content: `A professional product photograph of a single iced caramel latte in a tall clear glass, -placed on a rustic wooden cafe table. The drink features layers of espresso, milk, -and golden caramel syrup with ice cubes. - -Lighting: soft natural window light from the left, warm golden hour tone, -subtle rim lighting highlighting condensation droplets on the glass. - -Camera: Canon EOS R5, 85mm f/1.8 lens, shallow depth of field, -foreground blur with coffee beans and a sprig of dried lavender. - -Style: editorial food photography, Pinterest aesthetic, warm muted tones, -slightly desaturated with lifted shadows. - -Post-processing: film grain texture, subtle vignette, -color grade with warm highlights and cool shadows. - ---ar 4:5 --v 6.1 --style raw --q 2 - -Negative: text, watermark, multiple cups, cartoon, illustration, -overexposed, blurry, plastic look, artificial lighting`, - }, - cta: '패키지 구매 문의 →', - productId: 'prompt_image_gen', - }, - { - id: 'resume', - badge: 'CAREER COACHING', - badgeColor: '#34d399', - bgFrom: '#052e16', - bgTo: '#064e3b', - accentColor: '#10b981', - accentBg: 'rgba(16,185,129,0.12)', - accentBorder: 'rgba(16,185,129,0.3)', - title: 'AI 자소서·이력서 첨삭 마스터 프롬프트', - subtitle: 'ChatGPT · Claude 전용 · 대기업 HR 기준 적용', - price: '35,000원', - salePrice: '9,900원', - discountRate: '72% OFF', - saleLabel: '런칭 기념 특가', - priceNote: '/ 패키지 (즉시 다운로드)', - desc: '대기업 현직 개발자의 실전 경험과 수십 명의 신입/경력 지원자 첨삭 경험을 바탕으로 설계한 자소서·이력서 최적화 프롬프트 세트. 합격률을 높이는 구체적인 표현과 구조로 AI가 전문 컨설턴트처럼 첨삭하도록 만들어드립니다.', - features: [ - { label: '자기소개서 7가지 유형별 프롬프트', desc: '지원동기 · 성장과정 · 강점/약점 · 직무역량 · 팀워크 경험 · 위기극복 · 입사 후 포부 — 각 항목에 최적화된 별도 프롬프트 제공' }, - { label: 'STAR 기법 자동 적용', desc: '상황(Situation) → 과제(Task) → 행동(Action) → 결과(Result) 구조로 경험을 임팩트 있게 재구성하는 프롬프트 포함' }, - { label: '이력서 불릿포인트 최적화', desc: '단순 업무 나열이 아닌 "무엇을 했고, 어떤 방법으로, 어떤 결과를 냈는지" 3단 구조 + 수치화로 강점을 극대화하는 첨삭 프롬프트' }, - { label: 'ATS 키워드 최적화', desc: '채용 공고의 키워드를 분석하여 자소서에 자연스럽게 녹여내는 ATS(지원자 추적 시스템) 통과 최적화 프롬프트' }, - { label: '업종/직무별 맞춤 톤 설정', desc: 'IT · 금융 · 제조 · 마케팅 · 공공기관 등 업종별, 신입/경력별 적합한 문체와 표현 스타일로 자동 조정' }, - { label: '약점을 강점으로 전환하는 프롬프트', desc: '공백기 · 전공 불일치 · 낮은 학점 · 짧은 재직기간 등 불리한 스펙을 긍정적으로 표현하는 전략적 첨삭 프롬프트' }, - { label: '면접 질문 예측 & 답변 준비', desc: '작성된 자소서를 기반으로 예상 면접 질문을 생성하고, 모범 답변 구조를 잡아주는 면접 대비 프롬프트 포함' }, - { label: '실제 첨삭 Before/After 5사례', desc: '실제로 사용하여 개선된 자소서 전후 비교 예시 5가지를 PDF로 제공 (직무별 다양한 케이스)' }, - ], - promptPreview: { - title: '프롬프트 예시 — 지원동기 항목 첨삭', - content: `당신은 15년 경력의 대기업 HR 수석 컨설턴트입니다. -수백 명의 합격 자소서를 분석한 전문가 관점에서 다음 자소서를 첨삭해주세요. - -[첨삭 기준] -1. STAR 기법(상황-과제-행동-결과) 구조가 명확한가? -2. 지원 동기가 회사의 사업 방향/가치와 구체적으로 연결되는가? -3. "열정", "성장" 등 추상적 단어 대신 구체적 경험과 수치가 있는가? -4. 첫 문장이 면접관의 시선을 끄는 Hook으로 시작하는가? -5. 지원 직무에서 필요한 역량이 자연스럽게 드러나는가? - -[첨삭 방식] -① 현재 자소서의 강점 2가지 (구체적 근거 포함) -② 치명적 약점 3가지와 개선 방향 -③ 표현이 약한 문장 3개를 지목하여 강화 버전으로 재작성 -④ 전체 구조 리뉴얼 버전 (300자 이내로 압축한 임팩트 버전) -⑤ 이 자소서로 예상되는 면접 질문 2가지 - -[지원 정보] -- 회사/직무: [입력] -- 채용 공고 키워드: [입력] - -[첨삭할 자소서] -[여기에 자소서 붙여넣기]`, - }, - cta: '패키지 구매 문의 →', - productId: 'prompt_resume', - }, - { - id: 'email', - badge: 'BUSINESS EMAIL', - badgeColor: '#60a5fa', - bgFrom: '#0c1a3a', - bgTo: '#1e3a6e', - accentColor: '#3b82f6', - accentBg: 'rgba(59,130,246,0.12)', - accentBorder: 'rgba(59,130,246,0.3)', - title: '비즈니스 이메일 마스터 프롬프트 패키지', - subtitle: 'ChatGPT · Claude 전용 · 상황별 40종 템플릿', - price: '32,000원', - salePrice: '10,900원', - discountRate: '66% OFF', - saleLabel: '런칭 기념 특가', - priceNote: '/ 패키지 (즉시 다운로드)', - desc: '신규 파트너 제안부터 거절 메일, 클레임 응대, 계약 협상까지 — 실무에서 반복 사용하는 비즈니스 이메일 상황 40가지를 AI가 전문 비서처럼 작성하도록 설계한 완성형 프롬프트 패키지입니다.', - features: [ - { label: '40가지 상황별 이메일 프롬프트', desc: '제안·문의·거절·사과·팔로업·계약·협상·내부 보고 등 비즈니스 전 상황 커버' }, - { label: '상대방 직급별 톤 자동 조정', desc: '대표·임원·실무자·외부 파트너·고객별로 적합한 경어와 표현 강도를 자동으로 조정하는 프롬프트' }, - { label: '영어 이메일 동시 출력', desc: '한국어 초안 작성 후 비즈니스 영어 버전으로 즉시 변환하는 이중 출력 프롬프트 포함' }, - { label: '클레임·컴플레인 전문 대응 세트', desc: '감정적 고객·거래처 이메일을 분석하고 상황별 최적 응대 메일을 생성하는 CS 전문 프롬프트 7종' }, - { label: '이메일 후속 조치 자동화', desc: '1차 연락 후 답장 없을 때의 팔로업, 회신 독촉, 미팅 확정 등 타임라인별 후속 이메일 시리즈 프롬프트' }, - { label: '제목 라인 A/B 테스트 생성', desc: '오픈율을 높이는 이메일 제목 5가지 변형을 자동 생성하는 헤드라인 최적화 프롬프트' }, - { label: '실전 예시 40쌍 (Before/After)', desc: '평범한 이메일 → 임팩트 있는 전문 이메일로 변환된 실전 예시 40쌍 PDF 가이드북 포함' }, - ], - promptPreview: { - title: '프롬프트 예시 — 신규 파트너십 제안 이메일', - content: `당신은 10년 경력의 B2B 영업 전문가이자 비즈니스 라이터입니다. -아래 정보를 바탕으로 상대방이 반드시 읽고 싶어지는 파트너십 제안 이메일을 작성해주세요. - -[작성 원칙] -1. 첫 문장에서 상대방의 이익/관심사를 직접 언급 (내 소개 X) -2. 제안 핵심을 3줄 이내로 압축 (바쁜 담당자가 5초 내 파악) -3. 구체적 수치나 레퍼런스로 신뢰도 구축 -4. 명확한 CTA 1개만 포함 (회의 일정 링크 OR 짧은 통화 요청) -5. 전체 300자 이내, 첨부 자료는 1개 이하 - -[제안 정보] -- 우리 회사/서비스: [입력] -- 제안 대상 회사: [입력] -- 협업으로 상대방이 얻는 이익: [입력] -- 기존 레퍼런스/실적: [입력] -- 원하는 다음 액션: [입력] - -[출력 형식] -① 이메일 제목 3가지 옵션 (각각 다른 각도) -② 본문 이메일 (원칙 준수) -③ 수신자가 거절할 가능성이 높은 이유와 예방 팁`, - }, - cta: '패키지 구매 문의 →', - productId: 'prompt_email', - }, - { - id: 'marketing', - badge: 'MARKETING COPY', - badgeColor: '#fb923c', - bgFrom: '#1c0a00', - bgTo: '#431407', - accentColor: '#f97316', - accentBg: 'rgba(249,115,22,0.12)', - accentBorder: 'rgba(249,115,22,0.3)', - title: '마케팅 카피라이팅 마스터 프롬프트', - subtitle: 'SNS · 광고 · 상세페이지 · 유튜브 전용 35종', - price: '38,000원', - salePrice: '12,900원', - discountRate: '66% OFF', - saleLabel: '런칭 기념 특가', - priceNote: '/ 패키지 (즉시 다운로드)', - desc: '인스타그램·유튜브·쿠팡·스마트스토어·카카오 채널까지 — 각 플랫폼의 알고리즘과 소비자 심리를 반영하여 클릭률·전환율을 극대화하는 마케팅 카피 전문 프롬프트 35종 세트입니다.', - features: [ - { label: 'SNS 플랫폼별 최적화 카피 세트', desc: '인스타그램 피드/릴스·유튜브 제목·스레드·카카오 비즈메시지 각 채널 알고리즘 특성을 반영한 별도 프롬프트' }, - { label: '감성 카피 ↔ 이성 카피 전환', desc: '같은 상품을 감성 스토리텔링 방식과 스펙·기능 중심 이성 방식으로 각각 작성하여 A/B 테스트용 카피 쌍 생성' }, - { label: '상세페이지 전환율 최적화 구조', desc: '훅(Hook) → 문제 공감 → 해결책 제시 → 사회적 증거 → CTA의 5단계 전환 공식을 자동 적용하는 상세페이지 카피 프롬프트' }, - { label: '제품 소개글 10초 요약 공식', desc: '핵심 USP(독보적 강점)를 10초 안에 전달하는 엘리베이터 피치형 짧은 소개글 자동 생성 프롬프트' }, - { label: '유튜브/숏폼 썸네일 제목 생성기', desc: '조회수를 높이는 클릭베이트형 제목과 커뮤니티 공감형 제목을 구분하여 생성하는 유튜브 최적화 프롬프트' }, - { label: '리뷰·후기 마케팅 카피 변환', desc: '고객 후기·댓글을 분석하여 그 안의 핵심 감동 포인트를 광고 카피로 변환하는 소셜 프루프 카피 프롬프트' }, - { label: '업종별 금지 표현 자동 필터', desc: '식품·의료·금융·부동산 등 규제 업종의 법적 주의 표현을 사전에 체크하고 대안 표현을 제시하는 컴플라이언스 프롬프트' }, - ], - promptPreview: { - title: '프롬프트 예시 — 인스타그램 상품 소개 릴스 스크립트', - content: `당신은 팔로워 50만 명의 인스타그램 쇼핑 인플루언서이자 -마케팅 전환율 전문가입니다. -다음 상품 정보를 바탕으로 구매 욕구를 자극하는 릴스 스크립트를 작성해주세요. - -[스크립트 구조 — 60초 이내] -00~03초: 즉각 멈추게 하는 훅 문장 (의문형 OR 공감형 OR 충격 통계) -04~10초: "이게 뭔데?" 궁금증 유발 — 상품 첫 노출 -11~30초: 핵심 기능 3가지를 BEFORE/AFTER 방식으로 시연 -31~45초: 소셜 프루프 (실제 고객 반응 또는 수치) -46~55초: 한정 혜택/가격 공개 -56~60초: 명확한 CTA (링크 인 바이오, DM, 댓글 단어) - -[상품 정보] -- 상품명/카테고리: [입력] -- 핵심 기능/차별점: [입력] -- 타겟 고객: [입력] -- 가격/혜택: [입력] - -[출력] -① 릴스 스크립트 본문 -② 자막용 텍스트 (10자 이내 임팩트 문구 5개) -③ 해시태그 20개 (도달 최적화)`, - }, - cta: '패키지 구매 문의 →', - productId: 'prompt_marketing', - }, - { - id: 'report', - badge: 'BUSINESS REPORT', - badgeColor: '#a78bfa', - bgFrom: '#13082b', - bgTo: '#1e1148', - accentColor: '#8b5cf6', - accentBg: 'rgba(139,92,246,0.12)', - accentBorder: 'rgba(139,92,246,0.3)', - title: '업무 보고서·기획서 자동화 프롬프트 패키지', - subtitle: 'ChatGPT · Claude 전용 · 직장인 필수 30종', - price: '30,000원', - salePrice: '10,900원', - discountRate: '64% OFF', - saleLabel: '런칭 기념 특가', - priceNote: '/ 패키지 (즉시 다운로드)', - desc: '주간 업무 보고부터 임원 발표용 기획서, 투자 제안서, 회의록 요약까지 — 직장인이 매주 반복 작성하는 문서를 AI가 체계적으로 작성하도록 설계한 업무 자동화 프롬프트 30종 세트입니다.', - features: [ - { label: '주간·월간 업무 보고서 자동화', desc: '팀원별 진행 현황, 완료 사항, 이슈, 다음 주 계획을 표 형식으로 정리하는 구조화된 보고서 생성 프롬프트' }, - { label: '임원 보고용 1페이지 요약 공식', desc: '"Bottom Line Up Front" 원칙으로 핵심 결론 → 근거 → 요청사항 순서의 임원 친화적 요약 프롬프트' }, - { label: '신사업 기획서 뼈대 자동 생성', desc: '시장 분석 → 목표 설정 → 실행 방안 → 예산 계획 → KPI 설정의 5단계 기획서 프레임워크 자동 구성 프롬프트' }, - { label: '회의록 → 액션 아이템 변환', desc: '날것의 회의 내용을 입력하면 결정사항·담당자·기한·후속 과제로 즉시 분류하는 회의록 구조화 프롬프트' }, - { label: '데이터 분석 결과 스토리텔링', desc: '숫자·표·그래프 데이터를 경영진이 이해하기 쉬운 인사이트 내러티브로 변환하는 데이터 보고 프롬프트' }, - { label: 'RFP·제안서 경쟁력 강화', desc: '발주사의 선정 기준에 맞춰 제안서의 차별점·강점을 부각시키고 약점을 보완하는 제안서 최적화 프롬프트' }, - { label: '프레젠테이션 슬라이드 목차 자동 설계', desc: '보고 목적과 청중에 맞는 슬라이드 구성 순서, 각 슬라이드 핵심 메시지를 자동으로 설계해주는 프롬프트' }, - ], - promptPreview: { - title: '프롬프트 예시 — 주간 업무 보고서 자동 작성', - content: `당신은 5년 경력의 기업 커뮤니케이션 전문가이자 -비즈니스 라이터입니다. -아래 업무 내용을 바탕으로 팀장에게 보고할 주간 업무 보고서를 작성해주세요. - -[보고서 작성 원칙] -1. 첫 줄에 이번 주 가장 중요한 성과 1가지를 먼저 명시 -2. 수치로 표현 가능한 모든 항목은 반드시 수치화 -3. 이슈는 "문제 상황 → 조치 내용 → 현재 상태" 3단 구조로 기술 -4. 다음 주 계획은 담당자·기한과 함께 표 형식으로 정리 -5. 전체 A4 1장 이내, 핵심 위주 - -[업무 내용 입력] -- 이번 주 완료 업무: [입력] -- 진행 중인 업무: [입력] -- 발생한 이슈/리스크: [입력] -- 다음 주 계획: [입력] -- 요청 사항 (상급자에게): [입력] - -[출력 형식] -① 이번 주 핵심 성과 (1~2줄 요약) -② 완료 업무 목록 (수치 포함) -③ 진행 중 업무 + 달성률 -④ 이슈 및 대응 현황 -⑤ 다음 주 계획 (담당·기한 포함 표) -⑥ 협조 요청 사항`, - }, - cta: '패키지 구매 문의 →', - productId: 'prompt_report', - }, -]; - -const useCases = [ - { label: '이메일 작성', desc: '고객사별, 상황별 최적화된 비즈니스 이메일 프롬프트' }, - { label: '보고서·기획서', desc: '회사 내부 보고서, 제안서, 기획서 자동 작성용 프롬프트' }, - { label: '고객 응대', desc: 'CS 상담, FAQ 응답, 컴플레인 처리를 위한 프롬프트' }, - { label: '마케팅 카피', desc: '제품 소개글, 광고 카피, SNS 콘텐츠 생성 프롬프트' }, - { label: '개발 보조', desc: '코드 리뷰, 버그 설명, 문서화를 위한 개발자 전용 프롬프트' }, - { label: '학습·요약', desc: '문서 요약, 핵심 추출, 번역 최적화 프롬프트' }, -]; - -const plans = [ - { - name: '단건 설계', - price: '30,000원', - period: '/ 건', - desc: '특정 업무 1건 프롬프트 설계', - features: ['요구사항 분석 및 인터뷰', '목적별 프롬프트 1개 설계', 'ChatGPT / Claude 최적화', '수정 1회 포함', '사용 가이드 문서 제공'], - highlight: false, - productId: 'prompt_single', - }, - { - name: '비즈니스 패키지', - price: '99,000원', - period: '/ 패키지', - desc: '업무 유형별 5개 프롬프트 세트', - features: ['업무 분석 심층 인터뷰', '5개 프롬프트 맞춤 설계', '용도별 프롬프트 라이브러리', '수정 3회 포함', '활용 방법 1:1 교육 (30분)', '1개월 내 추가 조정 가능'], - highlight: true, - productId: 'prompt_business', - }, - { - name: '팀/기업 패키지', - price: '249,000원~', - period: '/ 세트', - desc: '부서·팀 전체 프롬프트 시스템 구축', - features: ['팀 업무 프로세스 전체 분석', '10개 이상 프롬프트 설계', '팀 공유 프롬프트 라이브러리', '사내 가이드 문서 작성', '전 직원 교육 자료 제공', '3개월 내 업데이트 지원'], - highlight: false, - productId: 'prompt_team', - }, -]; - -const examples = [ - { - type: '회의록 요약', - before: '회의 내용을 요약해줘', - after: '다음 회의록을 분석하여: 1) 핵심 결정사항 3가지, 2) 담당자별 Action Item, 3) 다음 회의 전 완료해야 할 사항을 불릿 형식으로 정리해줘. 회의록: [내용]', - improvement: '구조화된 출력 · 역할 분리 · 명확한 포맷', - }, - { - type: '코드 리뷰', - before: '이 코드 리뷰해줘', - after: '시니어 백엔드 개발자 관점에서 다음 코드를 리뷰해줘: 1) 버그 및 잠재적 오류, 2) 성능 개선 포인트, 3) 클린코드 관점에서의 개선사항을 각각 심각도(High/Medium/Low)와 함께 알려줘. 코드: [코드]', - improvement: '페르소나 설정 · 심각도 기준 · 다각도 분석', - }, -]; - -export default function PromptPage() { - const [modalOpen, setModalOpen] = useState(false); - const [modalService, setModalService] = useState('프롬프트 엔지니어링'); - const containerRef = useScrollReveal(); - - const openModal = (service: string) => { - trackCTAClick(service, '/services/prompt'); - setModalService(service); - setModalOpen(true); - }; - - return ( -
- - setModalOpen(false)} - service={modalService} - checklist={CHECKLIST} - accentColor="text-violet-400" - headerFrom="#0d0a2e" - headerTo="#1a0f5c" - /> - - {/* ─── Hero ─── */} -
- -
- - - 홈으로 - -

프롬프트 엔지니어링 · AI 활용 극대화

-

- AI를 제대로
- 100% 활용하기 -

-

- ChatGPT·Claude를 쓰는데 결과가 항상 애매하신가요?
- 업무에 딱 맞는 프롬프트를 전문 설계하여 AI를 제대로 활용하도록 도와드립니다. -

-
-
- 업무 효율 평균 3~5배 향상 -
-
- ChatGPT · Claude · Gemini 전용 최적화 -
-
-
-
- - {/* ─── 프리미엄 상품 ─── */} -
-
-
-
- - PREMIUM PRODUCTS -
-

바로 쓸 수 있는 프리미엄 프롬프트

-

전문가가 직접 설계하고 검증한 완성형 프롬프트 패키지 — 구매 즉시 사용 가능

-
-
- {premiumProducts.map((product, idx) => ( -
- {/* 헤더 */} -
-
- - {product.badge} - -
- {/* 할인 배지 */} -
- - {product.discountRate} - - - {product.saleLabel} - -
- {/* 원가 취소선 */} -
{product.price}
- {/* 세일가 */} -
{product.salePrice}
-
{product.priceNote}
-
-
-

{product.title}

-

{product.subtitle}

-

{product.desc}

-
- {/* 기능 목록 */} -
-

포함 내용

-
    - {product.features.map((f, i) => ( -
  • -
    -
    -
    -
    - {f.label} - — {f.desc} -
    -
  • - ))} -
-
- {/* 프롬프트 미리보기 */} -
-

- {product.promptPreview.title} -

-
- {product.promptPreview.content} -
-
- {/* CTA */} -
- - 지금 구매하기 → - -

- 결제 즉시 다운로드 · 로그인 필요 -

-
-
- ))} -
-
-
- - {/* ─── Before/After ─── */} -
-
-
-

BEFORE vs AFTER

-

이런 차이가 납니다

-
-
- {examples.map((ex, idx) => ( -
-
- {ex.type} 예시 - {ex.improvement} -
-
-
-
일반 프롬프트
-
“{ex.before}”
-
- - 모호한 지시 → 불완전한 결과 -
-
-
-
최적화 프롬프트
-
“{ex.after}”
-
- - 명확한 구조 → 바로 쓸 수 있는 결과 -
-
-
-
- ))} -
-
-
- - {/* ─── 활용 분야 ─── */} -
-
-
-

USE CASES

-

활용 분야

-
-
- {useCases.map((uc, i) => ( -
-
-
- {String(i + 1).padStart(2, '0')} -
-
-

{uc.label}

-

{uc.desc}

-
-
-
- ))} -
-
-
- - {/* ─── 요금제 ─── */} -
-
-
-

PRICING

-

요금제

-
-
- {plans.map((plan, idx) => ( -
- {plan.highlight && ( -
추천
- )} -
{plan.name.toUpperCase()}
-
- {plan.price} - {plan.period} -
-

{plan.desc}

-
    - {plan.features.map((f) => ( -
  • -
    -
    -
    - {f} -
  • - ))} -
- -
- ))} -
-
-
- - {/* ─── CTA ─── */} -
-
-
-

GET STARTED

-

AI를 제대로 활용하고 싶다면

-

업무 분석 인터뷰 → 맞춤 설계 → 가이드 제공

- -
-
-
-
- ); -} diff --git a/app/services/stock/layout.tsx b/app/services/stock/layout.tsx deleted file mode 100644 index 4631717..0000000 --- a/app/services/stock/layout.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { Metadata } from 'next'; - -export const metadata: Metadata = { - title: '주식 자동 매매 프로그램', - description: - 'NAS 서버에서 직접 운영 중인 주식 자동 매매 시스템. RSI·MACD·볼린저밴드 기반 매매 신호를 텔레그램으로 수신하고 자동 매수·매도합니다. 키움·한국투자 연동.', - keywords: [ - '주식 자동 매매', - '알고트레이딩', - '주식 자동화', - '텔레그램 주식 알림', - '키움 자동매매', - '주식 프로그램', - 'RSI 매매', - ], - openGraph: { - title: '주식 자동 매매 프로그램 | 쟁승메이드', - description: - '직접 운영 중인 알고트레이딩 시스템. 텔레그램 연동 · 자동 매수매도 · 설치 49,000원~.', - url: 'https://jaengseung-made.com/services/stock', - }, -}; - -export default function StockLayout({ children }: { children: React.ReactNode }) { - return children; -} diff --git a/app/services/stock/page.tsx b/app/services/stock/page.tsx deleted file mode 100644 index 1e2fa8b..0000000 --- a/app/services/stock/page.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { redirect } from 'next/navigation'; - -// 토스페이먼츠 심사 정책상 판매 불가 상품으로 분류 — 비공개 처리 -export default function StockPage() { - redirect('/'); -} diff --git a/app/services/website/layout.tsx b/app/services/website/layout.tsx index 81427ee..1d28ddb 100644 --- a/app/services/website/layout.tsx +++ b/app/services/website/layout.tsx @@ -23,6 +23,7 @@ export const metadata: Metadata = { '소상공인·스타트업 홈페이지 제작. 템플릿 없이 직접 개발, SEO 포함, 20만원~.', url: 'https://jaengseung-made.com/services/website', }, + robots: { index: false, follow: false }, }; export default function WebsiteLayout({ children }: { children: React.ReactNode }) { diff --git a/app/sitemap.ts b/app/sitemap.ts index e41d7ae..fa35588 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -5,47 +5,12 @@ export default function sitemap(): MetadataRoute.Sitemap { const now = new Date(); return [ - { - url: base, - lastModified: now, - changeFrequency: 'weekly', - priority: 1.0, - }, - { - url: `${base}/freelance`, - lastModified: now, - changeFrequency: 'weekly', - priority: 0.9, - }, - { - url: `${base}/services/automation`, - lastModified: now, - changeFrequency: 'weekly', - priority: 0.9, - }, - { - url: `${base}/services/prompt`, - lastModified: now, - changeFrequency: 'weekly', - priority: 0.8, - }, - { - url: `${base}/services/website`, - lastModified: now, - changeFrequency: 'monthly', - priority: 0.8, - }, - { - url: `${base}/services/ai-kit`, - lastModified: now, - changeFrequency: 'weekly', - priority: 0.9, - }, - { - url: `${base}/saju`, - lastModified: now, - changeFrequency: 'monthly', - priority: 0.5, - }, + { url: base, lastModified: now, changeFrequency: 'weekly', priority: 1.0 }, + { url: `${base}/services/music`, lastModified: now, changeFrequency: 'weekly', priority: 0.95 }, + { url: `${base}/services/blog`, lastModified: now, changeFrequency: 'weekly', priority: 0.9 }, + { url: `${base}/saju`, lastModified: now, changeFrequency: 'monthly', priority: 0.7 }, + { url: `${base}/legal/terms`, lastModified: now, changeFrequency: 'yearly', priority: 0.3 }, + { url: `${base}/legal/refund`, lastModified: now, changeFrequency: 'yearly', priority: 0.3 }, + { url: `${base}/legal/privacy`, lastModified: now, changeFrequency: 'yearly', priority: 0.3 }, ]; } diff --git a/app/tools/ebay-parts/page.tsx b/app/tools/ebay-parts/page.tsx deleted file mode 100644 index 770bb56..0000000 --- a/app/tools/ebay-parts/page.tsx +++ /dev/null @@ -1,541 +0,0 @@ -'use client'; - -import { useState, useCallback } from 'react'; -import type { SearchResult as FullSearchResult, FitmentEntry, PriceSource } from '@/lib/ebay-tools/types'; - -/* ── Types (페이지 전용) ──────────────────────────────────── */ -// SearchResult['data']에 해당하는 타입 (API 응답의 data 필드) -type PageSearchResult = FullSearchResult['data']; - -interface HistoryItem { - partNumber: string; - partName?: string; - time: string; - resultSummary: string; -} - -/* ── Tab IDs ────────────────────────────────────────────────── */ -const TABS = [ - { id: 'basic', label: '기본 정보' }, - { id: 'listing', label: '이베이 리스팅' }, - { id: 'fitment', label: '호환 차종' }, - { id: 'pricing', label: '가격 비교' }, - { id: 'raw', label: '원본 데이터' }, -] as const; - -type TabId = (typeof TABS)[number]['id']; - -/* ── Icons (inline SVGs) ────────────────────────────────────── */ -const SearchIcon = () => ( - - - -); - -const SpinnerIcon = () => ( - - - - -); - -const CopyIcon = () => ( - - - -); - -const ClockIcon = () => ( - - - -); - -/* ── Component ──────────────────────────────────────────────── */ -export default function EbayPartsPage() { - const [partNumber, setPartNumber] = useState(''); - const [partName, setPartName] = useState(''); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [result, setResult] = useState(null); - const [activeTab, setActiveTab] = useState('basic'); - const [history, setHistory] = useState([]); - const [copied, setCopied] = useState(false); - const [rawExpanded, setRawExpanded] = useState(false); - - /* ── Search ─────────────────────────────────────────────── */ - const handleSearch = useCallback( - async (pn?: string, pnm?: string) => { - const searchPartNumber = pn ?? partNumber; - const searchPartName = pnm ?? partName; - - if (!searchPartNumber.trim()) { - setError('품번을 입력해주세요.'); - return; - } - - setLoading(true); - setError(null); - setResult(null); - setActiveTab('basic'); - - try { - const res = await fetch('/api/tools/ebay-parts/search', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - partNumber: searchPartNumber.trim(), - partName: searchPartName.trim() || undefined, - }), - }); - - const json = await res.json(); - - if (!res.ok || !json.success) { - setError(json.error || '검색에 실패했습니다.'); - return; - } - - setResult(json.data); - - // Update history - setHistory((prev) => { - const entry: HistoryItem = { - partNumber: searchPartNumber.trim(), - partName: searchPartName.trim() || undefined, - time: new Date().toLocaleTimeString('ko-KR'), - resultSummary: `${json.data.fitment.length}개 차종, ${json.data.pricing.sources.length}개 소스`, - }; - return [entry, ...prev.filter((h) => h.partNumber !== entry.partNumber)].slice(0, 5); - }); - } catch { - setError('네트워크 오류가 발생했습니다. 다시 시도해주세요.'); - } finally { - setLoading(false); - } - }, - [partNumber, partName] - ); - - const handleHistoryClick = (item: HistoryItem) => { - setPartNumber(item.partNumber); - setPartName(item.partName || ''); - handleSearch(item.partNumber, item.partName); - }; - - const handleCopy = async (text: string) => { - await navigator.clipboard.writeText(text); - setCopied(true); - setTimeout(() => setCopied(false), 1500); - }; - - /* ── Confidence Badge ──────────────────────────────────── */ - const ConfidenceBadge = ({ level }: { level: string }) => { - const styles: Record = { - high: 'bg-emerald-50 text-emerald-700 border-emerald-200', - medium: 'bg-amber-50 text-amber-700 border-amber-200', - low: 'bg-red-50 text-red-700 border-red-200', - }; - const labels: Record = { high: 'High', medium: 'Medium', low: 'Low' }; - return ( - - {labels[level] || level} - - ); - }; - - /* ── Tab Content Renderers ─────────────────────────────── */ - const renderBasicInfo = () => { - if (!result) return null; - const { basicInfo } = result; - return ( -
-
- {[ - ['부품명', basicInfo.partName], - ['브랜드', basicInfo.brand], - ['품번', basicInfo.partNumber], - ['카테고리', basicInfo.category], - ].map(([label, value]) => ( -
-

{label}

-

{value}

-
- ))} -
-
-

OEM 번호

-
- {basicInfo.oemNumbers.map((num) => ( - - {num} - - ))} -
-
-
- ); - }; - - const renderListing = () => { - if (!result) return null; - const { listing } = result; - return ( -
- {/* Title */} -
-
-

추천 제목

- -
-

{listing.title}

-
- - {/* Category */} -
-

추천 카테고리

-

{listing.category}

-
- - {/* Item Specifics */} -
-

Item Specifics

-
- - - - - - - - - {Object.entries(listing.itemSpecifics).map(([key, value]) => ( - - - - - ))} - -
KeyValue
{key}{value}
-
-
-
- ); - }; - - const renderFitment = () => { - if (!result) return null; - return ( -
-

호환 차종 목록

-
- - - - - - - - - - - - {result.fitment.map((f, i) => ( - - - - - - - - ))} - -
YearMakeModelEngine신뢰도
{f.year}{f.make}{f.model}{f.engine} - -
-
-
- ); - }; - - const renderPricing = () => { - if (!result) return null; - const { pricing } = result; - return ( -
- {/* Price table */} -
-

소스별 가격 비교

-
- - - - - - - - - - - {pricing.sources.map((s) => ( - - - - - - - ))} - -
사이트가격 (USD)원화 환산링크
{s.site} - ${s.price.toFixed(2)} - - {Math.round(s.price * pricing.exchangeRate.rate).toLocaleString()}원 - - - 바로가기 - -
-
-
- - {/* Exchange + customs */} -
-
-

환율 정보

-

- 1 USD = {pricing.exchangeRate.rate.toLocaleString()}원 -

-

- {pricing.exchangeRate.source} ({pricing.exchangeRate.date}) -

-
-
-

관세/부가세 참고

- {pricing.customs.isExempt && ( -

- $150 이하 소액면세 대상 -

- )} -

- HS Code: {pricing.customs.hsCode} -

-

- 세율: {pricing.customs.dutyRate} -

-

- 예상 관세: {pricing.customs.estimatedDuty.toLocaleString()}원 -

-

- 부가세 (VAT 10%): {pricing.customs.vat.toLocaleString()}원 -

-

- 총 수입 비용: {pricing.customs.totalImportCost.toLocaleString()}원 -

-

{pricing.customs.disclaimer}

-
-
-
- ); - }; - - const renderRawData = () => { - if (!result) return null; - return ( -
- - {rawExpanded && ( -
-
-              {JSON.stringify(result.rawData, null, 2)}
-            
-
- )} -
- ); - }; - - const renderTabContent = () => { - switch (activeTab) { - case 'basic': - return renderBasicInfo(); - case 'listing': - return renderListing(); - case 'fitment': - return renderFitment(); - case 'pricing': - return renderPricing(); - case 'raw': - return renderRawData(); - } - }; - - /* ── Skeleton ──────────────────────────────────────────── */ - const Skeleton = () => ( -
-
- -

AI가 사이트를 탐색하고 있습니다...

-
-
- {[1, 2, 3].map((i) => ( -
- ))} -
-
- ); - - /* ── Render ────────────────────────────────────────────── */ - return ( -
- {/* ── Header ──────────────────────────────────────── */} -
-
-

이베이 자동차 부품 AI 리스팅 자동화

- - MVP 데모 - -
-

- 품번을 입력하면 AI가 자동으로 리스팅 정보를 수집합니다 -

-
- - {/* ── Input Form ──────────────────────────────────── */} -
-
-
- - setPartNumber(e.target.value)} - placeholder="예: 16610-0H040" - className="w-full px-3 py-2.5 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition text-slate-900 placeholder:text-slate-400" - onKeyDown={(e) => e.key === 'Enter' && handleSearch()} - /> -
-
- - setPartName(e.target.value)} - placeholder="예: Fuel Pump Assembly" - className="w-full px-3 py-2.5 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition text-slate-900 placeholder:text-slate-400" - onKeyDown={(e) => e.key === 'Enter' && handleSearch()} - /> -
-
- -
-
-
- - {/* ── Error ───────────────────────────────────────── */} - {error && ( -
-

{error}

-
- )} - - {/* ── Loading ─────────────────────────────────────── */} - {loading && } - - {/* ── Result Tabs ─────────────────────────────────── */} - {result && !loading && ( -
- {/* Tabs */} -
- {TABS.map((tab) => ( - - ))} -
- - {/* Tab Content */} -
{renderTabContent()}
- - {/* Meta footer */} -
- 검색 시각: {new Date(result.meta.searchedAt).toLocaleString('ko-KR')} - 소요 시간: {result.meta.processingTime} - 소스: {result.meta.sourcesChecked.join(', ')} - 모델: {result.meta.aiModel} -
-
- )} - - {/* ── Search History ──────────────────────────────── */} - {history.length > 0 && ( -
-
- -

최근 검색

-
-
- {history.map((item, i) => ( - - ))} -
-
- )} -
- ); -} diff --git a/app/tools/layout.tsx b/app/tools/layout.tsx deleted file mode 100644 index 70135e3..0000000 --- a/app/tools/layout.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Metadata } from 'next'; - -export const metadata: Metadata = { - title: '여긴 뭐 만들어요? — 자동화 도구 쇼케이스 | 쟁승메이드', - description: - '실제 고객 프로젝트 기반 자동화 도구를 직접 체험해보세요. 이베이 부품 AI 리스팅, 네이버 블로그 자동화 등 완성형 데모를 무료로 제공합니다.', - openGraph: { - title: '여긴 뭐 만들어요? — 자동화 도구 쇼케이스', - description: - '수작업 30분 → 10초. 실제로 작동하는 자동화 도구를 직접 체험해보세요.', - }, -}; - -export default function ToolsLayout({ children }: { children: React.ReactNode }) { - return children; -} diff --git a/app/tools/naver-blog/page.tsx b/app/tools/naver-blog/page.tsx deleted file mode 100644 index 0b5364e..0000000 --- a/app/tools/naver-blog/page.tsx +++ /dev/null @@ -1,524 +0,0 @@ -'use client'; - -import { useState, useCallback } from 'react'; -import Link from 'next/link'; - -/* ── Types ─────────────────────────────────────────────── */ -interface BlogSection { - heading: string; - body: string; - imageSlot?: boolean; -} - -interface ImageGuide { - position: string; - description: string; - searchKeyword: string; - altText: string; -} - -interface BlogData { - title: string; - subtitle: string; - content: BlogSection[]; - tags: string[]; - seoTitle: string; - seoDescription: string; - imageGuides: ImageGuide[]; - meta: { - charCount: number; - sectionCount: number; - estimatedReadTime: string; - generatedAt: string; - model: string; - }; -} - -/* ── Option configs ────────────────────────────────────── */ -const STYLES = [ - { value: 'informational', label: '정보 전달', desc: '사실 기반 정보 정리', icon: '📖' }, - { value: 'review', label: '리뷰/후기', desc: '제품·서비스 체험기', icon: '⭐' }, - { value: 'howto', label: '방법/튜토리얼', desc: '단계별 가이드', icon: '🔧' }, - { value: 'listicle', label: '리스트형', desc: 'OO가지 모음', icon: '📋' }, - { value: 'comparison', label: '비교 분석', desc: 'A vs B 비교', icon: '⚖️' }, - { value: 'story', label: '에세이/스토리', desc: '경험 기반 서사', icon: '✍️' }, -]; - -const TONES = [ - { value: 'professional', label: '전문적', color: 'border-blue-500/40 bg-blue-500/10 text-blue-300' }, - { value: 'friendly', label: '친근한', color: 'border-emerald-500/40 bg-emerald-500/10 text-emerald-300' }, - { value: 'casual', label: '캐주얼', color: 'border-amber-500/40 bg-amber-500/10 text-amber-300' }, - { value: 'formal', label: '격식체', color: 'border-violet-500/40 bg-violet-500/10 text-violet-300' }, -]; - -const LENGTHS = [ - { value: 'short', label: '짧게', desc: '800~1,200자', time: '~2분' }, - { value: 'medium', label: '보통', desc: '1,500~2,500자', time: '~5분' }, - { value: 'long', label: '길게', desc: '3,000~4,500자', time: '~9분' }, -]; - -/* ── Component ─────────────────────────────────────────── */ -export default function NaverBlogPage() { - // Form state - const [topic, setTopic] = useState(''); - const [keywordInput, setKeywordInput] = useState(''); - const [keywords, setKeywords] = useState([]); - const [style, setStyle] = useState('informational'); - const [tone, setTone] = useState('friendly'); - const [length, setLength] = useState('medium'); - const [sections, setSections] = useState(5); - const [imageGuide, setImageGuide] = useState(true); - - // Result state - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [result, setResult] = useState(null); - const [viewMode, setViewMode] = useState<'preview' | 'seo' | 'image'>('preview'); - const [copied, setCopied] = useState(false); - - /* ── Keyword management ───────────────────────────────── */ - const addKeyword = () => { - const kw = keywordInput.trim(); - if (kw && !keywords.includes(kw) && keywords.length < 10) { - setKeywords([...keywords, kw]); - setKeywordInput(''); - } - }; - - const removeKeyword = (kw: string) => { - setKeywords(keywords.filter((k) => k !== kw)); - }; - - /* ── Generate ─────────────────────────────────────────── */ - const handleGenerate = useCallback(async () => { - if (!topic.trim()) { - setError('주제를 입력해주세요.'); - return; - } - if (keywords.length === 0) { - setError('키워드를 최소 1개 추가해주세요.'); - return; - } - - setLoading(true); - setError(null); - setResult(null); - - try { - const res = await fetch('/api/tools/naver-blog/generate', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ topic: topic.trim(), keywords, style, tone, length, imageGuide, sections }), - }); - - const json = await res.json(); - - if (!res.ok || !json.success) { - throw new Error(json.error || '생성에 실패했습니다.'); - } - - setResult(json.data); - } catch (err) { - setError(err instanceof Error ? err.message : '오류가 발생했습니다.'); - } finally { - setLoading(false); - } - }, [topic, keywords, style, tone, length, imageGuide, sections]); - - /* ── Copy full content ────────────────────────────────── */ - const copyContent = () => { - if (!result) return; - const text = result.content.map((s) => `## ${s.heading}\n\n${s.body}`).join('\n\n'); - const full = `# ${result.title}\n\n${result.subtitle}\n\n${text}\n\n태그: ${result.tags.map((t) => '#' + t).join(' ')}`; - navigator.clipboard.writeText(full); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; - - return ( -
- {/* Header */} -
- - - - - 도구 목록 - -
-
- - - -
-
-

네이버 블로그 자동화

-

Naver Blog AI Writer

-
-
-
- -
- {/* ── Left: Settings Panel ──────────────────────────── */} -
- {/* Topic */} -
- - setTopic(e.target.value)} - placeholder="예: 2026년 제주도 가족여행 추천 코스" - className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2.5 text-white text-sm placeholder-slate-500 focus:outline-none focus:border-blue-500/50" - maxLength={100} - /> - - {/* Keywords */} - -
- setKeywordInput(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && (e.preventDefault(), addKeyword())} - placeholder="키워드 입력 후 Enter" - className="flex-1 bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white text-sm placeholder-slate-500 focus:outline-none focus:border-blue-500/50" - /> - -
- {keywords.length > 0 && ( -
- {keywords.map((kw) => ( - - {kw} - - - ))} -
- )} -
- - {/* Style */} -
- -
- {STYLES.map((s) => ( - - ))} -
-
- - {/* Tone */} -
- -
- {TONES.map((t) => ( - - ))} -
-
- - {/* Length + Sections + Image */} -
-
- -
- {LENGTHS.map((l) => ( - - ))} -
-
- -
-
- -

3~8개

-
-
- - {sections} - -
-
- - -
- - {/* Generate Button */} - - - {error && ( -
- {error} -
- )} -
- - {/* ── Right: Result Panel ───────────────────────────── */} -
- {!result && !loading && ( -
- - - -

왼쪽에서 옵션을 선택하고
블로그 글을 생성해보세요

-
- )} - - {loading && ( -
- - - - -

AI가 블로그 글을 작성하고 있습니다

-

주제 분석 → 구조 설계 → 본문 작성 → SEO 최적화

-
- )} - - {result && ( -
- {/* View mode tabs */} -
- {[ - { id: 'preview' as const, label: '글 미리보기' }, - { id: 'seo' as const, label: 'SEO 정보' }, - { id: 'image' as const, label: '이미지 가이드' }, - ].map((tab) => ( - - ))} -
- - {/* Meta bar */} -
-
- {result.meta.charCount.toLocaleString()}자 - {result.meta.sectionCount}개 섹션 - 읽기 {result.meta.estimatedReadTime} -
-
- -
-
- - {/* Content based on viewMode */} - {viewMode === 'preview' && ( -
- {/* Blog preview (light theme to mimic Naver Blog) */} -
-

{result.title}

-

{result.subtitle}

-
- - {result.content.map((section, idx) => ( -
-

- - {section.heading} -

- {section.imageSlot && ( -
- - 이미지 배치 위치 - -
- )} -
{section.body}
-
- ))} - - {/* Tags */} -
- {result.tags.map((tag) => ( - - #{tag} - - ))} -
-
-
- )} - - {viewMode === 'seo' && ( -
-
- -

{result.seoTitle}

-
-
- -

{result.seoDescription}

-
-
- -
- {result.tags.map((tag) => ( - - #{tag} - - ))} -
-
-
- -
- {result.content.map((section, idx) => ( -
- H2 - {section.heading} - {section.body.length}자 -
- ))} -
-
-
- 모델: {result.meta.model} · 생성: {new Date(result.meta.generatedAt).toLocaleString('ko-KR')} -
-
- )} - - {viewMode === 'image' && ( -
- {result.imageGuides.length === 0 ? ( -

이미지 가이드가 없습니다.

- ) : ( -
- {result.imageGuides.map((guide, idx) => ( -
-
- - {idx + 1} - - {guide.position} -
-

{guide.description}

-
- - 검색어: {guide.searchKeyword} - - - Alt: {guide.altText} - -
-
- ))} -
- )} -
- )} - - {/* CTA */} -
-

- 이런 블로그 자동화를 우리 사업에 맞게 커스텀하고 싶다면? -

- - 맞춤 자동화 상담하기 - - - - -
-
- )} -
-
-
- ); -} diff --git a/app/tools/page.tsx b/app/tools/page.tsx deleted file mode 100644 index bbfd729..0000000 --- a/app/tools/page.tsx +++ /dev/null @@ -1,383 +0,0 @@ -'use client'; - -import { useEffect, useRef } from 'react'; -import Link from 'next/link'; -import { trackToolDemo, trackCTAClick } from '../../lib/gtag'; - -/* ═══════════════════════════════════════════════════ - 도구 쇼케이스 — 리디자인 v2 - 설계 원칙: - 1. 홈 페이지 에디토리얼 톤 계승 — 증거 중심, 텍스트 우선 - 2. Supanova: 비대칭 레이아웃, 스크롤 애니메이션, 프리미엄 카드 - 3. 사이트 디자인 시스템 완전 통일 (라이트 bg + 다크 카드) - 4. 실제 수치와 체험 유도 — 전환율 중심 구조 -═══════════════════════════════════════════════════ */ - -interface ToolCard { - id: string; - title: string; - subtitle: string; - description: string; - tags: string[]; - href: string; - status: 'live' | 'beta' | 'coming'; - gradient: string; - iconPath: string; - metric: { value: string; label: string }; - highlight: string; -} - -const TOOLS: ToolCard[] = [ - { - id: 'ebay-parts', - title: '이베이 부품 AI 리스팅', - subtitle: 'eBay Auto Parts Listing Tool', - description: - '품번 하나 입력하면 AI가 RockAuto·eBay를 크롤링하고, 리스팅 제목·Fitment·관세까지 자동 생성합니다.', - tags: ['크롤링', 'Claude AI', '관세 계산', 'eBay Motors'], - href: '/tools/ebay-parts', - status: 'live', - gradient: 'from-blue-600 to-cyan-500', - iconPath: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z', - metric: { value: '10초', label: '30분 작업 → 10초로 단축' }, - highlight: '수작업 대비 180배 빠름', - }, - { - id: 'naver-blog', - title: '네이버 블로그 자동화', - subtitle: 'Naver Blog AI Writer', - description: - '주제·톤·분량만 선택하면 AI가 SEO 최적화된 블로그 글을 자동 작성합니다. 소제목 구조, 이미지 배치 가이드까지.', - tags: ['GPT/Claude', 'SEO 최적화', '자동 포스팅', '이미지 가이드'], - href: '/tools/naver-blog', - status: 'live', - gradient: 'from-emerald-600 to-teal-500', - iconPath: 'M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z', - metric: { value: '3분', label: '1시간 글쓰기 → 3분 자동 완성' }, - highlight: 'SEO 최적화 자동 포함', - }, -]; - -const STATUS_BADGE: Record = { - live: { label: '체험 가능', className: 'bg-emerald-50 text-emerald-700 border-emerald-200' }, - beta: { label: 'BETA', className: 'bg-amber-50 text-amber-700 border-amber-200' }, - coming: { label: '준비 중', className: 'bg-slate-100 text-slate-500 border-slate-200' }, -}; - -const PROCESS_STEPS = [ - { step: '01', title: '문제 정의', desc: '고객의 반복 업무를 분석합니다' }, - { step: '02', title: '자동화 설계', desc: 'AI + 크롤링 + API 조합을 설계합니다' }, - { step: '03', title: '프로토타입', desc: '실제 데이터로 동작하는 MVP를 만듭니다' }, - { step: '04', title: '체험 배포', desc: '이 페이지에 데모를 올려 직접 테스트합니다' }, -]; - -function useScrollReveal() { - const ref = useRef(null); - - useEffect(() => { - const el = ref.current; - if (!el) return; - - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - entry.target.classList.add('is-visible'); - observer.unobserve(entry.target); - } - }); - }, - { threshold: 0.15, rootMargin: '0px 0px -40px 0px' } - ); - - const children = el.querySelectorAll('.reveal'); - children.forEach((child) => observer.observe(child)); - - return () => observer.disconnect(); - }, []); - - return ref; -} - -export default function ToolsShowcasePage() { - const containerRef = useScrollReveal(); - - return ( -
- {/* ── Scroll-reveal animation styles ── */} - - - {/* ═══════════════════════════════════════════ - HERO — 좌측 텍스트 / 우측 수치 비대칭 레이아웃 - ═══════════════════════════════════════════ */} -
-
- {/* 좌측: 텍스트 블록 */} -
-
- - 실제로 작동하는 완성형 데모 -
-

- 여긴 뭐 만들어요? -

-

- “이런 것도 자동화돼요?” — 직접 체험해보세요. - 아래 도구들은 실제 고객 프로젝트를 기반으로 제작된 완성형 레퍼런스입니다. -

-
- - {/* 우측: 수치 카드 */} -
-
-
-
-
{TOOLS.filter(t => t.status === 'live').length}개
-
라이브 도구
-
-
-
180x
-
최대 속도 향상
-
-
-
무료
-
데모 체험
-
-
-
즉시
-
결과 확인
-
-
-
-
-
-
- - {/* ═══════════════════════════════════════════ - TOOL CARDS — 피처드 카드 (풀와이드) 패턴 - ═══════════════════════════════════════════ */} -
-
- {TOOLS.map((tool, idx) => { - const badge = STATUS_BADGE[tool.status]; - const isEven = idx % 2 === 1; - - return ( - trackToolDemo(tool.title)} - className={`group block tool-card bg-white rounded-2xl border border-slate-200 overflow-hidden active:scale-[0.99] reveal reveal-delay-${idx + 1}`} - > -
- {/* 좌측 (또는 우측): 메트릭 비주얼 */} -
- {/* 배경 패턴 (모바일 숨김) */} -
-
-
-
- - {/* 모바일: 수평 compact / 데스크톱: 수직 */} -
-
- - - -
- -
-
- {tool.metric.value} -
-

{tool.metric.label}

-
-
-
- - {/* 우측 (또는 좌측): 콘텐츠 */} -
-
- {/* 상단: 배지 + 하이라이트 */} -
- - {badge.label} - - - {tool.highlight} - -
- - {/* 제목 */} -

- {tool.title} -

-

{tool.subtitle}

- - {/* 설명 */} -

- {tool.description} -

- - {/* 태그 */} -
- {tool.tags.map((tag) => ( - - {tag} - - ))} -
-
- - {/* CTA */} -
- 직접 체험하기 - - - -
-
-
- - ); - })} -
-
- - {/* ═══════════════════════════════════════════ - PROCESS — 어떻게 만들어지나? (수직 타임라인) - ═══════════════════════════════════════════ */} -
-
-

도구는 이렇게 만들어집니다

-

고객의 반복 업무가 자동화 도구로 바뀌는 4단계

-
- -
- {PROCESS_STEPS.map((item, idx) => ( -
- {/* 스텝 카드 */} -
-
- - {item.step} - - {idx < PROCESS_STEPS.length - 1 && ( -
- - - -
- )} -
-

{item.title}

-

{item.desc}

-
-
- ))} -
-
- - {/* ═══════════════════════════════════════════ - BOTTOM CTA — 풀블리드 전환 섹션 - ═══════════════════════════════════════════ */} -
-
- {/* 배경 데코 */} -
-
-
-
- -
-
-

- 우리 업무에도 이런 자동화가 가능할까? -

-

- 위 데모를 참고해 원하시는 자동화를 구체적으로 의뢰하세요. 반복하는 업무가 있다면, 도구로 만들 수 있습니다. -

-
- trackCTAClick('무료 상담 신청하기', '/tools')} - className="group inline-flex items-center gap-3 px-7 py-4 rounded-xl bg-blue-600 hover:bg-blue-500 active:scale-[0.98] text-white text-sm font-bold transition-all duration-300 shadow-lg shadow-blue-600/20 hover:shadow-blue-500/30 flex-shrink-0" - > - 무료 상담 신청하기 - - - - - - -
- - {/* 하단 신뢰 요소 */} -
-
- - - - 상담 무료 -
-
- - - - 계약서 선행 -
-
- - - - 소스코드 전체 이관 -
-
-
-
-
- ); -} diff --git a/lib/telegram.ts b/lib/telegram.ts index c7b2990..76c4c38 100644 --- a/lib/telegram.ts +++ b/lib/telegram.ts @@ -49,7 +49,6 @@ export function formatLottoMessage( `📊 합계: ${numbers.reduce((a, b) => a + b, 0)} | 홀수: ${numbers.filter((n) => n % 2 !== 0).length}개`, ``, `⚠️ 통계 기반 추천이며 당첨을 보장하지 않습니다.`, - `🔗 [번호 추천 받기](https://jaengseung.com/services/lotto/recommend)`, ].join('\n'); }