diff --git a/client/index.html b/client/index.html index 87e20c9..df5efdd 100644 --- a/client/index.html +++ b/client/index.html @@ -2,9 +2,14 @@ - + + - projectss + + + + Project SS — 종말을 결재하시겠습니까?
diff --git a/client/package.json b/client/package.json index e58c87c..fbe4e6b 100644 --- a/client/package.json +++ b/client/package.json @@ -7,7 +7,9 @@ "dev": "vite", "build": "vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "deploy": "bash ../deploy.sh", + "deploy:dry": "bash ../deploy.sh --dry-run" }, "dependencies": { "framer-motion": "^12.40.0", diff --git a/client/src/core/components/CombinePanel.css b/client/src/core/components/CombinePanel.css index de6338f..ac85c07 100644 --- a/client/src/core/components/CombinePanel.css +++ b/client/src/core/components/CombinePanel.css @@ -104,7 +104,7 @@ .combine-card.selected { opacity: 1; - outline: 2px solid var(--color-token-budget); + outline: 2px solid var(--color-coin); border-radius: 6px; } @@ -148,7 +148,7 @@ } .combine-match:hover { - border-color: var(--color-token-budget); + border-color: var(--color-coin); } .match-top { @@ -162,7 +162,7 @@ font-weight: 700; } -.match-score { color: var(--color-token-budget); } +.match-score { color: var(--color-coin); } .match-detail { font-size: 11px; diff --git a/client/src/core/components/CombinePanel.jsx b/client/src/core/components/CombinePanel.jsx index ffe00df..9fb2bb6 100644 --- a/client/src/core/components/CombinePanel.jsx +++ b/client/src/core/components/CombinePanel.jsx @@ -10,7 +10,7 @@ import './CombinePanel.css'; // 명분 종류를 고르기 위해 일부러 하위 족보를 쓰는 선택이 존재해야 하기 때문이다. // // 2026-09-15 결정 반영: 선택은 최대 5장(§5.1.1). 족보에 실제로 쓰이는 카드만 소멸하므로, -// 각 족보 후보가 어떤 카드를 가져가고 토큰이 얼마 나오는지(등급 합계, §8.2)를 미리 보여준다. +// 각 족보 후보가 어떤 카드를 가져가고 코인이 얼마 나오는지(등급 합계, §8.2)를 미리 보여준다. export default function CombinePanel({ deskCards, onConfirm, onClose, activeProtocols = [] }) { const [selectedIds, setSelectedIds] = useState([]); @@ -93,7 +93,7 @@ export default function CombinePanel({ deskCards, onConfirm, onClose, activeProt {matches.map((patternId) => { const preview = resolveCombine(selection, patternId); if (!preview) return null; - const { pattern, usedCards, tokens, justification, performanceScore } = preview; + const { pattern, usedCards, coins, justification, performanceScore } = preview; return ( - {tokensPaid} - - 납입 1개 = 실적 5 +
+ 코인 {coins - coinsPaid} 보유 + + {coinsPaid} + + 납입 1개 = 실적 5
diff --git a/client/src/core/components/DeskBar.css b/client/src/core/components/DeskBar.css index a52ac07..3369c23 100644 --- a/client/src/core/components/DeskBar.css +++ b/client/src/core/components/DeskBar.css @@ -8,10 +8,6 @@ font-family: var(--font-typewriter); } -/* 경고/한도초과(bust) 구간을 테두리로 알린다 (§4.2-B) — bust는 실제로 라운드를 강제 종료시킨다. */ -.desk-bar.zone-warning { border-bottom-color: var(--color-risk-critical); } -.desk-bar.zone-bust { border-bottom-color: var(--color-entropy-critical); } - .desk-bar-top { display: flex; align-items: center; @@ -29,24 +25,8 @@ font-size: 14px; } -.zone-warning .desk-attention { color: var(--color-risk-critical); } -.zone-bust .desk-attention { color: var(--color-entropy-critical); } - .desk-surcharge { color: var(--color-risk-critical); } -.desk-tokens { opacity: 0.8; } - -.desk-zone-banner { - font-size: 11px; - padding: 4px 8px; - border-radius: 4px; - background: rgba(255, 215, 0, 0.1); - color: var(--color-risk-critical); -} - -.zone-bust .desk-zone-banner { - background: rgba(139, 0, 0, 0.18); - color: #ff8a80; -} +.desk-coins { opacity: 0.8; } .desk-bar-actions { display: flex; @@ -71,7 +51,7 @@ } .desk-combine-btn { - background: var(--color-token-budget); + background: var(--color-coin); color: #0a0a0a; border-color: transparent; } @@ -167,7 +147,3 @@ opacity: 1; } -.desk-carried { - opacity: 0.55; - font-size: 11px; -} diff --git a/client/src/core/components/DeskBar.jsx b/client/src/core/components/DeskBar.jsx index fe46ffb..49fd5b3 100644 --- a/client/src/core/components/DeskBar.jsx +++ b/client/src/core/components/DeskBar.jsx @@ -1,53 +1,40 @@ import React, { useState } from 'react'; +import { Coins } from 'lucide-react'; import { AXES, AXIS_META } from '../engine/symbols'; import { - totalCount, totalAttention, roundAttention, attentionByAxis, attentionZone, tokenSurcharge, - gaugeRiseRateByAxis, + totalCount, totalAttention, attentionByAxis, coinSurcharge, gaugeRiseRateByAxis, } from '../engine/desk'; import { AxisIcon } from './SymbolBadge'; import DeskDetailPopup from './DeskDetailPopup'; import './DeskBar.css'; // 책상 상태 상시 노출 (§3.4) — "이 표시가 없으면 합본 설계가 불가능하며 스와이프는 무의미한 도박이 된다." -// 2026-09-16 결정 반영: 페널티 기준이 등급 합계(주목도)이고, 이제 주목도가 **이번 라운드의 -// 한도(attentionCap)를 넘으면 그 자리에서 라운드가 강제 종료**된다(§4.2-B, 결정 노트 -// 2026-09-16-라운드-주목도-하드캡-복원). 그래서 총 주목도뿐 아니라 한도까지 항상 보여야 -// "한 장 더 받을지, 지금 정산할지" 판단이 선다. +// 2026-09-16 결정 반영: 페널티 기준이 등급 합계(주목도)다. // -// 2026-09-17 수정: (1) "정산하기" 버튼은 결재 카드 아래(CoreSwipeCard)로 옮겼다 — 툴바 구석에 -// 있으면 핵심 결정 버튼인 걸 모른다는 피드백. (2) 한도 판정에 쓰는 주목도는 **이번 라운드분** -// 이다. 책상 전체 주목도는 이월 카드까지 포함해 게이지·토큰 가산에 계속 작용하므로 둘 다 보여준다. +// 2026-09-18 결정(역할 분리): 이번 라운드 접수량/한도("카드를 더 받을지" 판단)는 결재 화면 +// (CoreSwipeCard)이 전담한다. 여기 DeskBar는 **책상에 지금 쌓여 있는 것**(이월 포함 총 +// 주목도, 축별 분포, 게이지·지출 가산)만 보여준다 — "무엇을 합본할 수 있는가"에 필요한 정보다. const MAX_GRADE_CHIPS = 6; export default function DeskBar({ - deskCards, gauges, tokens, round, attentionCap, onOpenCombine, onOpenGuide, - gaugeExtraFlat = 0, tokenExtraFlat = 0, + deskCards, gauges, coins, onOpenCombine, onOpenGuide, + gaugeExtraFlat = 0, coinExtraFlat = 0, }) { const [detailAxis, setDetailAxis] = useState(null); const total = totalCount(deskCards); const deskAttention = totalAttention(deskCards); - const current = roundAttention(deskCards, round); - const carried = deskAttention - current; const perAxis = attentionByAxis(deskCards); const rates = gaugeRiseRateByAxis(deskCards, gaugeExtraFlat); - const surcharge = tokenSurcharge(deskCards, tokenExtraFlat); - const zone = attentionZone(deskCards, attentionCap, round); - - const zoneLabel = { - normal: null, - warning: `한도 근접 (${attentionCap - 5}~${attentionCap}) — 정산할지 고려하십시오`, - bust: `한도 초과! (${attentionCap}+) — 이번 라운드는 강제 종료됩니다`, - }[zone]; + const surcharge = coinSurcharge(deskCards, coinExtraFlat); return ( -
+
- 책상 {total}장 · 이번 라운드 {current}/{attentionCap} - {carried > 0 && (이월 {carried})} + 책상 {total}장 · 주목도 {deskAttention} {surcharge > 0 && · 지출 가산 +{surcharge}} - · 토큰 {tokens} + · {coins}
@@ -57,8 +44,6 @@ export default function DeskBar({
- {zoneLabel &&
{zoneLabel}
} -
{AXES.map((axis) => { const cards = deskCards.filter((c) => c.axis === axis); diff --git a/client/src/core/components/HandGuidePopup.css b/client/src/core/components/HandGuidePopup.css index 23640f1..2de7253 100644 --- a/client/src/core/components/HandGuidePopup.css +++ b/client/src/core/components/HandGuidePopup.css @@ -47,7 +47,7 @@ } .guide-hint strong { - color: var(--color-token-budget); + color: var(--color-coin); } .guide-list { @@ -75,7 +75,7 @@ flex: 1; } -.guide-score { font-size: 12px; color: var(--color-token-budget); } +.guide-score { font-size: 12px; color: var(--color-coin); } .guide-justification { font-size: 12px; opacity: 0.65; } .guide-condition { diff --git a/client/src/core/components/HandGuidePopup.jsx b/client/src/core/components/HandGuidePopup.jsx index 07522d1..9622ab2 100644 --- a/client/src/core/components/HandGuidePopup.jsx +++ b/client/src/core/components/HandGuidePopup.jsx @@ -34,7 +34,7 @@ export default function HandGuidePopup({ onClose }) { "같은 등급"은 트럼프의 같은 숫자, "같은 축"은 같은 무늬와 같다. 한 번에 {' '}{MIN_COMBINE_CARDS}~{MAX_COMBINE_CARDS}장을 선택하면 성립하는 족보가 자동 판정되며, 족보에 실제로 쓰인 카드만 소멸한다 (나머지는 책상에 남는다). - 토큰은 사용된 카드의 등급 합계만큼 나오고, 등급 {TOP_SECRET_THRESHOLD} 이상 + 코인은 사용된 카드의 등급 합계만큼 나오고, 등급 {TOP_SECRET_THRESHOLD} 이상 카드가 끼면 명분이 +1 된다.

diff --git a/client/src/core/components/InfoTooltip.css b/client/src/core/components/InfoTooltip.css index 626813e..6e51242 100644 --- a/client/src/core/components/InfoTooltip.css +++ b/client/src/core/components/InfoTooltip.css @@ -33,7 +33,7 @@ .info-tip-trigger.tone-good { background: rgba(76, 175, 80, 0.12); border-color: rgba(76, 175, 80, 0.4); - color: var(--color-token-budget); + color: var(--color-coin); } /* 물음표 배지 — 누르면 설명이 나온다는 신호. 이게 없으면 툴팁의 존재 자체를 모른다. */ @@ -101,7 +101,7 @@ } .tone-bad .info-tip-summary { color: var(--symbol-threat, #ff7b6b); } -.tone-good .info-tip-summary { color: var(--color-token-budget); } +.tone-good .info-tip-summary { color: var(--color-coin); } .info-tip-detail { font-size: 11px; diff --git a/client/src/core/components/ShopScreen.css b/client/src/core/components/ShopScreen.css index 54cf643..05ffad9 100644 --- a/client/src/core/components/ShopScreen.css +++ b/client/src/core/components/ShopScreen.css @@ -22,7 +22,7 @@ line-height: 1.5; } -.shop-tokens { +.shop-coins { font-family: var(--font-typewriter); font-size: 13px; padding: 8px 12px; @@ -61,7 +61,7 @@ .shop-buy-btn { font-family: var(--font-typewriter); font-size: 11px; - background: var(--color-token-budget); + background: var(--color-coin); color: #0a0a0a; border: none; border-radius: 4px; diff --git a/client/src/core/components/ShopScreen.jsx b/client/src/core/components/ShopScreen.jsx index 875acc1..0f711d5 100644 --- a/client/src/core/components/ShopScreen.jsx +++ b/client/src/core/components/ShopScreen.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Coins } from 'lucide-react'; import SymbolBadge from './SymbolBadge'; import { purgePrice, REROLL_COST, canAfford } from '../engine/shop'; import './ShopScreen.css'; @@ -6,18 +7,18 @@ import './ShopScreen.css'; // 상점 (§8.3) — 라운드 종료 후 의회 진입 전 정산 단계. 자산 구매·소모품·전례 매입은 // 각각 §10·§12·§11(3차 범위)이 있어야 성립해서 아직 없다. 지금 성립하는 품목은 // "오염 카드 직접 처리" 하나뿐이다 — 족보가 안 맞을 때의 탈출구, 명분·실적은 안 준다. -export default function ShopScreen({ display, tokens, deskSurcharge, onPurge, onReroll, rerolled, onContinue }) { +export default function ShopScreen({ display, coins, deskSurcharge, onPurge, onReroll, rerolled, onContinue }) { return (

정산 — 상점

- 족보가 안 맞는 오염 카드를 토큰으로 직접 처리할 수 있습니다 (명분·실적은 없음). + 족보가 안 맞는 오염 카드를 코인으로 직접 처리할 수 있습니다 (명분·실적은 없음). 가격은 등급만큼이며 지출 가산 +{deskSurcharge}이 얹힙니다.

-
보유 토큰 {tokens}
+
보유 코인 {coins}
{display.length === 0 &&

진열할 오염 카드가 없습니다.

} @@ -29,7 +30,7 @@ export default function ShopScreen({ display, tokens, deskSurcharge, onPurge, on {card.name || '미상 서류'}