프로토타입 v1.0 업로드
This commit is contained in:
925
src/index.html
Normal file
925
src/index.html
Normal file
@@ -0,0 +1,925 @@
|
|||||||
|
<!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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* [수정] dvh를 사용하여 모바일 주소창 영역 제외하고 완벽히 꽉 차게 변경. 스크롤 원천 차단 */
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* [수정] PC/태블릿처럼 화면이 넓고 높은 환경에서만 폰 모양 테두리 적용 (비율로 자동 크기 조절) */
|
||||||
|
@media (min-width: 450px) and (min-height: 700px) {
|
||||||
|
.phone-wrapper {
|
||||||
|
height: 90vh; /* 화면 높이의 90%만 차지하게 하여 상하단 안 잘림 */
|
||||||
|
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 Styles */
|
||||||
|
.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; }
|
||||||
|
.res-val.deficit { color: #ef4444; animation: pulse-red 1s infinite; }
|
||||||
|
@keyframes pulse-red { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
|
||||||
|
|
||||||
|
.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; }
|
||||||
|
|
||||||
|
.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; /* 화면 높이의 60%만 차지하게 설정 */
|
||||||
|
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; }
|
||||||
|
.conflict-badge { background: #222; color: #fff; font-size: 9px; padding: 2px 4px; border-radius: 4px; display: inline-block; font-weight: bold; }
|
||||||
|
.asset-badge { background: #059669; color: #fff; font-size: 9px; padding: 2px 4px; border-radius: 4px; margin-left: 4px; display: inline-block; font-weight: bold; }
|
||||||
|
|
||||||
|
/* 내용이 길 경우 스크롤 가능하게 처리 */
|
||||||
|
.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; }
|
||||||
|
.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; text-align: right; }
|
||||||
|
|
||||||
|
.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; animation: pulse 2s infinite; font-size: 13px; flex-shrink: 0; }
|
||||||
|
.action-btn.secondary { background: #10b981; animation: none; 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>
|
||||||
|
|
||||||
|
<div class="res-row">
|
||||||
|
<div class="res-box"><span class="text-[9px] text-gray-400">총 예산</span><span class="res-val" id="val-total">$500</span></div>
|
||||||
|
<div class="res-box" id="dept-budget-box" style="border-color: #c92a2a;"><span class="text-[9px] text-gray-400" id="dept-name-display">부서 예산</span><span class="res-val" id="val-dept">$100</span></div>
|
||||||
|
<div class="res-box" style="border-color: #666; background:#222; width:20%;"><span class="text-[9px] text-gray-400">비자금</span><span class="res-val text-yellow-500" id="val-black">$20</span></div>
|
||||||
|
<div class="res-box" style="border-color: #666; background:#222; width:20%;"><span class="text-[9px] text-gray-400"><i class="fa-solid fa-eye"></i> 감시</span><span class="res-val text-red-400" id="val-suspicion">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-bars" id="bar-share"></div><span class="stat-label">지분율 <i class="fa-solid fa-circle-info text-[8px]"></i></span></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 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><span class="conflict-badge" id="c-conflict">VS</span><span class="asset-badge" id="c-asset-badge" style="display:none;">📦 자산 획득</span></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="p-row">예산: <span id="p-budget"></span></div><div class="p-row">엔트로피: <span id="p-entropy"></span></div>
|
||||||
|
<div class="desc-text" id="p-desc"></div>
|
||||||
|
<div class="p-row mt-1">신임도: <span id="p-trust"></span></div><hr class="border-gray-600 my-1"><div class="p-row" id="p-faction-gain"></div><div class="p-row" id="p-faction-loss"></div><div class="p-row" id="p-asset-gain"></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('asset-modal')"><i class="fa-solid fa-box-open"></i> 자산 (<span id="asset-count">0</span>)</div>
|
||||||
|
<div class="menu-btn" onclick="openModal('law-modal')"><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 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">여당안 (A)</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">야당안 (B)</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>
|
||||||
|
</div>
|
||||||
|
<div class="action-btn" onclick="endCouncil()">결과 확정 및 다음 주 시작</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modals -->
|
||||||
|
<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: "https://docs.google.com/spreadsheets/d/e/2PACX-1vSSzLgVoqnhadH-1oUvITvjZI8UBmBCkoDma4zumjopFXC2hHcvglgyfVpBLt5lhXnk9z20ZDvZdm5k/pub?gid=0&single=true&output=csv",
|
||||||
|
agendas: "https://docs.google.com/spreadsheets/d/e/2PACX-1vSSzLgVoqnhadH-1oUvITvjZI8UBmBCkoDma4zumjopFXC2hHcvglgyfVpBLt5lhXnk9z20ZDvZdm5k/pub?gid=529320998&single=true&output=csv",
|
||||||
|
quests: "https://docs.google.com/spreadsheets/d/e/2PACX-1vSSzLgVoqnhadH-1oUvITvjZI8UBmBCkoDma4zumjopFXC2hHcvglgyfVpBLt5lhXnk9z20ZDvZdm5k/pub?gid=57714287&single=true&output=csv",
|
||||||
|
bills: "https://docs.google.com/spreadsheets/d/e/2PACX-1vSSzLgVoqnhadH-1oUvITvjZI8UBmBCkoDma4zumjopFXC2hHcvglgyfVpBLt5lhXnk9z20ZDvZdm5k/pub?gid=1174273004&single=true&output=csv",
|
||||||
|
assets: "https://docs.google.com/spreadsheets/d/e/2PACX-1vSSzLgVoqnhadH-1oUvITvjZI8UBmBCkoDma4zumjopFXC2hHcvglgyfVpBLt5lhXnk9z20ZDvZdm5k/pub?gid=640542678&single=true&output=csv",
|
||||||
|
scenarios: "https://docs.google.com/spreadsheets/d/e/2PACX-1vSSzLgVoqnhadH-1oUvITvjZI8UBmBCkoDma4zumjopFXC2hHcvglgyfVpBLt5lhXnk9z20ZDvZdm5k/pub?gid=1732769140&single=true&output=csv"
|
||||||
|
};
|
||||||
|
|
||||||
|
const EFFECT_NAMES = { budget: "예산", black_fund: "비자금", trust: "신임도", entropy: "엔트로피", suspicion: "의심도" };
|
||||||
|
|
||||||
|
let DB_FACTIONS = []; let DB_AGENDAS = []; let DB_QUESTS = []; let DB_BILLS = []; let DB_ASSETS = []; let DB_SCENARIOS = [];
|
||||||
|
|
||||||
|
let state = {
|
||||||
|
week: 1, day: 1, budget: 500, blackFund: 20, trust: 80, entropy: 10, suspicion: 0,
|
||||||
|
deptBudgets: { "작전 본부": 150, "정보 본부": 100, "R&D 본부": 100, "행정 본부": 150 },
|
||||||
|
assets: [], activeLaws: [],
|
||||||
|
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 parseCSV(url) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (!url || url.trim() === "") { resolve(null); return; }
|
||||||
|
Papa.parse(url, {
|
||||||
|
download: true,
|
||||||
|
header: true,
|
||||||
|
skipEmptyLines: true,
|
||||||
|
complete: function(results) {
|
||||||
|
const normalizedData = results.data.map(row => {
|
||||||
|
let newRow = {};
|
||||||
|
for(let key in row) { newRow[normalizeKey(key)] = row[key]; }
|
||||||
|
return newRow;
|
||||||
|
});
|
||||||
|
resolve(normalizedData);
|
||||||
|
},
|
||||||
|
error: function(err) {
|
||||||
|
console.error("CSV 파싱 에러:", err);
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFallbackData() {
|
||||||
|
if (DB_FACTIONS.length === 0) DB_FACTIONS = [
|
||||||
|
{ id: 'iron', name: '철의 수호당', color: '#ef4444', share: 30, rival: 'libra', ideology: 0, desc: "괴수 즉결 처형 및 군사력 증강을 주장합니다." },
|
||||||
|
{ id: 'libra', name: '지식 보존당', color: '#3b82f6', share: 20, rival: 'iron', ideology: 1, desc: "괴수 생포 및 연구를 통한 이해를 중시합니다." },
|
||||||
|
{ id: 'pure', name: '순수 인간당', color: '#10b981', share: 15, rival: 'gene', ideology: 1, desc: "인간 중심주의, 이종족 및 변이를 배척합니다." },
|
||||||
|
{ id: 'gene', name: '진화 미래당', color: '#8b5cf6', share: 10, rival: 'pure', ideology: 0, desc: "신체 개조 및 초인 양성을 통한 진화를 꿈꿉니다." },
|
||||||
|
{ id: 'trade', name: '차원 무역당', color: '#f59e0b', share: 10, rival: 'covenant', ideology: 0, desc: "괴수 부산물 판매 및 이계 무역을 지지합니다." },
|
||||||
|
{ id: 'covenant', name: '고대 공존당', color: '#06b6d4', share: 5, rival: 'trade', ideology: 1, desc: "괴수를 숭배하며 평화적 공존을 모색합니다." },
|
||||||
|
{ id: 'veil', name: '침묵의 장막당', color: '#6b7280', share: 5, rival: 'reshape', ideology: 1, desc: "대중에게 진실을 숨기고 비밀을 유지합니다." },
|
||||||
|
{ id: 'reshape', name: '세계 재건당', color: '#000000', share: 5, rival: 'veil', ideology: 0, desc: "조직의 양지화 및 세계 정부 수립을 목표로 합니다." }
|
||||||
|
];
|
||||||
|
if (DB_AGENDAS.length === 0) DB_AGENDAS = [
|
||||||
|
{ id: "AG_01", title: "대토벌의 시대", desc: "작전부 안건 2배 증가 / 전투 예산 소모 +20%" },
|
||||||
|
];
|
||||||
|
if (DB_QUESTS.length === 0) DB_QUESTS = [
|
||||||
|
{ id: "Q_01", title: "작전 승인 2회", target: { type: "approve_count", dept: "작전 본부", val: 2 }, reward_txt: "예산 +$100", penalty_txt: "신임도 -10" }
|
||||||
|
];
|
||||||
|
if (DB_BILLS.length === 0) DB_BILLS = [
|
||||||
|
{ id: "B_01", name: "사살 금지법", desc: "사살 관련 안건 승인 금지", tag: "kill", type: "lock_approve", flavor: "생명은 소중합니다." },
|
||||||
|
{ id: "B_03", name: "강제 징집령", desc: "작전부 안건 거절 불가", tag: "ops", type: "lock_reject", flavor: "국가가 부릅니다." }
|
||||||
|
];
|
||||||
|
if (DB_ASSETS.length === 0) DB_ASSETS = [
|
||||||
|
{ id: "AST_001", name: "유령 커피머신", type: "Relic", desc: "커피 맛은 천상, 밤마다 악몽.", passive_effect: { budget: 5 }, holding_risk: { entropy: 1 }, sale_value: 50, sale_risk: { suspicion: 5 }, flavor: "카페인과 공포." }
|
||||||
|
];
|
||||||
|
if (DB_SCENARIOS.length === 0) DB_SCENARIOS = [
|
||||||
|
{ id: 101, act: 1, dept: "행정 본부", color: "#d97706", title: "탕비실 믹스커피 횡령", body: "야간조 요원들이 믹스커피를 횡령했습니다.", faction: "지식 보존당", rival: "iron", tags: ["audit"], conflict: "원칙 vs 융통성", cost: 0, yes_e: -1, no_e: +1, flavor: "맥심 골드는 중대 사항입니다.", reward_asset_id: "AST_001" },
|
||||||
|
{ id: 102, act: 1, dept: "작전 본부", color: "#c92a2a", title: "하급 슬라임 민원", body: "하수구 슬라임을 소각하시겠습니까?", faction: "철의 수호당", rival: "libra", tags: ["kill", "ops"], conflict: "과잉진압 vs 방치", cost: 20, yes_e: -5, no_e: +5, flavor: "소금 뿌리면 안 됩니까?" }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initDB() {
|
||||||
|
document.getElementById('loading-screen').style.display = 'flex';
|
||||||
|
document.getElementById('loading-msg').innerText = "구글 시트 6개 탭 동기화 중...";
|
||||||
|
|
||||||
|
DB_FACTIONS = []; DB_AGENDAS = []; DB_QUESTS = []; DB_BILLS = []; DB_ASSETS = []; DB_SCENARIOS = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [factions, agendas, quests, bills, assets, scenarios] = 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)
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (factions && factions.length > 0 && factions[0].id) {
|
||||||
|
DB_FACTIONS = factions.map(f => ({
|
||||||
|
...f, share: Number(f.share) || 0, ideology: Number(f.ideology) || 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (agendas && agendas.length > 0) DB_AGENDAS = agendas;
|
||||||
|
if (quests && quests.length > 0) {
|
||||||
|
DB_QUESTS = quests.map(q => ({
|
||||||
|
...q, target: { type: q.target_type, dept: q.target_dept, val: Number(q.target_val) || 0 }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (bills && bills.length > 0) DB_BILLS = bills;
|
||||||
|
if (assets && assets.length > 0) {
|
||||||
|
DB_ASSETS = assets.map(a => ({
|
||||||
|
id: a.id, name: a.name, type: a.type, desc: a.desc,
|
||||||
|
passive_effect: a.passive_eff_key && a.passive_eff_key !== '-' ? { [a.passive_eff_key]: Number(a.passive_eff_val) } : null,
|
||||||
|
holding_risk: a.risk_eff_key && a.risk_eff_key !== '-' ? { [a.risk_eff_key]: Number(a.risk_eff_val) } : null,
|
||||||
|
sale_value: Number(a.sale_value) || 0,
|
||||||
|
sale_risk: a.sale_risk_key && a.sale_risk_key !== '-' ? { [a.sale_risk_key]: Number(a.sale_risk_val) } : null,
|
||||||
|
flavor: a.flavor
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (scenarios && scenarios.length > 0 && scenarios[0].id) {
|
||||||
|
DB_SCENARIOS = scenarios.map(s => ({
|
||||||
|
...s,
|
||||||
|
act: Number(s.act) || 1, cost: Number(s.cost) || 0,
|
||||||
|
yes_e: Number(s.yes_e) || 0, no_e: Number(s.no_e) || 0,
|
||||||
|
tags: s.tags && s.tags !== '-' ? String(s.tags).split(',').map(tag => tag.trim()) : []
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(DB_FACTIONS.length === 0 || DB_SCENARIOS.length === 0) {
|
||||||
|
throw new Error("구글 시트에서 필수 데이터(Factions 또는 Scenarios)를 찾지 못했습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('loading-screen').style.display = 'none';
|
||||||
|
startGame();
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
console.error("DB Load failed:", e);
|
||||||
|
document.getElementById('loading-msg').innerHTML = `<span class="text-yellow-400">데이터 로드 실패. 내장 데이터로 시작합니다.</span><br><span class="text-[10px] text-gray-500 font-normal mt-2 block">${e.message}</span>`;
|
||||||
|
document.querySelector('.loader-spinner').style.display = 'none';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
loadFallbackData();
|
||||||
|
document.getElementById('loading-screen').style.display = 'none';
|
||||||
|
startGame();
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startGame() {
|
||||||
|
state = { ...state, week: 1, day: 1, budget: 500, blackFund: 20, trust: 80, entropy: 10, suspicion: 0, assets: [], activeLaws: [] };
|
||||||
|
document.getElementById('desk').style.display = 'flex';
|
||||||
|
document.getElementById('council').style.display = 'none';
|
||||||
|
document.getElementById('empty-state').style.display = 'none';
|
||||||
|
document.getElementById('passive-toast').className = 'passive-toast';
|
||||||
|
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 nextDay() {
|
||||||
|
if (state.day > 5) { startCouncil(); return; }
|
||||||
|
|
||||||
|
state.budget -= 10;
|
||||||
|
applyAssetPassives();
|
||||||
|
|
||||||
|
let pool = DB_SCENARIOS.filter(s => s.act === state.week);
|
||||||
|
if(pool.length === 0) pool = DB_SCENARIOS; // Fallback for acts beyond configured data
|
||||||
|
|
||||||
|
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 applyAssetPassives() {
|
||||||
|
let msg = [];
|
||||||
|
state.assets.forEach(a => {
|
||||||
|
if(a.passive_effect) {
|
||||||
|
if(a.passive_effect.budget) { state.budget += a.passive_effect.budget; msg.push(`예산+${a.passive_effect.budget}`); }
|
||||||
|
if(a.passive_effect.black_fund) { state.blackFund += a.passive_effect.black_fund; msg.push(`비자금+${a.passive_effect.black_fund}`); }
|
||||||
|
if(a.passive_effect.suspicion) { state.suspicion += a.passive_effect.suspicion; msg.push(`의심+${a.passive_effect.suspicion}`); }
|
||||||
|
}
|
||||||
|
if(a.holding_risk) {
|
||||||
|
if(a.holding_risk.entropy) { state.entropy += a.holding_risk.entropy; msg.push(`엔트로피+${a.holding_risk.entropy}`); }
|
||||||
|
if(a.holding_risk.trust) { state.trust += a.holding_risk.trust; msg.push(`신임도${a.holding_risk.trust}`); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(msg.length > 0) {
|
||||||
|
const toast = document.getElementById('passive-toast');
|
||||||
|
toast.innerText = "자산 효과: " + msg.join(', ');
|
||||||
|
toast.classList.remove('active'); 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.boxShadow = "0 10px 25px rgba(0,0,0,0.6)";
|
||||||
|
|
||||||
|
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 || '무소속';
|
||||||
|
document.getElementById('c-sender').innerText = data.dept || '발신자 불명';
|
||||||
|
document.getElementById('c-conflict').innerText = data.conflict || '선택';
|
||||||
|
|
||||||
|
const badge = document.getElementById('c-asset-badge');
|
||||||
|
if (data.reward_asset_id && data.reward_asset_id !== '-') {
|
||||||
|
const asset = DB_ASSETS.find(a => a.id === data.reward_asset_id);
|
||||||
|
badge.style.display = 'inline-block'; badge.innerText = `📦 ${asset ? asset.name : '자산'} 획득`;
|
||||||
|
} else { badge.style.display = 'none'; }
|
||||||
|
|
||||||
|
let lockType = data.lock_type || "none"; let lockReason = data.warning;
|
||||||
|
if(data.tags && Array.isArray(data.tags)) {
|
||||||
|
state.activeLaws.forEach(bill => {
|
||||||
|
if(data.tags.includes(bill.tag)) {
|
||||||
|
if(bill.type === "lock_approve") { lockType = "approve"; lockReason = bill.name; }
|
||||||
|
if(bill.type === "lock_reject") { lockType = "reject"; lockReason = bill.name; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
state.lockState = lockType;
|
||||||
|
const overlay = document.getElementById('lock-overlay');
|
||||||
|
const msg = document.getElementById('lock-type-text');
|
||||||
|
const reason = document.getElementById('lock-reason');
|
||||||
|
const warning = document.getElementById('c-warning');
|
||||||
|
|
||||||
|
if (lockType === 'approve') {
|
||||||
|
overlay.classList.add('active');
|
||||||
|
msg.innerText = "승인 금지"; reason.innerText = lockReason ? `사유: ${lockReason}` : "법안 위반";
|
||||||
|
warning.style.display = 'block'; warning.innerText = "승인 금지";
|
||||||
|
document.getElementById('lock-overlay-msg').className = "lock-msg right";
|
||||||
|
} else if (lockType === 'reject') {
|
||||||
|
overlay.classList.add('active');
|
||||||
|
msg.innerText = "거절 금지"; reason.innerText = lockReason ? `사유: ${lockReason}` : "강제 집행";
|
||||||
|
warning.style.display = 'block'; warning.innerText = "거절 불가";
|
||||||
|
document.getElementById('lock-overlay-msg').className = "lock-msg left";
|
||||||
|
} else {
|
||||||
|
overlay.classList.remove('active'); warning.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
const dBudget = state.deptBudgets[data.dept] || 0;
|
||||||
|
const valEl = document.getElementById('val-dept');
|
||||||
|
valEl.innerText = `$${dBudget}`;
|
||||||
|
if (dBudget < (data.cost || 0)) valEl.classList.add('deficit'); else valEl.classList.remove('deficit');
|
||||||
|
document.getElementById('dept-budget-box').style.borderColor = data.color || '#444';
|
||||||
|
|
||||||
|
// Reset Stamp
|
||||||
|
document.getElementById('stamp-approve').style.opacity = 0;
|
||||||
|
document.getElementById('stamp-reject').style.opacity = 0;
|
||||||
|
document.getElementById('predict-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHUD() {
|
||||||
|
document.getElementById('week-num').innerText = state.week;
|
||||||
|
document.getElementById('day-num').innerText = state.day;
|
||||||
|
if(state.currentAgenda) document.getElementById('agenda-title').innerText = state.currentAgenda.title;
|
||||||
|
if(state.currentQuest) {
|
||||||
|
const qTarget = state.currentQuest.target.val;
|
||||||
|
document.getElementById('quest-status').innerText = `${state.currentQuest.title} (${state.questProgress}/${qTarget})`;
|
||||||
|
}
|
||||||
|
document.getElementById('val-total').innerText = `$${state.budget}`;
|
||||||
|
document.getElementById('val-black').innerText = `$${state.blackFund}`;
|
||||||
|
document.getElementById('val-suspicion').innerText = `${state.suspicion}%`;
|
||||||
|
document.getElementById('bar-trust').style.width = `${state.trust}%`;
|
||||||
|
document.getElementById('txt-trust').innerText = `${state.trust}%`;
|
||||||
|
const deg = 270 + (state.entropy / 100) * 90;
|
||||||
|
document.getElementById('hand-entropy').style.transform = `rotate(${deg}deg)`;
|
||||||
|
|
||||||
|
const barContainer = document.getElementById('bar-share');
|
||||||
|
if(barContainer) {
|
||||||
|
barContainer.innerHTML = '';
|
||||||
|
const listContainer = document.getElementById('faction-list-detail');
|
||||||
|
if(listContainer) listContainer.innerHTML = '';
|
||||||
|
|
||||||
|
DB_FACTIONS.sort((a,b) => b.share - a.share).forEach(f => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'share-segment'; div.style.width = `${f.share}%`; div.style.backgroundColor = f.color;
|
||||||
|
barContainer.appendChild(div);
|
||||||
|
|
||||||
|
if(listContainer) {
|
||||||
|
listContainer.innerHTML += `
|
||||||
|
<div class="bg-gray-800 p-2 rounded mb-2 text-xs">
|
||||||
|
<div class="flex justify-between font-bold mb-1"><span style="color:${f.color}">${f.name}</span><span>${f.share}%</span></div>
|
||||||
|
<div class="text-gray-400 text-[10px]">이념: ${f.ideology === 0 ? '급진' : '보수'} | 라이벌: ${DB_FACTIONS.find(r=>r.id===f.rival)?.name || '없음'}</div>
|
||||||
|
<div class="text-gray-500 mt-1 italic">"${f.desc}"</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const top = DB_FACTIONS[0];
|
||||||
|
const topEl = document.getElementById('top-faction');
|
||||||
|
if(topEl && top) { topEl.innerText = top.name; topEl.style.color = top.color; }
|
||||||
|
document.getElementById('asset-count').innerText = state.assets.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const card = document.getElementById('current-card');
|
||||||
|
let startX = 0, currentX = 0, isDragging = false, pressTimer = null;
|
||||||
|
|
||||||
|
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;
|
||||||
|
if(state.lockState !== 'none') {
|
||||||
|
card.style.transform = "scale(0.98)";
|
||||||
|
pressTimer = setTimeout(() => {
|
||||||
|
if(Math.abs(currentX) < 10) {
|
||||||
|
state.lockState = 'none';
|
||||||
|
document.getElementById('lock-overlay').classList.remove('active');
|
||||||
|
card.style.transform = "scale(1.05)";
|
||||||
|
if(navigator.vibrate) navigator.vibrate(200);
|
||||||
|
}
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveDrag(e) {
|
||||||
|
if(!isDragging) return;
|
||||||
|
let x = (e.type==='touchmove')?e.touches[0].clientX:e.clientX;
|
||||||
|
currentX = x - startX;
|
||||||
|
|
||||||
|
const lawPopup = document.getElementById('law-popup');
|
||||||
|
const reason = document.getElementById('lock-reason').innerText;
|
||||||
|
|
||||||
|
if((state.lockState === 'approve' && currentX > 20) || (state.lockState === 'reject' && currentX < -20)) {
|
||||||
|
lawPopup.classList.add('visible');
|
||||||
|
document.getElementById('law-popup-title').innerText = state.lockState === 'approve' ? "🚫 승인 금지" : "🚫 거절 불가";
|
||||||
|
document.getElementById('law-popup-desc').innerText = reason;
|
||||||
|
} else lawPopup.classList.remove('visible');
|
||||||
|
|
||||||
|
if(state.lockState === 'approve' && currentX > 0) currentX *= 0.1;
|
||||||
|
if(state.lockState === 'reject' && currentX < 0) currentX *= 0.1;
|
||||||
|
|
||||||
|
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) > 50) showPrediction(currentX > 0);
|
||||||
|
else document.getElementById('predict-overlay').style.display = 'none';
|
||||||
|
|
||||||
|
if (currentX > 0) {
|
||||||
|
document.getElementById('stamp-approve').style.opacity = Math.min(currentX / 100, 1);
|
||||||
|
document.getElementById('stamp-reject').style.opacity = 0;
|
||||||
|
} else {
|
||||||
|
document.getElementById('stamp-reject').style.opacity = Math.min(Math.abs(currentX) / 100, 1);
|
||||||
|
document.getElementById('stamp-approve').style.opacity = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
card.style.transform = `translateX(${currentX}px) rotate(0deg)`;
|
||||||
|
}
|
||||||
|
if (Math.abs(currentX) > 10) clearTimeout(pressTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function endDrag() {
|
||||||
|
isDragging = false; clearTimeout(pressTimer);
|
||||||
|
document.getElementById('predict-overlay').style.display = 'none';
|
||||||
|
document.getElementById('law-popup').classList.remove('visible');
|
||||||
|
card.style.borderColor = "#cbb";
|
||||||
|
document.getElementById('stamp-approve').style.opacity = 0;
|
||||||
|
document.getElementById('stamp-reject').style.opacity = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPrediction(isApprove) {
|
||||||
|
const p = document.getElementById('predict-overlay');
|
||||||
|
p.style.display = 'flex';
|
||||||
|
p.style.zIndex = "100";
|
||||||
|
const data = state.currentCard;
|
||||||
|
if(!data) return;
|
||||||
|
|
||||||
|
const fac = DB_FACTIONS.find(f => f.name === data.faction) || { name: data.faction || '무소속', color: "#aaa" };
|
||||||
|
const rivalName = fac.rival ? DB_FACTIONS.find(r => r.id === fac.rival)?.name : "라이벌";
|
||||||
|
const rivalColor = fac.rival ? DB_FACTIONS.find(r => r.id === fac.rival)?.color : "#aaa";
|
||||||
|
|
||||||
|
const header = document.getElementById('p-header');
|
||||||
|
const desc = document.getElementById('p-desc');
|
||||||
|
const eVal = isApprove ? data.yes_e : data.no_e;
|
||||||
|
let eHtml = eVal > 0 ? `<span class="p-down">▲${eVal}</span>` : `<span class="p-up">▼${Math.abs(eVal)}</span>`;
|
||||||
|
|
||||||
|
if (isApprove) {
|
||||||
|
header.innerText = "승인 (APPROVE)"; header.className = "font-bold border-b border-gray-600 mb-2 pb-1 text-center text-red-400";
|
||||||
|
desc.innerText = "결재 승인";
|
||||||
|
document.getElementById('p-budget').innerHTML = `<span class="p-down">-$${data.cost || 0}</span>`;
|
||||||
|
document.getElementById('p-entropy').innerHTML = eHtml;
|
||||||
|
document.getElementById('p-trust').innerHTML = state.lockState === 'approve' ? `<span class="p-down">-20 (강행)</span>` : `-`;
|
||||||
|
document.getElementById('p-faction-gain').innerHTML = `<span style="color:${fac.color}">${fac.name} ▲</span>`;
|
||||||
|
document.getElementById('p-faction-loss').innerHTML = `<span style="color:${rivalColor}">${rivalName} ▼</span>`;
|
||||||
|
if (data.reward_asset_id && data.reward_asset_id !== '-') {
|
||||||
|
const asset = DB_ASSETS.find(a => a.id === data.reward_asset_id);
|
||||||
|
document.getElementById('p-asset-gain').innerHTML = `<span class="text-green-400 text-[9px]">+ ${asset ? asset.name : '자산'}</span>`;
|
||||||
|
} else { document.getElementById('p-asset-gain').innerHTML = ""; }
|
||||||
|
} else {
|
||||||
|
header.innerText = "거절 (REJECT)"; header.className = "font-bold border-b border-gray-600 mb-2 pb-1 text-center text-blue-400";
|
||||||
|
desc.innerText = "결재 반려";
|
||||||
|
document.getElementById('p-budget').innerHTML = `-`;
|
||||||
|
document.getElementById('p-entropy').innerHTML = eHtml;
|
||||||
|
document.getElementById('p-trust').innerHTML = state.lockState === 'reject' ? `<span class="p-down">-20 (거부권)</span>` : `<span class="p-up">+2</span>`;
|
||||||
|
document.getElementById('p-faction-gain').innerHTML = "";
|
||||||
|
document.getElementById('p-faction-loss').innerHTML = `<span style="color:${rivalColor}">${rivalName} ▲ (반사익)</span>`;
|
||||||
|
document.getElementById('p-asset-gain').innerHTML = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processDecision(type) {
|
||||||
|
const data = state.currentCard;
|
||||||
|
const fac = DB_FACTIONS.find(f => f.name === data.faction);
|
||||||
|
const rival = fac ? DB_FACTIONS.find(f => f.id === fac.rival) : null;
|
||||||
|
let animateDir = type === 'approve' ? 500 : -500;
|
||||||
|
|
||||||
|
if ((type === 'approve' && data.lock_type === 'approve') || (type === 'reject' && data.lock_type === 'reject')) { state.trust -= 20; }
|
||||||
|
|
||||||
|
if (type === 'approve') {
|
||||||
|
state.budget -= (data.cost || 0);
|
||||||
|
if(state.deptBudgets[data.dept]) state.deptBudgets[data.dept] -= (data.cost || 0);
|
||||||
|
state.entropy += (data.yes_e || 0);
|
||||||
|
if(fac) fac.share += 2;
|
||||||
|
if(rival) rival.share -= 1;
|
||||||
|
|
||||||
|
if (state.currentQuest && state.currentQuest.target.dept === data.dept) state.questProgress++;
|
||||||
|
if (data.reward_asset_id && data.reward_asset_id !== '-') {
|
||||||
|
const assetData = DB_ASSETS.find(a => a.id === data.reward_asset_id);
|
||||||
|
if (assetData) addAsset(assetData);
|
||||||
|
}
|
||||||
|
const tube = document.getElementById('dividend-tube');
|
||||||
|
const b = document.createElement('div'); b.className = 'dividend-block'; b.style.backgroundColor = data.color || '#fff';
|
||||||
|
tube.appendChild(b);
|
||||||
|
} else {
|
||||||
|
state.entropy += (data.no_e || 0);
|
||||||
|
state.trust += 2;
|
||||||
|
if(rival) rival.share += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 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');
|
||||||
|
|
||||||
|
const top = DB_FACTIONS[0];
|
||||||
|
document.getElementById('ruling-faction').innerText = top.name;
|
||||||
|
document.getElementById('ruling-faction').style.color = top.color;
|
||||||
|
|
||||||
|
document.getElementById('next-agenda-title').innerText = state.currentAgenda.title;
|
||||||
|
document.getElementById('next-agenda-desc').innerText = state.currentAgenda.desc;
|
||||||
|
document.getElementById('next-quest-title').innerText = state.currentQuest.title;
|
||||||
|
document.getElementById('next-quest-reward').innerText = state.currentQuest.reward_txt;
|
||||||
|
document.getElementById('next-quest-penalty').innerText = state.currentQuest.penalty_txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startVotePhase() {
|
||||||
|
document.getElementById('council-phase-1').classList.remove('active');
|
||||||
|
document.getElementById('council-phase-2').classList.add('active');
|
||||||
|
|
||||||
|
let b1 = DB_BILLS[0]; let b2 = DB_BILLS[1] || DB_BILLS[0];
|
||||||
|
if (DB_BILLS.length > 1) {
|
||||||
|
let i = Math.floor(Math.random()*DB_BILLS.length);
|
||||||
|
let j = Math.floor(Math.random()*DB_BILLS.length);
|
||||||
|
while(i===j) j = Math.floor(Math.random()*DB_BILLS.length);
|
||||||
|
b1 = DB_BILLS[i]; b2 = DB_BILLS[j];
|
||||||
|
}
|
||||||
|
state.council.billA = b1; state.council.billB = b2;
|
||||||
|
|
||||||
|
const top = DB_FACTIONS[0]; const rival = DB_FACTIONS[1];
|
||||||
|
document.getElementById('bill-a-faction').innerText = top.name;
|
||||||
|
document.getElementById('bill-b-faction').innerText = rival.name;
|
||||||
|
document.getElementById('bill-a-name').innerText = state.council.billA.name;
|
||||||
|
document.getElementById('bill-a-flavor').innerText = `"${state.council.billA.flavor}"`;
|
||||||
|
document.getElementById('bill-b-name').innerText = state.council.billB.name;
|
||||||
|
document.getElementById('bill-b-flavor').innerText = `"${state.council.billB.flavor}"`;
|
||||||
|
|
||||||
|
let supportersA = [], supportersB = [], swingers = [];
|
||||||
|
let votesA = 0;
|
||||||
|
|
||||||
|
DB_FACTIONS.forEach(f => {
|
||||||
|
if(f.id === top.id) { supportersA.push(f.name); votesA += f.share; }
|
||||||
|
else if(f.id === rival.id) { supportersB.push(f.name); }
|
||||||
|
else { swingers.push(f); }
|
||||||
|
});
|
||||||
|
|
||||||
|
state.council.supportersA = supportersA;
|
||||||
|
state.council.supportersB = supportersB;
|
||||||
|
state.council.votesA = votesA;
|
||||||
|
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>`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function lobbyFaction(fid, e) {
|
||||||
|
if(state.blackFund < 10) { alert("비자금 부족"); return; }
|
||||||
|
state.blackFund -= 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 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.join(', ');
|
||||||
|
document.getElementById('list-support-b').innerText = 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAsset(a) { state.assets.push(a); }
|
||||||
|
function renderAssets() {
|
||||||
|
const list = document.getElementById('asset-list'); list.innerHTML = "";
|
||||||
|
state.assets.forEach((a, idx) => {
|
||||||
|
let pass = "";
|
||||||
|
if(a.passive_effect) { const k = Object.keys(a.passive_effect)[0]; pass = `[매턴 ${EFFECT_NAMES[k] || k} ${a.passive_effect[k]}]`; }
|
||||||
|
list.innerHTML += `<div class="list-item"><div class="flex-1"><span class="font-bold block">${a.name}</span><span class="text-[9px] text-gray-400">${a.desc} <span class="text-green-400">${pass}</span></span></div><button class="bg-green-700 px-2 py-1 rounded text-xs text-white ml-2 flex-shrink-0" onclick="sellAsset(${idx})">판매 (+$${a.sale_value})</button></div>`;
|
||||||
|
});
|
||||||
|
if(state.assets.length===0) list.innerHTML = "<div class='text-center text-xs text-gray-500'>없음</div>";
|
||||||
|
}
|
||||||
|
function sellAsset(idx) {
|
||||||
|
const a = state.assets[idx]; state.budget += a.sale_value;
|
||||||
|
if(a.sale_risk && a.sale_risk.suspicion) state.suspicion += a.sale_risk.suspicion;
|
||||||
|
state.assets.splice(idx, 1); renderAssets(); updateHUD();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.openModal = (id) => {
|
||||||
|
document.getElementById(id).classList.add('active');
|
||||||
|
if(id==='asset-modal') renderAssets();
|
||||||
|
if(id==='law-modal') {
|
||||||
|
document.getElementById('modal-agenda').innerText = state.currentAgenda?.title || "";
|
||||||
|
document.getElementById('modal-agenda-desc').innerText = state.currentAgenda?.desc || "";
|
||||||
|
document.getElementById('modal-quest').innerText = state.currentQuest?.title || "";
|
||||||
|
document.getElementById('quest-reward').innerText = state.currentQuest?.reward_txt || "";
|
||||||
|
document.getElementById('quest-penalty').innerText = state.currentQuest?.penalty_txt || "";
|
||||||
|
document.getElementById('modal-laws').innerHTML = state.activeLaws.map(l => `<li>${l.name}: ${l.desc}</li>`).join('');
|
||||||
|
}
|
||||||
|
if(id==='faction-modal') updateHUD();
|
||||||
|
}
|
||||||
|
window.closeModal = (id) => document.getElementById(id).classList.remove('active');
|
||||||
|
window.sellAsset = sellAsset;
|
||||||
|
|
||||||
|
// 앱 시작 지점
|
||||||
|
initDB();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user