서사형 덱빌딩 게임 프로토 타입

This commit is contained in:
Kyoung5seo
2026-06-15 23:10:54 +09:00
parent e1310bc872
commit e1f5cafdf1
49 changed files with 3261 additions and 7477 deletions

View File

@@ -0,0 +1,11 @@
id,title,desc
AG_01,대토벌의 시대,괴수 토벌 집중 주간. (작전부 안건 빈도 증가 / 전투 예산 소모 심화)
AG_02,긴축 재정,허리띠를 졸라맵니다. (매 턴 기본 예산 $10 추가 차감 / 자산 매각 이익 증가)
AG_03,투명한 사회,감사팀이 활개칩니다. (비자금 사용 및 불법 자산 매각 시 의심도 상승폭 2배)
AG_04,그림자 작전,어둠의 뒷거래가 활성화됩니다. (안건 승인 시 비자금 획득 확률 및 양 증가)
AG_05,이단 심문기,마녀사냥의 주간입니다. (오컬트/마법 관련 안건 승인 시 엔트로피 폭등)
AG_06,연구 장려 주간,과학 발전에 예산을 쏟습니다. (R&D 본부 비용 30% 감소 및 자산 획득률 증가)
AG_07,피의 숙청,"배신자를 색출합니다. (안건 거절 시 신임도 대폭 상승, 하지만 의심도도 동반 상승)"
AG_08,대규모 차원 균열,우주적 재난 주간. (모든 안건의 엔트로피 기본 증가량이 1.5배로 증폭됨)
AG_09,글로벌 후원금,스폰서들의 기분이 좋습니다. (시작 시 총 예산 +$150 일시불 지원)
AG_10,통제 사회,대중을 통제합니다. (정보 통제 관련 안건 비용 절반 감소)
1 id title desc
2 AG_01 대토벌의 시대 괴수 토벌 집중 주간. (작전부 안건 빈도 증가 / 전투 예산 소모 심화)
3 AG_02 긴축 재정 허리띠를 졸라맵니다. (매 턴 기본 예산 $10 추가 차감 / 자산 매각 이익 증가)
4 AG_03 투명한 사회 감사팀이 활개칩니다. (비자금 사용 및 불법 자산 매각 시 의심도 상승폭 2배)
5 AG_04 그림자 작전 어둠의 뒷거래가 활성화됩니다. (안건 승인 시 비자금 획득 확률 및 양 증가)
6 AG_05 이단 심문기 마녀사냥의 주간입니다. (오컬트/마법 관련 안건 승인 시 엔트로피 폭등)
7 AG_06 연구 장려 주간 과학 발전에 예산을 쏟습니다. (R&D 본부 비용 30% 감소 및 자산 획득률 증가)
8 AG_07 피의 숙청 배신자를 색출합니다. (안건 거절 시 신임도 대폭 상승, 하지만 의심도도 동반 상승)
9 AG_08 대규모 차원 균열 우주적 재난 주간. (모든 안건의 엔트로피 기본 증가량이 1.5배로 증폭됨)
10 AG_09 글로벌 후원금 스폰서들의 기분이 좋습니다. (시작 시 총 예산 +$150 일시불 지원)
11 AG_10 통제 사회 대중을 통제합니다. (정보 통제 관련 안건 비용 절반 감소)

View File

@@ -0,0 +1,21 @@
id,name,type,desc,passive_eff_key,passive_eff_val,risk_eff_key,risk_eff_val,sale_value,sale_risk_key,sale_risk_val,flavor
AST_001,유령 커피머신,Relic,"커피 맛은 천상, 밤마다 악몽.",budget,5,entropy,1,50,suspicion,5,카페인과 공포는 훌륭한 각성제.
AST_002,말하는 곰팡이,Bio,벽속의 비밀을 엿듣고 알려줍니다.,black_fund,5,suspicion,2,80,entropy,10,내부 고발자보다 낫습니다.
AST_003,트롤 고기,Bio,잘라내도 계속 자라나는 고기 덩어리.,budget,15,trust,-2,120,suspicion,20,메뉴가 함박스테이크로 고정됨.
AST_004,저주받은 금화,Relic,소유자의 수명을 대가로 금화 생성.,black_fund,10,trust,-5,200,entropy,15,시간으로 돈을 사는 겁니다.
AST_005,비유클리드 칩,Tech,현실에 존재하지 않는 연산을 수행.,budget,30,entropy,5,300,suspicion,30,1+1=3이 되는 기적.
AST_006,베헤모스의 알,Bio,S급 괴수의 알입니다. 따뜻합니다.,trust,2,entropy,10,500,entropy,50,좋은 징조는 아니죠.
AST_007,예언자의 눈알,Relic,10초 뒤의 미래를 보여줍니다.,black_fund,15,entropy,3,150,suspicion,10,내일의 하한가를 아는 고통.
AST_008,자동 서명 깃펜,Relic,서류에 자동으로 서명합니다.,trust,5,black_fund,-5,40,trust,-10,팔이 아플 일은 없겠군요.
AST_009,흡혈귀 혈액팩,Bio,강력한 재생 효과가 있는 흡혈귀 피.,-,-,suspicion,1,100,suspicion,15,빈티지 1980년산 O형.
AST_010,외계 통신기,Tech,우주 어딘가로 좌표를 송출합니다.,-,-,entropy,5,80,entropy,20,"응답하라, 오버."
AST_011,거짓말 탐지 넥타이,Relic,거짓말을 하면 목을 강하게 조릅니다.,trust,3,trust,-1,60,suspicion,5,숨 막히는 진실 공방.
AST_012,무한 동력 햄스터,Bio,지치지 않고 쳇바퀴를 도는 돌연변이.,budget,20,entropy,5,250,entropy,10,존재해선 안 될 생물입니다.
AST_013,부패한 장부 원본,Info,전임 국장의 횡령 내역 파일.,black_fund,20,suspicion,10,0,trust,-50,이걸 태우면 당신도 공범입니다.
AST_014,둠스데이 버튼,Tech,세계를 멸망시킬 버튼.,-,-,entropy,15,1000,entropy,100,절대 누르지 마시오.
AST_015,사랑의 묘약,Relic,상대를 맹목적으로 따르게 만듭니다.,trust,5,suspicion,5,150,suspicion,20,화학적 동의도 동의입니까?
AST_016,좀비 바이러스 백신,Bio,세계 유일의 치료제 샘플.,-,-,entropy,5,600,entropy,30,손 떨지 마세요.
AST_017,악마의 계약서,Relic,대가 란이 비어있는 백지 계약서.,black_fund,30,entropy,8,5,entropy,2,핏방울을 떨어뜨리세요.
AST_018,투명 망토,Tech,투명해지지만 소리는 남음.,suspicion,-2,-,-,90,suspicion,10,보이지 않는다고 없는 건 아니죠.
AST_019,기밀 하드디스크,Info,주요국 정상들의 치명적 스캔들.,budget,50,trust,-10,400,trust,-100,판도라의 폴더.
AST_020,식인 식물 화분,Bio,시체 처리용으로 훌륭합니다.,suspicion,-5,trust,-1,30,entropy,,
1 id name type desc passive_eff_key passive_eff_val risk_eff_key risk_eff_val sale_value sale_risk_key sale_risk_val flavor
2 AST_001 유령 커피머신 Relic 커피 맛은 천상, 밤마다 악몽. budget 5 entropy 1 50 suspicion 5 카페인과 공포는 훌륭한 각성제.
3 AST_002 말하는 곰팡이 Bio 벽속의 비밀을 엿듣고 알려줍니다. black_fund 5 suspicion 2 80 entropy 10 내부 고발자보다 낫습니다.
4 AST_003 트롤 고기 Bio 잘라내도 계속 자라나는 고기 덩어리. budget 15 trust -2 120 suspicion 20 메뉴가 함박스테이크로 고정됨.
5 AST_004 저주받은 금화 Relic 소유자의 수명을 대가로 금화 생성. black_fund 10 trust -5 200 entropy 15 시간으로 돈을 사는 겁니다.
6 AST_005 비유클리드 칩 Tech 현실에 존재하지 않는 연산을 수행. budget 30 entropy 5 300 suspicion 30 1+1=3이 되는 기적.
7 AST_006 베헤모스의 알 Bio S급 괴수의 알입니다. 따뜻합니다. trust 2 entropy 10 500 entropy 50 좋은 징조는 아니죠.
8 AST_007 예언자의 눈알 Relic 10초 뒤의 미래를 보여줍니다. black_fund 15 entropy 3 150 suspicion 10 내일의 하한가를 아는 고통.
9 AST_008 자동 서명 깃펜 Relic 서류에 자동으로 서명합니다. trust 5 black_fund -5 40 trust -10 팔이 아플 일은 없겠군요.
10 AST_009 흡혈귀 혈액팩 Bio 강력한 재생 효과가 있는 흡혈귀 피. - - suspicion 1 100 suspicion 15 빈티지 1980년산 O형.
11 AST_010 외계 통신기 Tech 우주 어딘가로 좌표를 송출합니다. - - entropy 5 80 entropy 20 응답하라, 오버.
12 AST_011 거짓말 탐지 넥타이 Relic 거짓말을 하면 목을 강하게 조릅니다. trust 3 trust -1 60 suspicion 5 숨 막히는 진실 공방.
13 AST_012 무한 동력 햄스터 Bio 지치지 않고 쳇바퀴를 도는 돌연변이. budget 20 entropy 5 250 entropy 10 존재해선 안 될 생물입니다.
14 AST_013 부패한 장부 원본 Info 전임 국장의 횡령 내역 파일. black_fund 20 suspicion 10 0 trust -50 이걸 태우면 당신도 공범입니다.
15 AST_014 둠스데이 버튼 Tech 세계를 멸망시킬 버튼. - - entropy 15 1000 entropy 100 절대 누르지 마시오.
16 AST_015 사랑의 묘약 Relic 상대를 맹목적으로 따르게 만듭니다. trust 5 suspicion 5 150 suspicion 20 화학적 동의도 동의입니까?
17 AST_016 좀비 바이러스 백신 Bio 세계 유일의 치료제 샘플. - - entropy 5 600 entropy 30 손 떨지 마세요.
18 AST_017 악마의 계약서 Relic 대가 란이 비어있는 백지 계약서. black_fund 30 entropy 8 5 entropy 2 핏방울을 떨어뜨리세요.
19 AST_018 투명 망토 Tech 투명해지지만 소리는 남음. suspicion -2 - - 90 suspicion 10 보이지 않는다고 없는 건 아니죠.
20 AST_019 기밀 하드디스크 Info 주요국 정상들의 치명적 스캔들. budget 50 trust -10 400 trust -100 판도라의 폴더.
21 AST_020 식인 식물 화분 Bio 시체 처리용으로 훌륭합니다. suspicion -5 trust -1 30 entropy

View File

@@ -0,0 +1,13 @@
id,name,desc,tag,type,flavor
B_01,생명 존중법,무력 사용 및 사살 안건의 승인을 금지합니다.,kill,lock_approve,괴수의 생명도 생명입니까?
B_02,무제한 발포령,작전부의 전투/사살 안건 거절을 금지합니다.,kill,lock_reject,탄약은 충분합니다. 쏘십시오.
B_03,마법 통제법,통제 불능의 오컬트/마법 안건 승인을 금지합니다.,magic,lock_approve,미신의 시대는 끝났습니다.
B_04,생명 윤리법,비윤리적인 생체 실험 및 변이 안건 승인을 금지합니다.,bio,lock_approve,우리는 아직 인간으로 남아있어야 합니다.
B_05,예산 삭감안,예산 낭비가 우려되는 대규모 안건 승인을 금지합니다.,budget,lock_approve,영수증 처리가 안 되는 일은 하지 마십시오.
B_06,강제 징집령,작전 본부의 모든 방어/전투 안건 거절을 금지합니다.,ops,lock_reject,도망칠 곳은 없습니다.
B_07,정보 공개법,대중을 기만하는 은폐/기억소거 안건 승인을 금지합니다.,secret,lock_approve,대중은 진실을 알 권리가 있습니다.
B_08,비밀 유지법,기구를 외부로 노출시키는 양지화 안건 승인을 금지합니다.,public,lock_approve,우리는 영원히 그림자 속에 남습니다.
B_09,특별 감사법,자금 유용 및 횡령 관련 안건 거절을 금지(강제 수사)합니다.,audit,lock_reject,숨길 것이 없다면 두려울 것도 없습니다.
B_10,연구 지원법,연구 부서의 모든 기술/연구 안건 거절을 금지합니다.,research,lock_reject,과학 발전만이 인류의 살 길입니다.
B_11,순혈주의 선언,이종족 및 비인간 관련 안건의 승인을 엄격히 금지합니다.,pure,lock_approve,뾰족한 귀를 가진 자들을 믿지 마십시오.
B_12,진화 강제법,신체 개조를 미루거나 거절하는 행위를 금지합니다.,bio,lock_reject,진화를 거부하는 것은 곧 도태입니다.
1 id name desc tag type flavor
2 B_01 생명 존중법 무력 사용 및 사살 안건의 승인을 금지합니다. kill lock_approve 괴수의 생명도 생명입니까?
3 B_02 무제한 발포령 작전부의 전투/사살 안건 거절을 금지합니다. kill lock_reject 탄약은 충분합니다. 쏘십시오.
4 B_03 마법 통제법 통제 불능의 오컬트/마법 안건 승인을 금지합니다. magic lock_approve 미신의 시대는 끝났습니다.
5 B_04 생명 윤리법 비윤리적인 생체 실험 및 변이 안건 승인을 금지합니다. bio lock_approve 우리는 아직 인간으로 남아있어야 합니다.
6 B_05 예산 삭감안 예산 낭비가 우려되는 대규모 안건 승인을 금지합니다. budget lock_approve 영수증 처리가 안 되는 일은 하지 마십시오.
7 B_06 강제 징집령 작전 본부의 모든 방어/전투 안건 거절을 금지합니다. ops lock_reject 도망칠 곳은 없습니다.
8 B_07 정보 공개법 대중을 기만하는 은폐/기억소거 안건 승인을 금지합니다. secret lock_approve 대중은 진실을 알 권리가 있습니다.
9 B_08 비밀 유지법 기구를 외부로 노출시키는 양지화 안건 승인을 금지합니다. public lock_approve 우리는 영원히 그림자 속에 남습니다.
10 B_09 특별 감사법 자금 유용 및 횡령 관련 안건 거절을 금지(강제 수사)합니다. audit lock_reject 숨길 것이 없다면 두려울 것도 없습니다.
11 B_10 연구 지원법 연구 부서의 모든 기술/연구 안건 거절을 금지합니다. research lock_reject 과학 발전만이 인류의 살 길입니다.
12 B_11 순혈주의 선언 이종족 및 비인간 관련 안건의 승인을 엄격히 금지합니다. pure lock_approve 뾰족한 귀를 가진 자들을 믿지 마십시오.
13 B_12 진화 강제법 신체 개조를 미루거나 거절하는 행위를 금지합니다. bio lock_reject 진화를 거부하는 것은 곧 도태입니다.

View File

@@ -0,0 +1,9 @@
id,name,color,share,rival,love_tag,hate_tag,desc
iron,철의 수호당,#ef4444,25,libra,kill,bio,괴수 즉결 처형 및 압도적 군사력 증강을 주장하는 강경파.
libra,지식 보존당,#3b82f6,20,iron,research,kill,괴수를 생포하고 연구하여 미지의 지식을 축적하려는 학자들.
pure,순수 인간당,#10b981,15,gene,pure,bio,"인간 중심주의를 표방하며, 이종족과 신체 개조를 극도로 혐오함."
gene,진화 미래당,#8b5cf6,10,pure,bio,pure,인류의 생물학적 한계를 초월하기 위해 괴수 DNA 이식 등을 시도함.
trade,차원 무역당,#f59e0b,10,covenant,budget,magic,이계와의 무역과 괴수 부산물 판매로 막대한 부를 축적하려는 자본가들.
covenant,고대 공존당,#06b6d4,5,trade,magic,budget,심연의 고대 신들을 숭배하며 그들과의 평화적(굴종적) 공존을 모색함.
veil,침묵의 장막당,#6b7280,10,reshape,secret,public,"대중이 진실을 알면 미쳐버릴 거라 믿으며, 철저한 은폐와 기억 소거를 집행함."
reshape,세계 재건당,#000000,5,veil,public,secret,비밀주의를 버리고 기구가 전면에 나서 세계 정부로 군림해야 한다고 믿음.
1 id name color share rival love_tag hate_tag desc
2 iron 철의 수호당 #ef4444 25 libra kill bio 괴수 즉결 처형 및 압도적 군사력 증강을 주장하는 강경파.
3 libra 지식 보존당 #3b82f6 20 iron research kill 괴수를 생포하고 연구하여 미지의 지식을 축적하려는 학자들.
4 pure 순수 인간당 #10b981 15 gene pure bio 인간 중심주의를 표방하며, 이종족과 신체 개조를 극도로 혐오함.
5 gene 진화 미래당 #8b5cf6 10 pure bio pure 인류의 생물학적 한계를 초월하기 위해 괴수 DNA 이식 등을 시도함.
6 trade 차원 무역당 #f59e0b 10 covenant budget magic 이계와의 무역과 괴수 부산물 판매로 막대한 부를 축적하려는 자본가들.
7 covenant 고대 공존당 #06b6d4 5 trade magic budget 심연의 고대 신들을 숭배하며 그들과의 평화적(굴종적) 공존을 모색함.
8 veil 침묵의 장막당 #6b7280 10 reshape secret public 대중이 진실을 알면 미쳐버릴 거라 믿으며, 철저한 은폐와 기억 소거를 집행함.
9 reshape 세계 재건당 #000000 5 veil public secret 비밀주의를 버리고 기구가 전면에 나서 세계 정부로 군림해야 한다고 믿음.

View File

@@ -0,0 +1 @@
id,title,req_tags,cost_anomaly,reward_asset_id,penalty_entropy
1 id title req_tags cost_anomaly reward_asset_id penalty_entropy

View File

@@ -0,0 +1,9 @@
id,title,target_type,target_dept,target_val,reward_txt,penalty_txt
Q_01,강경 진압,approve_count,작전 본부,3,예산 +$100,신임도 -10
Q_02,안정화 작업,maintain_entropy,-,25,신임도 +15,엔트로피 +15
Q_03,흑자 전환,black_fund,-,60,의심도 초기화,비자금 전액 몰수
Q_04,지식의 축적,approve_count,R&D 본부,2,새로운 자산 지급,예산 -$50
Q_05,정보 통제,approve_count,정보 본부,2,신임도 +10,의심도 +20
Q_06,철저한 관리,reject_count,-,3,예산 +$80,엔트로피 +10
Q_07,행정 쇄신,approve_count,행정 본부,2,의심도 -10,신임도 -5
Q_08,자본의 흐름,budget_up,-,600,비자금 +30,의심도 +15
1 id title target_type target_dept target_val reward_txt penalty_txt
2 Q_01 강경 진압 approve_count 작전 본부 3 예산 +$100 신임도 -10
3 Q_02 안정화 작업 maintain_entropy - 25 신임도 +15 엔트로피 +15
4 Q_03 흑자 전환 black_fund - 60 의심도 초기화 비자금 전액 몰수
5 Q_04 지식의 축적 approve_count R&D 본부 2 새로운 자산 지급 예산 -$50
6 Q_05 정보 통제 approve_count 정보 본부 2 신임도 +10 의심도 +20
7 Q_06 철저한 관리 reject_count - 3 예산 +$80 엔트로피 +10
8 Q_07 행정 쇄신 approve_count 행정 본부 2 의심도 -10 신임도 -5
9 Q_08 자본의 흐름 budget_up - 600 비자금 +30 의심도 +15

View File

@@ -0,0 +1,41 @@
id,act,dept,color,title,body,faction,rival,tags,conflict,cost,yes_e,no_e,flavor,reward_asset_id
101,1,행정 본부,#d97706,탕비실 믹스커피 횡령,야간조 요원들이 비품실의 믹스커피 50박스를 사적으로 빼돌렸습니다. 횡령으로 징계할까요?,지식 보존당,iron,audit,원칙 vs 융통성,0,-1,1,맥심 골드는 중대 사항입니다.,AST_001
102,1,작전 본부,#c92a2a,하수구 슬라임,하수구에서 악취를 유발하는 하급 슬라임이 발견되었습니다. 화학반을 투입해 소각하시겠습니까?,철의 수호당,libra,"kill,ops",강경진압 vs 방치,20,-3,3,소금 뿌리면 안 됩니까?,-
103,1,정보 본부,#1c4f82,인터넷 괴담 검열,SNS에 퍼진 기현상 목격담을 통제망을 가동해 강제 삭제하고 작성자를 추적할까요?,침묵의 장막당,reshape,secret,은폐 vs 자유,15,-2,4,좋아요가 만 개를 넘으면 실체가 됩니다.,-
104,1,R&D 본부,#6b21a8,말하는 곰팡이 배양,창고에서 인간의 언어를 모방하는 곰팡이가 발견되었습니다. 소각 규정을 무시하고 배양할까요?,진화 미래당,pure,"bio,research",호기심 vs 규정,30,4,-2,목소리가 꽤 좋답니다.,AST_002
105,1,행정 본부,#d97706,국회 로비 자금,기구의 예산을 늘리기 위해 의원들에게 '도서 구입비' 명목의 비자금을 찔러 넣어줄까요?,세계 재건당,veil,"budget,audit",타협 vs 청렴,60,0,0,돈 냄새는 귀신도 좋아합니다.,-
106,1,작전 본부,#c92a2a,이단 종교 집회,괴수를 섬기는 '별빛 교단'이 민간인을 모아 집회를 엽니다. 무장 타격대로 무력 해산시킬까요?,철의 수호당,covenant,"kill,ops",무력 vs 평화,40,-4,5,이단에게 자비는 없습니다.,-
107,1,정보 본부,#1c4f82,상수도 기억소거,전투를 목격한 시민이 너무 많습니다. 상수도에 약한 기억소거제를 타서 광역 은폐를 실시할까요?,침묵의 장막당,reshape,"secret,bio",통제 vs 윤리,30,-5,6,수돗물 맛이 조금 달콤해집니다.,-
108,1,R&D 본부,#6b21a8,이단 유물 발굴,재개발 구역에서 고대 마법 유적이 발견되었습니다. 군의 폭파 주장을 묵살하고 발굴할까요?,지식 보존당,iron,"magic,research",지식 vs 파괴,40,3,-3,흙 묻은 역사가 미래를 엽니다.,-
109,1,행정 본부,#d97706,괴수 가죽 밀거래,창고팀이 죽은 괴수 가죽을 블랙마켓에 팔려다 걸렸습니다. 징계 대신 가죽을 팔아 예산에 보탤까요?,차원 무역당,covenant,"budget,audit",자본 vs 규정,-80,5,-2,돈 냄새가 피 냄새를 덮습니다.,-
110,1,작전 본부,#c92a2a,뱀파이어 요원 채용,전향한 흡혈귀를 요원으로 쓰자는 건의입니다. 피를 주기적으로 제공해야 하지만 효율은 좋습니다.,고대 공존당,pure,"bio,ops",공존 vs 순혈,20,2,-2,피만 제때 주면 우리 편입니다.,AST_009
111,1,정보 본부,#1c4f82,라디오 전파 납치,심야 라디오 DJ가 우리 조직을 폭로하려 합니다. 납치 세뇌 후 선전 방송 스피커로 재활용할까요?,세계 재건당,veil,"public,secret",세뇌 vs 침묵,50,4,-3,통제의 꽃은 대중의 열광입니다.,-
112,1,R&D 본부,#6b21a8,부상 요원 기계화,팔을 잃은 요원이 사이보그 개조 수술을 자원했습니다. 순수 인간주의 원칙을 깨고 수술할까요?,진화 미래당,pure,"bio,research",진화 vs 순수,40,3,-2,살점보다 강철이 믿음직합니다.,-
113,1,행정 본부,#d97706,이종족 구호소 건립,차원 균열 난민들을 위해 도심 외곽에 이종족 난민 구호소를 세울까요? 시민 반발이 거셀 겁니다.,고대 공존당,trade,"pure,budget",공존 vs 배척,60,4,-4,우리는 모두 우주의 미아입니다.,-
114,1,작전 본부,#c92a2a,예언자 스카웃,10초 뒤를 보는 사기꾼이 고액 연봉을 요구하며 합류를 제안했습니다. 채용할까요?,차원 무역당,covenant,"magic,ops",실용 vs 원칙,70,5,-2,하한가를 피하는 값싼 비용.,AST_007
115,1,정보 본부,#1c4f82,기밀문서 유출자,내부 고발자가 비윤리적 실험을 폭로하려다 체포되었습니다. 그를 '조용히 처리'할까요?,침묵의 장막당,reshape,"secret,kill",은폐 vs 진실,30,-5,8,진실은 무덤 속에 있어야 합니다.,AST_013
201,2,작전 본부,#c92a2a,베헤모스의 알 회수,불법 경매장에 도시 붕괴급 괴수의 알이 나왔습니다. 파괴하지 않고 연구용으로 회수할까요?,진화 미래당,iron,"ops,bio",연구 vs 안전,100,8,-5,품속에 넣으니 따뜻하네요.,AST_006
202,2,R&D 본부,#6b21a8,비유클리드 칩 도입,연산력은 무한하지만 주변 공간을 뒤틀어버리는 외계 칩을 메인 서버에 장착할까요?,세계 재건당,pure,"research,magic",기술 vs 현실붕괴,60,6,0,1+1=3이 되는 기적.,AST_005
203,2,행정 본부,#d97706,저주받은 금화,소지자의 수명을 갉아먹지만 무한히 금화를 쏟아내는 유적 항아리를 자금원으로 쓸까요?,차원 무역당,covenant,"magic,budget",자본 vs 생명,-150,10,-5,돈 냄새가 핏물보다 진하군요.,AST_004
204,2,작전 본부,#c92a2a,전술핵 타격,12구역에 S급 괴수가 출현했습니다. 수만 명의 희생을 감수하고 소형 전술핵 타격을 승인할까요?,철의 수호당,libra,"kill,ops",학살 vs 방어실패,120,-15,20,도시는 다시 지으면 됩니다.,-
205,2,정보 본부,#1c4f82,해킹 그룹 참수,적대 해커들이 1급 기밀을 훔치고 있습니다. 역추적 암살 프로그램을 가동해 뇌를 태워버릴까요?,침묵의 장막당,reshape,"secret,kill",암살 vs 유출,50,-8,15,모니터 너머에 죽음 배달.,-
206,2,R&D 본부,#6b21a8,초인 부대 스파르탄,고통을 느끼지 못하는 생체 병기 부대를 창설하기 위해 인간의 존엄성을 포기하시겠습니까?,진화 미래당,pure,"bio,ops",진화 vs 윤리,90,10,-8,괴물을 잡기 위해 괴물이 됩니다.,-
207,2,행정 본부,#d97706,외계 물질 매각,창고의 위험한 외계 광물을 군수 기업이 거액에 사겠다고 합니다. 나중 파장은 무시하고 팔까요?,차원 무역당,veil,"budget,public",자본 vs 은폐,-200,12,-6,위험은 저들에게 전가합시다.,-
208,2,정보 본부,#1c4f82,숭배자 국회 진출,괴수를 섬기는 유력 인사가 국회에 입성하려 합니다. 선거를 조작하여 무조건 당선을 저지할까요?,철의 수호당,covenant,"secret,public",개입 vs 중립,60,-5,8,정치는 총칼 없는 전쟁입니다.,-
209,2,작전 본부,#c92a2a,생체 병기 폭주,피아를 식별하지 못하는 개조 요원이 폭주 중입니다. 목의 자폭 목걸이를 기폭시켜 처형할까요?,순수 인간당,gene,"kill,bio",처분 vs 관용,30,-6,10,불량품은 폐기해야 합니다.,-
210,2,R&D 본부,#6b21a8,타임머신 가동,작전 실패를 돌리기 위해 현실 우주 붕괴 리스크(70%)를 안고 회귀 장치를 가동하시겠습니까?,지식 보존당,iron,"research,magic",시간조작 vs 순응,150,18,-5,시간을 속이면 시간도 우리를 속입니다.,-
211,2,행정 본부,#d97706,차원문 상업 개방,이종족 상인들에게 통행료를 받고 차원문을 열어주는 관세법을 승인하시겠습니까?,차원 무역당,pure,"budget,magic",개방 vs 통제,-120,10,-4,돈은 차원을 넘어서도 통용됩니다.,-
212,2,정보 본부,#1c4f82,언론사 대표 암살,방송사 대표가 내일 아침 기구의 실체를 폭로하려 합니다. 오늘 밤 특수 요원을 보내 암살할까요?,침묵의 장막당,reshape,"kill,secret",암살 vs 폭로,70,-10,18,기사는 발행되지 않을 때 완벽합니다.,-
213,2,작전 본부,#c92a2a,지성체 괴수 휴전,언어를 구사하는 괴수 여왕이 학살을 멈추는 조건으로 영토 인정을 요구합니다. 휴전할까요?,고대 공존당,iron,ops,평화 vs 자존심,0,5,-12,적과 악수하려면 칼을 놔야 합니다.,-
214,2,R&D 본부,#6b21a8,복제 요원 양산,감정이 삭제된 복제 인간 요원들을 공장에서 찍어내듯 양산하는 비윤리적 계획을 승인할까요?,진화 미래당,pure,"bio,ops",복제 vs 윤리,110,12,-6,슬퍼할 가족이 없는 완벽한 소모품.,-
215,2,행정 본부,#d97706,최후의 벙커,대중을 버리고 수뇌부만 피신할 수 있는 초거대 지하 벙커 건설에 막대한 예산을 쏟아부을까요?,세계 재건당,trade,budget,생존 vs 방치,180,8,-4,지하엔 버려진 자들의 비명이 안 들립니다.,-
301,3,정보 본부,#1c4f82,비밀 결사 공개,세계가 패닉에 빠졌습니다. 음지에서 벗어나 우리가 전 세계를 직접 통치하겠다고 선포할까요?,세계 재건당,veil,"public,secret",양지화 vs 은폐,100,15,-5,구원자는 그림자에 숨지 않습니다.,-
302,3,작전 본부,#c92a2a,차원 붕괴 폭탄,적의 차원을 영구 붕괴시키는 블랙홀 폭탄입니다. 여파로 지구도 절반 파괴됩니다. 쏘시겠습니까?,철의 수호당,libra,"kill,ops",파멸 vs 지속,200,25,-10,불타는 세계 위에서 승리를 외칩니다.,AST_014
303,3,R&D 본부,#6b21a8,인류 강제 진화,전 인류의 DNA를 변이시키는 가스를 살포해 강제로 진화시킵니다. 부작용 치사율은 50%입니다.,진화 미래당,pure,bio,진화 vs 순수,250,30,-15,낡은 허물을 벗어던질 때입니다.,-
304,3,행정 본부,#d97706,글로벌 금융 해킹,자금이 완전히 고갈되었습니다. 대공황을 감수하고 적대국의 중앙 은행을 해킹해 예산을 증발시킬까요?,차원 무역당,audit,"budget,secret",자본 vs 경제붕괴,-300,20,-8,세상이 망해도 돈은 씁니다.,-
305,3,정보 본부,#1c4f82,지구 뇌파 리셋,폭동을 잠재우기 위해 위성에서 펄스를 쏴 전 인류를 기억 상실의 꼭두각시로 만들까요?,침묵의 장막당,reshape,"secret,public",백지화 vs 혼돈,180,22,-12,무지가 곧 평화요 망각이 구원입니다.,-
306,3,R&D 본부,#6b21a8,아카식 레코드 접속,우주의 모든 진리가 담긴 서버에 접속합니다. 연구원 90%가 뇌사할 확률을 안고 강행할까요?,지식 보존당,covenant,"research,magic",진리 vs 파멸,120,18,-10,우주의 비밀 앞에 인간은 나약합니다.,-
307,3,작전 본부,#c92a2a,군단 맹약,외계 군단이 인류 절반을 노예로 바치는 대가로 침공을 멈추겠답니다. 항복 문서에 서명할까요?,고대 공존당,iron,ops,굴복 vs 멸망,80,15,-18,무릎 꿇어서라도 숨을 이어가야 합니다.,-
308,3,행정 본부,#d97706,유전자 방주 발사,지구를 포기하고 순수 인류 엘리트들만 태운 방주를 우주로 발사해 탈출하시겠습니까?,순수 인간당,gene,"pure,budget",도피 vs 투쟁,200,25,-8,불순물들은 죽어가는 지구에 둡니다.,-
309,3,R&D 본부,#6b21a8,차원 융합 폭주,물리법칙을 무시하고 적 차원과 우리 차원을 융합시켜 아예 새로운 우주를 창조할까요?,세계 재건당,trade,"magic,research",재창조 vs 유지,220,35,-20,헌 우주를 부수고 새 우주를 엽시다.,-
310,3,작전 본부,#c92a2a,대재앙 방관,다른 대륙에 초거대 괴수가 깨어났습니다. 우리 전력을 보존하기 위해 그들의 멸망을 방관할까요?,침묵의 장막당,iron,"ops,kill",방관 vs 저지,0,20,-15,지는 싸움은 피하는 게 상책입니,-
1 id act dept color title body faction rival tags conflict cost yes_e no_e flavor reward_asset_id
2 101 1 행정 본부 #d97706 탕비실 믹스커피 횡령 야간조 요원들이 비품실의 믹스커피 50박스를 사적으로 빼돌렸습니다. 횡령으로 징계할까요? 지식 보존당 iron audit 원칙 vs 융통성 0 -1 1 맥심 골드는 중대 사항입니다. AST_001
3 102 1 작전 본부 #c92a2a 하수구 슬라임 하수구에서 악취를 유발하는 하급 슬라임이 발견되었습니다. 화학반을 투입해 소각하시겠습니까? 철의 수호당 libra kill,ops 강경진압 vs 방치 20 -3 3 소금 뿌리면 안 됩니까? -
4 103 1 정보 본부 #1c4f82 인터넷 괴담 검열 SNS에 퍼진 기현상 목격담을 통제망을 가동해 강제 삭제하고 작성자를 추적할까요? 침묵의 장막당 reshape secret 은폐 vs 자유 15 -2 4 좋아요가 만 개를 넘으면 실체가 됩니다. -
5 104 1 R&D 본부 #6b21a8 말하는 곰팡이 배양 창고에서 인간의 언어를 모방하는 곰팡이가 발견되었습니다. 소각 규정을 무시하고 배양할까요? 진화 미래당 pure bio,research 호기심 vs 규정 30 4 -2 목소리가 꽤 좋답니다. AST_002
6 105 1 행정 본부 #d97706 국회 로비 자금 기구의 예산을 늘리기 위해 의원들에게 '도서 구입비' 명목의 비자금을 찔러 넣어줄까요? 세계 재건당 veil budget,audit 타협 vs 청렴 60 0 0 돈 냄새는 귀신도 좋아합니다. -
7 106 1 작전 본부 #c92a2a 이단 종교 집회 괴수를 섬기는 '별빛 교단'이 민간인을 모아 집회를 엽니다. 무장 타격대로 무력 해산시킬까요? 철의 수호당 covenant kill,ops 무력 vs 평화 40 -4 5 이단에게 자비는 없습니다. -
8 107 1 정보 본부 #1c4f82 상수도 기억소거 전투를 목격한 시민이 너무 많습니다. 상수도에 약한 기억소거제를 타서 광역 은폐를 실시할까요? 침묵의 장막당 reshape secret,bio 통제 vs 윤리 30 -5 6 수돗물 맛이 조금 달콤해집니다. -
9 108 1 R&D 본부 #6b21a8 이단 유물 발굴 재개발 구역에서 고대 마법 유적이 발견되었습니다. 군의 폭파 주장을 묵살하고 발굴할까요? 지식 보존당 iron magic,research 지식 vs 파괴 40 3 -3 흙 묻은 역사가 미래를 엽니다. -
10 109 1 행정 본부 #d97706 괴수 가죽 밀거래 창고팀이 죽은 괴수 가죽을 블랙마켓에 팔려다 걸렸습니다. 징계 대신 가죽을 팔아 예산에 보탤까요? 차원 무역당 covenant budget,audit 자본 vs 규정 -80 5 -2 돈 냄새가 피 냄새를 덮습니다. -
11 110 1 작전 본부 #c92a2a 뱀파이어 요원 채용 전향한 흡혈귀를 요원으로 쓰자는 건의입니다. 피를 주기적으로 제공해야 하지만 효율은 좋습니다. 고대 공존당 pure bio,ops 공존 vs 순혈 20 2 -2 피만 제때 주면 우리 편입니다. AST_009
12 111 1 정보 본부 #1c4f82 라디오 전파 납치 심야 라디오 DJ가 우리 조직을 폭로하려 합니다. 납치 세뇌 후 선전 방송 스피커로 재활용할까요? 세계 재건당 veil public,secret 세뇌 vs 침묵 50 4 -3 통제의 꽃은 대중의 열광입니다. -
13 112 1 R&D 본부 #6b21a8 부상 요원 기계화 팔을 잃은 요원이 사이보그 개조 수술을 자원했습니다. 순수 인간주의 원칙을 깨고 수술할까요? 진화 미래당 pure bio,research 진화 vs 순수 40 3 -2 살점보다 강철이 믿음직합니다. -
14 113 1 행정 본부 #d97706 이종족 구호소 건립 차원 균열 난민들을 위해 도심 외곽에 이종족 난민 구호소를 세울까요? 시민 반발이 거셀 겁니다. 고대 공존당 trade pure,budget 공존 vs 배척 60 4 -4 우리는 모두 우주의 미아입니다. -
15 114 1 작전 본부 #c92a2a 예언자 스카웃 10초 뒤를 보는 사기꾼이 고액 연봉을 요구하며 합류를 제안했습니다. 채용할까요? 차원 무역당 covenant magic,ops 실용 vs 원칙 70 5 -2 하한가를 피하는 값싼 비용. AST_007
16 115 1 정보 본부 #1c4f82 기밀문서 유출자 내부 고발자가 비윤리적 실험을 폭로하려다 체포되었습니다. 그를 '조용히 처리'할까요? 침묵의 장막당 reshape secret,kill 은폐 vs 진실 30 -5 8 진실은 무덤 속에 있어야 합니다. AST_013
17 201 2 작전 본부 #c92a2a 베헤모스의 알 회수 불법 경매장에 도시 붕괴급 괴수의 알이 나왔습니다. 파괴하지 않고 연구용으로 회수할까요? 진화 미래당 iron ops,bio 연구 vs 안전 100 8 -5 품속에 넣으니 따뜻하네요. AST_006
18 202 2 R&D 본부 #6b21a8 비유클리드 칩 도입 연산력은 무한하지만 주변 공간을 뒤틀어버리는 외계 칩을 메인 서버에 장착할까요? 세계 재건당 pure research,magic 기술 vs 현실붕괴 60 6 0 1+1=3이 되는 기적. AST_005
19 203 2 행정 본부 #d97706 저주받은 금화 소지자의 수명을 갉아먹지만 무한히 금화를 쏟아내는 유적 항아리를 자금원으로 쓸까요? 차원 무역당 covenant magic,budget 자본 vs 생명 -150 10 -5 돈 냄새가 핏물보다 진하군요. AST_004
20 204 2 작전 본부 #c92a2a 전술핵 타격 12구역에 S급 괴수가 출현했습니다. 수만 명의 희생을 감수하고 소형 전술핵 타격을 승인할까요? 철의 수호당 libra kill,ops 학살 vs 방어실패 120 -15 20 도시는 다시 지으면 됩니다. -
21 205 2 정보 본부 #1c4f82 해킹 그룹 참수 적대 해커들이 1급 기밀을 훔치고 있습니다. 역추적 암살 프로그램을 가동해 뇌를 태워버릴까요? 침묵의 장막당 reshape secret,kill 암살 vs 유출 50 -8 15 모니터 너머에 죽음 배달. -
22 206 2 R&D 본부 #6b21a8 초인 부대 스파르탄 고통을 느끼지 못하는 생체 병기 부대를 창설하기 위해 인간의 존엄성을 포기하시겠습니까? 진화 미래당 pure bio,ops 진화 vs 윤리 90 10 -8 괴물을 잡기 위해 괴물이 됩니다. -
23 207 2 행정 본부 #d97706 외계 물질 매각 창고의 위험한 외계 광물을 군수 기업이 거액에 사겠다고 합니다. 나중 파장은 무시하고 팔까요? 차원 무역당 veil budget,public 자본 vs 은폐 -200 12 -6 위험은 저들에게 전가합시다. -
24 208 2 정보 본부 #1c4f82 숭배자 국회 진출 괴수를 섬기는 유력 인사가 국회에 입성하려 합니다. 선거를 조작하여 무조건 당선을 저지할까요? 철의 수호당 covenant secret,public 개입 vs 중립 60 -5 8 정치는 총칼 없는 전쟁입니다. -
25 209 2 작전 본부 #c92a2a 생체 병기 폭주 피아를 식별하지 못하는 개조 요원이 폭주 중입니다. 목의 자폭 목걸이를 기폭시켜 처형할까요? 순수 인간당 gene kill,bio 처분 vs 관용 30 -6 10 불량품은 폐기해야 합니다. -
26 210 2 R&D 본부 #6b21a8 타임머신 가동 작전 실패를 돌리기 위해 현실 우주 붕괴 리스크(70%)를 안고 회귀 장치를 가동하시겠습니까? 지식 보존당 iron research,magic 시간조작 vs 순응 150 18 -5 시간을 속이면 시간도 우리를 속입니다. -
27 211 2 행정 본부 #d97706 차원문 상업 개방 이종족 상인들에게 통행료를 받고 차원문을 열어주는 관세법을 승인하시겠습니까? 차원 무역당 pure budget,magic 개방 vs 통제 -120 10 -4 돈은 차원을 넘어서도 통용됩니다. -
28 212 2 정보 본부 #1c4f82 언론사 대표 암살 방송사 대표가 내일 아침 기구의 실체를 폭로하려 합니다. 오늘 밤 특수 요원을 보내 암살할까요? 침묵의 장막당 reshape kill,secret 암살 vs 폭로 70 -10 18 기사는 발행되지 않을 때 완벽합니다. -
29 213 2 작전 본부 #c92a2a 지성체 괴수 휴전 언어를 구사하는 괴수 여왕이 학살을 멈추는 조건으로 영토 인정을 요구합니다. 휴전할까요? 고대 공존당 iron ops 평화 vs 자존심 0 5 -12 적과 악수하려면 칼을 놔야 합니다. -
30 214 2 R&D 본부 #6b21a8 복제 요원 양산 감정이 삭제된 복제 인간 요원들을 공장에서 찍어내듯 양산하는 비윤리적 계획을 승인할까요? 진화 미래당 pure bio,ops 복제 vs 윤리 110 12 -6 슬퍼할 가족이 없는 완벽한 소모품. -
31 215 2 행정 본부 #d97706 최후의 벙커 대중을 버리고 수뇌부만 피신할 수 있는 초거대 지하 벙커 건설에 막대한 예산을 쏟아부을까요? 세계 재건당 trade budget 생존 vs 방치 180 8 -4 지하엔 버려진 자들의 비명이 안 들립니다. -
32 301 3 정보 본부 #1c4f82 비밀 결사 공개 세계가 패닉에 빠졌습니다. 음지에서 벗어나 우리가 전 세계를 직접 통치하겠다고 선포할까요? 세계 재건당 veil public,secret 양지화 vs 은폐 100 15 -5 구원자는 그림자에 숨지 않습니다. -
33 302 3 작전 본부 #c92a2a 차원 붕괴 폭탄 적의 차원을 영구 붕괴시키는 블랙홀 폭탄입니다. 여파로 지구도 절반 파괴됩니다. 쏘시겠습니까? 철의 수호당 libra kill,ops 파멸 vs 지속 200 25 -10 불타는 세계 위에서 승리를 외칩니다. AST_014
34 303 3 R&D 본부 #6b21a8 인류 강제 진화 전 인류의 DNA를 변이시키는 가스를 살포해 강제로 진화시킵니다. 부작용 치사율은 50%입니다. 진화 미래당 pure bio 진화 vs 순수 250 30 -15 낡은 허물을 벗어던질 때입니다. -
35 304 3 행정 본부 #d97706 글로벌 금융 해킹 자금이 완전히 고갈되었습니다. 대공황을 감수하고 적대국의 중앙 은행을 해킹해 예산을 증발시킬까요? 차원 무역당 audit budget,secret 자본 vs 경제붕괴 -300 20 -8 세상이 망해도 돈은 씁니다. -
36 305 3 정보 본부 #1c4f82 지구 뇌파 리셋 폭동을 잠재우기 위해 위성에서 펄스를 쏴 전 인류를 기억 상실의 꼭두각시로 만들까요? 침묵의 장막당 reshape secret,public 백지화 vs 혼돈 180 22 -12 무지가 곧 평화요 망각이 구원입니다. -
37 306 3 R&D 본부 #6b21a8 아카식 레코드 접속 우주의 모든 진리가 담긴 서버에 접속합니다. 연구원 90%가 뇌사할 확률을 안고 강행할까요? 지식 보존당 covenant research,magic 진리 vs 파멸 120 18 -10 우주의 비밀 앞에 인간은 나약합니다. -
38 307 3 작전 본부 #c92a2a 군단 맹약 외계 군단이 인류 절반을 노예로 바치는 대가로 침공을 멈추겠답니다. 항복 문서에 서명할까요? 고대 공존당 iron ops 굴복 vs 멸망 80 15 -18 무릎 꿇어서라도 숨을 이어가야 합니다. -
39 308 3 행정 본부 #d97706 유전자 방주 발사 지구를 포기하고 순수 인류 엘리트들만 태운 방주를 우주로 발사해 탈출하시겠습니까? 순수 인간당 gene pure,budget 도피 vs 투쟁 200 25 -8 불순물들은 죽어가는 지구에 둡니다. -
40 309 3 R&D 본부 #6b21a8 차원 융합 폭주 물리법칙을 무시하고 적 차원과 우리 차원을 융합시켜 아예 새로운 우주를 창조할까요? 세계 재건당 trade magic,research 재창조 vs 유지 220 35 -20 헌 우주를 부수고 새 우주를 엽시다. -
41 310 3 작전 본부 #c92a2a 대재앙 방관 다른 대륙에 초거대 괴수가 깨어났습니다. 우리 전력을 보존하기 위해 그들의 멸망을 방관할까요? 침묵의 장막당 iron ops,kill 방관 vs 저지 0 20 -15 지는 싸움은 피하는 게 상책입니 -

Binary file not shown.

View File

@@ -0,0 +1,32 @@
## 1. 프로젝트 개요
- 프로젝트명: Project SS (가제: 비밀 결사단)
- 장르: 로그라이트 권력 시뮬레이션 / 엔진 빌딩 / 정치 스릴러
- 플랫폼: 모바일 웹앱 및 하이브리드 앱 (세로 모드 전용)
- 핵심 가치 (Core Value):
1. 원하는 자원과 태그를 수집하기 위한 [능동적 딜레마 스와이프]
2. 모은 자원을 폭발시켜 미스터리를 해결하고 자산을 얻는 [엔진 빌딩]
3. 사상적 우호도에 기반하여, 필요할 때만 권력을 개입시키는 [선택적 정치 공작]
## 2. 주요 기능 요구사항 (Functional Requirements)
### 2.1. 데이터 관리 (Live DB)
- Google Sheets Sync: 사건(결재 카드), 파벌, 자산, 미스터리 등 게임 내 모든 수치와 텍스트를 구글 시트에서 Fetch하여 로드하는 구조 필수.
- 상태 관리 (State Management): 세션 내 플레이어의 4대 속성, 획득한 태그, 파벌별 우호도 수치를 실시간으로 추적 및 업데이트해야 함.
### 2.2. [메인 화면] 결재 시스템 (A vs B Swipe)
- 기능: 메인 화면 중앙의 카드를 좌/우로 드래그하여 투입할 부서(대응 방식)를 선택.
- 오버레이 피드백: 드래그 중 화면 좌우측 및 상단에 예상되는 획득 자원과 태그 아이콘을 시각적으로 미리 표시(Preview).
### 2.3. [미스터리 모달] 미스터리 보드 시스템 (Mystery Dossier)
- 조건부 팝업: 조건(엔트로피 달성, 특정 태그 N개 누적 등) 만족 시 보드(별도 UI 모달)에 새로운 미스터리 항목 동적 생성.
- 상태 이상(디버프) 처리: 미결재 상태의 미스터리가 보드에 존재할 경우, 매 턴(스와이프)마다 지정된 디버프 수치(예: 신임도 -2) 자동 차감 로직 구현.
- 해결 및 보상 처리: 플레이어가 요구되는 '빌드업 태그'와 재화를 지불하고 '해결' 버튼 클릭 시, 디버프 제거 및 지정된 '자산(Asset)'을 인벤토리에 추가.
### 2.4. [주간 의회 모달] 정치 및 우호도 시스템 (Affinity & Parliament)
- 스펙트럼 로직: 플레이어의 '성향 태그'가 변경될 때마다 8개 파벌의 우호도 상태(5단계)를 자동 재계산 및 업데이트.
- 액션 개입 (Intervention): 주간 의회 페이즈 진입 시, 특정 조건(우호도)을 만족하는 파벌을 대상으로 '정치 자본'을 지불하여 액션(이면 계약, 블랙리스트)을 트리거하는 버튼 활성화.
- 카운터 입법: 적대 파벌은 플레이어가 가장 많이 보유한 '빌드업 태그'를 파악하여, 해당 기능을 정지시키는 카운터 법안을 상정하는 타겟팅 로직 필요.
## 3. UI/UX 및 비기능 요구사항
- 뷰포트 최적화: 좁은 모바일 화면에서 카드 스와이프 영역(메인)과 스탯/로그 영역을 침범하지 않도록 dvh 기반의 레이아웃 설계.
- 조작 피드백: 미스터리 해결, 정치 액션 실행, 게임 오버 발생 시 강력한 햅틱 진동 및 사운드 효과 필수.

View File

@@ -0,0 +1,88 @@
## 코어 루프 (Core Game Loop)
게임은 다음 4개의 톱니바퀴가 반복되며 굴러가는 구조를 가집니다.
1. 사건 대응 (스와이프): 매 턴 사건 카드가 등장하며, 유저는 [어느 부서를 투입할지(A vs B)] 선택합니다. 이를 통해 재화와 태그를 획득합니다.
2. 빌드업 (미스터리 보드): 획득한 태그와 재화가 쌓이면 **[미스터리]**가 해금됩니다. 방치 시 페널티를 받으며, 재화를 소모해 해결하면 시너지를 내는 **[자산(엔진)]**을 획득합니다.
3. 정치적 반작용 (우호도 스펙트럼): 유저의 선택(성향)에 따라 파벌들이 자동적으로 우호/적대 상태로 재편성됩니다.
4. 난관 돌파 (주간 의회): 주말이 되면 적대 파벌이 유저의 엔진을 부수는 **[카운터 법안]**을 냅니다. 유저는 권력을 개입(이면 계약 등)시켜 이를 방어하고 다시 일상 업무로 돌아갑니다.
## 2. 속성 정의 (Attributes Definition)
이 게임의 모든 행동과 목표, 패배 조건은 다음 속성들에 의해 결정됩니다.
### 2.1. 4대 핵심 재화 (자원)
- 🧬 변칙 데이터 (Anomaly Data): 연구/포획 시 획득. [미스터리 해결]에 소모되는 필수 성장 재화.
- 👑 정치 자본 (Political Clout): 은폐/통제 시 획득. [의회 액션]에 소모되는 필수 통제 재화.
- ⏱️ 엔트로피 (Entropy): 종말 척도 (0~100%).
- 효과: 일정 구간마다 시민 패닉 단계 상승 (게임 내 억까 이벤트 등장 확률 증가).
- 패배 조건: 100% 도달 시 즉시 [세계 멸망 엔딩 (Game Over)].
- 🛡️ 신임도 (Trust): 대중과 기구의 지지율. (플레이어의 HP)
- 패배 조건: 0에 도달 시 즉시 [국장 숙청 엔딩 (Game Over)].
### 3.2. 2대 태그 시스템 (결재 시 이중 획득)
- 🏷️ 빌드업 태그 (군사, 과학, 정보, 오컬트, 자본): 미스터리 해금 조건이자, 보유한 자산(엔진)의 시너지 발동 조건이 되는 실질적 재료.
- 🧭 성향 태그 (지배, 순응, 은폐, 혼돈): 파벌의 우호도를 결정짓고, 최종 엔딩의 분기를 나누는 사상적 나침반.
### 3.3. 파벌 우호도 5단계 (성향 태그에 의해 자동 변경)
1. 👑 핵심 동맹: 태그 공명. 유저의 주력 빌드업 속도 가속화.
2. 🤝 우호: 주간 의회 시, 유저에게 유리한 버프 법안 자동 상정.
3. ⚖️ 중립: 아무 효과 없음. (이면 계약 액션의 타겟)
4. ⚔️ 적대: 주간 의회 시, 유저의 주력 태그를 박살 내는 카운터 법안 자동 상정.
5. 🔥 파국: 성향이 극악이거나 블랙리스트 지정 시 도달. 법안 대신 극단적 '사보타주 테러(강제 페널티)' 이벤트 발생.
## 4. UI 화면 중심 시스템 정의
이 게임의 시스템은 크게 3개의 UI 화면을 중심으로 나뉘며, 각 화면에서 할 수 있는 행동이 곧 게임의 규칙입니다.
### 4.1. [메인 집무실 화면] : 결재 및 태그 수집 시스템
유저가 가장 많은 시간을 보내는 기본 화면입니다.
- UI 구조: 중앙에 틴더 형태의 스와이프 카드. 좌/우측에 대응 부서 아이콘 노출.
- 시스템 규칙:
- 카드는 '사건'을 의미하며, 좌우 스와이프는 '투입할 부서'를 의미합니다.
- 드래그 방향에 따라 명확히 어떤 자원(데이터, 자본, 신임도 등)과 태그(빌드업, 성향)를 획득/상실할지 미리 보여줍니다. (예산 제약 없음)
- 결재 완료 즉시 상단의 4대 재화 수치와 누적 태그 현황판이 업데이트됩니다.
### 3.2. [미스터리 결재판 화면] : 퀘스트 및 엔진 빌딩 시스템
유저가 직접 진척도를 관리하고 보상을 수령하는 서브 모달 창입니다.
- UI 구조: 현재 해금된 미스터리 목록 리스트. 각 항목에 [해결 비용], [방치 페널티], [보상 자산] 명시.
- 시스템 규칙:
- 해금: 특정 빌드업 태그가 N개 모이거나, 엔트로피가 N%를 넘으면 새로운 미스터리가 리스트에 추가됩니다.
- 디버프: 리스트에 미스터리가 남아있는 동안, 메인 화면에서 결재를 1회 할 때마다 지속 페널티(예: 엔트로피 자동 +1)가 부과됩니다.
- 해결: 유저가 요구되는 '빌드업 태그'와 '변칙 데이터'를 소모해 버튼을 누르면 미스터리가 지워지며, 인벤토리에 **[자산(패시브 엔진)]**이 추가됩니다.
- 승리 조건: 세계관급 재앙인 **[종결 미스터리 3종]**을 이 화면에서 모두 해결하면, 누적된 성향에 따른 **[진엔딩]**이 출력되며 승리합니다.
### 3.3. [주간 의회 화면] : 파벌 정치 및 난관 돌파 시스템
일정 턴(예: 7턴) 경과 시 강제로 진입하는 이벤트 화면입니다.
- UI 구조:
- 상단: 현재 상정된 법안의 내용과 상정 주체(적대 파벌).
- 중단: 8개 파벌의 현재 우호도 스펙트럼 현황.
- 하단: 유저가 개입할 수 있는 2개의 '액션 버튼(이면 계약, 블랙리스트)'.
- 시스템 규칙:
- 난관 발생: 적대 파벌은 유저 인벤토리에 가장 많은 '빌드업 태그'를 무력화하는 카운터 법안을 상정합니다.
- 능동적 방어 (액션): 유저는 상정된 법안을 막기 위해 아래의 선택을 할 수 있습니다.
1. 액션 포기: 법안이 통과되어 자산이 정지되는 페널티를 그대로 수용합니다.
2. [이면 계약] 실행: 정치 자본을 지불하고 우호도 [중립] 파벌을 선택합니다. 해당 파벌이 거부권을 행사해 법안이 즉시 무효화됩니다.
3. [블랙리스트] 실행: 정치 자본 지불 없이 [적대] 파벌을 탄압합니다. 해당 파벌은 [🔥파국] 상태가 되어 테러를 준비하지만, 유저는 다음 주 동안 이 파벌의 안건을 부결시킬 때마다 대량의 신임도와 정치 자본을 보상으로 수확할 수 있습니다.
## 4. UI 텍스트 레이아웃 구조 (가이드라인)
AI 및 개발 단계를 위한 최소화된 텍스트 기반 레이아웃 구조입니다.
### [Header: 대시보드 영역]
- [🛡️ 신임도: 80%] [⏱️ 엔트로피: 45%]
- [🧬 변칙 데이터: 150] [👑 정치 자본: 30]
- 우측 상단 햄버거 메뉴: [인벤토리(자산) 및 성향 로그 보기] 버튼
### [Body: 스와이프 결재 영역 (메인)]
- (좌측 표시기): ⚔️ 작전부 투입 (군사 태그 / 신임도+ / 변칙 데이터-)
- [카드 UI]: <사건 타이틀> 및 <사건 설명 텍스트>
- (우측 표시기): 🔬 R&D부 투입 (과학 태그 / 변칙 데이터+ / 신임도-)
### [Footer: 네비게이션 영역]
- [ 📁 미스터리 결재판 (🚨 1) ] 버튼 (알림 배지 표시)
- [ 🏛️ 이번 주 의회 동향 ] 버튼
### [Modal: 주간 의회 팝업]
- [상정된 법안]: <과학 기술 통제법> (효과: 과학 자산 모두 무효화)
- [파벌 현황표]: (철의 수호당: 우호) / (순수 인간당: 적대) ...
- [개입 액션 패널]:
- [ 🤝 이면 계약 (비용: 👑 50) ] -> 터치 시 중립 파벌 목록 드롭다운
- [ 🎯 블랙리스트 지정 (무료) ] -> 터치 시 적대 파벌 목록 드롭다운
- [ ⏩ 개입 없이 법안 통과 (방관) ]

View File

@@ -0,0 +1,955 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="theme-color" content="#000000">
<title>Project SS - Live DB Edition</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.4.1/papaparse.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&family=Nanum+Myeongjo:wght@700;800&family=Black+Han+Sans&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<style>
:root {
--bg-dark: #1a1a1a; --paper: #e6dec5; --ink: #2b2b2b;
--red-seal: #c92a2a; --blue-seal: #1c4f82; --ui-border: #444;
--safe-top: env(safe-area-inset-top, 30px);
}
body {
background-color: #000; display: flex; justify-content: center; align-items: center;
height: 100dvh; margin: 0; padding: 0; font-family: 'Courier Prime', 'Nanum Myeongjo', serif;
overflow: hidden; color: #eee; user-select: none; touch-action: none;
}
.phone-wrapper {
width: 100%; height: 100%; max-width: 420px; background: #111;
position: relative; overflow: hidden; display: flex; flex-direction: column;
box-shadow: 0 0 50px rgba(0,0,0,0.8);
}
@media (min-width: 450px) and (min-height: 700px) {
.phone-wrapper { height: 90vh; max-height: 850px; border-radius: 35px; border: 8px solid #333; }
}
.safe-area-top { width: 100%; height: var(--safe-top); min-height: 30px; background: #000; z-index: 1000; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 10px; color: #666; font-weight: bold; letter-spacing: 2px; }
.screen { flex: 1; display: none; flex-direction: column; position: relative; padding-top: 5px; box-sizing: border-box; overflow: hidden; }
.screen.active { display: flex; }
.reset-btn { position: absolute; top: env(safe-area-inset-top, 10px); right: 10px; background: #333; color: #aaa; font-size: 10px; padding: 4px 8px; border-radius: 4px; z-index: 2000; cursor: pointer; }
.loading-screen { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%; width: 100%; background: rgba(0,0,0,0.9); z-index: 5000; position: absolute; top: 0; left: 0; }
.loader-spinner { border: 4px solid rgba(255,255,255,0.1); width: 40px; height: 40px; border-radius: 50%; border-left-color: #c92a2a; animation: spin 1s linear infinite; margin-bottom: 20px; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.desk-bg { background: #2d1b14; background-image: repeating-linear-gradient(45deg, #281812 25%, transparent 25%, transparent 75%, #281812 75%, #281812); background-size: 20px 20px; }
.status-dashboard { background: rgba(20, 20, 20, 0.95); border-bottom: 2px solid var(--ui-border); padding: 8px 12px; box-shadow: 0 5px 15px rgba(0,0,0,0.5); display: flex; flex-direction: column; gap: 6px; z-index: 30; }
.week-display { display: flex; justify-content: space-between; font-size: 11px; color: #fff; border-bottom: 1px solid #333; padding-bottom: 4px; font-weight: bold; }
.week-highlight { color: #fcd34d; }
.mandate-sticky { background: #fef3c7; color: #4b5563; padding: 4px 8px; font-size: 10px; border-radius: 2px; box-shadow: 1px 1px 3px rgba(0,0,0,0.3); margin-bottom: 2px; display: flex; justify-content: space-between; align-items: center; transform: rotate(-1deg); }
.mandate-title { font-weight: 800; color: #000; margin-right: 5px; }
.quest-progress { font-family: monospace; font-weight: bold; color: #c92a2a; }
.res-row { display: flex; justify-content: space-between; gap: 4px; }
.res-box { flex: 1; background: #333; border: 1px solid #555; border-radius: 4px; padding: 4px; display: flex; flex-direction: column; font-size: 9px; position: relative; justify-content: center; }
.res-val { font-weight: bold; font-family: monospace; font-size: 11px; margin-top: 2px; }
.gauge-row { display: flex; justify-content: space-between; align-items: flex-end; padding-top: 2px; }
.stat-item { width: 32%; display: flex; flex-direction: column; align-items: center; position: relative; cursor: pointer; }
.stat-label { font-size: 9px; color: #888; margin-top: 4px; }
.trust-bar { width: 100%; height: 6px; background: #444; border-radius: 3px; overflow: hidden; }
.trust-fill { height: 100%; width: 80%; background: linear-gradient(90deg, #d4af37, #fcd34d); transition: width 0.3s; }
.entropy-clock { width: 34px; height: 34px; border-radius: 50%; border: 2px solid #555; background: #222; position: relative; }
.clock-face { width: 100%; height: 100%; border-radius: 50%; background: conic-gradient(#10b981 0deg 270deg, #ef4444 270deg 360deg); opacity: 0.4; }
.clock-hand { position: absolute; top: 50%; left: 50%; width: 45%; height: 2px; background: #fff; transform-origin: left center; transform: rotate(270deg); transition: transform 0.5s; }
.share-bars { width: 100%; display: flex; height: 6px; background: #000; border-radius: 3px; overflow: hidden; margin-top: 5px; }
.share-segment { height: 100%; transition: width 0.3s; }
.share-donut { width: 36px; height: 36px; border-radius: 50%; display: flex; justify-content: center; align-items: center; position: relative; }
.donut-hole { width: 22px; height: 22px; border-radius: 50%; background: #1a1a1a; position: absolute; }
.desk-area { flex: 1; position: relative; display: flex; justify-content: center; align-items: center; perspective: 1000px; background: #2d1b14; touch-action: none; overflow: hidden; }
.doc-card {
width: 80%; max-width: 300px; height: 60vh; max-height: 440px;
background: var(--paper); color: var(--ink); position: absolute;
box-shadow: 0 10px 25px rgba(0,0,0,0.6); display: flex; flex-direction: column;
padding: 15px; border: 1px solid #cbb; cursor: grab; transform-origin: 50% 100%;
will-change: transform; z-index: 10; transition: border-color 0.2s;
}
.doc-card:active { cursor: grabbing; }
.folder-tab { position: absolute; top: -20px; left: 0; width: 80px; height: 20px; background: var(--red-seal); border-radius: 5px 5px 0 0; color: #fff; font-size: 10px; display: flex; align-items: center; justify-content: center; font-weight: bold; }
.warning-postit { position: absolute; top: -10px; right: -10px; background: #ef4444; color: white; padding: 4px 8px; font-size: 10px; font-weight: bold; transform: rotate(5deg); box-shadow: 2px 2px 5px rgba(0,0,0,0.3); z-index: 20; display: none; }
.card-header { border-bottom: 2px solid #555; padding-bottom: 8px; margin-bottom: 8px; }
.card-title { font-family: 'Black Han Sans', sans-serif; font-size: 1.1rem; line-height: 1.2; margin-bottom: 4px; }
.tag-badge { background: #333; color: #fff; font-size: 9px; padding: 2px 4px; border-radius: 4px; display: inline-block; font-weight: bold; margin-right: 2px; }
.card-body { font-size: 12px; line-height: 1.4; flex: 1; margin-top: 5px; overflow-y: auto; padding-right: 5px; }
.card-body::-webkit-scrollbar { width: 4px; }
.card-body::-webkit-scrollbar-thumb { background: #bbb; border-radius: 4px; }
.flavor-text { margin-top: 10px; font-size: 10px; font-style: italic; color: #555; border-left: 3px solid #ccc; padding-left: 8px; margin-bottom: 30px; flex-shrink: 0; }
.faction-stamp { position: absolute; bottom: 10px; right: 10px; width: 60px; height: 60px; border: 3px solid var(--blue-seal); border-radius: 50%; color: var(--blue-seal); display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 10px; opacity: 0.8; transform: rotate(-15deg); mix-blend-mode: multiply; text-align: center; }
.overlay-stamp { position: absolute; top: 30px; font-size: 22px; font-weight: 900; border: 4px solid; padding: 5px 15px; border-radius: 8px; text-transform: uppercase; opacity: 0; pointer-events: none; z-index: 50; transform: rotate(-15deg); }
.stamp-reject { right: 20px; color: #1c4f82; border-color: #1c4f82; transform: rotate(15deg); }
.stamp-approve { left: 20px; color: #c92a2a; border-color: #c92a2a; transform: rotate(-15deg); }
.lock-chain-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: transparent; display: none; flex-direction: column; justify-content: center; align-items: center; pointer-events: none; z-index: 40; border: 3px solid #ef4444; box-shadow: inset 0 0 15px rgba(239, 68, 68, 0.3); }
.lock-chain-overlay.active { display: flex; }
.chains { font-size: 40px; color: #ef4444; opacity: 0.2; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: -1; }
.lock-msg { position: absolute; bottom: 60px; background: #ef4444; color: #fff; padding: 5px 12px; font-weight: bold; font-size: 11px; border-radius: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); animation: bounce 2s infinite; text-align: center; transition: all 0.3s; }
.lock-msg.right { right: 20px; left: auto; }
.lock-msg.left { left: 20px; right: auto; }
@keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
.law-warning-popup { position: absolute; top: 35%; left: 50%; transform: translate(-50%, -50%) rotate(-5deg); background: rgba(220, 38, 38, 0.95); color: #fff; padding: 10px 15px; border-radius: 8px; border: 2px solid #fff; box-shadow: 0 10px 20px rgba(0,0,0,0.5); text-align: center; z-index: 100; opacity: 0; transition: opacity 0.2s; pointer-events: none; width: 80%; }
.law-warning-popup.visible { opacity: 1; }
.law-title { font-weight: 900; font-size: 13px; margin-bottom: 2px; text-transform: uppercase; }
.law-desc { font-size: 10px; }
.predict-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 70; display: none; justify-content: center; align-items: center; }
.predict-box { background: rgba(0,0,0,0.95); color: #fff; padding: 12px; border-radius: 8px; font-size: 11px; display: flex; flex-direction: column; gap: 4px; box-shadow: 0 10px 30px rgba(0,0,0,0.8); border: 1px solid #555; width: 200px; text-align: center; }
.p-row { display: flex; justify-content: space-between; }
.p-up { color: #10b981; } .p-down { color: #ef4444; }
.desc-text { color: #aaa; font-size: 9px; margin-top: 2px; }
.menu-btn-group { position: absolute; bottom: 15px; left: 15px; right: 15px; display: flex; gap: 10px; z-index: 50; }
.menu-btn { flex: 1; background: #333; color: #ccc; border: 1px solid #555; padding: 10px; border-radius: 6px; font-size: 11px; display: flex; justify-content: center; gap: 5px; cursor: pointer; align-items: center; }
.modal { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.95); z-index: 2000; display: none; flex-direction: column; padding: 50px 20px 20px; }
.modal.active { display: flex; }
.close-btn { margin-top: auto; padding: 15px; background: #444; color: #fff; text-align: center; border-radius: 8px; cursor: pointer; font-weight: bold; }
.list-item { background: #222; padding: 10px; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; color: #ccc; font-size: 11px; margin-bottom: 5px; flex-wrap: wrap; }
.dividend-tube { position: absolute; right: 5px; top: 50%; transform: translateY(-50%); width: 12px; height: 150px; background: rgba(0,0,0,0.5); border: 1px solid #555; border-radius: 6px; display: flex; flex-direction: column-reverse; padding: 2px; overflow: hidden; z-index: 5; }
.dividend-block { width: 100%; height: 8px; margin-top: 2px; border-radius: 2px; opacity: 0; animation: dropIn 0.3s forwards; }
@keyframes dropIn { from { transform: translateY(-50px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
.passive-toast { position: absolute; top: 80px; left: 50%; transform: translateX(-50%); background: rgba(16, 185, 129, 0.9); color: white; padding: 8px 15px; border-radius: 20px; font-size: 10px; z-index: 200; box-shadow: 0 4px 10px rgba(0,0,0,0.3); pointer-events: none; opacity: 0; transition: opacity 0.5s; width: 80%; text-align: center; }
.passive-toast.active { opacity: 1; animation: floatUp 3s forwards; }
@keyframes floatUp { 0% { transform: translate(-50%, 0); opacity: 0; } 10% { transform: translate(-50%, -10px); opacity: 1; } 90% { transform: translate(-50%, -30px); opacity: 1; } 100% { transform: translate(-50%, -40px); opacity: 0; } }
/* Council Screen */
.council-bg { background: #111; background: radial-gradient(circle at 50% 30%, #1f1f1f 0%, #000 80%); justify-content: flex-start; padding: 15px; overflow-y: auto; }
.council-header { text-align: center; margin-bottom: 15px; border-bottom: 1px solid #444; padding-bottom: 10px; width: 100%; flex-shrink: 0; }
.council-title { font-size: 20px; font-weight: 900; color: #fff; font-family: 'Black Han Sans'; }
.council-phase { display: none; width: 100%; height: 100%; flex-direction: column; }
.council-phase.active { display: flex; }
.agenda-announce { background: #eee; color: #000; padding: 15px; transform: rotate(-1deg); box-shadow: 0 10px 30px rgba(0,0,0,0.8); margin-bottom: 15px; position: relative; }
.stamp-mark { position: absolute; top: 10px; right: 10px; border: 3px solid #c92a2a; color: #c92a2a; font-weight: bold; padding: 4px; transform: rotate(15deg); opacity: 0.8; font-size: 10px; }
.vote-section { width: 100%; background: #222; padding: 12px; border-radius: 8px; margin-bottom: 15px; border: 1px solid #444; }
.vote-title { font-size: 12px; font-weight: bold; color: #aaa; margin-bottom: 8px; text-align: center; }
.bill-card { display: flex; justify-content: space-between; align-items: center; background: #111; border: 1px solid #333; padding: 8px; margin-bottom: 5px; }
.bill-card.major { border-left: 4px solid #3b82f6; }
.bill-card.minor { border-left: 4px solid #ef4444; }
.bill-name { font-size: 11px; font-weight: bold; }
.vote-bar-container { height: 16px; background: #000; border-radius: 8px; overflow: hidden; display: flex; position: relative; margin-top: 8px; }
.vote-segment { height: 100%; transition: width 0.5s; }
.majority-line { position: absolute; left: 50%; top: 0; bottom: 0; width: 2px; background: #fff; z-index: 10; box-shadow: 0 0 5px #fff; }
.lobby-target-list { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; justify-content: center; }
.lobby-tag { background: #333; border: 1px solid #555; color: #ccc; padding: 4px 8px; border-radius: 15px; font-size: 9px; cursor: pointer; }
.lobby-tag:active { background: #555; }
.lobby-tag.active { border-color: #d4af37; color: #d4af37; }
.action-btn { margin-top: auto; width: 100%; background: #c92a2a; color: #fff; padding: 12px; font-weight: bold; border-radius: 8px; text-align: center; cursor: pointer; font-size: 13px; flex-shrink: 0; }
.action-btn:active { background: #991b1b; }
.action-btn.secondary { background: #10b981; margin-top: 10px; }
</style>
</head>
<body>
<div class="phone-wrapper">
<div id="loading-screen" class="loading-screen">
<div class="loader-spinner"></div>
<div class="text-sm font-bold text-white mb-1" id="loading-msg">데이터베이스 연동 중...</div>
<div class="text-[10px] text-gray-500">(Google Sheets 연결)</div>
</div>
<div class="safe-area-top">PROJECT SS</div>
<div class="reset-btn" onclick="initDB()">↻ RESTART</div>
<div id="desk" class="screen desk-bg">
<div id="passive-toast" class="passive-toast"></div>
<div class="status-dashboard">
<div class="week-display">
<span><span id="week-num" class="week-highlight">1</span></span>
<span>Day <span id="day-num" class="week-highlight">1</span>/5</span>
</div>
<div class="mandate-sticky">
<div><span class="text-xs text-gray-500">아젠다:</span> <span class="mandate-title" id="agenda-title">Loading...</span></div>
<div><span class="text-xs text-gray-500">퀘스트:</span> <span class="quest-progress" id="quest-status">0/0</span></div>
</div>
<!-- 4대 재화 (예산 제거 적용) -->
<div class="res-row">
<div class="res-box"><span class="text-[9px] text-gray-400 font-bold">🧬 데이터 (자원)</span><span class="res-val text-blue-300" id="val-anomaly">0</span></div>
<div class="res-box"><span class="text-[9px] text-gray-400 font-bold">👑 정치 자본</span><span class="res-val text-yellow-500" id="val-political">0</span></div>
</div>
<div class="gauge-row">
<div class="stat-item"><div class="trust-bar"><div class="trust-fill" id="bar-trust"></div></div><span class="stat-label">신임도 <span id="txt-trust">80%</span></span></div>
<div class="stat-item"><div class="entropy-clock"><div class="clock-face"></div><div class="clock-hand" id="hand-entropy"></div></div><span class="stat-label">종말시계</span></div>
<div class="stat-item" style="width: 35%;" onclick="openModal('faction-modal')"><div class="share-donut" id="donut-share"><div class="donut-hole"></div></div><span class="stat-label">성향(지분) <i class="fa-solid fa-circle-info text-[8px]"></i></span></div>
</div>
<div id="engine-hud" class="mt-2 text-[10px] bg-gray-800 p-2 w-full border border-gray-600 shadow-inner rounded" style="margin-top: 8px;">
<div class="flex justify-between items-center mb-1">
<div class="font-bold text-gray-300"><i class="fa-solid fa-microchip text-yellow-500"></i> 누적 빌드업 태그</div>
<div id="hud-synergy-badge" class="text-gray-500 transition-colors duration-500"><i class="fa-solid fa-bolt"></i> 연쇄 없음</div>
</div>
<div id="hud-tags-container" class="flex flex-wrap gap-1 mt-1"></div>
</div>
</div>
<div class="desk-area">
<div class="doc-card" id="current-card" style="display:none;">
<div class="folder-tab" id="c-dept">부서</div>
<div id="c-conflict" class="absolute top-2 right-2 font-bold text-yellow-500 bg-gray-800 px-3 py-1 rounded shadow-md border border-yellow-700 hidden text-[11px] z-10" style="transform: rotate(2deg);"></div>
<div class="warning-postit" id="c-warning">경고</div>
<div class="lock-chain-overlay" id="lock-overlay"><i class="fa-solid fa-link chains"></i><div class="lock-msg" id="lock-overlay-msg"><span id="lock-type-text"></span><br><span id="lock-reason" class="text-yellow-300 text-[10px] mt-1 block"></span></div></div>
<div class="law-warning-popup" id="law-popup"><div class="law-title" id="law-popup-title"></div><div class="law-desc" id="law-popup-desc"></div></div>
<div class="card-header">
<div class="card-title" id="c-title">Title</div>
<div class="flex justify-between items-center text-[9px] text-gray-600">
<span id="c-sender">Sender</span>
<div id="c-tags" class="flex gap-1"></div>
</div>
</div>
<div class="card-body" id="c-body">Body</div>
<div class="flavor-text" id="c-flavor">Flavor</div>
<div class="faction-stamp" id="c-faction">Faction</div>
<div class="predict-overlay" id="predict-overlay">
<div class="predict-box">
<div id="p-header" class="font-bold border-b border-gray-600 mb-2 pb-1 text-center"></div>
<div class="desc-text mb-2" id="p-desc"></div>
<div class="p-row">데이터 획득: <span id="p-anomaly" class="p-up"></span></div>
<div class="p-row">정치 자본: <span id="p-political" class="p-up"></span></div>
<div class="p-row">엔트로피: <span id="p-entropy"></span></div>
<div class="p-row mt-1">신임도: <span id="p-trust"></span></div>
<hr class="border-gray-600 my-1">
<div class="text-[9px] font-bold text-gray-300">획득 태그 (빌드업):</div>
<div id="p-tag-gain" class="text-[10px] font-bold text-yellow-400 mt-1 mb-2 bg-gray-800 rounded px-1 py-1"></div>
<div class="text-[9px] font-bold text-gray-300">성향 태그 (우호도) 변화:</div>
<div id="p-faction-change" class="text-[9px] mt-1 bg-gray-800 rounded px-1 py-1"></div>
</div>
</div>
<div class="overlay-stamp stamp-approve" id="stamp-approve">승인</div>
<div class="overlay-stamp stamp-reject" id="stamp-reject">거절</div>
</div>
<div id="empty-state" class="hidden text-center text-gray-500 text-xs absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"><i class="fa-solid fa-check-double text-4xl mb-2"></i><br>오늘의 업무 완료</div>
<div class="dividend-tube" id="dividend-tube"></div>
</div>
<div class="menu-btn-group">
<div class="menu-btn" onclick="openModal('mystery-modal')"><i class="fa-solid fa-magnifying-glass"></i> 미스터리</div>
<div class="menu-btn" onclick="openModal('asset-modal')" style="flex:0.5"><i class="fa-solid fa-box-open"></i> 자산(<span id="asset-count">0</span>)</div>
<div class="menu-btn" onclick="openModal('law-modal')" style="flex:0.5"><i class="fa-solid fa-gavel"></i> 법안</div>
</div>
</div>
<!-- Council Screen -->
<div id="council" class="screen council-bg">
<div class="council-header">
<div class="text-[10px] text-gray-500">WEEKLY COUNCIL</div>
<div class="council-title"><span id="c-week-num">1</span>주차 입법 회의</div>
</div>
<div id="council-phase-1" class="council-phase active">
<div class="flex-1 flex flex-col justify-center items-center py-2">
<div class="text-gray-400 text-[10px] mb-1">제1주주 통치 선포 (동맹 파벌)</div>
<div class="text-xl font-black text-white mb-4" id="ruling-faction">Loading</div>
<div class="agenda-announce w-full"><div class="stamp-mark">CONFIRMED</div><div class="text-[10px] text-gray-600 font-bold">금주의 아젠다</div><div class="text-base font-black mb-1" id="next-agenda-title"></div><div class="text-[10px] text-gray-500" id="next-agenda-desc"></div></div>
<div class="agenda-announce w-full bg-yellow-100"><div class="text-[10px] text-yellow-800 font-bold">금주의 퀘스트</div><div class="text-base font-black mb-1" id="next-quest-title"></div><div class="flex justify-between text-[9px] text-gray-600 mt-2"><span>보상: <span id="next-quest-reward"></span></span><span>실패: <span id="next-quest-penalty"></span></span></div></div>
</div>
<div class="action-btn secondary" onclick="startVotePhase()">입법 회의 시작</div>
</div>
<div id="council-phase-2" class="council-phase">
<div class="flex-1 w-full">
<div id="sabotage-alert" class="hidden mb-2 bg-red-900 border border-red-500 p-2 rounded text-center shadow-[0_0_10px_rgba(239,68,68,0.5)] animate-pulse">
<div class="text-red-200 text-[10px] font-bold">⚠️ 표적 견제 발의</div>
<div class="text-white text-xs font-black mt-1">적대 파벌이 국장의 [<span id="sabotage-target-tag" class="text-yellow-400"></span>] 엔진을 견제합니다!</div>
</div>
<div class="vote-section border-red-500 border"><div class="vote-title text-red-500"><i class="fa-solid fa-scale-balanced"></i> 입법 전쟁</div>
<div class="bill-card major"><div class="flex-1"><div class="flex justify-between"><span class="text-blue-400 text-[9px] font-bold">여당안 (동맹 파벌 제안)</span><span class="text-[9px]" id="bill-a-faction"></span></div><div class="bill-name" id="bill-a-name"></div><div class="bill-desc text-[9px] italic text-gray-400 mt-1" id="bill-a-flavor"></div></div></div>
<div class="text-center text-[10px] my-1 text-gray-500 font-bold">VS</div>
<div class="bill-card minor"><div class="flex-1"><div class="flex justify-between"><span class="text-red-400 text-[9px] font-bold">야당안 (적대 파벌 제안)</span><span class="text-[9px]" id="bill-b-faction"></span></div><div class="bill-name" id="bill-b-name"></div><div class="bill-desc text-[9px] italic text-gray-400 mt-1" id="bill-b-flavor"></div></div></div>
<div class="mt-3"><div class="flex justify-between text-[9px] font-bold mb-1"><span class="text-blue-400">A 지지 (<span id="vote-a-val">50</span>%)</span><span class="text-red-400">B 지지 (<span id="vote-b-val">50</span>%)</span></div><div class="vote-bar-container"><div class="majority-line"></div><div class="vote-segment bg-blue-600" id="vote-a" style="width:50%"></div><div class="vote-segment bg-red-600" id="vote-b" style="width:50%"></div></div></div>
<div class="grid grid-cols-2 gap-2 mt-3 text-[8px]"><div><div class="text-blue-400 border-b border-blue-900 mb-1">A 찬성 파벌</div><div id="list-support-a" class="text-gray-400 space-y-1"></div></div><div><div class="text-red-400 border-b border-red-900 mb-1">B 찬성 파벌</div><div id="list-support-b" class="text-gray-400 space-y-1"></div></div></div>
<div class="mt-3 pt-3 border-t border-gray-700"><div class="text-[9px] text-yellow-500 mb-1 font-bold">소수 정당 로비 (정치 자본 -10)</div><div class="lobby-target-list" id="lobby-targets"></div></div>
<div class="mt-2 pt-2 border-t border-gray-700"><div class="text-[9px] text-red-500 mb-1 font-bold">블랙리스트 탄압 (무료, 영구적 파국)</div><div class="lobby-target-list" id="blacklist-targets"></div></div>
</div>
</div>
<div class="action-btn" onclick="endCouncil()">결과 확정 및 다음 주 시작</div>
</div>
</div>
<!-- Modals -->
<div class="modal" id="mystery-modal">
<div class="flex justify-between items-center border-b-2 border-gray-600 pb-3 mb-3"><h2 class="text-white font-bold text-base">미스터리 보드</h2><div class="close-btn p-2 text-[10px]" onclick="closeModal('mystery-modal')">X</div></div>
<div class="text-[10px] text-gray-400 mb-3">누적된 빌드업 태그와 데이터를 소모하여 미스터리를 해결하고 강력한 영구 자산을 획득하세요. 방치된 미스터리는 매 턴 지속 페널티를 유발할 수 있습니다.</div>
<div class="flex-1 overflow-y-auto space-y-2" id="mystery-list"></div>
</div>
<div class="modal" id="asset-modal">
<div class="flex justify-between items-center border-b-2 border-gray-600 pb-3 mb-3"><h2 class="text-white font-bold text-base">보유 자산 (엔진)</h2><div class="close-btn p-2 text-[10px]" onclick="closeModal('asset-modal')">X</div></div>
<div class="flex-1 overflow-y-auto" id="asset-list"></div>
</div>
<div class="modal" id="law-modal">
<div class="flex justify-between items-center border-b-2 border-gray-600 pb-3 mb-3"><h2 class="text-white font-bold text-base">법안 및 규칙</h2><div class="close-btn p-2 text-[10px]" onclick="closeModal('law-modal')">X</div></div>
<div class="space-y-3 text-xs text-gray-300 overflow-y-auto">
<div><span class="text-yellow-500 font-bold block">현재 아젠다</span><span id="modal-agenda" class="font-bold text-white"></span><p id="modal-agenda-desc" class="text-[10px] text-gray-400 mt-1"></p></div>
<div><span class="text-red-500 font-bold block">현재 퀘스트</span><span id="modal-quest" class="font-bold text-white"></span><div class="text-[10px] mt-1 grid grid-cols-2 gap-2"><div class="bg-gray-800 p-2 rounded"><span class="text-green-400">보상:</span> <span id="quest-reward"></span></div><div class="bg-gray-800 p-2 rounded"><span class="text-red-400">패널티:</span> <span id="quest-penalty"></span></div></div></div>
<div><span class="text-blue-400 font-bold block">발효된 법안</span><ul id="modal-laws" class="list-disc pl-4 text-[10px] mt-1 space-y-1 text-gray-400"></ul></div>
</div>
</div>
<div class="modal" id="faction-modal">
<div class="flex justify-between items-center border-b-2 border-gray-600 pb-3 mb-3"><h2 class="text-white font-bold text-base">파벌 정보</h2><div class="close-btn p-2 text-[10px]" onclick="closeModal('faction-modal')">X</div></div>
<div class="flex-1 overflow-y-auto" id="faction-list-detail"></div>
</div>
</div>
<script>
const SHEET_URLS = {
factions: "./data/factions.csv", agendas: "./data/agendas.csv", quests: "./data/quests.csv",
bills: "./data/bills.csv", assets: "./data/assets.csv", scenarios: "./data/scenarios.csv",
mysteries: "./data/mysteries.csv"
};
const EFFECT_NAMES = { anomaly: "데이터", political: "정치 자본", trust: "신임도", entropy: "엔트로피" };
let DB_FACTIONS = []; let DB_AGENDAS = []; let DB_QUESTS = []; let DB_BILLS = []; let DB_ASSETS = []; let DB_SCENARIOS = []; let DB_MYSTERIES = [];
let state = {
week: 1, day: 1, anomaly: 20, political: 20, trust: 80, entropy: 10,
assets: [], builtTags: {}, activeLaws: [], activeMysteries: [],
currentAgenda: null, currentQuest: null, questProgress: 0, currentCard: null, lockState: "none",
council: { billA: null, billB: null, votesA: 50, votesB: 50, supportersA: [], supportersB: [] }
};
function normalizeKey(str) { return String(str).replace(/^\uFEFF/, '').trim().toLowerCase(); }
function normalizeValue(val) { return typeof val === 'string' ? val.trim() : val; }
function parseCSV(url) {
return new Promise((resolve) => {
if (!url || url.trim() === "") { resolve(null); return; }
Papa.parse(url, {
header: true, skipEmptyLines: true, download: true,
complete: function(results) {
resolve(results.data.map(row => {
let r={};
for(let k in row) r[normalizeKey(k)] = normalizeValue(row[k]);
return r;
}));
},
error: function() { resolve(null); }
});
});
}
async function initDB() {
document.getElementById('loading-screen').style.display = 'flex';
try {
const [factions, agendas, quests, bills, assets, scenarios, mysteries] = await Promise.all([
parseCSV(SHEET_URLS.factions), parseCSV(SHEET_URLS.agendas), parseCSV(SHEET_URLS.quests),
parseCSV(SHEET_URLS.bills), parseCSV(SHEET_URLS.assets), parseCSV(SHEET_URLS.scenarios),
parseCSV(SHEET_URLS.mysteries)
]);
if (factions && factions.length > 0) {
DB_FACTIONS = factions.map(f => ({
...f,
share: parseInt(f.share)||10,
like_tag: f.love_tag || f.like_tag || '',
hate_tag: f.hate_tag || ''
}));
}
if (agendas && agendas.length > 0) DB_AGENDAS = agendas;
if (quests && quests.length > 0) DB_QUESTS = quests;
if (bills && bills.length > 0) {
DB_BILLS = bills.map(b => {
let is_buff = b.type === 'lock_reject' || b.type === 'buff_tag';
return {
...b,
is_buff: is_buff,
target_tag: b.tag,
buff_tag: is_buff ? b.tag : null,
nullify_tag: !is_buff ? b.tag : null
};
});
}
if (assets && assets.length > 0) {
DB_ASSETS = assets.map(a => ({
...a,
tags: a.tags ? a.tags.split(',').map(t=>t.trim()) : [],
passive_effect: { anomaly: parseInt(a.passive_eff_val)||0 }, // 간단한 호환성 매핑
holding_risk: { entropy: parseInt(a.risk_eff_val)||0 }
}));
}
if (scenarios && scenarios.length > 0) {
DB_SCENARIOS = scenarios.map(s => ({
...s,
act: parseInt(s.act)||1,
tags: s.tags ? s.tags.split(',').map(t=>t.trim().replace(/^"|"$/g, '')) : [],
yes_a: parseInt(s.cost)||0, // cost를 데이터(anomaly) 획득/소모로 매핑
no_p: parseInt(s.conflict)||0, // conflict를 정치 자본 획득/소모로 매핑
yes_e: parseInt(s.yes_e)||0,
no_e: parseInt(s.no_e)||0,
}));
}
if (mysteries && mysteries.length > 0) {
DB_MYSTERIES = mysteries.map(m => {
let req_tags = {};
if(m.req_tags) m.req_tags.split(',').forEach(rt => { let parts = rt.split(':'); if(parts.length===2) req_tags[parts[0].trim()] = parseInt(parts[1]); });
return { ...m, req_tags: req_tags, cost_anomaly: parseInt(m.cost_anomaly)||0, penalty_entropy: parseInt(m.penalty_entropy)||0 };
});
}
} catch(e) {
console.error("CSV 파싱 에러:", e);
}
setTimeout(() => { document.getElementById('loading-screen').style.display = 'none'; startGame(); }, 500);
}
function startGame() {
state = { week: 1, day: 1, anomaly: 20, political: 20, trust: 80, entropy: 10, assets: [], builtTags: {}, activeLaws: [], activeMysteries: [...DB_MYSTERIES], questProgress: 0, lockState: "none" };
document.getElementById('desk').style.display = 'flex'; document.getElementById('council').style.display = 'none';
setupWeek();
}
function setupWeek() {
state.day = 1; state.questProgress = 0;
state.currentAgenda = DB_AGENDAS[Math.floor(Math.random()*DB_AGENDAS.length)] || DB_AGENDAS[0];
state.currentQuest = DB_QUESTS[Math.floor(Math.random()*DB_QUESTS.length)] || DB_QUESTS[0];
updateHUD(); nextDay();
}
function getAffinity(f) {
if (f.is_blacklisted) return { level: 5, name: "🔥 파국", color: "#ef4444" };
if (f.share >= 40) return { level: 1, name: "👑 핵심 동맹", color: "#3b82f6" };
if (f.share >= 20) return { level: 2, name: "🤝 우호", color: "#10b981" };
if (f.share >= 10) return { level: 3, name: "⚖️ 중립", color: "#9ca3af" };
return { level: 4, name: "⚔️ 적대", color: "#f59e0b" };
}
function nextDay() {
if (state.day > 5) { startCouncil(); return; }
if (state.entropy >= 100) { alert("종말 시계 100% 도달. 통제에 실패했습니다."); initDB(); return; }
applyAssetPassives();
state.activeMysteries.forEach(m => { if(m.penalty_entropy) state.entropy += m.penalty_entropy; });
let panicLevel = 1; let panicMsg = "";
if (state.entropy >= 81) panicLevel = 5;
else if (state.entropy >= 61) panicLevel = 4;
else if (state.entropy >= 41) panicLevel = 3;
else if (state.entropy >= 21) panicLevel = 2;
if (panicLevel === 2) { addStat('trust', -1); panicMsg = "[동요] 시민 불안 (신임도 -1)"; }
else if (panicLevel >= 3) { addStat('political', -1); addStat('trust', -2); panicMsg = "[혼란] 시스템 마비 (자본/신임도 하락)"; }
let terrorChance = panicLevel >= 4 ? 0.6 : 0.3;
let sabotageMsg = [];
if (panicMsg) sabotageMsg.push(panicMsg);
DB_FACTIONS.forEach(f => {
if(getAffinity(f).level === 5 && Math.random() < terrorChance) {
state.entropy += 5; state.trust -= 5;
sabotageMsg.push(`[파국] ${f.name} 테러!`);
}
});
if (sabotageMsg.length > 0) {
const toast = document.getElementById('passive-toast');
toast.innerText = sabotageMsg.join(' / ');
toast.className = 'passive-toast bg-red-600'; void toast.offsetWidth; toast.classList.add('active');
}
if (panicLevel === 5) {
state.currentCard = {
id: 999, dept: "재난 관리", color: "#991b1b",
title: "현실 붕괴 임박", body: "사회 시스템이 통제 불능 상태입니다. 막대한 자원을 희생해 시간을 벌겠습니까?",
faction: "전체 파벌", tags: [], yes_a: 40, no_p: 20, yes_e: -10, no_e: 15, flavor: "신은 우리를 버렸습니다."
};
} else {
let pool = DB_SCENARIOS.filter(s => s.act === state.week);
if(pool.length === 0) pool = DB_SCENARIOS;
state.currentCard = pool[Math.floor(Math.random() * pool.length)];
}
renderCard(state.currentCard);
const cardEl = document.getElementById('current-card');
cardEl.style.transition = 'none'; cardEl.style.transform = 'translate(0,0) scale(0.9)';
setTimeout(() => { cardEl.style.transition = 'transform 0.3s ease-out'; cardEl.style.transform = 'scale(1)'; }, 50);
updateHUD();
}
function getMainTag() {
let max = 0; let mainTag = null;
for(let t in state.builtTags) { if(state.builtTags[t] > max) { max = state.builtTags[t]; mainTag = t; } }
return { tag: mainTag, count: max, counts: state.builtTags };
}
function getNullifiedTags() {
return state.activeLaws.filter(l => l.type === "nullify_tag").map(l => l.nullify_tag);
}
function getBuffTags() {
return state.activeLaws.filter(l => l.type === "buff_tag").map(l => l.buff_tag);
}
let eventQueue = []; let isFlushing = false;
function enqueueEvent(triggerType, context) { eventQueue.push({ triggerType, context }); if(!isFlushing) flushEvents(); }
function flushEvents() {
isFlushing = true; let triggeredMsgs = [];
let counts = state.builtTags; let nTags = getNullifiedTags();
while(eventQueue.length > 0) {
let ev = eventQueue.shift();
state.assets.forEach(a => {
if (a.tags && a.tags.some(t => nTags.includes(t))) return;
if (a.combo_req > 0 && (!a.tags || !a.tags.some(t => counts[t] >= a.combo_req))) return;
if (a.trigger === ev.triggerType) {
if (a.effect_type === 'resource' && a.effect_target) {
state[a.effect_target] += a.effect_val;
triggeredMsgs.push(`[${a.name}] 연쇄 획득`);
}
}
});
}
if (triggeredMsgs.length > 0) {
const toast = document.getElementById('passive-toast');
toast.innerText = "연쇄: " + [...new Set(triggeredMsgs)].join(', ');
toast.className = 'passive-toast'; void toast.offsetWidth; toast.classList.add('active');
}
isFlushing = false; updateHUD();
}
function addStat(key, val) { if(val !== 0) { state[key] += val; if(val > 0) enqueueEvent('on_stat_increase', key); } }
function applyAssetPassives() {
let msg = []; let nTags = getNullifiedTags(); let bTags = getBuffTags();
state.assets.forEach(a => {
if (a.tags && a.tags.some(t => nTags.includes(t))) return;
let mult = (a.tags && a.tags.some(t => bTags.includes(t))) ? 2 : 1;
if(a.passive_effect) {
if(a.passive_effect.anomaly) { addStat('anomaly', a.passive_effect.anomaly * mult); msg.push(`데이터+${a.passive_effect.anomaly * mult}`); }
if(a.passive_effect.political) { addStat('political', a.passive_effect.political * mult); msg.push(`정치+${a.passive_effect.political * mult}`); }
}
if(a.holding_risk && a.holding_risk.entropy) { addStat('entropy', a.holding_risk.entropy); msg.push(`엔트로피+${a.holding_risk.entropy}`); }
if(!a.trigger && a.combo_req > 0 && a.tags && a.tags.some(t => state.builtTags[t] >= a.combo_req)) {
if(a.combo_effect_key) { addStat(a.combo_effect_key, a.combo_effect_val * mult); msg.push(`[시너지] +${a.combo_effect_val*mult}`); }
}
});
if(msg.length > 0) {
const toast = document.getElementById('passive-toast');
toast.innerText = "자산 효과: " + [...new Set(msg)].join(', ');
toast.className = 'passive-toast'; void toast.offsetWidth; toast.classList.add('active');
}
}
function renderCard(data) {
const card = document.getElementById('current-card');
card.style.display = 'flex'; card.style.borderColor = "#cbb"; card.style.transform = "translate(0,0)";
document.getElementById('c-dept').innerText = data.dept || '부서 불명';
document.getElementById('c-dept').style.backgroundColor = data.color || '#333';
document.getElementById('c-title').innerText = data.title || '제목 없음';
document.getElementById('c-body').innerText = data.body || '내용 없음';
document.getElementById('c-flavor').innerText = `"${data.flavor || ''}"`;
document.getElementById('c-faction').innerText = data.faction || '무소속';
let tHtml = "";
if(data.tags) data.tags.forEach(t => tHtml += `<span class="tag-badge">${t}</span>`);
document.getElementById('c-tags').innerHTML = tHtml;
let lockType = "none";
if(data.tags) {
state.activeLaws.forEach(bill => {
if(data.tags.includes(bill.tag)) { if(bill.type === "lock_approve") lockType = "approve"; if(bill.type === "lock_reject") lockType = "reject"; }
});
}
state.lockState = lockType;
const overlay = document.getElementById('lock-overlay');
if (lockType === 'approve') { overlay.classList.add('active'); document.getElementById('lock-type-text').innerText = "승인 금지"; }
else if (lockType === 'reject') { overlay.classList.add('active'); document.getElementById('lock-type-text').innerText = "거절 금지"; }
else overlay.classList.remove('active');
document.getElementById('stamp-approve').style.opacity = 0; document.getElementById('stamp-reject').style.opacity = 0;
document.getElementById('predict-overlay').style.display = 'none';
document.getElementById('law-popup').classList.remove('visible');
}
function updateHUD() {
document.getElementById('week-num').innerText = state.week; document.getElementById('day-num').innerText = state.day;
document.getElementById('val-anomaly').innerText = state.anomaly; document.getElementById('val-political').innerText = state.political;
document.getElementById('bar-trust').style.width = `${state.trust}%`; document.getElementById('txt-trust').innerText = `${state.trust}%`;
document.getElementById('hand-entropy').style.transform = `translateX(-50%) rotate(${(state.entropy / 100) * 360}deg)`;
let sortedFactions = [...DB_FACTIONS].sort((a,b)=>b.share - a.share);
// Donut Chart
const donut = document.getElementById('donut-share');
if(donut) {
let conicStr = []; let currentDeg = 0;
sortedFactions.forEach(f => {
if(f.share > 0) {
let deg = (f.share / 100) * 360;
conicStr.push(`${f.color} ${currentDeg}deg ${currentDeg + deg}deg`);
currentDeg += deg;
}
});
donut.style.background = `conic-gradient(${conicStr.join(', ')})`;
}
document.getElementById('asset-count').innerText = state.assets.length;
const tagsContainer = document.getElementById('hud-tags-container');
if(tagsContainer) {
tagsContainer.innerHTML = "";
let tagCount = 0;
for(let t in state.builtTags) {
if(state.builtTags[t] > 0) {
tagsContainer.innerHTML += `<div class="bg-gray-700 text-yellow-300 px-2 py-1 rounded text-[9px] font-bold shadow-sm">[${t}] ${state.builtTags[t]}</div>`;
tagCount++;
}
}
if(tagCount === 0) tagsContainer.innerHTML = "<span class='text-gray-500 italic'>없음</span>";
}
let hasActiveSynergy = state.assets.some(a => a.combo_req > 0 && a.tags && a.tags.some(t => state.builtTags[t] >= a.combo_req));
document.getElementById('hud-synergy-badge').className = hasActiveSynergy ? "text-yellow-400 font-bold animate-pulse" : "text-gray-500";
document.getElementById('hud-synergy-badge').innerHTML = hasActiveSynergy ? `<i class="fa-solid fa-bolt"></i> 시너지 가동` : `<i class="fa-solid fa-bolt"></i> 연쇄 없음`;
if (state.currentAgenda) {
const agTitle = document.getElementById('agenda-title'); if(agTitle) agTitle.innerText = state.currentAgenda.title;
}
}
function renderCard(data) {
const card = document.getElementById('current-card');
card.style.display = 'flex'; card.style.borderColor = "#cbb"; card.style.transform = "translate(0,0)";
document.getElementById('c-dept').innerText = data.dept || '부서 불명';
document.getElementById('c-dept').style.backgroundColor = data.color || '#333';
document.getElementById('c-title').innerText = data.title || '제목 없음';
document.getElementById('c-body').innerText = data.body || '내용 없음';
document.getElementById('c-flavor').innerText = `"${data.flavor || ''}"`;
document.getElementById('c-faction').innerText = data.faction || '무소속';
let tHtml = "";
if(data.tags) data.tags.forEach(t => tHtml += `<span class="tag-badge">[${t}]</span>`);
const cTags = document.getElementById('c-tags');
if(cTags) cTags.innerHTML = tHtml;
let conflictElem = document.getElementById('c-conflict');
if(conflictElem) {
if (data.no_p > 0) {
conflictElem.innerHTML = `거절 시 👑 +${data.no_p}`;
conflictElem.classList.remove('hidden');
} else {
conflictElem.classList.add('hidden');
}
}
let lockType = "none";
if(data.tags) {
state.activeLaws.forEach(bill => {
if(data.tags.includes(bill.tag)) { if(bill.type === "lock_approve") lockType = "approve"; if(bill.type === "lock_reject") lockType = "reject"; }
});
}
state.lockState = lockType;
const overlay = document.getElementById('lock-overlay');
if (lockType === 'approve') { overlay.classList.add('active'); document.getElementById('lock-type-text').innerText = "승인 금지"; }
else if (lockType === 'reject') { overlay.classList.add('active'); document.getElementById('lock-type-text').innerText = "거절 금지"; }
else overlay.classList.remove('active');
document.getElementById('stamp-approve').style.opacity = 0; document.getElementById('stamp-reject').style.opacity = 0;
document.getElementById('predict-overlay').style.display = 'none';
document.getElementById('law-popup').classList.remove('visible');
}
const card = document.getElementById('current-card');
let startX = 0, currentX = 0, isDragging = false;
card.addEventListener('mousedown', startDrag); card.addEventListener('touchstart', startDrag, {passive:false});
document.addEventListener('mousemove', moveDrag); document.addEventListener('touchmove', moveDrag, {passive:false});
document.addEventListener('mouseup', endDrag); document.addEventListener('touchend', endDrag);
function startDrag(e) { isDragging = true; startX = (e.type==='touchstart')?e.touches[0].clientX:e.clientX; }
function moveDrag(e) {
if(!isDragging) return;
currentX = ((e.type==='touchmove')?e.touches[0].clientX:e.clientX) - startX;
if (state.lockState === 'none' || (state.lockState==='approve' && currentX < 0) || (state.lockState==='reject' && currentX > 0)) {
card.style.transform = `translateX(${currentX}px) rotate(${currentX*0.05}deg)`;
if (Math.abs(currentX) > 40) showPrediction(currentX > 0); else document.getElementById('predict-overlay').style.display = 'none';
document.getElementById('stamp-approve').style.opacity = currentX > 0 ? Math.min(currentX/100, 1) : 0;
document.getElementById('stamp-reject').style.opacity = currentX < 0 ? Math.min(Math.abs(currentX)/100, 1) : 0;
} else {
document.getElementById('law-popup').classList.add('visible');
document.getElementById('law-popup-title').innerText = state.lockState === 'approve' ? "🚫 승인 당론 위배" : "🚫 거절 당론 위배";
}
}
function endDrag() {
isDragging = false; document.getElementById('predict-overlay').style.display = 'none'; document.getElementById('law-popup').classList.remove('visible');
if (currentX > 100 && state.lockState !== 'approve') processDecision('approve');
else if (currentX < -100 && state.lockState !== 'reject') processDecision('reject');
else { card.style.transition = "transform 0.3s"; card.style.transform = "translate(0,0)"; }
currentX = 0; document.getElementById('stamp-approve').style.opacity = 0; document.getElementById('stamp-reject').style.opacity = 0;
}
function showPrediction(isApprove) {
const p = document.getElementById('predict-overlay'); p.style.display = 'flex'; p.style.zIndex = "100";
const data = state.currentCard;
let tGainHtml = ""; let fGainHtml = "";
if(isApprove) {
document.getElementById('p-header').innerText = "승인 (APPROVE)"; document.getElementById('p-header').className = "font-bold border-b border-gray-600 mb-2 pb-1 text-center text-red-400";
document.getElementById('p-desc').innerText = "엔진 빌드업 (태그 확보)";
document.getElementById('p-anomaly').innerHTML = data.yes_a ? `<span class="${data.yes_a>0?'p-down':'p-up'}">${data.yes_a>0?'-':'+'}${Math.abs(data.yes_a)}</span>` : "0";
document.getElementById('p-political').innerHTML = "-";
document.getElementById('p-entropy').innerHTML = data.yes_e ? `<span class="p-down">${data.yes_e > 0?'+':''}${data.yes_e}</span>` : "0";
document.getElementById('p-trust').innerHTML = "-";
data.tags.forEach(t => tGainHtml += `[${t}] +1 `);
fGainHtml = "변동 없음 (침묵)";
} else {
document.getElementById('p-header').innerText = "거절 (REJECT)"; document.getElementById('p-header').className = "font-bold border-b border-gray-600 mb-2 pb-1 text-center text-blue-400";
document.getElementById('p-desc').innerText = "관료제 통제 (기각 및 정치공작)";
document.getElementById('p-anomaly').innerHTML = "-";
document.getElementById('p-political').innerHTML = data.no_p ? `+${data.no_p}` : "0";
document.getElementById('p-entropy').innerHTML = data.no_e ? `<span class="p-up">${data.no_e > 0?'+':''}${data.no_e}</span>` : "0";
document.getElementById('p-trust').innerHTML = `<span class="p-up">+2</span>`;
tGainHtml = "<span class='text-gray-500'>획득 불가</span>";
data.tags.forEach(t => {
DB_FACTIONS.forEach(f => {
if(f.like_tag === t) fGainHtml += ` <span style="color:${f.color}">${f.name} ▼▼</span>`;
if(f.hate_tag === t) fGainHtml += ` <span style="color:${f.color}">${f.name} ▲▲</span>`;
});
});
if(fGainHtml === "") fGainHtml = "영향력 변동 없음";
}
document.getElementById('p-tag-gain').innerHTML = tGainHtml;
document.getElementById('p-faction-change').innerHTML = fGainHtml;
}
function processDecision(type) {
const data = state.currentCard; let animateDir = type === 'approve' ? 500 : -500;
if (type === 'approve') {
addStat('anomaly', -(data.yes_a || 0)); addStat('entropy', data.yes_e || 0);
if(data.tags) {
data.tags.forEach(t => {
state.builtTags[t] = (state.builtTags[t]||0) + 1;
});
}
if (state.currentQuest) state.questProgress++;
enqueueEvent('on_approve', null);
} else {
addStat('political', data.no_p || 0); addStat('entropy', data.no_e || 0); addStat('trust', 2);
if(data.tags) {
data.tags.forEach(t => {
DB_FACTIONS.forEach(f=>{
if(f.like_tag===t) f.share=Math.max(0, f.share-3);
if(f.hate_tag===t) f.share+=2;
});
});
}
enqueueEvent('on_reject', null);
}
const tube = document.getElementById('dividend-tube');
const b = document.createElement('div'); b.className = 'dividend-block'; b.style.backgroundColor = type === 'approve' ? '#ef4444' : '#3b82f6'; tube.appendChild(b);
card.style.transition = "transform 0.3s ease-in"; card.style.transform = `translateX(${animateDir}px) rotate(${animateDir/20}deg)`;
updateHUD();
setTimeout(() => { card.style.display = 'none'; state.day++; nextDay(); }, 300);
}
// --- MYSTERY BOARD ---
function renderMysteries() {
const list = document.getElementById('mystery-list'); list.innerHTML = "";
let bTags = getBuffTags();
state.activeMysteries.forEach((m, idx) => {
let reqHtml = ""; let canSolve = true;
// 할인 로직
let discount = 0; if(m.req_tags) Object.keys(m.req_tags).forEach(t => { if(bTags.includes(t)) discount += 5; });
let finalCost = Math.max(0, m.cost_anomaly - discount);
if(m.req_tags) {
for(let t in m.req_tags) {
let has = state.builtTags[t] || 0; let need = m.req_tags[t];
reqHtml += `<span class="${has>=need?'text-green-400':'text-red-400'}">[${t}] ${has}/${need}</span> `;
if(has<need) canSolve = false;
}
}
if(state.anomaly < finalCost) canSolve = false;
let ast = DB_ASSETS.find(a=>a.id === m.reward_asset_id);
list.innerHTML += `<div class="bg-gray-800 p-3 rounded mb-2 border border-blue-900 shadow-md">
<div class="flex justify-between border-b border-gray-600 pb-1 mb-2"><span class="font-bold text-blue-300 text-sm">${m.title}</span><span class="text-xs text-red-400">매턴 엔트로피 +${m.penalty_entropy || 0}</span></div>
<div class="text-[10px] text-gray-300 flex justify-between items-center">
<div>
<div class="mb-1 text-yellow-300 font-bold">소모 태그: ${reqHtml}</div>
<div>데이터 비용: 🧬 <span class="${state.anomaly >= finalCost ? 'text-blue-300' : 'text-red-400'}">${finalCost}</span> ${discount>0 ? `<span class="text-green-400 ml-1">(법안 할인 -${discount})</span>`:''}</div>
<div class="mt-1 text-green-300 font-bold">보상: ${ast?ast.name:'엔진 자산'}</div>
</div>
<button class="${canSolve?'bg-blue-600 hover:bg-blue-500':'bg-gray-700 opacity-50 cursor-not-allowed'} px-3 py-2 rounded text-white font-bold flex flex-col items-center" onclick="${canSolve?`solveMystery(${idx})`:'return false;'}"><span>해독</span><span class="text-[8px] font-normal">(태그 소모)</span></button>
</div>
</div>`;
});
if(state.activeMysteries.length === 0) list.innerHTML = "<div class='text-center text-gray-500 text-xs mt-4'>진행 중인 미스터리가 없습니다.</div>";
}
function solveMystery(idx) {
let m = state.activeMysteries[idx];
let bTags = getBuffTags(); let discount = 0; if(m.req_tags) Object.keys(m.req_tags).forEach(t => { if(bTags.includes(t)) discount += 5; });
state.anomaly -= Math.max(0, m.cost_anomaly - discount);
if(m.req_tags) for(let t in m.req_tags) state.builtTags[t] -= m.req_tags[t];
let ast = DB_ASSETS.find(a=>a.id === m.reward_asset_id);
if(ast) state.assets.push(ast);
state.activeMysteries.splice(idx, 1);
alert(`[${m.title}] 미스터리 해결! ${ast?ast.name:''} 획득`);
renderMysteries(); updateHUD();
}
// --- COUNCIL ---
function startCouncil() {
document.getElementById('desk').style.display = 'none'; document.getElementById('council').style.display = 'flex';
updateHUD(); document.getElementById('council-phase-1').classList.add('active'); document.getElementById('council-phase-2').classList.remove('active');
let sortedFactions = [...DB_FACTIONS].sort((a,b)=>b.share - a.share);
document.getElementById('ruling-faction').innerText = sortedFactions[0].name; document.getElementById('ruling-faction').style.color = sortedFactions[0].color;
}
function startVotePhase() {
document.getElementById('council-phase-1').classList.remove('active'); document.getElementById('council-phase-2').classList.add('active');
let mainTagInfo = getMainTag(); let targetTag = mainTagInfo.tag;
// Affinity 적용하여 동맹(1,2)과 적대(4,5) 판별
let factionsWithAff = DB_FACTIONS.map(f => ({ ...f, aff: getAffinity(f) }));
let sortedFactions = factionsWithAff.sort((a,b) => a.aff.level - b.aff.level);
const top = sortedFactions[0]; // 가장 우호적인 파벌
const hostile = sortedFactions[sortedFactions.length-1]; // 가장 적대적인 파벌
let buffBills = DB_BILLS.filter(b => b.is_buff);
let debuffBills = DB_BILLS.filter(b => !b.is_buff);
let b1 = buffBills.find(b => b.target_tag === targetTag) || buffBills[0];
let b2 = debuffBills.find(b => b.target_tag === targetTag) || debuffBills[0];
if (targetTag && b2.target_tag === targetTag) {
document.getElementById('sabotage-alert').classList.remove('hidden'); document.getElementById('sabotage-target-tag').innerText = targetTag;
} else { document.getElementById('sabotage-alert').classList.add('hidden'); }
state.council.billA = b1; state.council.billB = b2;
document.getElementById('bill-a-faction').innerText = top.name; document.getElementById('bill-b-faction').innerText = hostile.name;
document.getElementById('bill-a-name').innerText = b1.name; document.getElementById('bill-a-flavor').innerText = `"${b1.flavor}"`;
document.getElementById('bill-b-name').innerText = b2.name; document.getElementById('bill-b-flavor').innerText = `"${b2.flavor}"`;
let supportersA = [], supportersB = [], swingers = [], enemies = []; let votesA = 0;
factionsWithAff.forEach(f => {
if(f.aff.level <= 2 || (f.like_tag === targetTag)) { supportersA.push(f.name); votesA += f.share; }
else if(f.aff.level >= 4 || (f.hate_tag === targetTag)) { supportersB.push(f.name); enemies.push(f); }
else { swingers.push(f); }
});
state.council.votesA = Math.max(0, votesA);
state.council.supportersA = supportersA;
state.council.supportersB = supportersB;
updateVoteUI();
const lobbyList = document.getElementById('lobby-targets'); lobbyList.innerHTML = "";
swingers.forEach(f => { lobbyList.innerHTML += `<div class="lobby-tag" onclick="lobbyFaction('${f.id}', event)">${f.name} (${f.share}%)</div>`; });
const blacklistList = document.getElementById('blacklist-targets'); blacklistList.innerHTML = "";
enemies.forEach(f => {
if(!f.is_blacklisted) blacklistList.innerHTML += `<div class="lobby-tag" style="border-color:#ef4444; color:#ef4444;" onclick="blacklistFaction('${f.id}', event)">${f.name}</div>`;
});
}
function lobbyFaction(fid, e) {
if(state.political < 10) { alert("정치 자본 부족"); return; }
state.political -= 10;
const fac = DB_FACTIONS.find(f => f.id === fid); state.council.votesA += fac.share;
const tgt = e.currentTarget; tgt.style.borderColor = "#10b981"; tgt.style.color = "#10b981"; tgt.innerText = "포섭됨"; tgt.onclick = null;
updateVoteUI(); updateHUD();
}
function blacklistFaction(fid, e) {
const fac = DB_FACTIONS.find(f => f.id === fid);
fac.is_blacklisted = true;
state.council.supportersB = state.council.supportersB.filter(name => name !== fac.name);
const tgt = e.currentTarget; tgt.style.background = "#ef4444"; tgt.style.color = "#fff"; tgt.innerText = "탄압됨"; tgt.onclick = null;
alert(`${fac.name}을(를) 영구적 파국 상태로 몰아넣었습니다. 다음 턴부터 사보타주 테러가 발생할 수 있습니다.`);
updateVoteUI(); updateHUD();
}
function updateVoteUI() {
const vA = Math.min(100, state.council.votesA);
document.getElementById('vote-a').style.width = `${vA}%`; document.getElementById('vote-b').style.width = `${100-vA}%`;
document.getElementById('vote-a-val').innerText = vA; document.getElementById('vote-b-val').innerText = 100-vA;
document.getElementById('list-support-a').innerText = state.council.supportersA.length > 0 ? state.council.supportersA.join(', ') : '없음';
document.getElementById('list-support-b').innerText = state.council.supportersB.length > 0 ? state.council.supportersB.join(', ') : '없음';
}
function endCouncil() {
const winner = state.council.votesA >= 50 ? state.council.billA : state.council.billB;
alert(`${winner.name} 가결! (다음 주 적용)`);
state.activeLaws.push(winner); state.week++; state.day = 1;
document.getElementById('council').style.display = 'none'; document.getElementById('desk').style.display = 'flex';
setupWeek();
}
window.openModal = (id) => {
document.getElementById(id).classList.add('active');
if(id==='mystery-modal') renderMysteries();
if(id==='asset-modal') {
document.getElementById('asset-list').innerHTML = state.assets.map(a => `<div class="list-item"><span class="font-bold flex items-center">${a.name} <span class="bg-gray-700 text-gray-300 px-1 rounded ml-1 text-[8px]">${a.tags?a.tags.join(','):''}</span></span><span class="text-[9px] text-green-400 ml-auto">[작동 중]</span></div>`).join('');
if(state.assets.length===0) document.getElementById('asset-list').innerHTML = "<div class='text-center text-xs text-gray-500'>없음</div>";
}
if(id==='faction-modal') {
let facHtml = "";
let sortedFactions = [...DB_FACTIONS].sort((a,b)=>b.share - a.share);
sortedFactions.forEach(f => {
let aff = getAffinity(f);
facHtml += `<div class="bg-gray-800 p-3 rounded mb-2 border-l-4" style="border-color:${f.color}">
<div class="flex justify-between items-center mb-1"><span class="font-bold" style="color:${f.color}">${f.name}</span><span class="text-xs font-bold" style="color:${aff.color}">${aff.name} (지분 ${f.share}%)</span></div>
<div class="text-[10px] text-gray-400 mb-1">${f.desc}</div>
<div class="text-[9px] text-gray-500">선호: <span class="text-green-400">[${f.like_tag||'없음'}]</span> / 혐오: <span class="text-red-400">[${f.hate_tag||'없음'}]</span></div>
</div>`;
});
document.getElementById('faction-list-detail').innerHTML = facHtml;
}
}
window.closeModal = (id) => document.getElementById(id).classList.remove('active');
initDB();
</script>
</body>
</html>