두 사람의 궁합을 점수로 봐주는 재미 궁합 테스트 웹서비스를 만들어줘. 두 사람 이름(+선택: 생년월일·MBTI)과 관계 유형을 넣으면 0~100점 궁합 점수·등급·해설이 나오고, 그 결과를 친구에게 공유하게 만드는 게 목적이야. 아래 계산 규칙과 문구는 확정값이니 그대로 쓰고 바꾸지 마.
[페이지]
- / : 입력 — 제목 "우리 궁합 몇 점일까?", 부제 "이름만 넣어도 돼요 — 생년월일·MBTI를 더하면 더 정확해져요", 참여 카운터, 두 사람 입력, 관계 유형, [궁합 보기]
- /result/{input_hash} : 결과 — 새로고침하거나 공유 링크로 들어와도 같은 결과
- /admin : 관리자 — 참여자 화면에는 관리자로 가는 링크·버튼을 두지 않는다(주소로만 들어감)
[입력]
- 두 사람(나 / 상대) 각각: 이름(필수, 10자 이내) · 생년월일(선택) · MBTI(선택 — 16개 중 고르기, "모름" 가능).
- 관계 유형: 연인 / 썸 / 친구 중 하나(기본 연인).
- 생년월일은 둘 다 넣었을 때만, MBTI는 둘 다 골랐을 때만 계산에 들어간다 — 한쪽만 넣었으면 "둘 다 넣어야 반영돼요"라고 알려줘.
- 참여 카운터: "지금까지 {N}쌍이 궁합을 봤어요"(누적). 오늘 기록이 1건 이상이면 옆에 "오늘 {n}쌍".
[계산 — 확정 규칙, 난수 금지]
- 정리: 이름은 공백 제거. 한 사람 = "이름|생년월일(YYYY-MM-DD, 없으면 -)|MBTI(대문자, 없으면 -)". 두 사람 문자열을 정렬해 A·B로 정한다 — 순서를 바꿔 넣어도 같은 결과.
- 키 = 정렬한 두 줄 + 관계 유형, h = SHA-256(키). input_hash = h 의 hex 앞 12자.
- 성분 점수(각 0~100). 이름은 항상, 생년월일 성분 6종은 둘 다 생년월일이 있을 때만, MBTI는 둘 다 있을 때만.
· 이름 궁합: 음절 획수 = 초성+중성+종성 자모 획수 합(자모 획수표는 하나 정해 고정, 한글이 아니면 문자코드 % 9 + 1). 각 음절 획수 % 10 을 A·B 음절 번갈아 늘어놓고(A1 B1 A2 B2 …, 남는 쪽은 뒤에 이어서) 이웃한 두 수의 합 % 10 으로 줄여 나가 두 자리가 남으면 그 두 자리 수(예: 7, 3 → 73).
· 띠: 번호 = (출생연도 − 4) % 12 (0 쥐 … 11 돼지). 육합(0·1, 2·11, 3·10, 4·9, 5·8, 6·7) 95 / 삼합(8·0·4, 5·9·1, 2·6·10, 11·3·7 중 같은 무리의 서로 다른 두 띠) 88 / 같은 띠 78 / 번호 차 6 이면 38 / 그 외 55 + ((i+j) % 5) × 3.
· 별자리: 양력 생일로 판정 — 양 3/21~, 황소 4/20~, 쌍둥이 5/21~, 게 6/22~, 사자 7/23~, 처녀 8/23~, 천칭 9/23~, 전갈 10/23~, 사수 11/23~, 염소 12/22~1/19, 물병 1/20~, 물고기 2/19~3/20. 원소는 양자리부터 불·흙·바람·물 반복. 같은 원소 82, 불+바람·흙+물 88, 바람+물 62, 불+흙 58, 바람+흙 48, 불+물 42. 같은 별자리면 −6.
· 운명수: 생년월일 8자리 숫자를 한 자리가 될 때까지 더한 수 x, y. x+y=10 이면 90, 같으면 84, 차가 1 이면 55, 그 외 60 + ((x×y) % 7) × 3.
· 감성·신체·지성 리듬: 두 생일 사이 일수 d 로 round(50 + 50×cos(2πd / 주기)) — 감성 28일, 신체 23일, 지성 33일.
· MBTI: 50 + E/I(다르면 12, 같으면 2) + S/N(같으면 22, 다르면 −12) + T/F(다르면 10, 같으면 4) + J/P(다르면 8, 같으면 2), 최대 100.
- 가중치(연인 / 썸 / 친구): 이름 .18/.14/.14 · 띠 .12/.08/.12 · 별자리 .13/.13/.08 · 운명수 .07/.05/.08 · 감성 .20/.15/.08 · 신체 .05/.20/.05 · 지성 .05/.05/.25 · MBTI .20/.20/.20.
- raw = Σ(성분 × 가중치) / Σ(계산에 들어간 성분의 가중치). 흔들림 = (h 앞 8바이트를 부호 없는 정수로 읽은 값 % 11) − 5. 점수 = round(60 + (raw − 55) × 1.5 + 흔들림) 을 1~100 으로 자른다.
- 단, 이름 궁합만 들어간 경우(생년월일·MBTI 없음)는 점수 = round(60 + (이름 궁합 − 50) × 0.7 + 흔들림) 을 1~100 으로 자른다 — 위 식을 그대로 쓰면 이름만 넣은 사람의 40% 가까이가 100점이나 10점 이하로 몰린다.
- 등급과 한줄 해설:
91~100 천생연분 "이런 궁합은 놓치면 범죄예요"
81~90 환상의 케미 "같이 있으면 시간 가는 줄 모르는 사이"
66~80 좋은 궁합 "안정감 속에 설렘이 숨어 있는 조합"
51~65 노력형 궁합 "맞춰가는 재미가 있는 사이 — 노력이 답"
31~50 밀당 궁합 "당기고 밀리는 스릴… 지치지만 않는다면?"
1~30 극과 극 "정반대라서 오히려 끌릴 수도? (아닐 수도)"
- 잘 맞는 점 = 성분 점수 상위 2개, 주의할 점 = 하위 2개(들어간 성분이 하나뿐이면 각 1개). 성분마다 아래 문구 2개 중 하나를 h 로 고른다(같은 입력이면 같은 문구).
[성분별 문구] 잘 맞는 점 ① / ② · 주의할 점 ① / ②
- 이름 궁합: 이름부터 운명처럼 얽혀 있어요 / 서로의 이름을 부를수록 가까워지는 사이 · 가끔 서로의 마음을 넘겨짚지 마세요 / 표현은 이름 부르기부터 — 자주 불러주세요
- 띠: 타고난 기운이 서로를 밀어줘요 / 어른들이 보면 고개를 끄덕일 조합 · 고집 대 고집이 되면 길어져요 / 양보 한 번이 큰 싸움을 막아요
- 별자리: 감성의 결이 같은 원소예요 / 말하지 않아도 통하는 분위기 · 감정 온도차가 날 때가 있어요 / 기분 상했을 땐 하루 묵히지 말기
- 운명수: 운명수가 서로를 채워주는 짝이에요 / 숫자로도 증명되는 인연 · 삶의 속도가 달라 답답할 수 있어요 / 서로의 페이스를 존중해 주세요
- 감성 리듬: 기분의 파도가 같이 움직여요 / 슬플 때 같이 슬퍼해줄 사람 · 기분이 엇갈리는 날엔 거리두기보다 대화 / 예민한 날을 서로 알려주세요
- 신체 리듬: 에너지 리듬이 딱 맞아요 / 같이 놀면 지치지 않는 조합 · 한쪽만 지치는 약속은 피하세요 / 쉬는 것도 같이 — 억지로 끌고 가지 말기
- 지성 리듬: 대화가 끊기지 않는 사이 / 취향 토론이 데이트가 되는 조합 · 관심사가 갈릴 땐 번갈아 맞춰주기 / 설명하려 들지 말고 들어주세요
- MBTI: 성격 퍼즐이 맞물리는 조합이에요 / 서로의 빈칸을 채워주는 성격 · 말투 차이를 성의 차이로 오해하지 마세요 / 계획파·즉흥파 싸움은 무승부예요
[결과 화면]
- 두 이름을 하트로 잇고 관계 유형 표시.
- 점수(크게) + 0~100 게이지, 등급 + 한줄 해설.
- "전체 {N}쌍 중 상위 {p}%" — p = (이 점수보다 높은 결과 수 + 1) / 전체 × 100 을 올림.
- 잘 맞는 점 · 주의할 점.
- 성분별 점수 막대(계산에 들어간 성분만): 이름 궁합 · 띠 · 별자리 · 운명수 · 감성 리듬 · 신체 리듬 · 지성 리듬 · MBTI.
- 맨 아래 작게 "재미로 보는 테스트예요".
- 버튼: [공유하기](휴대폰은 기기 공유창, 안 되면 링크 복사) · [이미지로 저장](결과 카드를 PNG로 내려받기) · [우리도 해보기](첫 화면으로). 직접 계산하지 않고 공유 링크로 들어온 사람에겐 [우리도 해보기]를 가장 크게(내가 계산한 결과인지는 브라우저에 기억).
- 없는 결과 주소면 "결과를 찾을 수 없어요" + [궁합 보러 가기].
[공유 미리보기 — 중요]
- 배포된 공개 주소에서는 /api 로 시작하는 요청만 Go 서버가 받고, 나머지 경로는 빌드된 정적 파일이 응답한다(vite 개발 서버·플러그인은 배포본에 없다). 그래서 /result/… 에는 서버가 결과별 메타를 넣을 수 없다 — 개발 미리보기에서 되는 것처럼 보여도 배포 후엔 안 된다.
- 그래서 공유 주소는 /api/s/{input_hash} 로 한다: Go 가 결과별 OG 메타를 채운 작은 HTML 을 응답하고, 사람은 스크립트(location.replace)로 바로 /result/{input_hash} 로 보낸다. meta refresh 는 쓰지 마(크롤러가 따라가면 결과별 메타를 놓친다). 스크립트가 안 돌 때를 위해 본문에 결과 보기 링크도 둔다.
- og:title "{A} ♥ {B} 궁합 {점수}점", og:description "{등급} — {한줄 해설} 우리도 궁합 볼까?", og:url 은 이 공유 주소 자신, og:image 는 절대 주소의 대표 이미지(1200×630 PNG — 카톡은 SVG를 못 읽는다. 글자 없이 앱 색과 하트 모양으로, 서버 코드로 그려 만들어도 된다).
- 메타 값은 HTML 이스케이프(Go html/template) — 해설 속 따옴표가 속성을 깨지 않게.
- [공유하기]·링크 복사는 이 공유 주소를 쓴다.
- index.html 의 <title>("우리 궁합 테스트")과 기본 OG(앱 이름·한 줄 소개·같은 대표 이미지)도 채운다.
[관리자 /admin]
- 데모 계정 admin / demo1234 를 시드하고 로그인 화면에 안내를 표시해.
- 통계: 총 참여·오늘 참여, 최근 30일 일별 참여 막대, 점수 분포(10점 구간), 등급별 비율, 관계 유형별 평균 점수.
- 문구 관리: 등급 6개의 한줄 해설과 성분별 문구를 고쳐 저장. 고친 문구는 기존 결과 화면에도 바로 반영. [기본 문구로 되돌리기] 버튼.
[데이터]
- results(input_hash UNIQUE, name_a, name_b, relation, score, grade, parts JSON — 성분별 점수, created_at). 생년월일·MBTI 원문은 저장하지 않는다. 같은 입력을 다시 넣으면 새로 기록하지 않고 기존 결과 주소로 보낸다.
- copy_templates(kind: grade|strength|caution, key: 등급명 또는 성분명, position, text) — 위 문구로 시드. 해설 문구는 결과에 저장하지 않고 보여줄 때 여기서 고른다.
- admins(username, password_hash)
[시드] 카운터·통계가 0으로 보이지 않게 참여 기록 10쌍 안팎을 지난 10일에 흩어 넣어 둬(미래 날짜 금지, 최근일수록 조금 많게). 입력은 흔한 한국 이름·1985~2006년생으로 무작위로 만들되 점수는 위 공식으로 실제 계산해 분포가 자연스럽게. 관계 유형은 연인 50%·썸 30%·친구 20%.
[제외] 참여자 로그인·히스토리, 정통 사주·명리 계산, 유료 결과, 광고, 카카오 SDK(앱 키가 필요).
[화면] 모바일 우선 — 폭 360px 휴대폰에서 입력이 한 화면 안에 들어오고, 한 손으로 누르기 쉬운 큰 버튼.
[확인] 마치기 전에: 두 사람 순서를 바꿔 넣어도 같은 점수·같은 결과 주소인지, /api/s/{input_hash} 를 curl 로 요청하면 결과별 og 태그가 나오는지.