웹페이지 제작 소개 페이지 생성 & 사주 분석 고도화

This commit is contained in:
2026-03-19 07:58:38 +09:00
parent b250d4b50c
commit 7f4fb8027a
15 changed files with 3397 additions and 384 deletions

View File

@@ -1,6 +1,6 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import OpenAI from 'openai'; import Anthropic from '@anthropic-ai/sdk';
import { createSajuPrompt } from '@/lib/saju-ai-prompt'; import { createSajuPrompt } from '@/lib/saju-ai-prompt';
import { performFullAnalysis } from '@/lib/ai-interpretation'; import { performFullAnalysis } from '@/lib/ai-interpretation';
@@ -8,7 +8,7 @@ export const runtime = 'nodejs';
const MOCK_INTERPRETATION = ` const MOCK_INTERPRETATION = `
## 1. 일간 분석과 타고난 기질 ## 1. 일간 분석과 타고난 기질
(API 키 문제 또는 할당량 초과로 인해 예시 데이터를 보여드립니다.) (AI 해석 서비스를 이용하려면 API 설정이 필요합니다. 아래는 예시 데이터니다.)
귀하는 **갑목(甲木)** 일간으로 태어나, 마치 곧게 뻗은 소나무와 같은 기상을 지니고 있다. 리더십이 강하고 추진력이 뛰어나며, 한번 마음먹은 일은 끝까지 해내는 뚝심이 있다. 귀하는 **갑목(甲木)** 일간으로 태어나, 마치 곧게 뻗은 소나무와 같은 기상을 지니고 있다. 리더십이 강하고 추진력이 뛰어나며, 한번 마음먹은 일은 끝까지 해내는 뚝심이 있다.
## 2. 오행 균형과 용신 기반 개운법 ## 2. 오행 균형과 용신 기반 개운법
@@ -45,9 +45,6 @@ const MOCK_INTERPRETATION = `
"서두르지 않아도 봄은 온다." 조급해하지 말고 때를 기다리는 지혜가 필요하다. "서두르지 않아도 봄은 온다." 조급해하지 말고 때를 기다리는 지혜가 필요하다.
`; `;
// 사용 가능한 모델 우선순위 (gpt-4o → gpt-4o-mini 폴백)
const MODELS = ['gpt-4o', 'gpt-4o-mini'] as const;
export async function POST(request: Request) { export async function POST(request: Request) {
try { try {
const { saju, daeun, daeunList, gender, engineData } = await request.json(); const { saju, daeun, daeunList, gender, engineData } = await request.json();
@@ -64,55 +61,59 @@ export async function POST(request: Request) {
); );
} }
if (!process.env.OPENAI_API_KEY) { if (!process.env.ANTHROPIC_API_KEY) {
console.warn('OpenAI API Key is missing'); console.warn('Anthropic API Key is missing — returning mock data');
return NextResponse.json({ interpretation: MOCK_INTERPRETATION, analysis }); return NextResponse.json({ interpretation: MOCK_INTERPRETATION, analysis });
} }
const openai = new OpenAI({ const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
apiKey: process.env.OPENAI_API_KEY,
});
const prompt = createSajuPrompt(saju, daeun, gender, analysis, daeunList || [], engineData); const prompt = createSajuPrompt(saju, daeun, gender, analysis, daeunList || [], engineData);
// 모델 폴백: gpt-4o 실패 시 gpt-4o-mini로 재시도 console.log('Generating saju analysis with claude-sonnet-4-6...');
let interpretation: string | null = null;
let usedModel = ''; let interpretation: string | null = null;
for (const model of MODELS) {
try { try {
console.log(`Generating saju analysis with model: ${model}`); const message = await client.messages.create({
const completion = await openai.chat.completions.create({ model: 'claude-sonnet-4-6',
messages: [{ role: 'system', content: prompt }], max_tokens: 8192,
model,
max_tokens: model === 'gpt-4o' ? 8192 : 4096,
temperature: 0.75, temperature: 0.75,
messages: [{ role: 'user', content: prompt }],
}); });
interpretation = completion.choices[0].message.content;
usedModel = model; const block = message.content[0];
console.log(`Successfully generated with model: ${model}`); if (block.type === 'text') {
break; interpretation = block.text;
} catch (modelError: any) { }
console.warn(`Model ${model} failed:`, modelError.message || modelError.status); console.log('Successfully generated saju analysis with claude-sonnet-4-6');
if (modelError.status === 401) { } catch (claudeError: any) {
console.warn('OpenAI API Key is invalid (401). Returning mock data.'); // claude-sonnet-4-6 실패 시 claude-haiku-4-5 폴백
console.warn('claude-sonnet-4-6 failed:', claudeError.message, '— trying haiku fallback');
try {
const fallback = await client.messages.create({
model: 'claude-haiku-4-5-20251001',
max_tokens: 4096,
temperature: 0.75,
messages: [{ role: 'user', content: prompt }],
});
const block = fallback.content[0];
if (block.type === 'text') {
interpretation = block.text;
}
console.log('Fallback to claude-haiku-4-5 succeeded');
} catch (haikusError: any) {
console.error('Both Claude models failed:', haikusError.message);
return NextResponse.json({ interpretation: MOCK_INTERPRETATION, analysis }); return NextResponse.json({ interpretation: MOCK_INTERPRETATION, analysis });
} }
if (modelError.status === 429 || (modelError.error && modelError.error.code === 'insufficient_quota')) { }
console.warn('OpenAI Quota Exceeded. Returning mock data.');
if (!interpretation) {
return NextResponse.json({ interpretation: MOCK_INTERPRETATION, analysis }); return NextResponse.json({ interpretation: MOCK_INTERPRETATION, analysis });
} }
if (model === MODELS[MODELS.length - 1]) {
throw modelError;
}
console.log(`Falling back to next model...`);
}
}
return NextResponse.json({ interpretation, analysis }); return NextResponse.json({ interpretation, analysis });
} catch (error: any) { } catch (error: any) {
console.error('Error generating saju interpretation:', error.message || error); console.error('Error generating saju interpretation:', error.message || error);
return NextResponse.json( return NextResponse.json(
{ error: error.message || 'Failed to generate interpretation' }, { error: error.message || 'Failed to generate interpretation' },
{ status: 500 } { status: 500 }

View File

@@ -1,9 +1,11 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server'; import { createClient } from '@/lib/supabase/server';
import { createAdminClient } from '@/lib/supabase/admin';
/** /**
* GET /api/subscription * GET /api/subscription
* 내 활성/만료 구독 목록 조회 * 내 활성/만료 구독 목록 조회
* - auth 검증은 anon client, DB 조회는 admin client (RLS 우회)
*/ */
export async function GET() { export async function GET() {
const supabase = await createClient(); const supabase = await createClient();
@@ -12,7 +14,9 @@ export async function GET() {
return NextResponse.json({ error: 'UNAUTHORIZED' }, { status: 401 }); return NextResponse.json({ error: 'UNAUTHORIZED' }, { status: 401 });
} }
const { data, error } = await supabase // admin client로 RLS 우회 (subscriptions 테이블 SELECT 정책 없을 때도 동작)
const admin = createAdminClient();
const { data, error } = await admin
.from('subscriptions') .from('subscriptions')
.select('id, product_id, status, auto_renew, started_at, expires_at, cancelled_at') .select('id, product_id, status, auto_renew, started_at, expires_at, cancelled_at')
.eq('user_id', user.id) .eq('user_id', user.id)
@@ -20,7 +24,7 @@ export async function GET() {
.limit(20); .limit(20);
if (error) { if (error) {
return NextResponse.json({ error: 'DB_ERROR' }, { status: 500 }); return NextResponse.json({ error: 'DB_ERROR', detail: error.message }, { status: 500 });
} }
return NextResponse.json({ ok: true, subscriptions: data ?? [] }); return NextResponse.json({ ok: true, subscriptions: data ?? [] });

View File

@@ -17,37 +17,16 @@ const navItems = [
desc: '대시보드 홈', desc: '대시보드 홈',
}, },
{ {
href: '/services/lotto', href: '/services/website',
icon: ( icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg> </svg>
), ),
label: '로또 번호 추천', label: '홈페이지 제작',
desc: '빅데이터 분석', desc: '외주 웹 개발',
badge: 'HOT',
},
{
href: '/services/stock',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z" />
</svg>
),
label: '주식 자동 매매',
desc: '텔레그램 연동',
badge: 'NEW', badge: 'NEW',
}, },
{
href: '/services/prompt',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
),
label: '프롬프트 엔지니어링',
desc: 'AI 최적화',
},
{ {
href: '/services/automation', href: '/services/automation',
icon: ( icon: (
@@ -59,6 +38,37 @@ const navItems = [
label: '업무 자동화', label: '업무 자동화',
desc: 'RPA 개발', desc: 'RPA 개발',
}, },
{
href: '/services/prompt',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
),
label: '프롬프트 엔지니어링',
desc: 'AI 최적화',
},
{
href: '/services/stock',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z" />
</svg>
),
label: '주식 자동 매매',
desc: '텔레그램 연동',
},
{
href: '/services/lotto',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
</svg>
),
label: '로또 번호 추천',
desc: '빅데이터 분석',
badge: 'HOT',
},
{ {
href: '/saju', href: '/saju',
icon: ( icon: (
@@ -70,16 +80,6 @@ const navItems = [
desc: '사주팔자 + AI 해석', desc: '사주팔자 + AI 해석',
badge: 'NEW', badge: 'NEW',
}, },
{
href: '/freelance',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
),
label: '외주 개발',
desc: '맞춤형 솔루션',
},
]; ];
interface SidebarProps { interface SidebarProps {

View File

@@ -248,7 +248,7 @@ export default function SajuAISection({
<h3 className="text-xl font-extrabold text-white mb-2">AI (12 )</h3> <h3 className="text-xl font-extrabold text-white mb-2">AI (12 )</h3>
<p className="text-blue-200/60 text-sm mb-6"> <p className="text-blue-200/60 text-sm mb-6">
, , , , , <br /> , , , , , <br />
GPT-4o . Claude AI .
</p> </p>
{/* 미리보기 섹션 목록 */} {/* 미리보기 섹션 목록 */}

View File

@@ -0,0 +1,351 @@
'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<string, number[]> = {
'水': [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<string, string> = {
'水': '수', '火': '화', '木': '목', '金': '금', '土': '토',
};
const ELEMENT_COLOR: Record<string, { bg: string; text: string; border: string; ball: string }> = {
'水': { 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<string, string> = {
'水': '흐르는 물처럼 지혜와 직관이 넘치는 수(水) 기운이 당신의 행운을 이끕니다. 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<string, string> = {
'子': '水', '亥': '水', '寅': '木', '卯': '木', '巳': '火', '午': '火',
'申': '金', '酉': '金', '丑': '土', '辰': '土', '未': '土', '戌': '土',
};
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 (
<div style={{
width: size, height: size,
borderRadius: '50%',
background: `radial-gradient(circle at 35% 35%, ${color}dd, ${color})`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: 'white', fontWeight: 800,
fontSize: size < 40 ? 11 : 14,
boxShadow: `0 3px 10px ${color}60`,
flexShrink: 0,
}}>
{num}
</div>
);
}
// 오행별 볼 색상
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 (
<div className="bg-white rounded-2xl border border-[#dbe8ff] overflow-hidden">
{/* 헤더 */}
<div className="bg-gradient-to-r from-[#04102b] via-[#0d1f5c] to-[#04102b] px-6 py-5">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center flex-shrink-0 shadow">
<span style={{ fontSize: 18 }}>🎱</span>
</div>
<div className="flex-1">
<h2 className="text-sm font-extrabold text-white"> </h2>
<p className="text-blue-300/60 text-[11px] mt-0.5">
({yongShinKr}·{yongShin})
</p>
</div>
<span className="text-[11px] bg-amber-400/20 border border-amber-400/30 text-amber-300 font-bold px-2.5 py-1 rounded-full">
</span>
</div>
</div>
<div className="p-6 space-y-6">
{/* 용신 설명 배너 */}
<div className={`rounded-xl border p-4 ${elemColor.bg} ${elemColor.border}`}>
<div className="flex items-start gap-3">
<div className={`w-10 h-10 rounded-xl flex items-center justify-center font-bold text-lg flex-shrink-0 ${elemColor.text}`}
style={{ background: 'rgba(255,255,255,0.6)' }}>
{yongShin}
</div>
<div>
<div className={`text-xs font-bold mb-1 ${elemColor.text}`}>
: {yongShinKr}({yongShin}) · : {heeShinKr}({heeShin})
</div>
<p className={`text-xs leading-relaxed ${elemColor.text}`} style={{ opacity: 0.85 }}>
{ELEMENT_LUCK_DESC[yongShin]}
</p>
</div>
</div>
</div>
{/* 추천 번호 */}
<div>
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-extrabold text-[#04102b]"> </h3>
<span className="text-[11px] text-slate-400">{currentYear} </span>
</div>
{/* 메인 볼 */}
<div className="flex items-center gap-2.5 flex-wrap mb-3">
{numbers.map((n) => (
<LottoBall key={n} num={n} color={getElementColor(n)} size={48} />
))}
</div>
{/* 용신/희신 구분 안내 */}
<div className="grid grid-cols-2 gap-3 mt-3">
<div className={`rounded-lg p-3 border ${elemColor.bg} ${elemColor.border}`}>
<div className={`text-[10px] font-bold mb-1.5 ${elemColor.text}`}>
({yongShinKr})
</div>
<div className="flex gap-1.5 flex-wrap">
{yongShinNums.map(n => (
<LottoBall key={n} num={n} color={elemColor.ball} size={34} />
))}
</div>
</div>
<div className="rounded-lg p-3 border bg-violet-50 border-violet-200">
<div className="text-[10px] font-bold mb-1.5 text-violet-700">
({heeShinKr})
</div>
<div className="flex gap-1.5 flex-wrap">
{heeShinNums.map(n => (
<LottoBall key={n} num={n} color="#7c3aed" size={34} />
))}
</div>
</div>
</div>
</div>
{/* 기본 사주 해석 내러티브 */}
<div className="bg-[#f8faff] rounded-xl border border-[#dbe8ff] p-4">
<div className="flex items-center gap-2 mb-3">
<span className="text-base"></span>
<h4 className="text-xs font-extrabold text-[#04102b]"> ?</h4>
</div>
<div className="space-y-2 text-xs text-slate-600 leading-relaxed">
<p>
<strong className={elemColor.text}>{dayStemKr}()</strong>
<strong className={elemColor.text}>{yongShin}({yongShinKr})</strong> .
() ,{' '}
<strong>{yongShin === '水' ? '1과 6' : yongShin === '火' ? '2와 7' : yongShin === '木' ? '3과 8' : yongShin === '金' ? '4와 9' : '5와 10'}</strong>
{yongShinKr}() , .
</p>
<p>
<strong className="text-violet-700">{heeShin}({heeShinKr})</strong>
, <strong>({dayBranch})</strong>
6 .
</p>
</div>
</div>
{/* 로또 구독 미가입 → 대운 연동 프리미엄 홍보 */}
{!hasLottoSubscription ? (
<div className="bg-gradient-to-br from-[#04102b] via-[#0a1f5c] to-[#04102b] rounded-xl p-5 relative overflow-hidden border border-[#1a3a7a]">
<div className="absolute inset-0 opacity-[0.04]"
style={{ backgroundImage: 'radial-gradient(circle, #a78bfa 1px, transparent 1px)', backgroundSize: '20px 20px' }} />
<div className="relative">
<div className="flex items-center gap-2 mb-3">
<span className="text-base">🔮</span>
<span className="text-xs font-extrabold text-amber-300"> </span>
</div>
{currentDaeun && (
<p className="text-xs text-blue-200/80 leading-relaxed mb-4">
<strong className="text-amber-300">{currentDaeun.stemKr}{currentDaeun.branchKr} </strong>
({currentDaeun.startYear}~{currentDaeun.endYear}) .
<strong className="text-white"> </strong>
.
</p>
)}
<div className="grid grid-cols-2 gap-2 mb-4">
{[
{ icon: '📊', text: '대운 × 사주 교차 분석' },
{ icon: '🔄', text: '매주 업데이트 번호' },
{ icon: '🎯', text: '빅데이터 Monte Carlo 시뮬레이션' },
{ icon: '📈', text: '핫넘버 / 콜드넘버 통계' },
].map((item, i) => (
<div key={i} className="flex items-center gap-1.5 bg-white/5 rounded-lg px-2.5 py-2">
<span className="text-sm">{item.icon}</span>
<span className="text-[11px] text-blue-200/70 font-medium">{item.text}</span>
</div>
))}
</div>
<Link
href="/services/lotto"
className="block w-full text-center bg-gradient-to-r from-amber-500 to-amber-400 hover:from-amber-400 hover:to-amber-300 text-[#04102b] text-sm font-bold px-4 py-2.5 rounded-xl transition-all shadow-lg"
>
</Link>
</div>
</div>
) : (
/* 로또 구독 가입자 → 대운 교차 분석 심화 */
<div className="bg-gradient-to-br from-emerald-50 to-teal-50 rounded-xl border border-emerald-200 p-5">
<div className="flex items-center gap-2 mb-3">
<div className="w-6 h-6 rounded-full bg-emerald-500 flex items-center justify-center flex-shrink-0">
<svg className="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
</svg>
</div>
<span className="text-xs font-extrabold text-emerald-800"> · </span>
</div>
{currentDaeun ? (
<div className="space-y-3">
<div className="bg-white/70 rounded-lg p-3 border border-emerald-200">
<div className="text-[11px] text-emerald-600 font-semibold mb-1.5"> </div>
<p className="text-xs text-slate-700 leading-relaxed">
<strong className="text-emerald-700">{currentDaeun.stemKr}{currentDaeun.branchKr} </strong>
({currentDaeun.startYear}~{currentDaeun.endYear}, {currentDaeun.age}~{currentDaeun.age + 9})
<strong className="text-[#04102b]">{yongShin}({yongShinKr})</strong>
{currentDaeun.stemKr.includes(yongShinKr) || currentDaeun.branchKr.includes(yongShinKr)
? <strong className="text-emerald-700"> . !</strong>
: ' 상호작용하고 있습니다. 용신 번호를 중심으로 추천합니다.'
}
</p>
</div>
<div className="bg-white/70 rounded-lg p-3 border border-emerald-200">
<div className="text-[11px] text-emerald-600 font-semibold mb-1.5"> </div>
<p className="text-xs text-slate-700 leading-relaxed">
{currentYear} {currentDaeun.stemKr}{currentDaeun.branchKr}
, .
<strong className="text-[#04102b]"> 3</strong> ,
.
</p>
</div>
<Link
href="/services/lotto/recommend"
className="block w-full text-center bg-gradient-to-r from-emerald-500 to-teal-500 hover:from-emerald-400 hover:to-teal-400 text-white text-sm font-bold px-4 py-2.5 rounded-xl transition-all shadow"
>
</Link>
</div>
) : (
<p className="text-xs text-slate-600">
. .
</p>
)}
</div>
)}
{/* 하단 면책 */}
<p className="text-center text-[11px] text-slate-400 leading-relaxed">
/ .<br />
, .
</p>
</div>
</div>
);
}

View File

@@ -6,6 +6,7 @@ import { EARTHLY_BRANCHES_KR, FIVE_ELEMENTS_KR, FIVE_ELEMENTS } from '@/lib/saju
import { calculateElementScore, performFullAnalysis } from '@/lib/ai-interpretation'; import { calculateElementScore, performFullAnalysis } from '@/lib/ai-interpretation';
import { createClient } from '@/lib/supabase/server'; import { createClient } from '@/lib/supabase/server';
import SajuAISection from './SajuAISection'; import SajuAISection from './SajuAISection';
import SajuLottoSection from './SajuLottoSection';
interface PageProps { interface PageProps {
searchParams: Promise<{ searchParams: Promise<{
@@ -116,13 +117,15 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
const solarTermIndex = getCurrentSolarTerm(yearNum, monthNum, dayNum); const solarTermIndex = getCurrentSolarTerm(yearNum, monthNum, dayNum);
const solarTermName = getSolarTermName(solarTermIndex); const solarTermName = getSolarTermName(solarTermIndex);
// ── 결제 여부 + 저장된 AI 해석 ──────────────────────────────────────── // ── 결제 여부 + 저장된 AI 해석 + 로또 구독 확인 ─────────────────────
let hasPaid = false; let hasPaid = false;
let savedInterpretation: string | null = null; let savedInterpretation: string | null = null;
let hasLottoSubscription = false;
try { try {
const supabase = await createClient(); const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser(); const { data: { user } } = await supabase.auth.getUser();
if (user) { if (user) {
// 사주 결제 확인 (anon client — 본인 orders는 RLS 허용 가정)
const { data: order } = await supabase const { data: order } = await supabase
.from('orders').select('id') .from('orders').select('id')
.eq('user_id', user.id).eq('product_id', 'saju_detail').eq('status', 'paid') .eq('user_id', user.id).eq('product_id', 'saju_detail').eq('status', 'paid')
@@ -138,6 +141,31 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
.contains('saju_data', birthKey).maybeSingle(); .contains('saju_data', birthKey).maybeSingle();
savedInterpretation = record?.interpretation ?? null; savedInterpretation = record?.interpretation ?? null;
} }
// 로또 구독 확인 — subscriptions 테이블 (세션 클라이언트로 RLS select_own 통과)
const { data: lottoSub } = await supabase
.from('subscriptions')
.select('id')
.eq('user_id', user.id)
.eq('status', 'active')
.in('product_id', ['lotto_gold', 'lotto_platinum', 'lotto_diamond', 'lotto_annual'])
.maybeSingle();
hasLottoSubscription = !!lottoSub;
// subscriptions에서 못 찾으면 orders 테이블로 폴백 (구독 마이그레이션 전 데이터)
if (!hasLottoSubscription) {
const now = new Date().toISOString();
const thirtyOneDaysAgo = new Date(Date.now() - 31 * 24 * 60 * 60 * 1000).toISOString();
const { data: lottoOrder } = await supabase
.from('orders')
.select('id, created_at')
.eq('user_id', user.id)
.eq('status', 'paid')
.in('product_id', ['lotto_gold', 'lotto_platinum', 'lotto_diamond', 'lotto_annual'])
.gte('created_at', thirtyOneDaysAgo)
.maybeSingle();
hasLottoSubscription = !!lottoOrder;
}
} }
} catch { } catch {
// 미로그인 시 무시 // 미로그인 시 무시
@@ -565,6 +593,29 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
); );
})()} })()}
{/* 사주 연동 로또 번호 추천 (사주 결제 시 표시) */}
{hasPaid && (
<SajuLottoSection
yongShin={analysis.yongShin.yongShin}
yongShinKr={analysis.yongShin.yongShinKr}
heeShin={analysis.yongShin.heeShin}
heeShinKr={analysis.yongShin.heeShinKr}
dayBranch={sajuData.day.branch}
dayStemKr={sajuData.day.stemKr}
currentDaeun={currentDaeun ? {
stemKr: currentDaeun.stemKr,
branchKr: currentDaeun.branchKr,
startYear: currentDaeun.startYear,
endYear: currentDaeun.endYear,
age: currentDaeun.age,
} : null}
yearNum={yearNum}
monthNum={monthNum}
dayNum={dayNum}
hasLottoSubscription={hasLottoSubscription}
/>
)}
{/* 대운 */} {/* 대운 */}
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-6"> <div className="bg-white rounded-2xl border border-[#dbe8ff] p-6">
<h2 className="text-lg font-extrabold text-[#04102b] mb-5 text-center"> <h2 className="text-lg font-extrabold text-[#04102b] mb-5 text-center">

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,443 @@
'use client';
import Link from 'next/link';
import { useState } from 'react';
const samples = [
{
type: 'corporate',
title: '기업 홈페이지',
subtitle: '테크솔루션㈜',
desc: '신뢰감 있는 기업 브랜드를 구축하는 전문 비즈니스 사이트',
gradient: 'linear-gradient(135deg, #0a192f 0%, #112240 50%, #1a3a6c 100%)',
accent: '#4fc3f7',
tags: ['기업', 'B2B', '신뢰'],
icon: '🏢',
},
{
type: 'bakery',
title: '베이커리 홈페이지',
subtitle: '르 쁘띠 포르',
desc: '따뜻하고 감성적인 분위기로 고객의 마음을 사로잡는 매장 사이트',
gradient: 'linear-gradient(135deg, #78350f 0%, #92400e 50%, #d97706 100%)',
accent: '#fbbf24',
tags: ['F&B', '로컬', '감성'],
icon: '🥐',
},
{
type: 'portfolio',
title: '개인 포트폴리오',
subtitle: 'Kim Jisu',
desc: '크리에이티브한 개성을 드러내는 임팩트 있는 포트폴리오 사이트',
gradient: 'linear-gradient(135deg, #000000 0%, #0d0d0d 50%, #001a00 100%)',
accent: '#00ff88',
tags: ['크리에이터', '디자이너', '개발자'],
icon: '✦',
},
{
type: 'dashboard',
title: '관리자 대시보드',
subtitle: 'DataFlow SaaS',
desc: '데이터를 한눈에 파악하는 직관적인 SaaS 대시보드 시스템',
gradient: 'linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f2a3a 100%)',
accent: '#38bdf8',
tags: ['SaaS', '분석', '관리'],
icon: '📊',
},
{
type: 'game',
title: '게임 매칭 시스템',
subtitle: 'NEXUS ARENA',
desc: '플레이어를 흥분시키는 사이버펑크 스타일의 게임 매칭 플랫폼',
gradient: 'linear-gradient(135deg, #000000 0%, #0a0a1a 50%, #0d0029 100%)',
accent: '#a855f7',
tags: ['게임', '멀티플레이', '랭킹'],
icon: '⚡',
},
];
const processSteps = [
{ step: '01', title: '무료 상담', desc: '요구사항 파악 및 방향성 논의', icon: '💬' },
{ step: '02', title: '기획', desc: '사이트맵 & 와이어프레임', icon: '📋' },
{ step: '03', title: '디자인', desc: 'UI/UX 시안 제작', icon: '🎨' },
{ step: '04', title: '개발', desc: '반응형 퍼블리싱 & 기능 구현', icon: '⚙️' },
{ step: '05', title: '납품', desc: '검수 완료 후 도메인 배포', icon: '🚀' },
];
const plans = [
{
name: '스타터',
price: '50',
unit: '만원~',
color: '#38bdf8',
features: ['5페이지 이내', '반응형 디자인', '기본 SEO 설정', '1개월 유지보수', '3~5영업일 납품'],
note: '개인 블로그, 소규모 소개 사이트',
},
{
name: '비즈니스',
price: '150',
unit: '만원~',
color: '#818cf8',
featured: true,
features: ['10페이지 이내', '반응형 디자인', '관리자 페이지', 'SEO 최적화', '3개월 유지보수', '1~2주 납품'],
note: '기업 사이트, 브랜드 페이지',
},
{
name: '프리미엄',
price: '300',
unit: '만원~',
color: '#f472b6',
features: ['페이지 수 무제한', '맞춤 디자인', '결제/회원 시스템', 'DB 연동', '6개월 유지보수', '일정 협의'],
note: '쇼핑몰, SaaS, 복합 시스템',
},
];
const faqs = [
{
q: '제작 기간은 얼마나 걸리나요?',
a: '규모에 따라 다르지만, 스타터는 3~5영업일, 비즈니스는 1~2주, 프리미엄은 협의 후 결정합니다. 빠른 납품이 필요한 경우 별도 상담해 주세요.',
},
{
q: '수정은 몇 번까지 가능한가요?',
a: '기획 확정 후 디자인 시안 수정은 2회, 개발 완료 후 기능 수정은 유지보수 기간 내 자유롭게 가능합니다. 추가 기능 개발은 별도 견적으로 진행합니다.',
},
{
q: '도메인과 호스팅도 도와주시나요?',
a: '네, 도메인 구매부터 서버 세팅, 배포까지 전 과정을 도와드립니다. Vercel, AWS, 카페24 등 원하시는 플랫폼에 맞춰 배포해 드립니다.',
},
];
export default function WebsiteServicePage() {
const [openFaq, setOpenFaq] = useState<number | null>(null);
return (
<div style={{ background: '#020817', minHeight: '100vh', color: 'white' }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;700;800&family=Noto+Sans+KR:wght@300;400;500;700&display=swap');
@keyframes fadeUp { from { opacity: 0; transform: translateY(28px); } to { opacity: 1; transform: translateY(0); } }
@keyframes gridScroll { from { background-position: 0 0; } to { background-position: 48px 48px; } }
.ws-card:hover { transform: translateY(-5px); box-shadow: 0 20px 60px rgba(0,0,0,0.5); }
.ws-card { transition: transform 0.3s ease, box-shadow 0.3s ease; }
.ws-plan:hover { transform: translateY(-3px); }
.ws-plan { transition: transform 0.3s ease; }
`}</style>
{/* Hero */}
<section style={{ padding: '72px 24px 56px', textAlign: 'center', position: 'relative', overflow: 'hidden' }}>
<div style={{
position: 'absolute', inset: 0,
backgroundImage: 'linear-gradient(rgba(99,102,241,0.06) 1px, transparent 1px), linear-gradient(90deg, rgba(99,102,241,0.06) 1px, transparent 1px)',
backgroundSize: '48px 48px',
animation: 'gridScroll 8s linear infinite',
}} />
<div style={{
position: 'absolute', inset: 0,
background: 'radial-gradient(ellipse 70% 60% at 50% 0%, rgba(99,102,241,0.18) 0%, transparent 70%)',
}} />
<div style={{ maxWidth: 820, margin: '0 auto', position: 'relative', animation: 'fadeUp 0.8s ease forwards' }}>
<span style={{
display: 'inline-block', fontSize: 11, fontWeight: 700, letterSpacing: '0.2em',
color: '#818cf8', textTransform: 'uppercase',
border: '1px solid rgba(129,140,248,0.35)', padding: '5px 16px', borderRadius: 100,
marginBottom: 28, fontFamily: 'Syne, sans-serif',
}}>
Homepage Development Service
</span>
<h1 style={{
fontFamily: 'Syne, sans-serif', fontSize: 'clamp(30px, 5vw, 58px)', fontWeight: 800,
lineHeight: 1.15, marginBottom: 20,
background: 'linear-gradient(135deg, #ffffff 0%, #c7d2fe 50%, #818cf8 100%)',
WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
}}>
,<br />
</h1>
<p style={{
fontSize: 16, color: '#94a3b8', lineHeight: 1.8, marginBottom: 36,
fontFamily: "'Noto Sans KR', sans-serif",
}}>
7 ··· .<br />
, .
</p>
<div style={{ display: 'flex', gap: 12, justifyContent: 'center', flexWrap: 'wrap' }}>
<Link href="/freelance?service=website" style={{
display: 'inline-block', padding: '14px 32px',
background: 'linear-gradient(135deg, #6366f1, #818cf8)',
borderRadius: 12, color: 'white', fontWeight: 700, fontSize: 15,
textDecoration: 'none', fontFamily: "'Noto Sans KR', sans-serif",
boxShadow: '0 8px 32px rgba(99,102,241,0.4)',
}}>
</Link>
<a href="#samples" style={{
display: 'inline-block', padding: '14px 32px',
border: '1px solid rgba(255,255,255,0.12)', borderRadius: 12,
color: '#cbd5e1', fontWeight: 600, fontSize: 15,
textDecoration: 'none', fontFamily: "'Noto Sans KR', sans-serif",
}}>
</a>
</div>
{/* Stats */}
<div style={{ display: 'flex', gap: 32, justifyContent: 'center', marginTop: 48, flexWrap: 'wrap' }}>
{[
{ num: '3~5일', label: '최단 납품' },
{ num: '50만원~', label: '시작 가격' },
{ num: '100%', label: '반응형 지원' },
].map((s) => (
<div key={s.label} style={{ textAlign: 'center' }}>
<div style={{ fontSize: 24, fontWeight: 800, color: 'white', fontFamily: 'Syne, sans-serif' }}>{s.num}</div>
<div style={{ fontSize: 12, color: '#475569', fontFamily: "'Noto Sans KR', sans-serif", marginTop: 2 }}>{s.label}</div>
</div>
))}
</div>
</div>
</section>
{/* Sample Portfolio */}
<section id="samples" style={{ padding: '56px 24px', maxWidth: 1100, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 40 }}>
<h2 style={{
fontFamily: 'Syne, sans-serif', fontSize: 28, fontWeight: 800,
color: 'white', marginBottom: 10,
}}>
</h2>
<p style={{ color: '#64748b', fontFamily: "'Noto Sans KR', sans-serif", fontSize: 15 }}>
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 20 }}>
{samples.map((s) => (
<Link key={s.type} href={`/services/website/samples/${s.type}`} style={{ textDecoration: 'none' }}>
<div className="ws-card" style={{
borderRadius: 20, overflow: 'hidden',
border: '1px solid rgba(255,255,255,0.07)',
background: '#0a1020', cursor: 'pointer',
}}>
<div style={{
height: 170, background: s.gradient, position: 'relative',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<span style={{ fontSize: 52, filter: 'drop-shadow(0 4px 16px rgba(0,0,0,0.6))' }}>{s.icon}</span>
<div style={{
position: 'absolute', top: 12, left: 12,
display: 'flex', gap: 5,
}}>
{s.tags.map((tag) => (
<span key={tag} style={{
fontSize: 10, fontWeight: 600, color: '#e2e8f0',
background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(8px)',
border: '1px solid rgba(255,255,255,0.12)',
padding: '2px 8px', borderRadius: 100,
}}>{tag}</span>
))}
</div>
<div style={{
position: 'absolute', bottom: 12, right: 12,
background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(8px)',
border: `1px solid ${s.accent}50`,
borderRadius: 8, padding: '5px 12px',
fontSize: 11, color: s.accent, fontFamily: 'Syne, sans-serif', fontWeight: 700,
}}>
</div>
</div>
<div style={{ padding: '18px 22px 22px' }}>
<div style={{ fontSize: 11, color: '#475569', fontFamily: 'Syne, sans-serif', marginBottom: 5, letterSpacing: '0.05em' }}>
{s.subtitle}
</div>
<div style={{ fontSize: 17, fontWeight: 700, color: 'white', fontFamily: 'Syne, sans-serif', marginBottom: 8 }}>
{s.title}
</div>
<div style={{ fontSize: 13, color: '#64748b', lineHeight: 1.65, fontFamily: "'Noto Sans KR', sans-serif" }}>
{s.desc}
</div>
</div>
</div>
</Link>
))}
</div>
</section>
{/* Process */}
<section style={{ padding: '56px 24px', background: 'rgba(255,255,255,0.02)', borderTop: '1px solid rgba(255,255,255,0.05)', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ maxWidth: 1000, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 40 }}>
<h2 style={{ fontFamily: 'Syne, sans-serif', fontSize: 28, fontWeight: 800, color: 'white', marginBottom: 10 }}>
</h2>
<p style={{ color: '#64748b', fontFamily: "'Noto Sans KR', sans-serif", fontSize: 15 }}>
5
</p>
</div>
<div style={{ display: 'flex', alignItems: 'stretch', flexWrap: 'wrap', justifyContent: 'center', gap: 0 }}>
{processSteps.map((p, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center' }}>
<div style={{
textAlign: 'center', padding: '24px 20px', minWidth: 130,
background: '#0f172a', borderRadius: 16,
border: '1px solid rgba(255,255,255,0.06)',
}}>
<div style={{ fontSize: 30, marginBottom: 10 }}>{p.icon}</div>
<div style={{
fontSize: 10, color: '#6366f1', fontFamily: 'Syne, sans-serif',
fontWeight: 700, letterSpacing: '0.1em', marginBottom: 6,
}}>
STEP {p.step}
</div>
<div style={{ fontSize: 14, fontWeight: 700, color: 'white', fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 5 }}>
{p.title}
</div>
<div style={{ fontSize: 11, color: '#475569', fontFamily: "'Noto Sans KR', sans-serif", lineHeight: 1.5 }}>
{p.desc}
</div>
</div>
{i < processSteps.length - 1 && (
<div style={{ color: '#1e293b', fontSize: 22, padding: '0 6px', flexShrink: 0 }}></div>
)}
</div>
))}
</div>
</div>
</section>
{/* Pricing */}
<section style={{ padding: '56px 24px', maxWidth: 1000, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 40 }}>
<h2 style={{ fontFamily: 'Syne, sans-serif', fontSize: 28, fontWeight: 800, color: 'white', marginBottom: 10 }}>
</h2>
<p style={{ color: '#64748b', fontFamily: "'Noto Sans KR', sans-serif", fontSize: 15 }}>
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 20 }}>
{plans.map((plan) => (
<div key={plan.name} className="ws-plan" style={{
padding: 30, borderRadius: 20,
background: plan.featured ? 'linear-gradient(135deg, #1e1b4b, #312e81)' : '#0f172a',
border: `1px solid ${plan.featured ? plan.color + '55' : 'rgba(255,255,255,0.06)'}`,
position: 'relative', overflow: 'hidden',
boxShadow: plan.featured ? `0 24px 64px ${plan.color}18` : 'none',
}}>
{plan.featured && (
<div style={{
position: 'absolute', top: 18, right: 18,
background: plan.color, color: '#1e1b4b',
fontSize: 10, fontWeight: 800, padding: '3px 10px', borderRadius: 100,
fontFamily: 'Syne, sans-serif',
}}>BEST</div>
)}
<div style={{ fontSize: 13, color: plan.color, fontFamily: 'Syne, sans-serif', fontWeight: 700, marginBottom: 10 }}>
{plan.name}
</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginBottom: 4 }}>
<span style={{ fontSize: 42, fontWeight: 800, color: 'white', fontFamily: 'Syne, sans-serif' }}>
{plan.price}
</span>
<span style={{ fontSize: 16, color: '#94a3b8', fontFamily: "'Noto Sans KR', sans-serif" }}>
{plan.unit}
</span>
</div>
<div style={{ fontSize: 12, color: '#475569', fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 22 }}>
{plan.note}
</div>
<div style={{ borderTop: '1px solid rgba(255,255,255,0.07)', paddingTop: 20, marginBottom: 24 }}>
{plan.features.map((f) => (
<div key={f} style={{ display: 'flex', alignItems: 'center', gap: 9, marginBottom: 11 }}>
<span style={{ color: plan.color, fontSize: 14, flexShrink: 0, fontWeight: 700 }}></span>
<span style={{ fontSize: 13, color: '#94a3b8', fontFamily: "'Noto Sans KR', sans-serif" }}>{f}</span>
</div>
))}
</div>
<Link href="/freelance?service=website" style={{
display: 'block', textAlign: 'center', padding: '12px',
background: plan.featured ? plan.color : 'rgba(255,255,255,0.05)',
borderRadius: 10, color: plan.featured ? '#1e1b4b' : '#cbd5e1',
fontWeight: 700, fontSize: 14, textDecoration: 'none',
fontFamily: "'Noto Sans KR', sans-serif",
border: plan.featured ? 'none' : '1px solid rgba(255,255,255,0.08)',
}}>
</Link>
</div>
))}
</div>
</section>
{/* FAQ */}
<section style={{ padding: '0 24px 56px', maxWidth: 720, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 32 }}>
<h2 style={{ fontFamily: 'Syne, sans-serif', fontSize: 28, fontWeight: 800, color: 'white', marginBottom: 10 }}>
</h2>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
{faqs.map((faq, i) => (
<div key={i} style={{
background: '#0f172a', borderRadius: 14,
border: `1px solid ${openFaq === i ? 'rgba(99,102,241,0.45)' : 'rgba(255,255,255,0.06)'}`,
overflow: 'hidden', transition: 'border-color 0.25s',
}}>
<button onClick={() => setOpenFaq(openFaq === i ? null : i)} style={{
width: '100%', textAlign: 'left', padding: '18px 22px',
background: 'none', border: 'none', cursor: 'pointer',
display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12,
}}>
<span style={{ fontSize: 14, fontWeight: 600, color: 'white', fontFamily: "'Noto Sans KR', sans-serif" }}>
{faq.q}
</span>
<span style={{
color: '#6366f1', fontSize: 22, flexShrink: 0,
transition: 'transform 0.2s',
transform: openFaq === i ? 'rotate(45deg)' : 'none',
display: 'inline-block',
}}>+</span>
</button>
{openFaq === i && (
<div style={{
padding: '0 22px 18px', fontSize: 14, color: '#64748b',
lineHeight: 1.75, fontFamily: "'Noto Sans KR', sans-serif",
}}>
{faq.a}
</div>
)}
</div>
))}
</div>
</section>
{/* CTA */}
<section style={{ padding: '0 24px 80px', textAlign: 'center' }}>
<div style={{
maxWidth: 620, margin: '0 auto',
padding: '52px 40px', borderRadius: 24,
background: 'linear-gradient(135deg, #1e1b4b, #312e81)',
border: '1px solid rgba(129,140,248,0.3)',
boxShadow: '0 24px 80px rgba(99,102,241,0.2)',
}}>
<h2 style={{ fontFamily: 'Syne, sans-serif', fontSize: 28, fontWeight: 800, color: 'white', marginBottom: 14 }}>
</h2>
<p style={{
color: '#a5b4fc', fontSize: 15, lineHeight: 1.7, marginBottom: 30,
fontFamily: "'Noto Sans KR', sans-serif",
}}>
24 .<br />
.
</p>
<Link href="/freelance?service=website" style={{
display: 'inline-block', padding: '15px 40px',
background: 'linear-gradient(135deg, #818cf8, #6366f1)',
borderRadius: 12, color: 'white', fontWeight: 700, fontSize: 16,
textDecoration: 'none', fontFamily: "'Noto Sans KR', sans-serif",
boxShadow: '0 8px 32px rgba(99,102,241,0.5)',
}}>
</Link>
</div>
</section>
</div>
);
}

View File

@@ -0,0 +1,325 @@
import Link from 'next/link';
export default function BakerySample() {
const menuItems = [
{ name: '버터 크루아상', price: '3,200', emoji: '🥐', tag: '인기', desc: '프랑스산 에슈레 버터만 사용한 겹겹이 살아있는 크루아상' },
{ name: '소금빵', price: '2,800', emoji: '🍞', tag: '베스트', desc: '오키나와 천연 소금과 발효 버터가 만나는 완벽한 짭조름함' },
{ name: '딸기 쇼트케이크', price: '7,500', emoji: '🍰', tag: '신메뉴', desc: '국내산 딸기와 생크림이 만나는 클래식 케이크' },
{ name: '캄파뉴', price: '8,900', emoji: '🫓', tag: '장인', desc: '72시간 저온 발효로 만든 시큼하고 깊은 맛의 통밀빵' },
];
const hours = [
{ day: '월~금', time: '07:00 20:00' },
{ day: '토요일', time: '07:00 21:00' },
{ day: '일요일', time: '09:00 18:00' },
{ day: '공휴일', time: '09:00 18:00' },
];
return (
<div style={{ background: '#fffbf5', minHeight: '100vh', color: '#1c1008' }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;1,400;1,700&family=Noto+Sans+KR:wght@300;400;500;700&display=swap');
@keyframes fadeUp { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }
@keyframes float { 0%, 100% { transform: translateY(0px) rotate(-2deg); } 50% { transform: translateY(-10px) rotate(2deg); } }
.menu-card:hover { transform: translateY(-6px); box-shadow: 0 24px 60px rgba(120,53,15,0.12); }
.menu-card { transition: transform 0.35s, box-shadow 0.35s; }
.bk-btn:hover { background: #92400e !important; }
.bk-btn { transition: background 0.2s; }
`}</style>
{/* Back Banner */}
<div style={{
background: 'linear-gradient(135deg, #1e1b4b, #312e81)',
padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 12,
}}>
<Link href="/services/website" style={{
color: '#a5b4fc', fontSize: 13, textDecoration: 'none',
fontFamily: "'Noto Sans KR', sans-serif",
}}>
</Link>
<span style={{ color: '#4c1d95', fontSize: 13 }}>|</span>
<span style={{ color: '#6366f1', fontSize: 12, fontFamily: 'Playfair Display, serif', fontWeight: 700 }}>
SAMPLE ·
</span>
</div>
{/* Navbar */}
<nav style={{
background: 'rgba(255,251,245,0.95)', backdropFilter: 'blur(12px)',
borderBottom: '1px solid #fde8c8',
padding: '0 48px', height: 68,
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
position: 'sticky', top: 0, zIndex: 100,
}}>
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: 20, fontFamily: 'Playfair Display, serif', fontStyle: 'italic', color: '#78350f', fontWeight: 700 }}>
Le Petit Fort
</div>
<div style={{ fontSize: 10, color: '#a78060', letterSpacing: '0.2em', textTransform: 'uppercase' }}>Artisan Boulangerie</div>
</div>
<div style={{ display: 'flex', gap: 28 }}>
{['메뉴', '스토리', '매장안내', '예약'].map((item) => (
<span key={item} style={{
fontSize: 14, color: '#78350f', cursor: 'pointer',
fontFamily: "'Noto Sans KR', sans-serif", fontWeight: 500,
}}>{item}</span>
))}
</div>
<button className="bk-btn" style={{
background: '#b45309', color: 'white', border: 'none',
padding: '9px 22px', borderRadius: 24, fontSize: 13,
fontWeight: 700, cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
}}>
</button>
</nav>
{/* Hero */}
<section style={{
background: 'linear-gradient(160deg, #fef3c7 0%, #fde68a 35%, #fbbf24 70%, #d97706 100%)',
padding: '80px 48px', position: 'relative', overflow: 'hidden', minHeight: 480,
display: 'flex', alignItems: 'center',
}}>
{/* Decorative circles */}
<div style={{
position: 'absolute', right: -60, top: -60,
width: 400, height: 400, borderRadius: '50%',
background: 'rgba(180,83,9,0.08)',
}} />
<div style={{
position: 'absolute', right: 80, bottom: -40,
width: 240, height: 240, borderRadius: '50%',
background: 'rgba(180,83,9,0.12)',
}} />
<div style={{
position: 'absolute', right: '10%', top: '50%', transform: 'translateY(-50%)',
fontSize: 120,
animation: 'float 4s ease-in-out infinite',
filter: 'drop-shadow(0 12px 24px rgba(120,53,15,0.25))',
}}>🥐</div>
<div style={{ maxWidth: 560, animation: 'fadeUp 0.8s ease forwards' }}>
<div style={{
fontSize: 13, color: '#78350f', fontFamily: 'Playfair Display, serif',
fontStyle: 'italic', marginBottom: 16, letterSpacing: '0.05em',
}}>
"매일 아침, 정성을 굽습니다"
</div>
<h1 style={{
fontFamily: 'Playfair Display, serif', fontSize: 'clamp(40px, 5vw, 64px)',
fontWeight: 700, color: '#451a03', lineHeight: 1.15, marginBottom: 18,
}}>
<br />
<span style={{ color: '#b45309' }}> </span><br />
</h1>
<p style={{
color: '#78350f', fontSize: 16, lineHeight: 1.8, marginBottom: 32,
fontFamily: "'Noto Sans KR', sans-serif",
}}>
4<br />
.
</p>
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
<button className="bk-btn" style={{
background: '#b45309', color: 'white', border: 'none',
padding: '14px 32px', borderRadius: 28, fontSize: 15, fontWeight: 700,
cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
boxShadow: '0 8px 24px rgba(180,83,9,0.35)',
}}>
</button>
<button style={{
background: 'rgba(255,255,255,0.6)', color: '#78350f',
border: '1px solid rgba(180,83,9,0.3)',
padding: '14px 32px', borderRadius: 28, fontSize: 15, fontWeight: 600,
cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
backdropFilter: 'blur(8px)',
}}>
</button>
</div>
</div>
</section>
{/* Menu */}
<section style={{ padding: '80px 48px', background: '#fffbf5' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 52 }}>
<div style={{
fontSize: 12, color: '#b45309', fontWeight: 700, letterSpacing: '0.2em',
textTransform: 'uppercase', fontFamily: 'Playfair Display, serif', marginBottom: 12,
}}>Today&apos;s Menu</div>
<h2 style={{ fontFamily: 'Playfair Display, serif', fontSize: 40, color: '#451a03', marginBottom: 12 }}>
</h2>
<p style={{ color: '#a78060', fontSize: 15, fontFamily: "'Noto Sans KR', sans-serif" }}>
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: 24 }}>
{menuItems.map((item) => (
<div key={item.name} className="menu-card" style={{
background: 'white', borderRadius: 20,
border: '1px solid #fde8c8', overflow: 'hidden',
boxShadow: '0 4px 20px rgba(120,53,15,0.05)',
}}>
<div style={{
height: 140, background: 'linear-gradient(135deg, #fef3c7, #fde68a)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 60, position: 'relative',
}}>
{item.emoji}
<span style={{
position: 'absolute', top: 12, right: 12,
background: '#b45309', color: 'white',
fontSize: 10, fontWeight: 700, padding: '3px 9px', borderRadius: 100,
fontFamily: "'Noto Sans KR', sans-serif",
}}>{item.tag}</span>
</div>
<div style={{ padding: '18px 20px' }}>
<div style={{ fontSize: 17, fontWeight: 700, color: '#451a03', fontFamily: 'Playfair Display, serif', marginBottom: 8 }}>
{item.name}
</div>
<div style={{ fontSize: 13, color: '#a78060', lineHeight: 1.65, fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 14 }}>
{item.desc}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontSize: 18, fontWeight: 700, color: '#b45309', fontFamily: 'Playfair Display, serif' }}>
{item.price}
</span>
<button style={{
background: '#fef3c7', color: '#b45309', border: 'none',
padding: '6px 14px', borderRadius: 12, fontSize: 12,
fontWeight: 700, cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
}}></button>
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* Story */}
<section style={{ padding: '80px 48px', background: '#fef9f0', borderTop: '1px solid #fde8c8' }}>
<div style={{ maxWidth: 900, margin: '0 auto', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 60, alignItems: 'center' }}>
<div>
<div style={{ fontSize: 12, color: '#b45309', fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', fontFamily: 'Playfair Display, serif', marginBottom: 12 }}>
Our Story
</div>
<h2 style={{ fontFamily: 'Playfair Display, serif', fontSize: 36, color: '#451a03', lineHeight: 1.3, marginBottom: 20 }}>
2009<br /> <br />
<em> </em>
</h2>
<p style={{ color: '#78350f', fontSize: 15, lineHeight: 1.9, fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 16 }}>
5 . , .
</p>
<p style={{ color: '#78350f', fontSize: 15, lineHeight: 1.9, fontFamily: "'Noto Sans KR', sans-serif" }}>
, , , . .
</p>
</div>
<div style={{
background: 'linear-gradient(135deg, #fde68a, #fbbf24)',
borderRadius: 24, padding: '48px 36px', textAlign: 'center',
border: '2px solid #fcd34d',
}}>
<div style={{ fontSize: 72, marginBottom: 16 }}>👨🍳</div>
<div style={{ fontSize: 22, fontFamily: 'Playfair Display, serif', color: '#451a03', fontWeight: 700 }}>
Chef Kim Dongwoo
</div>
<div style={{ fontSize: 13, color: '#78350f', fontFamily: "'Noto Sans KR', sans-serif", marginTop: 8, lineHeight: 1.6 }}>
Le Cordon Bleu Paris <br />
2009
</div>
<div style={{
marginTop: 20, display: 'flex', justifyContent: 'center', gap: 20,
}}>
{[{ n: '15+', l: '년 경력' }, { n: '200+', l: '종류의 빵' }, { n: '4시', l: '매일 기상' }].map((s) => (
<div key={s.l} style={{ textAlign: 'center' }}>
<div style={{ fontSize: 20, fontWeight: 800, color: '#451a03', fontFamily: 'Playfair Display, serif' }}>{s.n}</div>
<div style={{ fontSize: 11, color: '#78350f', fontFamily: "'Noto Sans KR', sans-serif" }}>{s.l}</div>
</div>
))}
</div>
</div>
</div>
</section>
{/* Hours & Location */}
<section style={{ padding: '72px 48px', background: '#451a03' }}>
<div style={{ maxWidth: 900, margin: '0 auto', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 60 }}>
<div>
<div style={{ fontSize: 12, color: '#fbbf24', fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', fontFamily: 'Playfair Display, serif', marginBottom: 16 }}>
Hours
</div>
<h3 style={{ fontFamily: 'Playfair Display, serif', fontSize: 28, color: 'white', marginBottom: 28 }}>
</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{hours.map((h) => (
<div key={h.day} style={{
display: 'flex', justifyContent: 'space-between',
borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: 12,
}}>
<span style={{ fontSize: 14, color: '#fde68a', fontFamily: "'Noto Sans KR', sans-serif", fontWeight: 600 }}>{h.day}</span>
<span style={{ fontSize: 14, color: '#fcd34d', fontFamily: 'Playfair Display, serif' }}>{h.time}</span>
</div>
))}
</div>
</div>
<div>
<div style={{ fontSize: 12, color: '#fbbf24', fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', fontFamily: 'Playfair Display, serif', marginBottom: 16 }}>
Location
</div>
<h3 style={{ fontFamily: 'Playfair Display, serif', fontSize: 28, color: 'white', marginBottom: 20 }}>
</h3>
<p style={{ color: '#fde68a', fontSize: 15, lineHeight: 1.7, fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 16 }}>
224-14<br />
68
</p>
<div style={{ display: 'flex', gap: 8, flexDirection: 'column' }}>
<div style={{ color: '#fcd34d', fontSize: 13, fontFamily: "'Noto Sans KR', sans-serif" }}>📍 2 3 5</div>
<div style={{ color: '#fcd34d', fontSize: 13, fontFamily: "'Noto Sans KR', sans-serif" }}>📞 02-334-5678</div>
<div style={{ color: '#fcd34d', fontSize: 13, fontFamily: "'Noto Sans KR', sans-serif" }}>📱 @lepetitfort_seoul</div>
</div>
</div>
</div>
</section>
{/* CTA */}
<section style={{
background: 'linear-gradient(135deg, #fef3c7, #fde68a)',
padding: '64px 48px', textAlign: 'center',
}}>
<h2 style={{ fontFamily: 'Playfair Display, serif', fontSize: 36, color: '#451a03', marginBottom: 14 }}>
</h2>
<p style={{ color: '#78350f', fontSize: 16, lineHeight: 1.7, marginBottom: 28, fontFamily: "'Noto Sans KR', sans-serif" }}>
, , .<br />
3 .
</p>
<button style={{
background: '#b45309', color: 'white', border: 'none',
padding: '15px 40px', borderRadius: 28, fontSize: 16, fontWeight: 700,
cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
boxShadow: '0 8px 24px rgba(180,83,9,0.4)',
}}>
</button>
</section>
{/* Footer */}
<footer style={{ background: '#1c1008', padding: '28px 48px', textAlign: 'center' }}>
<div style={{ color: '#78350f', fontSize: 13, fontFamily: 'Playfair Display, serif', fontStyle: 'italic', marginBottom: 6 }}>
Le Petit Fort Artisan Boulangerie
</div>
<div style={{ color: '#3c1a08', fontSize: 12, fontFamily: "'Noto Sans KR', sans-serif" }}>
© 2024 . All rights reserved.
</div>
</footer>
</div>
);
}

View File

@@ -0,0 +1,292 @@
import Link from 'next/link';
export default function CorporateSample() {
const services = [
{
icon: '🔧',
title: 'IT 인프라 구축',
desc: '기업 맞춤형 서버 환경 설계부터 클라우드 마이그레이션까지, 안정적인 IT 기반을 구축해드립니다.',
},
{
icon: '🔒',
title: '보안 솔루션',
desc: '최신 사이버 위협에 대응하는 엔터프라이즈급 보안 시스템을 제공합니다.',
},
{
icon: '📡',
title: '디지털 전환',
desc: '레거시 시스템을 현대화하고 비즈니스 프로세스를 효율화하는 DX 컨설팅을 제공합니다.',
},
];
const stats = [
{ num: '15+', label: '년 업력' },
{ num: '340+', label: '완료 프로젝트' },
{ num: '180+', label: '기업 고객사' },
{ num: '98%', label: '고객 만족도' },
];
const clients = ['삼성전자', 'LG유플러스', '현대모비스', 'SK하이닉스', 'KT', '신한은행', 'NH농협', '롯데정보통신'];
return (
<div style={{ background: '#ffffff', minHeight: '100vh', color: '#1e293b', fontFamily: "'Malgun Gothic', 'Apple SD Gothic Neo', sans-serif" }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700;800&family=Noto+Sans+KR:wght@300;400;500;700&display=swap');
@keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
@keyframes slideIn { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } }
.corp-nav-link:hover { color: #1d4ed8 !important; }
.corp-service-card:hover { transform: translateY(-4px); box-shadow: 0 20px 60px rgba(0,0,0,0.1); }
.corp-service-card { transition: transform 0.3s, box-shadow 0.3s; }
.corp-btn:hover { background: #1d4ed8 !important; }
.corp-btn { transition: background 0.2s; }
.corp-client:hover { background: #eff6ff !important; color: #1d4ed8 !important; }
.corp-client { transition: background 0.2s, color 0.2s; }
`}</style>
{/* Back Banner */}
<div style={{
background: 'linear-gradient(135deg, #1e1b4b, #312e81)',
padding: '10px 24px',
display: 'flex', alignItems: 'center', gap: 12,
}}>
<Link href="/services/website" style={{
color: '#a5b4fc', fontSize: 13, textDecoration: 'none', fontFamily: "'Noto Sans KR', sans-serif",
display: 'flex', alignItems: 'center', gap: 6,
}}>
</Link>
<span style={{ color: '#4c1d95', fontSize: 13 }}>|</span>
<span style={{ color: '#6366f1', fontSize: 12, fontFamily: 'Montserrat, sans-serif', fontWeight: 700 }}>
SAMPLE ·
</span>
</div>
{/* Navbar */}
<nav style={{
background: '#ffffff', borderBottom: '1px solid #e2e8f0',
padding: '0 48px', display: 'flex', alignItems: 'center',
justifyContent: 'space-between', height: 64, position: 'sticky', top: 0, zIndex: 100,
boxShadow: '0 2px 20px rgba(0,0,0,0.06)',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{
width: 36, height: 36, borderRadius: 8,
background: 'linear-gradient(135deg, #1d4ed8, #1e40af)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: 'white', fontSize: 16, fontWeight: 800, fontFamily: 'Montserrat, sans-serif',
}}>T</div>
<div>
<div style={{ fontSize: 15, fontWeight: 700, color: '#0f172a', fontFamily: 'Montserrat, sans-serif', lineHeight: 1 }}>
TechSolution
</div>
<div style={{ fontSize: 10, color: '#64748b', letterSpacing: '0.05em' }}></div>
</div>
</div>
<div style={{ display: 'flex', gap: 28, alignItems: 'center' }}>
{['회사소개', '서비스', '포트폴리오', '고객사', '채용', '연락처'].map((item) => (
<span key={item} className="corp-nav-link" style={{
fontSize: 14, color: '#475569', cursor: 'pointer',
fontFamily: "'Noto Sans KR', sans-serif", fontWeight: 500,
}}>{item}</span>
))}
<button style={{
background: '#1d4ed8', color: 'white', border: 'none',
padding: '8px 20px', borderRadius: 8, fontSize: 13, fontWeight: 700,
cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
}}>
</button>
</div>
</nav>
{/* Hero */}
<section style={{
background: 'linear-gradient(150deg, #0a192f 0%, #112240 45%, #0d3b7e 100%)',
padding: '100px 48px', position: 'relative', overflow: 'hidden',
}}>
<div style={{
position: 'absolute', inset: 0,
backgroundImage: 'radial-gradient(circle at 80% 50%, rgba(59,130,246,0.15) 0%, transparent 60%)',
}} />
<div style={{
position: 'absolute', right: 80, top: '50%', transform: 'translateY(-50%)',
width: 360, height: 360,
background: 'linear-gradient(135deg, rgba(59,130,246,0.12), rgba(139,92,246,0.08))',
borderRadius: '50%', border: '1px solid rgba(59,130,246,0.2)',
}} />
<div style={{ maxWidth: 700, position: 'relative', animation: 'fadeInUp 0.8s ease forwards' }}>
<div style={{
display: 'inline-block', fontSize: 11, fontWeight: 700, letterSpacing: '0.2em',
color: '#60a5fa', textTransform: 'uppercase',
border: '1px solid rgba(96,165,250,0.3)', padding: '5px 14px', borderRadius: 4,
marginBottom: 24, fontFamily: 'Montserrat, sans-serif',
}}>
Enterprise IT Solutions
</div>
<h1 style={{
fontFamily: 'Montserrat, sans-serif', fontSize: 'clamp(36px, 4vw, 54px)',
fontWeight: 800, color: 'white', lineHeight: 1.2, marginBottom: 20,
}}>
<br />
<span style={{ color: '#60a5fa' }}> </span><br />
</h1>
<p style={{
color: '#94a3b8', fontSize: 17, lineHeight: 1.8, marginBottom: 36,
fontFamily: "'Noto Sans KR', sans-serif",
}}>
15 IT .<br />
, , .
</p>
<div style={{ display: 'flex', gap: 14, flexWrap: 'wrap' }}>
<button className="corp-btn" style={{
background: '#2563eb', color: 'white', border: 'none',
padding: '14px 32px', borderRadius: 8, fontSize: 15, fontWeight: 700,
cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
}}>
</button>
<button style={{
background: 'transparent', color: 'white',
border: '1px solid rgba(255,255,255,0.25)',
padding: '14px 32px', borderRadius: 8, fontSize: 15, fontWeight: 600,
cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
}}>
</button>
</div>
</div>
</section>
{/* Stats */}
<section style={{
background: '#1d4ed8', padding: '40px 48px',
display: 'flex', justifyContent: 'center', gap: 0, flexWrap: 'wrap',
}}>
{stats.map((s, i) => (
<div key={i} style={{
textAlign: 'center', padding: '16px 48px',
borderRight: i < stats.length - 1 ? '1px solid rgba(255,255,255,0.2)' : 'none',
}}>
<div style={{ fontSize: 36, fontWeight: 800, color: 'white', fontFamily: 'Montserrat, sans-serif' }}>
{s.num}
</div>
<div style={{ fontSize: 13, color: '#bfdbfe', fontFamily: "'Noto Sans KR', sans-serif", marginTop: 4 }}>
{s.label}
</div>
</div>
))}
</section>
{/* Services */}
<section style={{ padding: '80px 48px', background: '#f8fafc' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 52 }}>
<div style={{ fontSize: 12, color: '#2563eb', fontWeight: 700, letterSpacing: '0.15em', textTransform: 'uppercase', fontFamily: 'Montserrat, sans-serif', marginBottom: 12 }}>
Our Services
</div>
<h2 style={{ fontFamily: 'Montserrat, sans-serif', fontSize: 36, fontWeight: 800, color: '#0f172a', marginBottom: 14 }}>
</h2>
<p style={{ color: '#64748b', fontSize: 16, fontFamily: "'Noto Sans KR', sans-serif" }}>
IT
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 28 }}>
{services.map((svc) => (
<div key={svc.title} className="corp-service-card" style={{
padding: 36, borderRadius: 16, background: 'white',
border: '1px solid #e2e8f0', boxShadow: '0 4px 24px rgba(0,0,0,0.05)',
}}>
<div style={{
width: 56, height: 56, borderRadius: 14,
background: 'linear-gradient(135deg, #eff6ff, #dbeafe)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 26, marginBottom: 20,
}}>
{svc.icon}
</div>
<h3 style={{ fontSize: 20, fontWeight: 700, color: '#0f172a', fontFamily: 'Montserrat, sans-serif', marginBottom: 12 }}>
{svc.title}
</h3>
<p style={{ fontSize: 14, color: '#64748b', lineHeight: 1.75, fontFamily: "'Noto Sans KR', sans-serif" }}>
{svc.desc}
</p>
<div style={{ marginTop: 20, color: '#2563eb', fontSize: 13, fontWeight: 700, cursor: 'pointer', fontFamily: 'Montserrat, sans-serif' }}>
</div>
</div>
))}
</div>
</div>
</section>
{/* Clients */}
<section style={{ padding: '72px 48px', background: 'white' }}>
<div style={{ maxWidth: 1100, margin: '0 auto', textAlign: 'center' }}>
<div style={{ fontSize: 12, color: '#2563eb', fontWeight: 700, letterSpacing: '0.15em', textTransform: 'uppercase', fontFamily: 'Montserrat, sans-serif', marginBottom: 12 }}>
Trusted By
</div>
<h2 style={{ fontFamily: 'Montserrat, sans-serif', fontSize: 32, fontWeight: 800, color: '#0f172a', marginBottom: 40 }}>
</h2>
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: 12 }}>
{clients.map((c) => (
<div key={c} className="corp-client" style={{
padding: '12px 24px', borderRadius: 10,
border: '1px solid #e2e8f0', background: '#f8fafc',
fontSize: 14, fontWeight: 600, color: '#475569',
fontFamily: "'Noto Sans KR', sans-serif", cursor: 'pointer',
}}>{c}</div>
))}
</div>
</div>
</section>
{/* Contact */}
<section style={{
background: 'linear-gradient(135deg, #0f172a, #1e293b)',
padding: '80px 48px', textAlign: 'center',
}}>
<div style={{ maxWidth: 600, margin: '0 auto' }}>
<h2 style={{ fontFamily: 'Montserrat, sans-serif', fontSize: 36, fontWeight: 800, color: 'white', marginBottom: 16 }}>
</h2>
<p style={{ color: '#94a3b8', fontSize: 16, lineHeight: 1.7, marginBottom: 32, fontFamily: "'Noto Sans KR', sans-serif" }}>
IT .<br />
.
</p>
<div style={{ display: 'flex', gap: 12, justifyContent: 'center', flexWrap: 'wrap' }}>
<button style={{
background: '#2563eb', color: 'white', border: 'none',
padding: '14px 36px', borderRadius: 8, fontSize: 15, fontWeight: 700,
cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
}}>
</button>
<button style={{
background: 'transparent', color: 'white',
border: '1px solid rgba(255,255,255,0.2)',
padding: '14px 36px', borderRadius: 8, fontSize: 15, fontWeight: 600,
cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
}}>
02-1234-5678
</button>
</div>
</div>
</section>
{/* Footer */}
<footer style={{ background: '#020817', padding: '32px 48px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>
<div style={{ color: '#475569', fontSize: 13, fontFamily: "'Noto Sans KR', sans-serif" }}>
© 2024 . All rights reserved.
</div>
<div style={{ color: '#334155', fontSize: 12, fontFamily: 'Montserrat, sans-serif' }}>
123 15F
</div>
</div>
</footer>
</div>
);
}

View File

@@ -0,0 +1,339 @@
'use client';
import Link from 'next/link';
import { useState } from 'react';
export default function DashboardSample() {
const [activeMenu, setActiveMenu] = useState('overview');
const kpis = [
{ label: '월 활성 사용자', value: '124,832', change: '+12.4%', up: true, icon: '👤', color: '#3b82f6' },
{ label: '월 매출', value: '₩284M', change: '+8.7%', up: true, icon: '💰', color: '#10b981' },
{ label: '전환율', value: '3.62%', change: '-0.3%', up: false, icon: '📈', color: '#f59e0b' },
{ label: '고객 만족도', value: '4.8 / 5', change: '+0.2', up: true, icon: '⭐', color: '#8b5cf6' },
];
const chartData = [
{ month: 'Jan', value: 65 },
{ month: 'Feb', value: 78 },
{ month: 'Mar', value: 72 },
{ month: 'Apr', value: 89 },
{ month: 'May', value: 95 },
{ month: 'Jun', value: 82 },
{ month: 'Jul', value: 110 },
{ month: 'Aug', value: 128 },
];
const maxVal = Math.max(...chartData.map((d) => d.value));
const activities = [
{ user: 'lee@company.com', action: '프리미엄 플랜 구독', time: '2분 전', status: 'success' },
{ user: 'park@startup.io', action: 'API 한도 초과 경고', time: '14분 전', status: 'warning' },
{ user: 'kim@corp.kr', action: '팀 멤버 5명 초대', time: '31분 전', status: 'info' },
{ user: 'choi@brand.com', action: '결제 실패 (카드 만료)', time: '1시간 전', status: 'error' },
{ user: 'jung@agency.co', action: '새 워크스페이스 생성', time: '2시간 전', status: 'success' },
];
const menus = [
{ id: 'overview', icon: '⊞', label: 'Overview' },
{ id: 'analytics', icon: '◈', label: 'Analytics' },
{ id: 'users', icon: '◉', label: 'Users' },
{ id: 'revenue', icon: '◆', label: 'Revenue' },
{ id: 'reports', icon: '▣', label: 'Reports' },
{ id: 'settings', icon: '◎', label: 'Settings' },
];
const statusColor = { success: '#10b981', warning: '#f59e0b', error: '#ef4444', info: '#3b82f6' };
return (
<div style={{ background: '#0a0f1e', minHeight: '100vh', color: 'white', fontFamily: 'system-ui, sans-serif' }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&family=DM+Mono:wght@400;500&display=swap');
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes barGrow { from { height: 0; } to { height: var(--h); } }
.dash-menu-item:hover { background: rgba(255,255,255,0.05) !important; }
.dash-menu-item { transition: background 0.15s; }
.dash-kpi:hover { border-color: rgba(255,255,255,0.12) !important; transform: translateY(-2px); }
.dash-kpi { transition: border-color 0.2s, transform 0.2s; }
.dash-row:hover { background: rgba(255,255,255,0.03) !important; }
.dash-row { transition: background 0.15s; }
`}</style>
{/* Back Banner */}
<div style={{
background: 'linear-gradient(135deg, #1e1b4b, #312e81)',
padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 12,
position: 'relative', zIndex: 200,
}}>
<Link href="/services/website" style={{
color: '#a5b4fc', fontSize: 13, textDecoration: 'none',
fontFamily: 'DM Sans, sans-serif',
}}>
</Link>
<span style={{ color: '#4c1d95', fontSize: 13 }}>|</span>
<span style={{ color: '#38bdf8', fontSize: 12, fontFamily: 'DM Mono, monospace', fontWeight: 500 }}>
SAMPLE ·
</span>
</div>
<div style={{ display: 'flex', height: 'calc(100vh - 40px)' }}>
{/* Sidebar */}
<aside style={{
width: 220, background: '#060b18',
borderRight: '1px solid rgba(255,255,255,0.05)',
display: 'flex', flexDirection: 'column', flexShrink: 0,
}}>
{/* Logo */}
<div style={{ padding: '20px 20px 16px', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{
width: 32, height: 32, borderRadius: 8,
background: 'linear-gradient(135deg, #3b82f6, #06b6d4)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 14, fontWeight: 700, fontFamily: 'DM Mono, monospace',
}}>DF</div>
<div>
<div style={{ fontSize: 14, fontWeight: 700, color: 'white', fontFamily: 'DM Sans, sans-serif' }}>DataFlow</div>
<div style={{ fontSize: 10, color: '#334155', fontFamily: 'DM Mono, monospace' }}>v2.4.1 · PRO</div>
</div>
</div>
</div>
{/* Nav */}
<nav style={{ flex: 1, padding: '12px 10px', overflowY: 'auto' }}>
<div style={{ fontSize: 9, color: '#1e3a5f', fontFamily: 'DM Mono, monospace', letterSpacing: '0.15em', padding: '8px 10px 4px', textTransform: 'uppercase' }}>
MAIN
</div>
{menus.map((m) => (
<button
key={m.id}
className="dash-menu-item"
onClick={() => setActiveMenu(m.id)}
style={{
width: '100%', textAlign: 'left',
padding: '9px 12px', borderRadius: 8, border: 'none', cursor: 'pointer',
display: 'flex', alignItems: 'center', gap: 10, marginBottom: 2,
background: activeMenu === m.id ? 'rgba(59,130,246,0.15)' : 'transparent',
color: activeMenu === m.id ? '#60a5fa' : '#475569',
}}
>
<span style={{ fontSize: 14 }}>{m.icon}</span>
<span style={{ fontSize: 13, fontWeight: activeMenu === m.id ? 600 : 400, fontFamily: 'DM Sans, sans-serif' }}>
{m.label}
</span>
{activeMenu === m.id && (
<div style={{ marginLeft: 'auto', width: 4, height: 4, borderRadius: '50%', background: '#3b82f6' }} />
)}
</button>
))}
</nav>
{/* User */}
<div style={{ padding: '12px 14px', borderTop: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{
width: 32, height: 32, borderRadius: '50%',
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 13, fontWeight: 700,
}}>A</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 12, fontWeight: 600, color: '#e2e8f0', fontFamily: 'DM Sans, sans-serif', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
Admin
</div>
<div style={{ fontSize: 10, color: '#334155', fontFamily: 'DM Mono, monospace' }}>Super Admin</div>
</div>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#10b981', flexShrink: 0 }} />
</div>
</div>
</aside>
{/* Main */}
<main style={{ flex: 1, overflowY: 'auto', padding: '24px 28px', animation: 'fadeIn 0.4s ease' }}>
{/* Header */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<div>
<h1 style={{ fontFamily: 'DM Sans, sans-serif', fontSize: 22, fontWeight: 700, color: 'white', marginBottom: 2 }}>
Overview
</h1>
<div style={{ fontSize: 12, color: '#334155', fontFamily: 'DM Mono, monospace' }}>
2024.08.14 · 10:32
</div>
</div>
<div style={{ display: 'flex', gap: 10 }}>
<select style={{
background: '#0f172a', border: '1px solid rgba(255,255,255,0.07)',
color: '#94a3b8', padding: '8px 14px', borderRadius: 8, fontSize: 12,
fontFamily: 'DM Sans, sans-serif', cursor: 'pointer',
}}>
<option> 30</option>
</select>
<button style={{
background: '#3b82f6', border: 'none', color: 'white',
padding: '8px 18px', borderRadius: 8, fontSize: 12, fontWeight: 600,
cursor: 'pointer', fontFamily: 'DM Sans, sans-serif',
}}>
</button>
</div>
</div>
{/* KPI Cards */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: 16, marginBottom: 24 }}>
{kpis.map((kpi) => (
<div key={kpi.label} className="dash-kpi" style={{
padding: '18px 20px', borderRadius: 14,
background: '#0f172a', border: '1px solid rgba(255,255,255,0.06)',
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 12 }}>
<div style={{ fontSize: 11, color: '#475569', fontFamily: 'DM Sans, sans-serif', fontWeight: 500 }}>{kpi.label}</div>
<span style={{ fontSize: 18 }}>{kpi.icon}</span>
</div>
<div style={{ fontSize: 26, fontWeight: 700, color: 'white', fontFamily: 'DM Sans, sans-serif', marginBottom: 6 }}>
{kpi.value}
</div>
<div style={{
display: 'inline-flex', alignItems: 'center', gap: 4,
background: kpi.up ? 'rgba(16,185,129,0.12)' : 'rgba(239,68,68,0.12)',
borderRadius: 6, padding: '2px 8px',
fontSize: 11, fontWeight: 700,
color: kpi.up ? '#10b981' : '#ef4444',
fontFamily: 'DM Mono, monospace',
}}>
{kpi.up ? '↑' : '↓'} {kpi.change}
</div>
</div>
))}
</div>
{/* Chart + Progress */}
<div style={{ display: 'grid', gridTemplateColumns: '1.6fr 1fr', gap: 16, marginBottom: 24 }}>
{/* Bar Chart */}
<div style={{
padding: '22px 24px', borderRadius: 14,
background: '#0f172a', border: '1px solid rgba(255,255,255,0.06)',
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<div style={{ fontSize: 14, fontWeight: 700, color: 'white', fontFamily: 'DM Sans, sans-serif' }}>
</div>
<div style={{ display: 'flex', gap: 8 }}>
{['1M', '3M', '6M', '1Y'].map((p) => (
<button key={p} style={{
background: p === '6M' ? '#1e3a5f' : 'transparent',
border: 'none', color: p === '6M' ? '#60a5fa' : '#334155',
padding: '3px 8px', borderRadius: 6, fontSize: 11, cursor: 'pointer',
fontFamily: 'DM Mono, monospace',
}}>{p}</button>
))}
</div>
</div>
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 12, height: 140 }}>
{chartData.map((d, i) => (
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, height: '100%', justifyContent: 'flex-end' }}>
<div style={{
width: '100%', borderRadius: '4px 4px 0 0',
background: i === chartData.length - 1
? 'linear-gradient(180deg, #60a5fa, #3b82f6)'
: 'rgba(59,130,246,0.25)',
height: `${(d.value / maxVal) * 100}%`,
transition: 'height 0.6s ease',
}} />
<div style={{ fontSize: 9, color: '#334155', fontFamily: 'DM Mono, monospace' }}>{d.month}</div>
</div>
))}
</div>
</div>
{/* Progress */}
<div style={{
padding: '22px 24px', borderRadius: 14,
background: '#0f172a', border: '1px solid rgba(255,255,255,0.06)',
}}>
<div style={{ fontSize: 14, fontWeight: 700, color: 'white', fontFamily: 'DM Sans, sans-serif', marginBottom: 20 }}>
</div>
{[
{ label: 'Organic Search', val: 78, color: '#3b82f6' },
{ label: 'Direct', val: 55, color: '#10b981' },
{ label: 'Social Media', val: 42, color: '#a855f7' },
{ label: 'Email', val: 34, color: '#f59e0b' },
{ label: 'Referral', val: 20, color: '#ec4899' },
].map((p) => (
<div key={p.label} style={{ marginBottom: 14 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 5 }}>
<span style={{ fontSize: 12, color: '#94a3b8', fontFamily: 'DM Sans, sans-serif' }}>{p.label}</span>
<span style={{ fontSize: 11, color: p.color, fontFamily: 'DM Mono, monospace', fontWeight: 500 }}>{p.val}%</span>
</div>
<div style={{ height: 4, background: '#1e293b', borderRadius: 2, overflow: 'hidden' }}>
<div style={{
height: '100%', borderRadius: 2, background: p.color,
width: `${p.val}%`, opacity: 0.8,
}} />
</div>
</div>
))}
</div>
</div>
{/* Activity Table */}
<div style={{
borderRadius: 14, background: '#0f172a',
border: '1px solid rgba(255,255,255,0.06)', overflow: 'hidden',
}}>
<div style={{
padding: '16px 20px', borderBottom: '1px solid rgba(255,255,255,0.05)',
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
}}>
<div style={{ fontSize: 14, fontWeight: 700, color: 'white', fontFamily: 'DM Sans, sans-serif' }}>
</div>
<button style={{
background: 'none', border: '1px solid rgba(255,255,255,0.08)',
color: '#475569', padding: '5px 12px', borderRadius: 6,
fontSize: 11, cursor: 'pointer', fontFamily: 'DM Sans, sans-serif',
}}> </button>
</div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,0.04)' }}>
{['사용자', '활동', '시간', '상태'].map((h) => (
<th key={h} style={{
padding: '10px 20px', textAlign: 'left',
fontSize: 10, color: '#334155', fontFamily: 'DM Mono, monospace',
letterSpacing: '0.1em', textTransform: 'uppercase', fontWeight: 500,
}}>{h}</th>
))}
</tr>
</thead>
<tbody>
{activities.map((a, i) => (
<tr key={i} className="dash-row" style={{ borderBottom: '1px solid rgba(255,255,255,0.03)' }}>
<td style={{ padding: '12px 20px', fontFamily: 'DM Mono, monospace', fontSize: 12, color: '#60a5fa' }}>
{a.user}
</td>
<td style={{ padding: '12px 20px', fontFamily: 'DM Sans, sans-serif', fontSize: 13, color: '#94a3b8' }}>
{a.action}
</td>
<td style={{ padding: '12px 20px', fontFamily: 'DM Mono, monospace', fontSize: 11, color: '#334155' }}>
{a.time}
</td>
<td style={{ padding: '12px 20px' }}>
<span style={{
display: 'inline-block',
width: 6, height: 6, borderRadius: '50%',
background: statusColor[a.status as keyof typeof statusColor],
boxShadow: `0 0 6px ${statusColor[a.status as keyof typeof statusColor]}`,
}} />
</td>
</tr>
))}
</tbody>
</table>
</div>
</main>
</div>
</div>
);
}

View File

@@ -0,0 +1,437 @@
'use client';
import Link from 'next/link';
import { useState, useEffect } from 'react';
export default function GameSample() {
const [onlinePlayers, setOnlinePlayers] = useState(48_219);
const [matchingCount, setMatchingCount] = useState(1_342);
const [matchingActive, setMatchingActive] = useState(false);
const [matchTimer, setMatchTimer] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setOnlinePlayers((p) => p + Math.floor(Math.random() * 6 - 2));
setMatchingCount((c) => c + Math.floor(Math.random() * 4 - 1));
}, 2000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
let timer: ReturnType<typeof setInterval>;
if (matchingActive) {
timer = setInterval(() => {
setMatchTimer((t) => t + 1);
}, 1000);
} else {
setMatchTimer(0);
}
return () => clearInterval(timer);
}, [matchingActive]);
const rankings = [
{ rank: 1, name: 'ShadowViper_KR', score: 9_842, tier: 'GRANDMASTER', wins: 312, kda: '18.4' },
{ rank: 2, name: 'NightFalcon', score: 9_610, tier: 'GRANDMASTER', wins: 289, kda: '15.2' },
{ rank: 3, name: 'Xenon_X', score: 9_241, tier: 'MASTER', wins: 267, kda: '12.9' },
{ rank: 4, name: 'KR_Dominator', score: 8_970, tier: 'MASTER', wins: 251, kda: '11.7' },
{ rank: 5, name: 'Pulse_Wave', score: 8_834, tier: 'DIAMOND', wins: 238, kda: '10.3' },
];
const modes = [
{
id: 'solo',
name: 'SOLO',
sub: '1 vs 1',
desc: '순수한 실력으로 맞붙는 1대1 대결',
icon: '⚡',
color: '#06b6d4',
players: '12,400',
},
{
id: 'duo',
name: 'DUO',
sub: '2 vs 2',
desc: '파트너와 함께하는 팀플레이',
icon: '◈',
color: '#a855f7',
players: '28,700',
},
{
id: 'squad',
name: 'SQUAD',
sub: '5 vs 5',
desc: '전략과 팀워크로 승리를 쟁취',
icon: '▲',
color: '#f59e0b',
players: '7,100',
},
];
const tierColor: Record<string, string> = {
GRANDMASTER: '#fbbf24',
MASTER: '#a855f7',
DIAMOND: '#60a5fa',
};
return (
<div style={{ background: '#000000', minHeight: '100vh', color: 'white', overflowX: 'hidden' }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@400;500;600;700&display=swap');
@keyframes pulse-ring { 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(6,182,212,0.6); } 70% { transform: scale(1); box-shadow: 0 0 0 16px rgba(6,182,212,0); } 100% { transform: scale(0.95); } }
@keyframes scan { 0% { top: 0; } 100% { top: 100%; } }
@keyframes glitch { 0%, 90%, 100% { transform: none; } 92% { transform: translate(-2px, 1px); } 94% { transform: translate(2px, -1px); } 96% { transform: translate(-1px, 0); } }
@keyframes neonFlicker { 0%, 19%, 21%, 23%, 25%, 54%, 56%, 100% { opacity: 1; } 20%, 24%, 55% { opacity: 0.4; } }
@keyframes matchPulse { 0%, 100% { box-shadow: 0 0 20px rgba(6,182,212,0.4); } 50% { box-shadow: 0 0 40px rgba(6,182,212,0.9), 0 0 80px rgba(6,182,212,0.4); } }
@keyframes counter { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
.mode-card:hover { border-color: var(--card-color) !important; transform: translateY(-4px); }
.mode-card { transition: border-color 0.3s, transform 0.3s; }
.rank-row:hover { background: rgba(6,182,212,0.04) !important; }
.rank-row { transition: background 0.15s; }
`}</style>
{/* Back Banner */}
<div style={{
background: 'linear-gradient(135deg, #1e1b4b, #312e81)',
padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 12,
position: 'relative', zIndex: 200,
}}>
<Link href="/services/website" style={{
color: '#a5b4fc', fontSize: 13, textDecoration: 'none',
fontFamily: 'Rajdhani, sans-serif',
}}>
</Link>
<span style={{ color: '#4c1d95', fontSize: 13 }}>|</span>
<span style={{ color: '#06b6d4', fontSize: 12, fontFamily: 'Orbitron, sans-serif', fontWeight: 700 }}>
SAMPLE ·
</span>
</div>
{/* Navbar */}
<nav style={{
position: 'sticky', top: 0, zIndex: 100,
background: 'rgba(0,0,0,0.92)', backdropFilter: 'blur(20px)',
borderBottom: '1px solid rgba(6,182,212,0.2)',
padding: '0 48px', height: 60,
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
}}>
<div style={{
fontFamily: 'Orbitron, sans-serif', fontSize: 18, fontWeight: 900,
background: 'linear-gradient(90deg, #06b6d4, #a855f7)',
WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
letterSpacing: '0.1em',
animation: 'neonFlicker 8s infinite',
}}>
NEXUS ARENA
</div>
<div style={{ display: 'flex', gap: 28, alignItems: 'center' }}>
{['HOME', 'MATCH', 'RANK', 'SHOP', 'CLAN'].map((item) => (
<span key={item} style={{
fontFamily: 'Orbitron, sans-serif', fontSize: 11, fontWeight: 700,
color: '#1e3a5f', cursor: 'pointer', letterSpacing: '0.1em',
}}>{item}</span>
))}
</div>
<div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
<div style={{
width: 6, height: 6, borderRadius: '50%',
background: '#10b981', boxShadow: '0 0 8px rgba(16,185,129,0.8)',
}} />
<span style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 13, color: '#10b981', fontWeight: 600 }}>
{onlinePlayers.toLocaleString()} ONLINE
</span>
</div>
</nav>
{/* Hero */}
<section style={{
minHeight: '85vh', position: 'relative', overflow: 'hidden',
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
padding: '60px 48px',
background: 'radial-gradient(ellipse 80% 70% at 50% 40%, rgba(6,182,212,0.08) 0%, rgba(168,85,247,0.05) 50%, transparent 100%)',
}}>
{/* Grid */}
<div style={{
position: 'absolute', inset: 0,
backgroundImage: 'linear-gradient(rgba(6,182,212,0.06) 1px, transparent 1px), linear-gradient(90deg, rgba(6,182,212,0.06) 1px, transparent 1px)',
backgroundSize: '60px 60px',
}} />
{/* Scan line */}
<div style={{
position: 'absolute', left: 0, right: 0, height: '1px',
background: 'linear-gradient(90deg, transparent, rgba(6,182,212,0.5), transparent)',
animation: 'scan 6s linear infinite',
pointerEvents: 'none',
}} />
{/* Corner decorations */}
{[
{ top: 40, left: 40, borderTop: '2px solid #06b6d4', borderLeft: '2px solid #06b6d4' },
{ top: 40, right: 40, borderTop: '2px solid #a855f7', borderRight: '2px solid #a855f7' },
{ bottom: 40, left: 40, borderBottom: '2px solid #06b6d4', borderLeft: '2px solid #06b6d4' },
{ bottom: 40, right: 40, borderBottom: '2px solid #a855f7', borderRight: '2px solid #a855f7' },
].map((s, i) => (
<div key={i} style={{ position: 'absolute', width: 40, height: 40, ...s }} />
))}
<div style={{ textAlign: 'center', position: 'relative', zIndex: 1 }}>
<div style={{
fontFamily: 'Orbitron, sans-serif', fontSize: 11, fontWeight: 700,
letterSpacing: '0.3em', color: '#06b6d4', marginBottom: 20,
textTransform: 'uppercase',
}}>
Season 7 · RANKED MATCH
</div>
<h1 style={{
fontFamily: 'Orbitron, sans-serif',
fontSize: 'clamp(48px, 8vw, 100px)',
fontWeight: 900, lineHeight: 1.0, marginBottom: 16,
background: 'linear-gradient(180deg, #ffffff 0%, rgba(255,255,255,0.6) 100%)',
WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
animation: 'glitch 10s infinite',
}}>
NEXUS<br />
<span style={{
background: 'linear-gradient(90deg, #06b6d4, #a855f7)',
WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
}}>ARENA</span>
</h1>
<p style={{
fontFamily: 'Rajdhani, sans-serif', fontSize: 18, color: '#475569',
letterSpacing: '0.05em', marginBottom: 48,
}}>
ENTER THE ARENA. CLAIM YOUR GLORY.
</p>
{/* Live Stats */}
<div style={{ display: 'flex', gap: 32, justifyContent: 'center', marginBottom: 48 }}>
{[
{ label: 'ONLINE', val: onlinePlayers.toLocaleString(), color: '#06b6d4' },
{ label: 'IN MATCH', val: matchingCount.toLocaleString(), color: '#a855f7' },
{ label: 'SERVERS', val: '24', color: '#10b981' },
].map((s) => (
<div key={s.label} style={{
padding: '16px 24px',
background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(8px)',
border: `1px solid ${s.color}30`,
borderTop: `2px solid ${s.color}`,
textAlign: 'center',
}}>
<div style={{
fontFamily: 'Orbitron, sans-serif', fontSize: 28, fontWeight: 900,
color: s.color, letterSpacing: '-0.02em',
}}>{s.val}</div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 11, color: '#334155', letterSpacing: '0.2em', marginTop: 4 }}>
{s.label}
</div>
</div>
))}
</div>
{/* Matching Button */}
{!matchingActive ? (
<button
onClick={() => setMatchingActive(true)}
style={{
background: 'linear-gradient(135deg, #06b6d4, #0891b2)',
border: 'none', color: 'white',
padding: '18px 56px', fontSize: 16, fontWeight: 900,
cursor: 'pointer', fontFamily: 'Orbitron, sans-serif',
letterSpacing: '0.1em',
clipPath: 'polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 16px 100%, 0 calc(100% - 16px))',
animation: 'pulse-ring 2s infinite',
}}
>
FIND MATCH
</button>
) : (
<div style={{
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12,
}}>
<div style={{
padding: '18px 56px',
border: '2px solid #06b6d4', color: '#06b6d4',
fontFamily: 'Orbitron, sans-serif', fontSize: 16, fontWeight: 900,
letterSpacing: '0.1em', animation: 'matchPulse 1.5s infinite',
clipPath: 'polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 16px 100%, 0 calc(100% - 16px))',
}}>
MATCHING... {String(Math.floor(matchTimer / 60)).padStart(2, '0')}:{String(matchTimer % 60).padStart(2, '0')}
</div>
<button onClick={() => setMatchingActive(false)} style={{
background: 'none', border: 'none', color: '#334155',
fontFamily: 'Rajdhani, sans-serif', fontSize: 13, cursor: 'pointer',
letterSpacing: '0.1em', textDecoration: 'underline',
}}>
CANCEL
</button>
</div>
)}
</div>
</section>
{/* Game Modes */}
<section style={{ padding: '60px 48px', background: 'rgba(0,0,0,0.8)' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 40 }}>
<h2 style={{
fontFamily: 'Orbitron, sans-serif', fontSize: 28, fontWeight: 900,
color: 'white', letterSpacing: '0.05em', marginBottom: 8,
}}>
GAME MODES
</h2>
<div style={{ width: 60, height: 2, background: 'linear-gradient(90deg, #06b6d4, #a855f7)', margin: '0 auto' }} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 20 }}>
{modes.map((mode) => (
<div
key={mode.id}
className="mode-card"
// @ts-expect-error CSS variable
style={{ '--card-color': mode.color, border: `1px solid ${mode.color}25`, borderRadius: 4, padding: '28px 24px', background: 'rgba(0,0,0,0.6)', cursor: 'pointer', position: 'relative', overflow: 'hidden' }}
>
<div style={{
position: 'absolute', top: 0, left: 0, right: 0, height: 2,
background: `linear-gradient(90deg, transparent, ${mode.color}, transparent)`,
}} />
<div style={{
fontSize: 36, marginBottom: 16, color: mode.color,
textShadow: `0 0 20px ${mode.color}`,
}}>{mode.icon}</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 8 }}>
<div style={{
fontFamily: 'Orbitron, sans-serif', fontSize: 24, fontWeight: 900, color: 'white',
}}>{mode.name}</div>
<div style={{
fontFamily: 'Rajdhani, sans-serif', fontSize: 13, color: mode.color,
fontWeight: 700, letterSpacing: '0.1em',
}}>{mode.sub}</div>
</div>
<div style={{
fontFamily: 'Rajdhani, sans-serif', fontSize: 15, color: '#475569',
lineHeight: 1.5, marginBottom: 20,
}}>{mode.desc}</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{
fontFamily: 'Orbitron, sans-serif', fontSize: 11,
color: mode.color, letterSpacing: '0.1em',
}}>
{mode.players} IN QUEUE
</div>
<button style={{
background: `${mode.color}20`, border: `1px solid ${mode.color}50`,
color: mode.color, padding: '6px 16px', borderRadius: 2,
fontSize: 11, fontWeight: 700, cursor: 'pointer',
fontFamily: 'Orbitron, sans-serif', letterSpacing: '0.08em',
}}>
PLAY
</button>
</div>
</div>
))}
</div>
</div>
</section>
{/* Rankings */}
<section style={{ padding: '60px 48px', background: '#000000' }}>
<div style={{ maxWidth: 900, margin: '0 auto' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 32 }}>
<h2 style={{
fontFamily: 'Orbitron, sans-serif', fontSize: 24, fontWeight: 900,
letterSpacing: '0.05em', color: 'white',
}}>
GLOBAL RANKING<span style={{ color: '#06b6d4' }}>.</span>
</h2>
<div style={{
fontFamily: 'Rajdhani, sans-serif', fontSize: 13, color: '#1e3a5f',
letterSpacing: '0.1em', textTransform: 'uppercase',
}}>
Season 7 · Top 100
</div>
</div>
<div style={{
border: '1px solid rgba(6,182,212,0.15)', borderRadius: 4, overflow: 'hidden',
}}>
{/* Header */}
<div style={{
display: 'grid', gridTemplateColumns: '60px 1fr 100px 80px 80px',
padding: '10px 20px', borderBottom: '1px solid rgba(6,182,212,0.1)',
background: 'rgba(6,182,212,0.05)',
}}>
{['RANK', 'PLAYER', 'SCORE', 'WINS', 'K/D/A'].map((h) => (
<div key={h} style={{
fontFamily: 'Orbitron, sans-serif', fontSize: 9, color: '#06b6d4',
letterSpacing: '0.15em',
}}>{h}</div>
))}
</div>
{rankings.map((r, i) => (
<div
key={i}
className="rank-row"
style={{
display: 'grid', gridTemplateColumns: '60px 1fr 100px 80px 80px',
padding: '14px 20px',
borderBottom: i < rankings.length - 1 ? '1px solid rgba(255,255,255,0.03)' : 'none',
alignItems: 'center',
}}
>
<div style={{
fontFamily: 'Orbitron, sans-serif', fontSize: 20, fontWeight: 900,
color: i === 0 ? '#fbbf24' : i === 1 ? '#9ca3af' : i === 2 ? '#cd7c2f' : '#1e3a5f',
}}>
{r.rank < 10 ? `0${r.rank}` : r.rank}
</div>
<div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 15, fontWeight: 700, color: 'white' }}>
{r.name}
</div>
<div style={{
display: 'inline-block', marginTop: 2,
fontFamily: 'Orbitron, sans-serif', fontSize: 8, fontWeight: 700,
color: tierColor[r.tier] || '#6b7280',
border: `1px solid ${tierColor[r.tier] || '#6b7280'}40`,
padding: '1px 6px', letterSpacing: '0.1em',
}}>{r.tier}</div>
</div>
<div style={{
fontFamily: 'Orbitron, sans-serif', fontSize: 14, fontWeight: 700,
color: '#06b6d4',
}}>{r.score.toLocaleString()}</div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 14, color: '#475569', fontWeight: 600 }}>
{r.wins}
</div>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 13, color: '#10b981' }}>
{r.kda}
</div>
</div>
))}
</div>
</div>
</section>
{/* Footer */}
<footer style={{
background: '#000000', borderTop: '1px solid rgba(6,182,212,0.1)',
padding: '24px 48px', display: 'flex', justifyContent: 'space-between', alignItems: 'center',
}}>
<div style={{
fontFamily: 'Orbitron, sans-serif', fontSize: 14, fontWeight: 900,
color: '#1e3a5f', letterSpacing: '0.1em',
}}>NEXUS ARENA</div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 12, color: '#1e293b', letterSpacing: '0.1em' }}>
© 2024 NEXUS ARENA STUDIOS. ALL RIGHTS RESERVED.
</div>
<div style={{ display: 'flex', gap: 16 }}>
{['Twitter', 'Discord', 'YouTube'].map((s) => (
<span key={s} style={{
fontFamily: 'Rajdhani, sans-serif', fontSize: 12, color: '#1e3a5f',
cursor: 'pointer', letterSpacing: '0.05em',
}}>{s}</span>
))}
</div>
</footer>
</div>
);
}

View File

@@ -0,0 +1,345 @@
'use client';
import Link from 'next/link';
import { useState } from 'react';
export default function PortfolioSample() {
const [hoveredProject, setHoveredProject] = useState<number | null>(null);
const skills = [
{ name: 'Figma', level: 98 },
{ name: 'React', level: 90 },
{ name: 'TypeScript', level: 85 },
{ name: 'After Effects', level: 88 },
{ name: 'Three.js', level: 75 },
{ name: 'Framer', level: 92 },
];
const projects = [
{ title: 'NEON CITY UI', desc: '사이버펑크 게임 인터페이스', gradient: 'linear-gradient(135deg, #00ff88, #00d4ff)', tag: 'UI/UX' },
{ title: 'FLOW BRAND', desc: '핀테크 스타트업 브랜딩', gradient: 'linear-gradient(135deg, #a855f7, #ec4899)', tag: 'Branding' },
{ title: 'ORBITAL APP', desc: '위성 추적 대시보드', gradient: 'linear-gradient(135deg, #3b82f6, #06b6d4)', tag: 'Web App' },
{ title: 'TERRA SHOP', desc: '친환경 D2C 쇼핑몰', gradient: 'linear-gradient(135deg, #22c55e, #84cc16)', tag: 'E-commerce' },
{ title: 'PULSE MOTION', desc: '모션 그래픽 패키지', gradient: 'linear-gradient(135deg, #f59e0b, #ef4444)', tag: 'Motion' },
{ title: 'AXIS SYSTEM', desc: '물류 관리 SaaS', gradient: 'linear-gradient(135deg, #64748b, #475569)', tag: 'Dashboard' },
];
const timeline = [
{ year: '2023', event: 'Google UX Design Certificate 취득', type: 'award' },
{ year: '2022', event: 'Awwwards SOTD 2회 수상', type: 'award' },
{ year: '2021', event: 'LINE Corp. UI 디자이너 입사', type: 'career' },
{ year: '2020', event: 'Hongik University 시각디자인과 졸업', type: 'edu' },
{ year: '2019', event: 'Adobe Design Award Korea 은상', type: 'award' },
];
return (
<div style={{ background: '#000000', minHeight: '100vh', color: 'white' }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap');
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
@keyframes scanline { 0% { top: -10%; } 100% { top: 110%; } }
@keyframes glow { 0%, 100% { text-shadow: 0 0 20px rgba(0,255,136,0.5); } 50% { text-shadow: 0 0 40px rgba(0,255,136,0.9), 0 0 80px rgba(0,255,136,0.3); } }
@keyframes fadeUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
@keyframes slideBar { from { width: 0; } to { width: 100%; } }
.proj-card:hover { border-color: rgba(0,255,136,0.4) !important; }
.proj-card { transition: border-color 0.3s; }
`}</style>
{/* Back Banner */}
<div style={{
background: 'linear-gradient(135deg, #1e1b4b, #312e81)',
padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 12,
}}>
<Link href="/services/website" style={{
color: '#a5b4fc', fontSize: 13, textDecoration: 'none',
fontFamily: 'Space Grotesk, sans-serif',
}}>
</Link>
<span style={{ color: '#4c1d95', fontSize: 13 }}>|</span>
<span style={{ color: '#00ff88', fontSize: 12, fontFamily: 'Space Mono, monospace', fontWeight: 700 }}>
SAMPLE ·
</span>
</div>
{/* Navbar */}
<nav style={{
position: 'sticky', top: 0, zIndex: 100,
background: 'rgba(0,0,0,0.9)', backdropFilter: 'blur(20px)',
borderBottom: '1px solid rgba(0,255,136,0.15)',
padding: '0 48px', height: 64,
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
}}>
<div style={{
fontFamily: 'Space Mono, monospace', fontSize: 15, fontWeight: 700,
color: '#00ff88', letterSpacing: '-0.02em',
animation: 'glow 3s ease-in-out infinite',
}}>
KJ<span style={{ color: 'rgba(0,255,136,0.4)' }}>_</span>
</div>
<div style={{ display: 'flex', gap: 32 }}>
{['About', 'Work', 'Skills', 'Contact'].map((item) => (
<span key={item} style={{
fontFamily: 'Space Grotesk, sans-serif', fontSize: 13, fontWeight: 600,
color: '#4b5563', cursor: 'pointer', letterSpacing: '0.05em',
textTransform: 'uppercase',
}}>{item}</span>
))}
</div>
<button style={{
background: 'transparent', border: '1px solid #00ff88',
color: '#00ff88', padding: '8px 20px', borderRadius: 4,
fontSize: 12, fontWeight: 700, cursor: 'pointer',
fontFamily: 'Space Mono, monospace', letterSpacing: '0.08em',
}}>
HIRE ME
</button>
</nav>
{/* Hero */}
<section style={{ padding: '100px 48px 80px', position: 'relative', overflow: 'hidden' }}>
{/* Scanline effect */}
<div style={{
position: 'absolute', left: 0, right: 0, height: '2px',
background: 'linear-gradient(90deg, transparent, rgba(0,255,136,0.4), transparent)',
animation: 'scanline 8s linear infinite',
pointerEvents: 'none',
}} />
{/* Grid */}
<div style={{
position: 'absolute', inset: 0,
backgroundImage: 'linear-gradient(rgba(0,255,136,0.04) 1px, transparent 1px), linear-gradient(90deg, rgba(0,255,136,0.04) 1px, transparent 1px)',
backgroundSize: '40px 40px',
}} />
<div style={{ maxWidth: 900, position: 'relative', animation: 'fadeUp 0.8s ease forwards' }}>
<div style={{
fontFamily: 'Space Mono, monospace', fontSize: 12,
color: 'rgba(0,255,136,0.6)', letterSpacing: '0.2em',
marginBottom: 20, textTransform: 'uppercase',
}}>
{'> Hello, World. I am'}
</div>
<h1 style={{
fontFamily: 'Space Grotesk, sans-serif',
fontSize: 'clamp(52px, 7vw, 96px)',
fontWeight: 700, lineHeight: 1.0, marginBottom: 16,
letterSpacing: '-0.03em',
}}>
Kim<br />
<span style={{ color: '#00ff88' }}>Jisu</span>
<span style={{
display: 'inline-block', width: 6, height: 'clamp(52px, 7vw, 96px)',
background: '#00ff88', marginLeft: 8, verticalAlign: 'middle',
animation: 'blink 1.2s step-end infinite',
}} />
</h1>
<div style={{
fontFamily: 'Space Grotesk, sans-serif', fontSize: 20,
color: '#4b5563', fontWeight: 500, marginBottom: 24,
}}>
Product Designer & Creative Developer
</div>
<p style={{
fontFamily: 'Space Grotesk, sans-serif', fontSize: 16,
color: '#6b7280', lineHeight: 1.8, maxWidth: 520, marginBottom: 36,
}}>
. .
</p>
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'center' }}>
<button style={{
background: '#00ff88', color: '#000000', border: 'none',
padding: '14px 32px', fontSize: 14, fontWeight: 700, cursor: 'pointer',
fontFamily: 'Space Mono, monospace', letterSpacing: '0.05em',
clipPath: 'polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px))',
}}>
VIEW WORK
</button>
<button style={{
background: 'transparent', color: '#9ca3af',
border: '1px solid #1f2937',
padding: '14px 32px', fontSize: 14, fontWeight: 600, cursor: 'pointer',
fontFamily: 'Space Grotesk, sans-serif', borderRadius: 4,
}}>
Download CV
</button>
<div style={{ display: 'flex', gap: 8, marginLeft: 8 }}>
{['6+', '30+', '2x'].map((s, i) => (
<div key={i} style={{
padding: '8px 14px', border: '1px solid #1f2937', borderRadius: 4,
fontFamily: 'Space Mono, monospace', fontSize: 12,
color: '#4b5563',
}}>
<span style={{ color: '#00ff88', fontWeight: 700 }}>{s}</span>
<span style={{ marginLeft: 4 }}>{['YEARS', 'PROJECTS', 'AWWWARDS'][i]}</span>
</div>
))}
</div>
</div>
</div>
</section>
{/* Projects */}
<section style={{ padding: '80px 48px', background: '#050505' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 48 }}>
<h2 style={{
fontFamily: 'Space Grotesk, sans-serif', fontSize: 36, fontWeight: 700,
letterSpacing: '-0.02em',
}}>
Selected Work<span style={{ color: '#00ff88' }}>.</span>
</h2>
<span style={{ color: '#374151', fontSize: 12, fontFamily: 'Space Mono, monospace' }}>
2019 2024
</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: 16 }}>
{projects.map((proj, i) => (
<div
key={i}
className="proj-card"
onMouseEnter={() => setHoveredProject(i)}
onMouseLeave={() => setHoveredProject(null)}
style={{
border: '1px solid #111827', borderRadius: 12, overflow: 'hidden',
cursor: 'pointer', background: '#0a0a0a',
}}
>
<div style={{
height: 180, background: proj.gradient, position: 'relative',
display: 'flex', alignItems: 'center', justifyContent: 'center',
transition: 'transform 0.3s',
transform: hoveredProject === i ? 'scale(1.02)' : 'scale(1)',
}}>
<div style={{
fontFamily: 'Space Grotesk, sans-serif', fontSize: 24, fontWeight: 700,
color: 'rgba(0,0,0,0.4)', letterSpacing: '-0.02em',
}}>
{proj.title}
</div>
<div style={{
position: 'absolute', top: 12, right: 12,
background: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(8px)',
border: '1px solid rgba(255,255,255,0.15)',
borderRadius: 4, padding: '3px 10px',
fontSize: 10, fontWeight: 700, color: 'white',
fontFamily: 'Space Mono, monospace', letterSpacing: '0.1em',
}}>{proj.tag}</div>
</div>
<div style={{ padding: '16px 18px' }}>
<div style={{ fontSize: 15, fontWeight: 700, color: 'white', fontFamily: 'Space Grotesk, sans-serif', marginBottom: 4 }}>
{proj.title}
</div>
<div style={{ fontSize: 13, color: '#4b5563', fontFamily: 'Space Grotesk, sans-serif' }}>
{proj.desc}
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* Skills */}
<section style={{ padding: '80px 48px', background: '#000000' }}>
<div style={{ maxWidth: 900, margin: '0 auto', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 60, alignItems: 'center' }}>
<div>
<h2 style={{
fontFamily: 'Space Grotesk, sans-serif', fontSize: 36, fontWeight: 700,
marginBottom: 36, letterSpacing: '-0.02em',
}}>
Skills<span style={{ color: '#00ff88' }}>.</span>
</h2>
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
{skills.map((s) => (
<div key={s.name}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
<span style={{ fontFamily: 'Space Mono, monospace', fontSize: 12, color: '#9ca3af', letterSpacing: '0.05em' }}>
{s.name}
</span>
<span style={{ fontFamily: 'Space Mono, monospace', fontSize: 11, color: '#00ff88' }}>
{s.level}%
</span>
</div>
<div style={{ height: 3, background: '#111827', borderRadius: 2, overflow: 'hidden' }}>
<div style={{
height: '100%', borderRadius: 2,
background: 'linear-gradient(90deg, #00ff88, #00d4ff)',
width: `${s.level}%`,
animation: 'slideBar 1.5s ease forwards',
}} />
</div>
</div>
))}
</div>
</div>
<div>
<h2 style={{
fontFamily: 'Space Grotesk, sans-serif', fontSize: 36, fontWeight: 700,
marginBottom: 36, letterSpacing: '-0.02em',
}}>
Timeline<span style={{ color: '#00ff88' }}>.</span>
</h2>
<div style={{ position: 'relative', paddingLeft: 20 }}>
<div style={{
position: 'absolute', left: 4, top: 8, bottom: 8,
width: 1, background: '#1f2937',
}} />
{timeline.map((t, i) => (
<div key={i} style={{ position: 'relative', paddingBottom: 24 }}>
<div style={{
position: 'absolute', left: -20, top: 4,
width: 8, height: 8, borderRadius: '50%',
background: t.type === 'award' ? '#00ff88' : t.type === 'career' ? '#3b82f6' : '#a855f7',
boxShadow: `0 0 8px ${t.type === 'award' ? 'rgba(0,255,136,0.6)' : t.type === 'career' ? 'rgba(59,130,246,0.6)' : 'rgba(168,85,247,0.6)'}`,
}} />
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 11, color: '#374151', marginBottom: 2 }}>
{t.year}
</div>
<div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 14, color: '#d1d5db' }}>
{t.event}
</div>
</div>
))}
</div>
</div>
</div>
</section>
{/* Contact */}
<section style={{
padding: '80px 48px', textAlign: 'center',
background: 'linear-gradient(180deg, #000000, #001a00)',
borderTop: '1px solid rgba(0,255,136,0.1)',
}}>
<div style={{
fontFamily: 'Space Mono, monospace', fontSize: 12,
color: 'rgba(0,255,136,0.5)', letterSpacing: '0.2em', marginBottom: 20,
}}>
{'> LET\'S COLLABORATE'}
</div>
<h2 style={{
fontFamily: 'Space Grotesk, sans-serif', fontSize: 'clamp(36px, 5vw, 60px)',
fontWeight: 700, letterSpacing: '-0.03em', marginBottom: 16,
animation: 'glow 3s ease-in-out infinite',
}}>
Have a project<span style={{ color: '#00ff88' }}>?</span>
</h2>
<p style={{
fontFamily: 'Space Grotesk, sans-serif', fontSize: 16,
color: '#4b5563', marginBottom: 36,
}}>
jisu.kim@design.studio · @jisu_creates
</p>
<button style={{
background: '#00ff88', color: '#000000', border: 'none',
padding: '16px 44px', fontSize: 14, fontWeight: 700,
cursor: 'pointer', fontFamily: 'Space Mono, monospace', letterSpacing: '0.08em',
clipPath: 'polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px))',
}}>
START A PROJECT
</button>
</section>
</div>
);
}

49
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "jaengseung-made", "name": "jaengseung-made",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@anthropic-ai/sdk": "^0.79.0",
"@supabase/ssr": "^0.5.2", "@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.99.0", "@supabase/supabase-js": "^2.99.0",
"@tosspayments/tosspayments-sdk": "^2.6.0", "@tosspayments/tosspayments-sdk": "^2.6.0",
@@ -44,6 +45,26 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@anthropic-ai/sdk": {
"version": "0.79.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.79.0.tgz",
"integrity": "sha512-ietmtM6glcnnrWq26H+BZm8J07iay9Cob6hRzDTr/A9QWF1m2T//TQhFO4MTKcZht2/7LS8bG9wUYEhcizKRnA==",
"license": "MIT",
"dependencies": {
"json-schema-to-ts": "^3.1.1"
},
"bin": {
"anthropic-ai-sdk": "bin/cli"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
}
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.29.0", "version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
@@ -236,6 +257,15 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@babel/runtime": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.28.6", "version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
@@ -5048,6 +5078,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/json-schema-to-ts": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz",
"integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.3",
"ts-algebra": "^2.0.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/json-schema-traverse": { "node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -7970,6 +8013,12 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/ts-algebra": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz",
"integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==",
"license": "MIT"
},
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",

View File

@@ -9,6 +9,7 @@
"lint": "eslint" "lint": "eslint"
}, },
"dependencies": { "dependencies": {
"@anthropic-ai/sdk": "^0.79.0",
"@supabase/ssr": "^0.5.2", "@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.99.0", "@supabase/supabase-js": "^2.99.0",
"@tosspayments/tosspayments-sdk": "^2.6.0", "@tosspayments/tosspayments-sdk": "^2.6.0",