Files
saju-web/app/compatibility/result/page.tsx
gahusb affbdf1a44 feat: 음력 변환, 대운 계산, 소셜 공유 기능 추가
- 음력 변환 기능 구현
  - lunar-calendar 라이브러리 추가
  - 음력-양력 변환 유틸리티 생성
  - 모든 입력 폼에 양력/음력 선택 및 윤달 옵션 추가
  - SajuForm, CompatibilityForm에 음력 지원

- 대운(大運) 계산 기능 구현
  - 10년 단위 대운 계산 알고리즘
  - 현재 대운 표시 및 해석
  - 사주팔자 결과 페이지에 대운 섹션 추가
  - 8개 대운 (80년치) 표시

- 소셜 공유 기능 구현
  - ShareButtons 컴포넌트 생성
  - 카카오톡, 페이스북, 트위터 공유
  - 네이티브 공유 API 지원
  - 링크 복사 기능
  - 모든 결과 페이지에 공유 버튼 추가

- 메타데이터 개선
  - 사이트 제목 및 설명 최적화
  - 한국어(ko) 설정
  - 카카오 SDK 추가

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 23:57:53 +09:00

404 lines
16 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from 'next/link';
import { calculateSaju, FIVE_ELEMENTS } from '@/lib/saju-calculator';
import PDFButton from '../../components/PDFButton';
import ShareButtons from '../../components/ShareButtons';
interface PageProps {
searchParams: Promise<{
year1: string;
month1: string;
day1: string;
hour1?: string;
gender1: 'male' | 'female';
year2: string;
month2: string;
day2: string;
hour2?: string;
gender2: 'male' | 'female';
}>;
}
export default async function CompatibilityResultPage({ searchParams }: PageProps) {
const params = await searchParams;
const { year1, month1, day1, hour1, gender1, year2, month2, day2, hour2, gender2 } = params;
// Person 1 Saju
const saju1 = calculateSaju(
parseInt(year1),
parseInt(month1),
parseInt(day1),
hour1 ? parseInt(hour1) : null,
gender1
);
// Person 2 Saju
const saju2 = calculateSaju(
parseInt(year2),
parseInt(month2),
parseInt(day2),
hour2 ? parseInt(hour2) : null,
gender2
);
// 궁합 점수 계산
const calculateCompatibility = () => {
let score = 50; // 기본 점수
// 오행 상생/상극 관계
const element1 = saju1.day.element;
const element2 = saju2.day.element;
const produceMap: { [key: string]: string } = {
'木': '火', '火': '土', '土': '金', '金': '水', '水': '木'
};
const overcomeMap: { [key: string]: string } = {
'木': '土', '火': '金', '土': '水', '金': '木', '水': '火'
};
// 같은 오행: 보통
if (element1 === element2) {
score += 10;
}
// 상생 관계: 매우 좋음
else if (produceMap[element1] === element2 || produceMap[element2] === element1) {
score += 25;
}
// 상극 관계: 주의 필요
else if (overcomeMap[element1] === element2 || overcomeMap[element2] === element1) {
score -= 10;
}
// 지지 삼합/육합 관계 확인
const branch1 = saju1.day.branch;
const branch2 = saju2.day.branch;
// 육합 (六合) - 특별히 좋은 궁합
const sixHarmony: { [key: string]: string } = {
'子': '丑', '丑': '子',
'寅': '亥', '亥': '寅',
'卯': '戌', '戌': '卯',
'辰': '酉', '酉': '辰',
'巳': '申', '申': '巳',
'午': '未', '未': '午'
};
if (sixHarmony[branch1] === branch2) {
score += 20;
}
// 삼합 (三合) - 좋은 궁합
const threeHarmony = [
['申', '子', '辰'], // 수국
['寅', '午', '戌'], // 화국
['亥', '卯', '未'], // 목국
['巳', '酉', '丑'] // 금국
];
for (const harmony of threeHarmony) {
if (harmony.includes(branch1) && harmony.includes(branch2)) {
score += 15;
break;
}
}
// 충 (沖) - 나쁜 궁합
const conflict: { [key: string]: string } = {
'子': '午', '午': '子',
'丑': '未', '未': '丑',
'寅': '申', '申': '寅',
'卯': '酉', '酉': '卯',
'辰': '戌', '戌': '辰',
'巳': '亥', '亥': '巳'
};
if (conflict[branch1] === branch2) {
score -= 20;
}
return Math.min(100, Math.max(0, score));
};
const compatibilityScore = calculateCompatibility();
const getScoreGrade = (score: number): string => {
if (score >= 90) return 'S';
if (score >= 80) return 'A';
if (score >= 70) return 'B';
if (score >= 60) return 'C';
return 'D';
};
const getScoreText = (score: number): string => {
if (score >= 90) return '천생연분';
if (score >= 80) return '매우 좋음';
if (score >= 70) return '좋음';
if (score >= 60) return '보통';
return '주의 필요';
};
const getScoreColor = (score: number): string => {
if (score >= 90) return 'from-pink-500 to-red-500';
if (score >= 80) return 'from-pink-400 to-purple-400';
if (score >= 70) return 'from-purple-400 to-indigo-400';
if (score >= 60) return 'from-indigo-400 to-blue-400';
return 'from-gray-400 to-gray-500';
};
// 세부 궁합 점수
const detailedScores = [
{ name: '연애운', score: compatibilityScore + Math.floor(Math.random() * 10 - 5), icon: '💑' },
{ name: '결혼운', score: compatibilityScore + Math.floor(Math.random() * 10 - 5), icon: '💍' },
{ name: '금전운', score: compatibilityScore + Math.floor(Math.random() * 10 - 5), icon: '💰' },
{ name: '사업운', score: compatibilityScore + Math.floor(Math.random() * 10 - 5), icon: '💼' },
].map(item => ({ ...item, score: Math.min(100, Math.max(0, item.score)) }));
return (
<div className="min-h-screen bg-gradient-to-br from-pink-50 via-purple-50 to-indigo-50">
{/* Navigation */}
<nav className="bg-white/80 backdrop-blur-md border-b border-gray-200 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<Link href="/" className="text-2xl font-bold bg-gradient-to-r from-pink-600 to-purple-600 bg-clip-text text-transparent">
🔮
</Link>
<div className="flex gap-4">
<Link
href="/compatibility"
className="text-gray-700 hover:text-pink-600 transition font-medium"
>
</Link>
<Link
href="/"
className="text-gray-700 hover:text-pink-600 transition font-medium"
>
</Link>
</div>
</div>
</div>
</nav>
{/* Result Content */}
<div id="pdf-content" className="max-w-6xl mx-auto px-4 py-12">
{/* Header */}
<div className="text-center mb-12">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
💕
</h1>
<p className="text-xl text-gray-600">
</p>
</div>
{/* 두 사람 정보 */}
<div className="grid md:grid-cols-2 gap-6 mb-12">
{/* Person 1 */}
<div className="bg-gradient-to-br from-pink-500 to-pink-600 rounded-2xl shadow-xl p-8 text-white">
<div className="text-center mb-6">
<div className="text-5xl mb-3">👤</div>
<h3 className="text-2xl font-bold"> </h3>
<p className="text-pink-100 mt-2">
{saju1.birthDate.year} {saju1.birthDate.month} {saju1.birthDate.day}
</p>
<p className="text-pink-100">
{gender1 === 'male' ? '남성' : '여성'}
</p>
</div>
<div className="bg-white/20 backdrop-blur-sm rounded-xl p-4">
<p className="text-center mb-2 font-semibold"> ()</p>
<div className="text-center">
<span className="text-4xl font-bold">{saju1.day.stem}</span>
<span className="text-2xl ml-2">({saju1.day.stemKr})</span>
</div>
<p className="text-center mt-2 text-pink-100">
{saju1.day.element} ({['木', '火', '土', '金', '水'].indexOf(saju1.day.element) >= 0
? ['목', '화', '토', '금', '수'][['木', '火', '土', '金', '水'].indexOf(saju1.day.element)]
: ''})
</p>
</div>
</div>
{/* Person 2 */}
<div className="bg-gradient-to-br from-purple-500 to-purple-600 rounded-2xl shadow-xl p-8 text-white">
<div className="text-center mb-6">
<div className="text-5xl mb-3">👤</div>
<h3 className="text-2xl font-bold"> </h3>
<p className="text-purple-100 mt-2">
{saju2.birthDate.year} {saju2.birthDate.month} {saju2.birthDate.day}
</p>
<p className="text-purple-100">
{gender2 === 'male' ? '남성' : '여성'}
</p>
</div>
<div className="bg-white/20 backdrop-blur-sm rounded-xl p-4">
<p className="text-center mb-2 font-semibold"> ()</p>
<div className="text-center">
<span className="text-4xl font-bold">{saju2.day.stem}</span>
<span className="text-2xl ml-2">({saju2.day.stemKr})</span>
</div>
<p className="text-center mt-2 text-purple-100">
{saju2.day.element} ({['木', '火', '土', '金', '水'].indexOf(saju2.day.element) >= 0
? ['목', '화', '토', '금', '수'][['木', '火', '土', '金', '水'].indexOf(saju2.day.element)]
: ''})
</p>
</div>
</div>
</div>
{/* 총합 궁합 점수 */}
<div className={`bg-gradient-to-r ${getScoreColor(compatibilityScore)} rounded-3xl shadow-2xl p-12 mb-12 text-white`}>
<div className="text-center">
<h2 className="text-3xl font-bold mb-6"> </h2>
<div className="flex items-center justify-center gap-8 mb-6">
<div className="text-8xl font-bold">{compatibilityScore}</div>
<div className="text-left">
<div className="text-6xl font-bold mb-2">{getScoreGrade(compatibilityScore)}</div>
<div className="text-2xl">{getScoreText(compatibilityScore)}</div>
</div>
</div>
<div className="max-w-2xl mx-auto">
<div className="w-full bg-white/30 rounded-full h-4">
<div
className="bg-white h-4 rounded-full transition-all duration-1000"
style={{ width: `${compatibilityScore}%` }}
></div>
</div>
</div>
</div>
</div>
{/* 세부 궁합 */}
<div className="bg-white rounded-3xl shadow-2xl p-8 md:p-12 mb-8">
<h2 className="text-3xl font-bold text-gray-900 mb-8 text-center"> </h2>
<div className="grid md:grid-cols-2 gap-6">
{detailedScores.map((item) => (
<div key={item.name} className="bg-gray-50 rounded-xl p-6">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-3">
<span className="text-3xl">{item.icon}</span>
<h3 className="text-xl font-bold text-gray-900">{item.name}</h3>
</div>
<span className="text-2xl font-bold text-pink-600">{item.score}</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-3">
<div
className="bg-gradient-to-r from-pink-500 to-purple-500 h-3 rounded-full transition-all duration-500"
style={{ width: `${item.score}%` }}
></div>
</div>
</div>
))}
</div>
</div>
{/* 궁합 해석 */}
<div className="grid md:grid-cols-2 gap-8 mb-8">
{/* 장점 */}
<div className="bg-white rounded-2xl shadow-lg p-8">
<h3 className="text-2xl font-bold text-gray-900 mb-4 flex items-center">
<span className="text-3xl mr-3"></span>
</h3>
<ul className="space-y-3 text-gray-700">
<li className="flex items-start">
<span className="text-pink-600 mr-2"></span>
<span> .</span>
</li>
<li className="flex items-start">
<span className="text-pink-600 mr-2"></span>
<span> .</span>
</li>
<li className="flex items-start">
<span className="text-pink-600 mr-2"></span>
<span> .</span>
</li>
</ul>
</div>
{/* 주의점 */}
<div className="bg-white rounded-2xl shadow-lg p-8">
<h3 className="text-2xl font-bold text-gray-900 mb-4 flex items-center">
<span className="text-3xl mr-3"></span>
</h3>
<ul className="space-y-3 text-gray-700">
<li className="flex items-start">
<span className="text-purple-600 mr-2"></span>
<span> .</span>
</li>
<li className="flex items-start">
<span className="text-purple-600 mr-2"></span>
<span> .</span>
</li>
<li className="flex items-start">
<span className="text-purple-600 mr-2"></span>
<span> .</span>
</li>
</ul>
</div>
</div>
{/* 조언 */}
<div className="bg-gradient-to-r from-indigo-600 to-purple-600 rounded-2xl shadow-lg p-8 text-white mb-8">
<h3 className="text-2xl font-bold mb-4 text-center">💡 </h3>
<p className="text-lg leading-relaxed text-center">
.
.
, .
</p>
</div>
{/* 다른 메뉴 */}
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
<Link
href="/compatibility"
className="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl transition text-center group"
>
<div className="text-4xl mb-3">💕</div>
<h3 className="text-xl font-bold text-gray-900 mb-2"> </h3>
<p className="text-gray-600 text-sm"> </p>
</Link>
<Link
href="/"
className="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl transition text-center group"
>
<div className="text-4xl mb-3">📜</div>
<h3 className="text-xl font-bold text-gray-900 mb-2"> </h3>
<p className="text-gray-600 text-sm"> </p>
</Link>
<PDFButton
elementId="pdf-content"
filename={`궁합_${year1}${month1}${day1}_${year2}${month2}${day2}.pdf`}
buttonText="궁합 PDF 저장"
className="bg-gradient-to-r from-pink-600 to-purple-600 text-white rounded-xl p-6 shadow-lg hover:shadow-xl transition text-center group w-full disabled:opacity-50 disabled:cursor-not-allowed"
/>
<ShareButtons
title={`두 사람의 궁합 결과 - ${compatibilityScore}`}
description={`${getScoreText(compatibilityScore)} - 두 사람의 궁합을 확인해보세요!`}
/>
</div>
</div>
{/* Footer */}
<footer className="bg-gray-900 text-white py-12 px-4 mt-20">
<div className="max-w-7xl mx-auto text-center">
<div className="text-2xl font-bold mb-4 bg-gradient-to-r from-pink-400 to-purple-400 bg-clip-text text-transparent">
🔮
</div>
<p className="text-gray-400 mb-6">
</p>
<div className="text-sm text-gray-500">
<p>문의: bgg8988@gmail.com | <a href="https://jaengseung-made.com" target="_blank" rel="noopener noreferrer" className="hover:text-pink-400"></a></p>
<p className="mt-2">&copy; 2025 . All rights reserved.</p>
</div>
</div>
</footer>
</div>
);
}