Files
ProjectSS/client/src/core/components/CouncilScreen.css
T
kyoung5seo cde44c4f2c 2차 구현 완료 (§17: 상점·규약·실적 감사·명분) - 플레이는 해봤지만 피드백은 아직 정리 안함
라운드 구조(결재 8건 → 상점 → 의회 → 다음 라운드)를 얹고, 신규 파일들로 구현했습니다. Playwright로 라운드 전체를 실제로 돌려 확인 완료(콘솔 에러 0건).

만든 것
엔진 (core/engine/)

council.js — §7 명분 산출/개입, §9.2 규약(유형A 5종 카탈로그 + 유형C 징벌 2종), §9.3 실적 요구치 공식
shop.js — §8.3 상점 진열/가격/리롤
UI

ShopScreen — 오염 카드 직접 소각(토큰), 진열 4~6개 무작위, 리롤 1회
CouncilScreen — 실적 감사(토큰·명분 납입으로 요구치 충족) + 규약 후보 3개 + 명분 개입(절차/여론/위기/내부/혼합)
DeskBar/CombinePanel — 징벌 규약의 게이지·토큰 가산 반영, 활성 규약이 막은 족보 표시
라운드 루프: CoreGameScreen이 결재 8건마다 자동으로 상점→의회로 전환하고, 의회 결의 시 실적 판정→징벌 부과/해제→규약 표결→다음 라운드로 넘어갑니다. 4대 게이지 100% 도달 시 게임오버 화면도 추가했습니다(엔딩 분기는 4차라 텍스트만).

검증 (Playwright)
8회 스와이프 → 상점 화면 전환 → 의회 화면(요구실적 25, 후보 3개) → 결의 확정 → 2라운드 진입, 상단에 ⚖ 통과된 규약명 표시 → 합본 패널 열면 해당 규약이 칩으로 뜨고 filterHandsByProtocols가 막는 족보를 실제로 걸러냄. 스크린샷 다수 확인, 콘솔 에러 0건. 상점 아이템 버튼 텍스트가 화면 밖으로 살짝 잘리는 버그도 발견해서 즉시 수정했습니다.

의도적으로 축소한 부분 (문서에 근거 있음)
상점은 "오염 카드 직접 처리" 한 품목뿐 — 자산 구매(§10)·소모품(§12)·전례 매입(§11)은 전부 3차 항목이라 카탈로그 자체가 없습니다. §17 로드맵 순서를 지키기 위한 의도적 축소입니다.
규약 표결은 가중 무작위 — 13인 위원회의 실제 우호도·이중첩자·탄핵(§9.4~9.6)은 4차 범위. 지금은 후보 3개 중 하나를 무작위로 "통과"시킵니다.
혼합 명분("표 뒤집기")은 문자 그대로 구현할 표가 없어서, "후보 하나를 직접 통과 지정"으로 대체했습니다 — 최종 효과(원하는 규약 통과)는 같지만 4차에서 실제 위원회가 생기면 반드시 재설계해야 할 자리표시자로 코드에 표시해 뒀습니다.
§7.3 이념색 페널티(파벌 우호도 하락)는 스킵 — 8대 상임이사회 우호도 추적 자체가 4차 항목이라 지금은 걸 게 없습니다.
라운드당 결재 수 8건, 상점/징벌 가격 수치는 전부 가안 — 플레이해보고 조정 대상입니다.
2026-09-05 01:39:55 +09:00

213 lines
3.7 KiB
CSS

.council-screen {
flex: 1;
display: flex;
flex-direction: column;
gap: 16px;
padding: 20px;
max-width: 560px;
margin: 0 auto;
width: 100%;
overflow-y: auto;
font-family: var(--font-ui);
}
.council-screen h2 {
font-family: var(--font-doc-title);
margin: 0;
}
.council-screen h3 {
font-size: 13px;
margin: 0 0 8px;
opacity: 0.75;
font-family: var(--font-typewriter);
}
.council-audit {
background: var(--color-surface);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 14px;
}
.council-audit-row {
display: flex;
justify-content: space-between;
font-family: var(--font-typewriter);
font-size: 14px;
font-weight: 700;
}
.audit-met { color: var(--color-token-budget); }
.audit-short { color: var(--color-risk-critical); }
.council-audit-breakdown {
font-size: 11px;
opacity: 0.6;
margin: 6px 0 12px;
}
.council-token-pay {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.council-token-row {
display: flex;
align-items: center;
gap: 6px;
font-family: var(--font-typewriter);
font-size: 12px;
background: rgba(255, 255, 255, 0.04);
padding: 4px 8px;
border-radius: 5px;
}
.council-token-row button {
background: rgba(255, 255, 255, 0.1);
border: none;
color: var(--color-text);
width: 20px;
height: 20px;
border-radius: 4px;
cursor: pointer;
}
.council-token-row button:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.council-token-committed {
min-width: 14px;
text-align: center;
color: var(--color-token-budget);
}
.council-penalties {
display: flex;
flex-direction: column;
gap: 6px;
}
.council-penalty-chip {
font-size: 12px;
padding: 6px 10px;
background: rgba(255, 68, 68, 0.08);
border: 1px solid rgba(255, 68, 68, 0.25);
border-radius: 5px;
color: var(--color-risk-critical);
}
.council-penalty-chip.targetable {
cursor: pointer;
outline: 1px dashed var(--color-risk-critical);
}
.council-justifications {
display: flex;
flex-direction: column;
gap: 8px;
}
.council-empty {
font-size: 12px;
opacity: 0.5;
}
.council-justification-item {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
background: rgba(255, 255, 255, 0.03);
padding: 8px;
border-radius: 6px;
}
.justification-badge {
font-family: var(--font-typewriter);
font-size: 12px;
font-weight: 700;
padding: 2px 8px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.08);
}
.council-justification-item button {
font-family: var(--font-typewriter);
font-size: 11px;
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.2);
color: var(--color-text);
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
}
.council-justification-item button:hover {
border-color: var(--color-token-budget);
}
.council-pending-hint {
font-size: 11px;
color: var(--color-risk-critical);
}
.council-candidates {
display: flex;
flex-direction: column;
gap: 8px;
}
.council-candidate {
background: var(--color-surface);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
padding: 10px 12px;
}
.council-candidate.targetable {
cursor: pointer;
outline: 1px dashed var(--color-token-info);
}
.council-candidate.removed {
opacity: 0.4;
text-decoration: line-through;
}
.council-candidate.forced {
border-color: var(--color-token-budget);
}
.candidate-name {
font-size: 13px;
font-weight: 700;
}
.candidate-flavor {
font-size: 11px;
opacity: 0.6;
margin-top: 3px;
}
.council-deferred-note {
font-size: 12px;
opacity: 0.7;
}
.council-confirm-btn {
font-family: var(--font-typewriter);
background: var(--color-token-info);
color: #fff;
border: none;
border-radius: 6px;
padding: 12px;
font-weight: 700;
cursor: pointer;
font-size: 14px;
margin-top: 4px;
}