@mcptoolshop/sovereign 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/LICENSE +21 -0
  3. package/README.es.md +158 -0
  4. package/README.fr.md +158 -0
  5. package/README.hi.md +158 -0
  6. package/README.it.md +158 -0
  7. package/README.ja.md +158 -0
  8. package/README.md +158 -0
  9. package/README.pt-BR.md +158 -0
  10. package/README.zh.md +158 -0
  11. package/SECURITY.md +61 -0
  12. package/bin/sovereign.js +167 -0
  13. package/package.json +56 -0
  14. package/release/00-START-HERE.html +333 -0
  15. package/release/CHANGELOG.md +126 -0
  16. package/release/README.txt +144 -0
  17. package/release/balance-evidence/README.txt +81 -0
  18. package/release/balance-evidence/raw-data/sovereign-batch-v0.10-canonical-400.json +72134 -0
  19. package/release/balance-evidence/raw-data/sovereign-batch-v0.10-canonical-slot-swap.json +18137 -0
  20. package/release/balance-evidence/raw-data/sovereign-batch-v0.10-canonical.json +18137 -0
  21. package/release/balance-evidence/raw-data/sovereign-batch-v0.10-mc-mirror.json +18089 -0
  22. package/release/balance-evidence/raw-data/sovereign-batch-v0.10-mfg-mirror.json +18089 -0
  23. package/release/balance-evidence/raw-data/sovereign-batch-v0.10-tf-mirror.json +18089 -0
  24. package/release/balance-evidence/sovereign-batch-v0.10-canonical-400.html +1 -0
  25. package/release/balance-evidence/sovereign-batch-v0.10-canonical-slot-swap.html +1 -0
  26. package/release/balance-evidence/sovereign-batch-v0.10-canonical.html +1 -0
  27. package/release/balance-evidence/sovereign-batch-v0.10-mc-mirror.html +1 -0
  28. package/release/balance-evidence/sovereign-batch-v0.10-mfg-mirror.html +1 -0
  29. package/release/balance-evidence/sovereign-batch-v0.10-summary.html +2 -0
  30. package/release/balance-evidence/sovereign-batch-v0.10-tf-mirror.html +1 -0
  31. package/release/board-game/README.txt +48 -0
  32. package/release/board-game/sovereign-economy-audit.html +501 -0
  33. package/release/board-game/sovereign-print-audit.html +479 -0
  34. package/release/board-game/sovereign-prototype.html +1939 -0
  35. package/release/design-history/01-phase1-concept.html +632 -0
  36. package/release/design-history/02-phase2-prototype.html +1026 -0
  37. package/release/design-history/03-phase3-audit.html +268 -0
  38. package/release/design-history/04-phase4-audit.html +274 -0
  39. package/release/design-history/05-phase5-audit.html +305 -0
  40. package/release/design-history/README.txt +66 -0
  41. package/release/digital-mode/README.txt +89 -0
  42. package/release/digital-mode/sovereign-solo.html +3884 -0
  43. package/release/digital-mode/sovereign-v0.10-freeze-audit.html +67 -0
@@ -0,0 +1,1026 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Sovereign · Solo / Digital · Phase 2 Clickable Prototype</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <style>
8
+ :root {
9
+ --revolutionary-debt:#6E1F1E; --state-debt:#4A6B8A; --revenue-system:#C28A28;
10
+ --commercial-infrastructure:#2E7A6B; --national-finance:#1F2D52;
11
+ --internal-improvements:#B85A28; --manufactures:#8C8A2E; --strategic-industry:#3A3A3A;
12
+ --parchment:#F0E6CD; --parchment-2:#E6DABC; --ink:#1A1612; --highlight:#C8392E;
13
+ --rule:rgba(26,22,18,0.55); --rule-soft:rgba(26,22,18,0.22);
14
+ --display: "Baskerville","Big Caslon","Hoefler Text","Garamond","Times New Roman",serif;
15
+ --body: "Iowan Old Style","Georgia","Cambria","Times New Roman",serif;
16
+ --ui: -apple-system,"Segoe UI","Helvetica Neue","Arial",system-ui,sans-serif;
17
+ --mono: "SF Mono","Menlo","Consolas","Courier New",monospace;
18
+
19
+ --p1: var(--state-debt);
20
+ --p2: var(--revolutionary-debt);
21
+ --p3: var(--commercial-infrastructure);
22
+ }
23
+ *, *::before, *::after { box-sizing: border-box; }
24
+ html, body { margin:0; padding:0; font-family: var(--body); color: var(--ink); background:#2A2622; min-height:100vh; }
25
+ button { font-family: var(--ui); font-size:11px; letter-spacing:.12em; text-transform:uppercase; cursor:pointer; padding:6px 12px; background:var(--parchment); color:var(--ink); border:1px solid var(--ink); }
26
+ button:hover { background:var(--ink); color:var(--parchment); }
27
+ button:focus { outline:2px solid var(--ink); outline-offset:2px; }
28
+ button.primary { background:var(--ink); color:var(--parchment); }
29
+ button.primary:hover { background:var(--national-finance); }
30
+ button[disabled] { opacity:.4; cursor:not-allowed; }
31
+
32
+ .app { display:grid; grid-template-rows:auto 1fr; min-height:100vh; max-width:1600px; margin:0 auto; background:var(--parchment); border-left:1px solid var(--ink); border-right:1px solid var(--ink); }
33
+
34
+ /* ===== TOP BAR ============================================== */
35
+ .topbar { background:var(--parchment); border-bottom:1px solid var(--ink); padding:14px 22px; display:grid; grid-template-columns:1fr auto; gap:18px; align-items:center; }
36
+ .brand { display:flex; flex-direction:column; }
37
+ .brand .eyebrow { font-family:var(--ui); font-size:9px; letter-spacing:.32em; text-transform:uppercase; color:var(--national-finance); margin-bottom:2px; }
38
+ .brand .title { font-family:var(--display); font-size:24px; line-height:1; letter-spacing:-.01em; }
39
+ .brand .sub { font-family:var(--display); font-style:italic; font-size:11px; margin-top:2px; }
40
+ .brand .placeholder { display:inline-block; margin-left:8px; padding:1px 6px; font-family:var(--ui); font-size:8.5px; letter-spacing:.18em; text-transform:uppercase; background:var(--parchment-2); border:1px dashed var(--ink); color:var(--ink); vertical-align:middle; font-weight:700; }
41
+ .topbar .controls { display:flex; gap:8px; align-items:center; flex-wrap:wrap; justify-content:flex-end; }
42
+ .topbar .step-indicator { font-family:var(--mono); font-size:10px; letter-spacing:.08em; padding:6px 10px; background:var(--ink); color:var(--parchment); }
43
+
44
+ /* ===== LAYOUT GRID ========================================== */
45
+ .grid { display:grid; grid-template-columns: minmax(0, 1fr) 420px; grid-template-rows: minmax(0, 1fr) 200px; gap:12px; padding:14px 18px; min-height: 0; }
46
+ .grid > * { min-width:0; min-height:0; }
47
+ .board-pane { grid-column:1; grid-row:1; display:flex; flex-direction:column; gap:10px; }
48
+ .panel-pane { grid-column:2; grid-row:1 / span 2; display:flex; flex-direction:column; gap:10px; overflow-y:auto; max-height: calc(100vh - 110px); }
49
+ .ledger-pane { grid-column:1; grid-row:2; }
50
+
51
+ /* ===== SHARED PANEL CHROME ================================== */
52
+ .panel { background:var(--parchment); border:1.5px solid var(--ink); position:relative; padding:12px 14px; }
53
+ .panel::before { content:""; position:absolute; inset:5px; border:0.5px solid var(--rule-soft); pointer-events:none; }
54
+ .panel .panel-head { display:flex; justify-content:space-between; align-items:baseline; padding-bottom:6px; border-bottom:1px solid var(--ink); margin-bottom:8px; }
55
+ .panel .panel-head .name { font-family:var(--ui); font-size:9.5px; font-weight:700; letter-spacing:.26em; text-transform:uppercase; }
56
+ .panel .panel-head .surface-id { font-family:var(--mono); font-size:9px; letter-spacing:.08em; opacity:.7; }
57
+ .panel h3 { font-family:var(--display); font-weight:400; font-size:16px; margin:0 0 4px; letter-spacing:-.005em; }
58
+ .panel p, .panel li { font-family:var(--body); font-size:11px; line-height:1.45; margin:0 0 4px; }
59
+ .panel ul { padding-left:16px; margin:0 0 6px; }
60
+ .panel strong { font-family:var(--display); font-weight:700; }
61
+
62
+ /* ===== BOARD VIEW =========================================== */
63
+ .board-view { position:relative; aspect-ratio: 1; max-width:680px; margin:0 auto; width:100%; background:var(--parchment); border:2px solid var(--ink); }
64
+ .board-view::before { content:""; position:absolute; inset:6px; border:1px solid var(--ink); pointer-events:none; }
65
+ .board-grid { position:absolute; inset:14px; display:grid; grid-template-columns: 1fr repeat(9, 1fr) 1fr; grid-template-rows: 1fr repeat(9, 1fr) 1fr; gap:1px; background:var(--ink); }
66
+ .board-cell { background:var(--parchment); position:relative; padding:3px 4px; display:flex; flex-direction:column; justify-content:space-between; font-family:var(--ui); font-size:7.5px; letter-spacing:.04em; min-width:0; overflow:hidden; cursor:pointer; }
67
+ .board-cell:hover { background:var(--parchment-2); }
68
+ .board-cell .num { font-family:var(--mono); font-size:7.5px; opacity:.55; }
69
+ .board-cell .nm { font-family:var(--display); font-size:8.5px; line-height:1.05; text-wrap:balance; }
70
+ .board-cell .band { position:absolute; top:0; left:0; right:0; height:5px; }
71
+ .board-cell.empty { background: transparent; }
72
+ .board-cell.corner { background: var(--parchment-2); }
73
+ .board-cell.corner .nm { font-family: var(--display); font-size: 9px; font-weight:700; }
74
+ .board-cell.tax .band { background: var(--highlight); }
75
+ .board-cell.card-debate .band { background: var(--ink); }
76
+ .board-cell.card-shock .band { background: var(--ink); background-image: linear-gradient(90deg, var(--highlight), var(--highlight) 50%, var(--ink) 50%); }
77
+ .board-cell.route .band { background: var(--ink); background-image: repeating-linear-gradient(90deg, transparent 0 4px, rgba(255,255,255,.5) 4px 5px); }
78
+ .board-cell.institution .band { background: var(--ink); background-image: radial-gradient(rgba(255,255,255,.7) 0.8px, transparent 1.2px); background-size:3px 3px; }
79
+ .board-cell.sys-revolutionary-debt .band { background: var(--revolutionary-debt); }
80
+ .board-cell.sys-state-debt .band { background: var(--state-debt); }
81
+ .board-cell.sys-revenue-system .band { background: var(--revenue-system); }
82
+ .board-cell.sys-commercial-infrastructure .band { background: var(--commercial-infrastructure); }
83
+ .board-cell.sys-national-finance .band { background: var(--national-finance); }
84
+ .board-cell.sys-internal-improvements .band { background: var(--internal-improvements); }
85
+ .board-cell.sys-manufactures .band { background: var(--manufactures); }
86
+ .board-cell.sys-strategic-industry .band { background: var(--strategic-industry); }
87
+ .board-cell .owner-dot { position:absolute; bottom:2px; right:2px; width:9px; height:9px; border-radius:50%; border:1px solid var(--ink); }
88
+ .board-cell.owner-p1 .owner-dot { background: var(--p1); }
89
+ .board-cell.owner-p2 .owner-dot { background: var(--p2); }
90
+ .board-cell.owner-p3 .owner-dot { background: var(--p3); }
91
+ .board-cell .tier { position:absolute; top:6px; right:2px; font-family:var(--mono); font-size:6.5px; font-weight:700; color:var(--ink); background:var(--parchment); padding:0 2px; border:0.5px solid var(--ink); }
92
+
93
+ /* Token markers — small colored circles stacked on a cell */
94
+ .board-cell .tokens { position:absolute; top:6px; left:2px; display:flex; gap:1px; }
95
+ .board-cell .tokens .tok { width:10px; height:10px; border-radius:50%; border:1.5px solid var(--ink); }
96
+ .board-cell .tokens .tok.p1 { background: var(--p1); }
97
+ .board-cell .tokens .tok.p2 { background: var(--p2); }
98
+ .board-cell .tokens .tok.p3 { background: var(--p3); }
99
+
100
+ .board-cell.active-space { outline: 3px solid var(--highlight); outline-offset:-3px; }
101
+
102
+ /* board center */
103
+ .board-center { grid-column: 2 / span 9; grid-row: 2 / span 9; background: var(--parchment); display:flex; flex-direction:column; align-items:center; justify-content:center; padding:10px; text-align:center; gap:6px; position:relative; }
104
+ .board-center::before, .board-center::after { content:""; position:absolute; left:10px; right:10px; height:1px; background:var(--ink); opacity:.4; }
105
+ .board-center::before { top:10px; }
106
+ .board-center::after { bottom:10px; }
107
+ .board-center .bc-eyebrow { font-family:var(--ui); font-size:8px; letter-spacing:.32em; text-transform:uppercase; color:var(--national-finance); }
108
+ .board-center .bc-title { font-family:var(--display); font-size:24px; line-height:1; }
109
+ .board-center .bc-sub { font-family:var(--display); font-style:italic; font-size:10px; }
110
+ .board-center .bc-lap { font-family:var(--mono); font-size:10px; letter-spacing:.14em; background:var(--ink); color:var(--parchment); padding:3px 10px; margin-top:3px; }
111
+ .board-center .bc-tracks { display:flex; gap:8px; margin-top:6px; font-family:var(--ui); font-size:8.5px; }
112
+ .board-center .bc-tracks .t { padding:2px 7px; border:1px solid var(--ink); background:var(--parchment-2); }
113
+ .board-center .bc-tracks .t .v { font-family:var(--mono); font-size:10px; margin-left:3px; font-weight:700; }
114
+ .board-center .bc-tracks .t.credit { color: var(--national-finance); }
115
+ .board-center .bc-tracks .t.resist { color: var(--highlight); }
116
+ .board-center .bc-tracks .t.indust { color: var(--manufactures); }
117
+ .board-center .bc-acts { font-family:var(--ui); font-size:8px; letter-spacing:.18em; text-transform:uppercase; margin-top:4px; opacity:.75; }
118
+
119
+ /* ===== BOARD LEGEND ========================================= */
120
+ .board-legend { display:flex; gap:10px; justify-content:center; flex-wrap:wrap; font-family:var(--ui); font-size:9px; letter-spacing:.1em; }
121
+ .board-legend .p { display:flex; align-items:center; gap:5px; }
122
+ .board-legend .dot { width:11px; height:11px; border-radius:50%; border:1.5px solid var(--ink); }
123
+ .board-legend .dot.p1 { background: var(--p1); }
124
+ .board-legend .dot.p2 { background: var(--p2); }
125
+ .board-legend .dot.p3 { background: var(--p3); }
126
+
127
+ /* ===== LEDGER =============================================== */
128
+ .ledger { background: var(--parchment); border:1.5px solid var(--ink); padding:8px 12px; height:100%; overflow-y:auto; position:relative; font-family:var(--mono); font-size:10px; line-height:1.45; }
129
+ .ledger::before { content:""; position:absolute; inset:4px; border:0.5px solid var(--rule-soft); pointer-events:none; }
130
+ .ledger .head { display:flex; justify-content:space-between; font-family:var(--ui); font-size:9px; letter-spacing:.22em; text-transform:uppercase; border-bottom:1px solid var(--ink); padding-bottom:4px; margin-bottom:6px; font-weight:700; }
131
+ .ledger .row { display:grid; grid-template-columns: 64px 110px 1fr; gap:8px; padding:1px 0; border-bottom:0.5px dashed var(--rule-soft); }
132
+ .ledger .row .stamp { color:var(--ink); opacity:.7; }
133
+ .ledger .row .actor { font-family:var(--ui); font-weight:700; font-size:9px; letter-spacing:.08em; text-transform:uppercase; }
134
+ .ledger .row.cash .actor { color: var(--national-finance); }
135
+ .ledger .row.track .actor { color: var(--manufactures); }
136
+ .ledger .row.vote .actor { color: var(--state-debt); }
137
+ .ledger .row.card .actor { color: var(--highlight); }
138
+ .ledger .row.move .actor { color: var(--commercial-infrastructure); }
139
+
140
+ /* ===== TREASURY / PROFILE PANELS ============================ */
141
+ .treasury .holdings { display:flex; flex-direction:column; gap:3px; margin-top:6px; }
142
+ .treasury .h-row { display:grid; grid-template-columns: auto 1fr auto; gap:6px; align-items:center; font-size:10px; padding:2px 4px; border-bottom:0.5px dashed var(--rule-soft); }
143
+ .treasury .h-row .ic { width:14px; height:14px; }
144
+ .treasury .h-row .sw { width:9px; height:9px; border:1px solid var(--ink); margin-right:3px; display:inline-block; vertical-align:middle; }
145
+ .treasury .h-row .tier-badge { font-family:var(--mono); font-size:8px; padding:1px 4px; background:var(--ink); color:var(--parchment); }
146
+ .treasury .cash-row { display:flex; justify-content:space-between; align-items:baseline; padding:4px 0; border-bottom:1px solid var(--ink); margin-bottom:4px; }
147
+ .treasury .cash-row .cash { font-family:var(--display); font-size:22px; line-height:1; }
148
+ .treasury .cash-row .lbl { font-family:var(--ui); font-size:9px; letter-spacing:.18em; text-transform:uppercase; }
149
+ .treasury .role-tag { display:inline-block; padding:1px 6px; background:var(--ink); color:var(--parchment); font-family:var(--ui); font-size:8.5px; letter-spacing:.14em; text-transform:uppercase; font-weight:700; }
150
+ .treasury .inf { font-family:var(--mono); font-size:9px; text-align:right; opacity:.8; margin-top:4px; }
151
+
152
+ .opponents { display:flex; flex-direction:column; gap:8px; }
153
+ .opponent-card { padding:8px 10px; background:var(--parchment-2); border:1px solid var(--ink); }
154
+ .opponent-card .head { display:flex; justify-content:space-between; align-items:center; margin-bottom:4px; }
155
+ .opponent-card .head .nm { font-family:var(--display); font-size:13px; }
156
+ .opponent-card .head .pdot { width:12px; height:12px; border-radius:50%; border:1.5px solid var(--ink); }
157
+ .opponent-card.p2 .head .pdot { background: var(--p2); }
158
+ .opponent-card.p3 .head .pdot { background: var(--p3); }
159
+ .opponent-card .pl { font-family:var(--ui); font-size:8.5px; letter-spacing:.18em; text-transform:uppercase; color:var(--national-finance); font-weight:700; }
160
+ .opponent-card .holdings { font-family:var(--body); font-size:10px; line-height:1.4; margin-top:4px; }
161
+ .opponent-card .strategy { font-family:var(--display); font-style:italic; font-size:10px; margin-top:4px; padding-top:4px; border-top:0.5px dashed var(--rule-soft); }
162
+
163
+ /* ===== TRACKS PANEL ========================================= */
164
+ .tracks-panel .t-row { display:grid; grid-template-columns: 1fr auto; gap:8px; padding:6px 0; border-bottom:0.5px solid var(--rule-soft); }
165
+ .tracks-panel .t-row .lbl { font-family:var(--ui); font-size:9.5px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; }
166
+ .tracks-panel .t-row .val { font-family:var(--display); font-size:18px; line-height:1; font-variant-numeric:tabular-nums; }
167
+ .tracks-panel .t-row .scale { grid-column:1 / span 2; display:grid; grid-template-columns:repeat(13, 1fr); height:18px; border:1px solid var(--ink); margin-top:4px; }
168
+ .tracks-panel .t-row .tk { font-family:var(--mono); font-size:8.5px; display:flex; align-items:center; justify-content:center; border-right:0.5px solid var(--rule-soft); position:relative; }
169
+ .tracks-panel .t-row .tk:last-child { border-right:0; }
170
+ .tracks-panel .t-row .tk.major { background: rgba(26,22,18,0.05); }
171
+ .tracks-panel .t-row .tk.marker::after { content:""; position:absolute; inset:2px; border-radius:50%; border:2px solid var(--ink); }
172
+ .tracks-panel .t-row.credit .tk.marker::after { background: var(--national-finance); }
173
+ .tracks-panel .t-row.resist .tk.marker::after { background: var(--highlight); }
174
+ .tracks-panel .t-row.indust .tk.marker::after { background: var(--manufactures); }
175
+ .tracks-panel .t-row .threshold { grid-column:1 / span 2; font-family:var(--ui); font-size:8.5px; letter-spacing:.14em; text-transform:uppercase; margin-top:6px; padding:3px 6px; background:var(--parchment-2); border:0.5px solid var(--rule-soft); }
176
+ .tracks-panel .t-row.credit .threshold { color: var(--national-finance); }
177
+ .tracks-panel .t-row.resist .threshold { color: var(--highlight); }
178
+ .tracks-panel .t-row.indust .threshold { color: var(--manufactures); }
179
+ .tracks-panel .t-row .text-readout { font-family:var(--mono); font-size:9px; opacity:.75; margin-top:2px; }
180
+
181
+ /* ===== ACTS PANEL =========================================== */
182
+ .acts-panel .current-act { padding:8px 10px; background:var(--ink); color:var(--parchment); }
183
+ .acts-panel .current-act .pretitle { font-family:var(--ui); font-size:8px; letter-spacing:.26em; text-transform:uppercase; }
184
+ .acts-panel .current-act .nm { font-family:var(--display); font-size:18px; line-height:1; margin-top:2px; }
185
+ .acts-panel .current-act .effect { font-family:var(--body); font-size:10px; line-height:1.4; margin-top:6px; }
186
+ .acts-panel .votes { display:flex; flex-direction:column; gap:4px; margin-top:8px; }
187
+ .acts-panel .vote-row { display:grid; grid-template-columns: 14px 1fr auto; gap:8px; align-items:center; padding:5px 8px; background:var(--parchment-2); border:1px solid var(--ink); font-size:10px; }
188
+ .acts-panel .vote-row.pending { background: var(--parchment); border-style:dashed; }
189
+ .acts-panel .vote-row .pdot { width:12px; height:12px; border-radius:50%; border:1.5px solid var(--ink); }
190
+ .acts-panel .vote-row.p1 .pdot { background:var(--p1); }
191
+ .acts-panel .vote-row.p2 .pdot { background:var(--p2); }
192
+ .acts-panel .vote-row.p3 .pdot { background:var(--p3); }
193
+ .acts-panel .vote-row .who { font-family:var(--ui); font-size:9px; letter-spacing:.12em; text-transform:uppercase; font-weight:700; }
194
+ .acts-panel .vote-row .reason { font-family:var(--body); font-size:10px; line-height:1.35; opacity:.85; display:block; }
195
+ .acts-panel .vote-row .ballot { font-family:var(--display); font-size:13px; font-weight:700; padding:2px 8px; border:1.5px solid var(--ink); min-width:40px; text-align:center; }
196
+ .acts-panel .vote-row .ballot.yes { background: var(--commercial-infrastructure); color: var(--parchment); }
197
+ .acts-panel .vote-row .ballot.no { background: var(--highlight); color: var(--parchment); }
198
+ .acts-panel .vote-row .ballot.pending { background: var(--parchment); }
199
+ .acts-panel .you-vote { display:flex; gap:6px; margin-top:6px; }
200
+ .acts-panel .tally { font-family:var(--mono); font-size:10px; letter-spacing:.1em; padding:4px 8px; background:var(--parchment-2); border:1px solid var(--ink); margin-top:8px; text-align:center; }
201
+ .acts-panel .tally.passed { background: var(--commercial-infrastructure); color: var(--parchment); }
202
+ .acts-panel .tally.failed { background: var(--highlight); color: var(--parchment); }
203
+ .acts-panel .passed-tray { margin-top:8px; padding-top:6px; border-top:1px dashed var(--rule-soft); }
204
+ .acts-panel .passed-tray .lbl { font-family:var(--ui); font-size:8.5px; letter-spacing:.22em; text-transform:uppercase; font-weight:700; margin-bottom:4px; }
205
+ .acts-panel .passed-tray .tray-row { display:flex; gap:5px; flex-wrap:wrap; }
206
+ .acts-panel .passed-tray .slot { padding:3px 7px; background:var(--parchment); border:1px solid var(--ink); font-family:var(--display); font-size:10px; }
207
+ .acts-panel .passed-tray .slot .roman { font-weight:700; margin-right:3px; }
208
+
209
+ /* ===== INSPECTOR + CARD DRAWER ============================== */
210
+ .inspector .card-art { padding:8px 10px; background:var(--parchment-2); border:1px solid var(--ink); margin-bottom:8px; }
211
+ .inspector .card-art .band { padding:3px 6px; color:#fff; font-family:var(--ui); font-size:9px; letter-spacing:.18em; text-transform:uppercase; font-weight:700; display:flex; justify-content:space-between; }
212
+ .inspector .card-art .nm { font-family:var(--display); font-size:16px; margin-top:5px; line-height:1.1; }
213
+ .inspector .card-art .subkind { font-family:var(--ui); font-size:8.5px; letter-spacing:.18em; text-transform:uppercase; opacity:.75; margin-top:2px; }
214
+ .inspector .card-art .flavor { font-family:var(--display); font-style:italic; font-size:10px; line-height:1.4; margin-top:6px; }
215
+ .inspector .pay-table { width:100%; border-collapse:collapse; margin-top:6px; }
216
+ .inspector .pay-table th, .inspector .pay-table td { font-family:var(--ui); font-size:9px; padding:3px 6px; border-bottom:0.5px solid var(--rule-soft); }
217
+ .inspector .pay-table th { background:var(--ink); color:var(--parchment); text-align:left; letter-spacing:.14em; text-transform:uppercase; }
218
+ .inspector .pay-table td.val { font-family:var(--display); font-size:11px; text-align:right; font-weight:700; font-variant-numeric:tabular-nums; }
219
+ .inspector .actions { display:flex; gap:6px; margin-top:8px; }
220
+ .inspector .historical { margin-top:8px; padding-top:6px; border-top:1px dashed var(--rule-soft); }
221
+ .inspector .historical .lbl { font-family:var(--ui); font-size:8.5px; letter-spacing:.22em; text-transform:uppercase; font-weight:700; color:var(--national-finance); margin-bottom:3px; }
222
+ .inspector .historical .snip { font-family:var(--display); font-style:italic; font-size:10px; line-height:1.45; }
223
+ .inspector .historical .expand { font-family:var(--mono); font-size:8.5px; letter-spacing:.18em; opacity:.7; margin-top:3px; cursor:pointer; }
224
+
225
+ .card-drawer .drawer-card { padding:10px 12px; background:var(--parchment-2); border:1.5px solid var(--national-finance); position:relative; }
226
+ .card-drawer .drawer-card .band { background: var(--national-finance); color:#fff; padding:4px 8px; font-family:var(--ui); font-size:9px; letter-spacing:.22em; text-transform:uppercase; font-weight:700; margin:-10px -12px 8px; display:flex; justify-content:space-between; }
227
+ .card-drawer .alert { font-family:var(--display); font-style:italic; font-size:10px; text-align:center; padding:2px 0 6px; color:var(--national-finance); }
228
+ .card-drawer .nm { font-family:var(--display); font-size:16px; line-height:1.1; text-align:center; }
229
+ .card-drawer .effect { font-family:var(--body); font-size:11px; line-height:1.45; text-align:center; margin:8px 0; }
230
+ .card-drawer .effect strong { color:var(--national-finance); }
231
+ .card-drawer .outcomes { display:flex; gap:5px; justify-content:center; margin:6px 0; }
232
+ .card-drawer .chip { font-family:var(--ui); font-size:8.5px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; padding:2px 6px; border:1px solid var(--ink); }
233
+ .card-drawer .chip.credit { background:var(--national-finance); color:#fff; }
234
+ .card-drawer .chip.resist { background:var(--highlight); color:#fff; }
235
+ .card-drawer .historical { margin-top:8px; padding-top:6px; border-top:1px dashed var(--rule-soft); }
236
+ .card-drawer .historical .lbl { font-family:var(--ui); font-size:8.5px; letter-spacing:.22em; text-transform:uppercase; font-weight:700; color:var(--national-finance); margin-bottom:3px; }
237
+ .card-drawer .historical .snip { font-family:var(--display); font-style:italic; font-size:10px; line-height:1.45; }
238
+ .card-drawer .actions { display:flex; gap:6px; margin-top:8px; justify-content:center; }
239
+
240
+ /* ===== ENDGAME ============================================== */
241
+ .endgame { padding:18px 24px; }
242
+ .endgame h2 { font-family:var(--display); font-size:32px; line-height:1; margin:0 0 4px; }
243
+ .endgame .sub { font-family:var(--display); font-style:italic; font-size:13px; margin-bottom:12px; }
244
+ .endgame .score-block { display:grid; grid-template-columns: 1fr 1fr; gap:16px; }
245
+ .endgame .winner { padding:12px 16px; background:var(--commercial-infrastructure); color:var(--parchment); border:1.5px solid var(--ink); }
246
+ .endgame .winner .lbl { font-family:var(--ui); font-size:9px; letter-spacing:.26em; text-transform:uppercase; }
247
+ .endgame .winner .nm { font-family:var(--display); font-size:24px; line-height:1; margin-top:2px; }
248
+ .endgame .winner .score { font-family:var(--mono); font-size:14px; margin-top:4px; }
249
+ .endgame .runner-up { padding:10px 14px; background:var(--parchment-2); border:1px solid var(--ink); }
250
+ .endgame .runner-up .lbl { font-family:var(--ui); font-size:8.5px; letter-spacing:.22em; text-transform:uppercase; }
251
+ .endgame .runner-up .nm { font-family:var(--display); font-size:14px; line-height:1; margin-top:2px; }
252
+ .endgame table { width:100%; border-collapse:collapse; margin-top:12px; }
253
+ .endgame table th, .endgame table td { font-size:10.5px; padding:3px 6px; border-bottom:0.5px solid var(--rule-soft); text-align:left; }
254
+ .endgame table th { background:var(--ink); color:var(--parchment); font-family:var(--ui); font-size:9px; letter-spacing:.14em; text-transform:uppercase; }
255
+ .endgame table td.n { font-family:var(--mono); font-size:10px; text-align:right; }
256
+ .endgame .narration { margin-top:14px; padding:10px 14px; border-left:2px solid var(--ink); background:var(--parchment-2); }
257
+ .endgame .narration h4 { font-family:var(--display); font-weight:400; font-style:italic; font-size:14px; margin:0 0 4px; }
258
+ .endgame .narration p { font-family:var(--body); font-size:11px; line-height:1.55; }
259
+ .endgame .systems { margin-top:14px; display:grid; grid-template-columns:1fr 1fr; gap:10px; }
260
+ .endgame .systems .box { border:1px solid var(--rule-soft); padding:8px 10px; background:rgba(26,22,18,0.025); }
261
+ .endgame .systems .box h4 { font-family:var(--display); font-size:13px; margin:0 0 4px; }
262
+ .endgame .systems .box p { font-family:var(--body); font-size:10px; line-height:1.4; }
263
+
264
+ /* ===== UTILITY ============================================== */
265
+ .placeholder { display:inline-block; padding:1px 6px; font-family:var(--ui); font-size:8px; letter-spacing:.18em; text-transform:uppercase; background:var(--parchment-2); border:1px dashed var(--ink); color:var(--ink); font-weight:700; }
266
+ .placeholder.inline { vertical-align:middle; }
267
+ .hidden { display:none !important; }
268
+ .muted { opacity:.65; }
269
+
270
+ @media print { body { background:#fff; } }
271
+ </style>
272
+ </head>
273
+ <body>
274
+
275
+ <!-- ===================================================================
276
+ APP SHELL
277
+ =================================================================== -->
278
+ <div class="app">
279
+
280
+ <!-- ============= TOP BAR ============= -->
281
+ <header class="topbar">
282
+ <div class="brand">
283
+ <div class="eyebrow">Sovereign · Solo / Digital · Phase 2</div>
284
+ <div class="title">Static Clickable Prototype <span class="placeholder">demo state · Lap 3</span></div>
285
+ <div class="sub">Click-through screen flow. No state machine. No game logic.</div>
286
+ </div>
287
+ <div class="controls">
288
+ <span class="step-indicator" id="stepIndicator">Step 1 / 11</span>
289
+ <button id="btnReset">Reset Demo</button>
290
+ <button id="btnEndgame">Jump to Endgame</button>
291
+ <button id="btnPrev">◀ Prev</button>
292
+ <button id="btnNext" class="primary">Next ▶</button>
293
+ </div>
294
+ </header>
295
+
296
+ <!-- ============= MAIN GRID ============= -->
297
+ <main class="grid" id="mainGrid">
298
+
299
+ <!-- ===== BOARD VIEW ===== -->
300
+ <section class="board-pane">
301
+ <div class="panel" style="padding:8px 12px; border-bottom-width:0">
302
+ <div class="panel-head" style="margin-bottom:0; padding-bottom:0; border-bottom:0"><span class="name">Board View</span><span class="surface-id">A</span></div>
303
+ </div>
304
+ <div class="board-view" id="boardView">
305
+ <div class="board-grid" id="boardGrid">
306
+ <!-- 11x11 grid; rendered by JS -->
307
+ </div>
308
+ </div>
309
+ <div class="board-legend">
310
+ <span class="p"><span class="dot p1"></span><strong>You</strong> (Treasury Architect)</span>
311
+ <span class="p"><span class="dot p2"></span>Treasury / Finance</span>
312
+ <span class="p"><span class="dot p3"></span>Merchant / Infrastructure</span>
313
+ <span class="placeholder">3-player · fixed demo</span>
314
+ </div>
315
+ </section>
316
+
317
+ <!-- ===== PANEL STACK ===== -->
318
+ <aside class="panel-pane" id="panelPane">
319
+
320
+ <!-- Treasury panel (always visible) -->
321
+ <div class="panel treasury" id="panelTreasury">
322
+ <div class="panel-head"><span class="name">Your Treasury</span><span class="surface-id">B</span></div>
323
+ <div class="cash-row">
324
+ <div>
325
+ <div class="lbl">Cash on hand <span class="placeholder">demo</span></div>
326
+ <div class="cash" id="treasuryCash">$1,170</div>
327
+ </div>
328
+ <div style="text-align:right">
329
+ <span class="role-tag">Treasury Architect</span>
330
+ <div class="inf" id="treasuryInf">Influence est. 12</div>
331
+ </div>
332
+ </div>
333
+ <div class="holdings" id="treasuryHoldings"></div>
334
+ </div>
335
+
336
+ <!-- Acts panel -->
337
+ <div class="panel acts-panel" id="panelActs">
338
+ <div class="panel-head"><span class="name">Acts of Congress</span><span class="surface-id">E</span></div>
339
+ <div class="current-act">
340
+ <div class="pretitle">Lap 3 · Federal Act</div>
341
+ <div class="nm">Bank Charter</div>
342
+ <div class="effect"><strong>Bank of the United States</strong> activates lending and <strong>enhanced institution payments</strong>. <em>Bank now pays 10 × dice instead of 4 × dice.</em></div>
343
+ </div>
344
+ <div class="votes" id="actVotes"></div>
345
+ <div class="you-vote" id="actYouVote"></div>
346
+ <div class="tally hidden" id="actTally"></div>
347
+ <div class="passed-tray">
348
+ <div class="lbl">Passed Acts — face-up tray</div>
349
+ <div class="tray-row">
350
+ <div class="slot"><span class="roman">I</span>Funding Act</div>
351
+ <div class="slot"><span class="roman">II</span>Assumption Act</div>
352
+ </div>
353
+ </div>
354
+ </div>
355
+
356
+ <!-- Tracks panel -->
357
+ <div class="panel tracks-panel" id="panelTracks">
358
+ <div class="panel-head"><span class="name">Shared Tracks</span><span class="surface-id">F</span></div>
359
+ <div class="t-row credit">
360
+ <div class="lbl">Public Credit</div>
361
+ <div class="val" id="creditVal">9</div>
362
+ <div class="scale" data-track="credit"></div>
363
+ <div class="text-readout">Value 9 of 12 · threshold ≥ 8 already reached</div>
364
+ <div class="threshold">≥ 8 — National Finance owners split +5 Influence at game end</div>
365
+ </div>
366
+ <div class="t-row resist">
367
+ <div class="lbl">Public Resistance</div>
368
+ <div class="val" id="resistVal">3</div>
369
+ <div class="scale" data-track="resist"></div>
370
+ <div class="text-readout">Value 3 of 12 · threshold ≥ 8 not yet reached</div>
371
+ <div class="threshold">≥ 8 — Whiskey Rebellion card becomes live</div>
372
+ </div>
373
+ <div class="t-row indust">
374
+ <div class="lbl">Industrial Capacity</div>
375
+ <div class="val" id="industVal">1</div>
376
+ <div class="scale" data-track="indust"></div>
377
+ <div class="text-readout">Value 1 of 12 · threshold ≥ 6 not yet reached</div>
378
+ <div class="threshold">≥ 6 (v0.2) — Mfg / Strategic +25 % payments</div>
379
+ </div>
380
+ </div>
381
+
382
+ <!-- Opponents -->
383
+ <div class="panel" id="panelOpponents">
384
+ <div class="panel-head"><span class="name">Scripted Opponents</span><span class="surface-id">B′</span></div>
385
+ <div class="opponents">
386
+ <div class="opponent-card p2">
387
+ <div class="head"><span class="nm">Hamilton</span><span class="pdot"></span></div>
388
+ <div class="pl">Treasury / Finance profile</div>
389
+ <div class="holdings"><strong>Holds:</strong> Virginia Debt · Treasury Securities · Bank of the United States · <span class="placeholder">demo</span></div>
390
+ <div class="strategy">Strategy: stack federal credit. Will vote YES on Bank Charter — owns the Bank.</div>
391
+ </div>
392
+ <div class="opponent-card p3">
393
+ <div class="head"><span class="nm">Morris</span><span class="pdot"></span></div>
394
+ <div class="pl">Merchant / Infrastructure profile</div>
395
+ <div class="holdings"><strong>Holds:</strong> Western Turnpike · Potomac Canal · New York Harbor · Philadelphia Exchange · <span class="placeholder">demo</span></div>
396
+ <div class="strategy">Strategy: corner the route ladder. Will vote NO on Bank Charter — no financial assets.</div>
397
+ </div>
398
+ </div>
399
+ </div>
400
+
401
+ <!-- Asset Inspector (contextual) -->
402
+ <div class="panel inspector hidden" id="panelInspector">
403
+ <div class="panel-head"><span class="name">Asset Inspector</span><span class="surface-id">C</span></div>
404
+ <div id="inspectorBody"></div>
405
+ </div>
406
+
407
+ <!-- Card Drawer (contextual) -->
408
+ <div class="panel card-drawer hidden" id="panelCardDrawer">
409
+ <div class="panel-head"><span class="name">Event Drawer</span><span class="surface-id">D</span></div>
410
+ <div id="cardDrawerBody"></div>
411
+ </div>
412
+
413
+ </aside>
414
+
415
+ <!-- ===== LEDGER ===== -->
416
+ <section class="ledger-pane">
417
+ <div class="ledger" id="ledger">
418
+ <div class="head"><span>Turn Log / Ledger <span class="surface-id" style="font-family:var(--mono);font-size:9px;letter-spacing:.08em;opacity:.7;margin-left:6px;text-transform:none;letter-spacing:.18em">G</span></span><span id="ledgerCount">— entries</span></div>
419
+ <div id="ledgerRows"></div>
420
+ </div>
421
+ </section>
422
+ </main>
423
+
424
+ <!-- ============= ENDGAME OVERLAY ============= -->
425
+ <section class="endgame hidden" id="endgameView">
426
+ <h2>Endgame Report <span class="surface-id" style="font-family:var(--mono);font-size:11px;letter-spacing:.18em;background:var(--ink);color:var(--parchment);padding:2px 8px;vertical-align:middle;margin-left:6px">Surface H</span> <span class="placeholder">demo · placeholder values</span></h2>
427
+ <div class="sub">After lap 7. Sample Influence breakdown for the human player — Treasury Architect.</div>
428
+
429
+ <div class="score-block">
430
+ <div class="winner">
431
+ <div class="lbl">Winner</div>
432
+ <div class="nm">You · Treasury Architect</div>
433
+ <div class="score">Influence 34 · cash $620 · 3 systems complete</div>
434
+ </div>
435
+ <div>
436
+ <div class="runner-up" style="margin-bottom:6px">
437
+ <div class="lbl">2nd · Hamilton (Treasury / Finance)</div>
438
+ <div class="nm">Influence 31</div>
439
+ </div>
440
+ <div class="runner-up">
441
+ <div class="lbl">3rd · Morris (Merchant / Infrastructure)</div>
442
+ <div class="nm">Influence 28</div>
443
+ </div>
444
+ </div>
445
+ </div>
446
+
447
+ <table>
448
+ <thead><tr><th>Influence source</th><th>Detail</th><th>Points</th></tr></thead>
449
+ <tbody>
450
+ <tr><td>Cash held</td><td>$620 ÷ 200 (round down)</td><td class="n">+ 3</td></tr>
451
+ <tr><td>Properties owned</td><td>8 properties</td><td class="n">+ 8</td></tr>
452
+ <tr><td>Upgraded properties</td><td>5 upgraded</td><td class="n">+ 10</td></tr>
453
+ <tr><td>Complete sets</td><td>Rev Debt, State Debt, Improvements</td><td class="n">+ 9</td></tr>
454
+ <tr><td>Routes</td><td>1 route</td><td class="n">+ 1</td></tr>
455
+ <tr><td>Institutions</td><td>0</td><td class="n">+ 0</td></tr>
456
+ <tr><td>Credit ≥ 8 end-game</td><td>1 of 2 National Finance owners · split ⌊5/2⌋</td><td class="n">+ 2</td></tr>
457
+ <tr><td>Capacity ≥ 8 bonus</td><td>not reached</td><td class="n">+ 0</td></tr>
458
+ <tr><td>"You Are Hamilton" kept</td><td>yes</td><td class="n">+ 1</td></tr>
459
+ <tr><td>Bankrupt laps</td><td>0 laps spent bankrupt</td><td class="n">+ 0</td></tr>
460
+ <tr><td colspan="2"><strong>Total Influence</strong></td><td class="n"><strong>34</strong></td></tr>
461
+ </tbody>
462
+ </table>
463
+
464
+ <div class="systems">
465
+ <div class="box">
466
+ <h4>Economic system you built</h4>
467
+ <p>You backed three Acts of Congress (Funding, Assumption, Bank Charter), assembled the State Debt and Revolutionary Debt sets early, and added a route corridor. Public Credit closed at 9; Industrial Capacity never moved past 2.</p>
468
+ </div>
469
+ <div class="box">
470
+ <h4>Strongest holding · weakest system</h4>
471
+ <p><strong>Strongest:</strong> Massachusetts Debt (Tier I + Assumption permanent ×2) returned ~ 84 TN over 6 laps. <strong>Weakest:</strong> Industrial Capacity — never funded, no end-game bonus.</p>
472
+ </div>
473
+ </div>
474
+
475
+ <div class="narration">
476
+ <h4>Your Republic, after lap 7</h4>
477
+ <p>By the close of lap 7 you have funded the war debts, charted the Bank, and laid one rider relay between Boston and Philadelphia. The Treasury's balance sheet is solvent; Public Credit reads 9 of 12. You did not build the manufactories Hamilton wanted — Glassworks and Textile Works went to no one this game — and Industrial Capacity stayed cold. The republic you built is a creditor's republic: foreign lenders extend credit at par, merchants quote on the Philadelphia Exchange, and the Bank discounts merchant bills. What you did not build was the industrial base. Whether that constitutes a Hamiltonian victory or a Jeffersonian one is the historical question — and the score says you split the difference.</p>
478
+ </div>
479
+
480
+ <div style="margin-top:14px; display:flex; gap:8px;">
481
+ <button id="btnReturnFromEndgame" class="primary">◀ Return to demo</button>
482
+ </div>
483
+ </section>
484
+
485
+ </div>
486
+
487
+ <!-- ===================================================================
488
+ DATA + DEMO STATE
489
+ =================================================================== -->
490
+ <script>
491
+ /* Board spaces: subset of data needed for layout + ownership marking. */
492
+ const SPACES = [
493
+ { num:0, name:'Treasury Opens', kind:'corner' },
494
+ { num:1, name:'Continental Certificates', kind:'sys-revolutionary-debt' },
495
+ { num:2, name:'Republic Debate', kind:'card-debate' },
496
+ { num:3, name:'Soldier Pay Notes', kind:'sys-revolutionary-debt' },
497
+ { num:4, name:'Federal Excise', kind:'tax' },
498
+ { num:5, name:'Northern Post Road', kind:'route' },
499
+ { num:6, name:'Massachusetts Debt', kind:'sys-state-debt' },
500
+ { num:7, name:'Market Shock', kind:'card-shock' },
501
+ { num:8, name:'South Carolina Debt', kind:'sys-state-debt' },
502
+ { num:9, name:'Virginia Debt', kind:'sys-state-debt' },
503
+ { num:10, name:'Crisis', kind:'corner' },
504
+ { num:11, name:'Customs House', kind:'sys-revenue-system' },
505
+ { num:12, name:'Bank of U.S.', kind:'institution' },
506
+ { num:13, name:'Import Duties', kind:'sys-revenue-system' },
507
+ { num:14, name:'Whiskey Excise', kind:'sys-revenue-system' },
508
+ { num:15, name:'Western Turnpike', kind:'route' },
509
+ { num:16, name:'New York Harbor', kind:'sys-commercial-infrastructure' },
510
+ { num:17, name:'Republic Debate', kind:'card-debate' },
511
+ { num:18, name:'Philadelphia Exchange', kind:'sys-commercial-infrastructure' },
512
+ { num:19, name:'Coastal Shipping', kind:'sys-commercial-infrastructure' },
513
+ { num:20, name:'Nat\'l Dividend', kind:'corner' },
514
+ { num:21, name:'Treasury Securities', kind:'sys-national-finance' },
515
+ { num:22, name:'Market Shock', kind:'card-shock' },
516
+ { num:23, name:'Bank Subscription', kind:'sys-national-finance' },
517
+ { num:24, name:'Federal Deposits', kind:'sys-national-finance' },
518
+ { num:25, name:'Potomac Canal', kind:'route' },
519
+ { num:26, name:'Turnpike Charter', kind:'sys-internal-improvements' },
520
+ { num:27, name:'Canal Company', kind:'sys-internal-improvements' },
521
+ { num:28, name:'US Mint', kind:'institution' },
522
+ { num:29, name:'Postal Road Network', kind:'sys-internal-improvements' },
523
+ { num:30, name:'Go to Crisis', kind:'corner' },
524
+ { num:31, name:'Textile Works', kind:'sys-manufactures' },
525
+ { num:32, name:'Iron Foundry', kind:'sys-manufactures' },
526
+ { num:33, name:'Republic Debate', kind:'card-debate' },
527
+ { num:34, name:'Glassworks', kind:'sys-manufactures' },
528
+ { num:35, name:'Atlantic Port Chain', kind:'route' },
529
+ { num:36, name:'Market Shock', kind:'card-shock' },
530
+ { num:37, name:'Armory Works', kind:'sys-strategic-industry' },
531
+ { num:38, name:'Speculation Scandal', kind:'tax' },
532
+ { num:39, name:'Shipbuilding Yard', kind:'sys-strategic-industry' },
533
+ ];
534
+
535
+ /* Demo state — fixed placeholders. */
536
+ const DEMO = {
537
+ ownership: {
538
+ 1: 'p1', // Continental Certificates
539
+ 3: 'p1', // Soldier Pay Notes
540
+ 6: 'p1', // Massachusetts Debt (Tier I)
541
+ 5: 'p1', // Northern Post Road
542
+ 9: 'p2', // Virginia Debt
543
+ 12: 'p2', // Bank of US
544
+ 21: 'p2', // Treasury Securities
545
+ 15: 'p3', // Western Turnpike
546
+ 25: 'p3', // Potomac Canal
547
+ 16: 'p3', // NY Harbor
548
+ 18: 'p3', // Philly Exchange
549
+ },
550
+ tiers: { 6: 1 }, // Mass Debt at Tier I
551
+ };
552
+
553
+ /* Position grid: maps each space number to (row, col) on an 11x11 board. */
554
+ function spaceCoord(num) {
555
+ // Bottom-right corner (0) = (10, 10); bottom row right→left for 1..9; bottom-left corner (10) = (10, 0);
556
+ // left col bottom→top for 11..19; top-left (20) = (0, 0); top row left→right for 21..29; top-right (30) = (0, 10);
557
+ // right col top→bottom for 31..39.
558
+ if (num === 0) return [10, 10];
559
+ if (num >= 1 && num <= 9) return [10, 10 - num];
560
+ if (num === 10) return [10, 0];
561
+ if (num >= 11 && num <= 19) return [10 - (num - 10), 0];
562
+ if (num === 20) return [0, 0];
563
+ if (num >= 21 && num <= 29) return [0, num - 20];
564
+ if (num === 30) return [0, 10];
565
+ if (num >= 31 && num <= 39) return [num - 30, 10];
566
+ }
567
+
568
+ /* Demo turn step definitions. Each step describes a panel state and adds ledger lines. */
569
+
570
+ /* Eleven-step walkthrough that fits the brief. */
571
+ const TURN_STEPS = [
572
+ {
573
+ n: 1, label: 'Start turn — Treasury Panel shows current cash + assets',
574
+ state: { cash: 1170, tokens: {p1:5, p2:12, p3:18}, panel:'treasury', active:null },
575
+ ledger: [],
576
+ },
577
+ {
578
+ n: 2, label: 'Roll dice — result 6',
579
+ state: { cash: 1170, tokens: {p1:5, p2:12, p3:18}, panel:'treasury', active:5 },
580
+ ledger: [ { actor:'You', event:'ROLL', detail:'2d6 = 6 (2 + 4) · deterministic seed for demo', cls:'move' } ],
581
+ },
582
+ {
583
+ n: 3, label: 'Move token — advance 6 spaces to Customs House',
584
+ state: { cash: 1170, tokens: {p1:11, p2:12, p3:18}, panel:'treasury', active:11 },
585
+ ledger: [ { actor:'You', event:'MOVE', detail:'Advance 6 → space 11 · Customs House (Revenue System, unowned)', cls:'move' } ],
586
+ },
587
+ {
588
+ n: 4, label: 'Land on unowned asset — Asset Inspector opens',
589
+ state: { cash: 1170, tokens: {p1:11, p2:12, p3:18}, panel:'inspector', active:11 },
590
+ ledger: [],
591
+ },
592
+ {
593
+ n: 5, label: 'Click Buy — Treasury debits 140 TN, asset marker appears',
594
+ state: { cash: 1030, tokens: {p1:11, p2:12, p3:18}, panel:'inspector', active:11, owned:{11:'p1'} },
595
+ ledger: [
596
+ { actor:'You', event:'BUY', detail:'Purchase Customs House for $140 · Revenue System set 1/3 · ledger logs purchase reason', cls:'cash' },
597
+ { actor:'Bank', event:'CASH', detail:'You: -$140 → cash $1,030', cls:'cash' },
598
+ ],
599
+ },
600
+ {
601
+ n: 6, label: 'Draw a Republic Debate card — Event Drawer opens',
602
+ state: { cash: 1030, tokens: {p1:11, p2:12, p3:18}, panel:'cardDrawer', active:11, owned:{11:'p1'} },
603
+ ledger: [
604
+ { actor:'You', event:'DRAW', detail:'Drew Republic Debate · "Credit Restored" · resolution pending', cls:'card' },
605
+ ],
606
+ },
607
+ {
608
+ n: 7, label: 'Resolve card — Public Credit +1, bond owners collect 150 TN',
609
+ state: { cash: 1180, tokens: {p1:11, p2:12, p3:18}, panel:'cardDrawer', active:11, owned:{11:'p1'}, credit: 10 },
610
+ ledger: [
611
+ { actor:'Bank', event:'CASH', detail:'You hold 3 qualifying bonds (Continental Certs, Soldier Pay, Mass Debt): collect 3 × $50 → +$150 · cash $1,030 → $1,180', cls:'cash' },
612
+ { actor:'Track', event:'CREDIT', detail:'Credit 9 → 10 · reason: "Credit Restored" card +1', cls:'track' },
613
+ ],
614
+ },
615
+ {
616
+ n: 8, label: 'Bank Charter vote opens — opponent ballots visible with reasons',
617
+ state: { cash: 1180, tokens: {p1:11, p2:12, p3:18}, panel:'acts', active:null, owned:{11:'p1'}, credit:10, voting:true },
618
+ ledger: [
619
+ { actor:'Acts', event:'VOTE', detail:'Bank Charter put to vote · Treasury/Finance ballot: YES (owns Bank) · Merchant/Infrastructure ballot: NO (no financial assets)', cls:'vote' },
620
+ ],
621
+ },
622
+ {
623
+ n: 9, label: 'You click YES — Bank Charter passes 2-1, tray updates',
624
+ state: { cash: 1180, tokens: {p1:11, p2:12, p3:18}, panel:'acts', active:null, owned:{11:'p1'}, credit:10, voting:'passed' },
625
+ ledger: [
626
+ { actor:'You', event:'VOTE', detail:'You vote YES · reason: Treasury Architect role rewards Public Credit', cls:'vote' },
627
+ { actor:'Acts', event:'PASS', detail:'Bank Charter PASSES 2-1 · Bank now pays 10×dice (was 4×dice) · Mint state enables 20×dice when paired', cls:'vote' },
628
+ ],
629
+ },
630
+ {
631
+ n: 10, label: 'End turn — Treasury, Tracks, Acts, Ledger reflect final state',
632
+ state: { cash: 1180, tokens: {p1:11, p2:12, p3:18}, panel:'treasury', active:null, owned:{11:'p1'}, credit:10, voting:'passed' },
633
+ ledger: [
634
+ { actor:'Turn', event:'END', detail:'Your turn ends · lap 3, turn 1 → turn 2', cls:'move' },
635
+ ],
636
+ },
637
+ {
638
+ n: 11, label: 'Next: Treasury / Finance (Hamilton) takes its turn',
639
+ state: { cash: 1180, tokens: {p1:11, p2:12, p3:18}, panel:'treasury', active:null, owned:{11:'p1'}, credit:10, voting:'passed' },
640
+ ledger: [
641
+ { actor:'Hamilton', event:'TURN', detail:'Scripted opponent 1 turn begins · profile: Treasury / Finance · expect priority buys on National Finance', cls:'move' },
642
+ ],
643
+ },
644
+ ];
645
+
646
+ /* ===========================================================
647
+ RENDER
648
+ =========================================================== */
649
+ function el(tag, cls, html) { const e = document.createElement(tag); if (cls) e.className = cls; if (html !== undefined) e.innerHTML = html; return e; }
650
+
651
+ const state = {
652
+ stepIndex: 0,
653
+ cash: 1170,
654
+ tokens: { p1: 5, p2: 12, p3: 18 },
655
+ panel: 'treasury',
656
+ active: null,
657
+ owned: {},
658
+ credit: 9,
659
+ voting: false,
660
+ ledger: [],
661
+ };
662
+
663
+ function renderBoard() {
664
+ const grid = document.getElementById('boardGrid');
665
+ grid.innerHTML = '';
666
+ // 11x11 grid (rows 0-10, cols 0-10). Cells on the border are spaces; center is the meta center.
667
+ for (let r = 0; r < 11; r++) {
668
+ for (let c = 0; c < 11; c++) {
669
+ if (r === 0 || r === 10 || c === 0 || c === 10) {
670
+ const space = findSpaceByCoord(r, c);
671
+ if (!space) {
672
+ const empty = el('div', 'board-cell empty');
673
+ empty.style.gridColumn = (c + 1) + ' / span 1';
674
+ empty.style.gridRow = (r + 1) + ' / span 1';
675
+ grid.appendChild(empty);
676
+ continue;
677
+ }
678
+ const cell = el('div', `board-cell ${space.kind}` + (space.kind === 'corner' ? ' corner' : ''));
679
+ cell.dataset.num = space.num;
680
+ cell.style.gridColumn = (c + 1) + ' / span 1';
681
+ cell.style.gridRow = (r + 1) + ' / span 1';
682
+ cell.innerHTML = `
683
+ <div class="band"></div>
684
+ <div class="num">${String(space.num).padStart(2,'0')}</div>
685
+ <div class="nm">${space.name}</div>
686
+ <div class="tokens"></div>
687
+ `;
688
+ cell.addEventListener('click', () => focusSpace(space.num));
689
+ grid.appendChild(cell);
690
+ }
691
+ }
692
+ }
693
+ // Center
694
+ const center = el('div', 'board-center');
695
+ center.style.gridColumn = '2 / span 9';
696
+ center.style.gridRow = '2 / span 9';
697
+ center.innerHTML = `
698
+ <div class="bc-eyebrow">Sovereign · Hamilton System</div>
699
+ <div class="bc-title">Founding Credit</div>
700
+ <div class="bc-sub">Fund the debt. Build the bank. Industrialize the republic.</div>
701
+ <div class="bc-lap" id="bcLap">Lap 3 / 7</div>
702
+ <div class="bc-tracks">
703
+ <span class="t credit">Credit<span class="v" id="bcCredit">9</span></span>
704
+ <span class="t resist">Resist<span class="v" id="bcResist">3</span></span>
705
+ <span class="t indust">Indust<span class="v" id="bcIndust">1</span></span>
706
+ </div>
707
+ <div class="bc-acts">Acts passed: I · Funding · II · Assumption</div>
708
+ `;
709
+ grid.appendChild(center);
710
+ applyOwnership();
711
+ placeTokens();
712
+ }
713
+
714
+ function findSpaceByCoord(r, c) {
715
+ return SPACES.find(s => {
716
+ const [sr, sc] = spaceCoord(s.num);
717
+ return sr === r && sc === c;
718
+ });
719
+ }
720
+
721
+ function applyOwnership() {
722
+ document.querySelectorAll('.board-cell').forEach(cell => {
723
+ cell.classList.remove('owner-p1', 'owner-p2', 'owner-p3', 'active-space');
724
+ cell.querySelectorAll('.owner-dot, .tier').forEach(e => e.remove());
725
+ });
726
+ const ownership = { ...DEMO.ownership, ...(state.owned || {}) };
727
+ Object.entries(ownership).forEach(([numStr, owner]) => {
728
+ const num = parseInt(numStr);
729
+ const cell = document.querySelector(`.board-cell[data-num="${num}"]`);
730
+ if (!cell) return;
731
+ cell.classList.add('owner-' + owner);
732
+ const dot = el('div', 'owner-dot');
733
+ cell.appendChild(dot);
734
+ if (DEMO.tiers[num]) {
735
+ const tier = el('div', 'tier', 'I'.repeat(DEMO.tiers[num]));
736
+ cell.appendChild(tier);
737
+ }
738
+ });
739
+ if (state.active != null) {
740
+ const cell = document.querySelector(`.board-cell[data-num="${state.active}"]`);
741
+ if (cell) cell.classList.add('active-space');
742
+ }
743
+ }
744
+
745
+ function placeTokens() {
746
+ document.querySelectorAll('.board-cell .tokens').forEach(t => t.innerHTML = '');
747
+ Object.entries(state.tokens).forEach(([player, num]) => {
748
+ const cell = document.querySelector(`.board-cell[data-num="${num}"] .tokens`);
749
+ if (!cell) return;
750
+ const tok = el('div', `tok ${player}`);
751
+ tok.title = `Token: ${player}`;
752
+ cell.appendChild(tok);
753
+ });
754
+ }
755
+
756
+ /* ===== Treasury holdings render ===== */
757
+ function renderTreasuryHoldings() {
758
+ const root = document.getElementById('treasuryHoldings');
759
+ root.innerHTML = '';
760
+ const yours = [
761
+ { num:1, name:'Continental Certificates', sys:'revolutionary-debt', tier:0 },
762
+ { num:3, name:'Soldier Pay Notes', sys:'revolutionary-debt', tier:0 },
763
+ { num:6, name:'Massachusetts Debt', sys:'state-debt', tier:1 },
764
+ { num:5, name:'Northern Post Road', sys:'route', tier:0 },
765
+ ];
766
+ if (state.owned && state.owned[11]) {
767
+ yours.push({ num:11, name:'Customs House', sys:'revenue-system', tier:0 });
768
+ }
769
+ yours.forEach(p => {
770
+ const row = el('div', 'h-row');
771
+ const tier = p.tier > 0 ? `<span class="tier-badge">T${'I'.repeat(p.tier)}</span>` : '<span class="tier-badge muted">—</span>';
772
+ const sw = p.sys === 'route' ? '<span class="sw" style="background:var(--ink)"></span>'
773
+ : `<span class="sw" style="background:var(--${p.sys})"></span>`;
774
+ row.innerHTML = `${sw}<span>${p.name}</span>${tier}`;
775
+ root.appendChild(row);
776
+ });
777
+ }
778
+
779
+ /* ===== Tracks scale render ===== */
780
+ function renderTrackScales() {
781
+ ['credit', 'resist', 'indust'].forEach(key => {
782
+ const scale = document.querySelector(`[data-track="${key}"]`);
783
+ if (!scale) return;
784
+ scale.innerHTML = '';
785
+ const startVals = { credit:9, resist:3, indust:1 };
786
+ let current = startVals[key];
787
+ if (key === 'credit' && state.credit) current = state.credit;
788
+ for (let i = 0; i <= 12; i++) {
789
+ const tk = el('div', 'tk' + (i%3 === 0 ? ' major' : '') + (i === current ? ' marker' : ''));
790
+ tk.textContent = i;
791
+ scale.appendChild(tk);
792
+ }
793
+ });
794
+ document.getElementById('creditVal').textContent = state.credit || 9;
795
+ document.getElementById('resistVal').textContent = 3;
796
+ document.getElementById('industVal').textContent = 1;
797
+ document.getElementById('bcCredit').textContent = state.credit || 9;
798
+ document.getElementById('bcResist').textContent = 3;
799
+ document.getElementById('bcIndust').textContent = 1;
800
+ }
801
+
802
+ /* ===== Inspector body ===== */
803
+ function renderInspector() {
804
+ document.getElementById('inspectorBody').innerHTML = `
805
+ <div class="card-art">
806
+ <div class="band" style="background:var(--revenue-system)">
807
+ <span>Revenue System</span>
808
+ <span style="font-family:var(--mono);font-size:9px;background:rgba(0,0,0,.2);border:1px solid rgba(255,255,255,.55);padding:1px 4px">06</span>
809
+ </div>
810
+ <div class="nm">Customs House</div>
811
+ <div class="subkind">Federal Tariff · Customs</div>
812
+ <div class="flavor">Tidewater offices where federal revenue first enters the Treasury, one bonded crate at a time.</div>
813
+ </div>
814
+ <table class="pay-table">
815
+ <thead><tr><th>Tier</th><th>Multiplier</th><th>Payment</th></tr></thead>
816
+ <tbody>
817
+ <tr><td>Base</td><td>—</td><td class="val">$10</td></tr>
818
+ <tr><td>Full Set</td><td>× 2</td><td class="val">$20</td></tr>
819
+ <tr><td>Tier I</td><td>× 5</td><td class="val">$50</td></tr>
820
+ <tr><td>Tier II</td><td>× 15</td><td class="val">$150</td></tr>
821
+ <tr><td>Tier III</td><td>× 30</td><td class="val">$300</td></tr>
822
+ </tbody>
823
+ </table>
824
+ <p style="font-family:var(--mono);font-size:9px;margin-top:4px;opacity:.75">Cost: $140 · Upgrade: $75 · Mortgage: $70 · <span class="placeholder">demo</span></p>
825
+ <div class="actions">
826
+ <button class="primary" onclick="buyCustomsHouse()">Buy $140</button>
827
+ <button onclick="declineAndAuction()">Decline → Auction</button>
828
+ </div>
829
+ <div class="historical">
830
+ <div class="lbl">Historical · Revenue System</div>
831
+ <div class="snip">Tidewater customs houses funded the federal government's first dependable revenue stream. Duties on imports — wine, cloth, sugar, manufactured goods — passed through 75 collectors at the principal ports, generating roughly $4 million a year by 1795.</div>
832
+ <div class="expand" onclick="alert('Expand → 160 words on Hamilton\\'s Tariff of 1789 · placeholder for Phase 3')">▸ Expand (160 words) — placeholder</div>
833
+ </div>
834
+ `;
835
+ }
836
+
837
+ /* ===== Card drawer body ===== */
838
+ function renderCardDrawer(resolved) {
839
+ document.getElementById('cardDrawerBody').innerHTML = `
840
+ <div class="drawer-card">
841
+ <div class="band"><span>Republic Debate</span><span>№ 03</span></div>
842
+ <div class="alert">✦ Broadside · Political Debate ✦</div>
843
+ <div class="nm">Credit Restored</div>
844
+ <div class="effect">Bond owners — <strong>Revolutionary Debt</strong>, <strong>State Debt</strong>, or <strong>National Finance</strong> — each collect <strong>$50 TN</strong>.</div>
845
+ <div class="outcomes"><span class="chip credit">Credit +1</span></div>
846
+ ${resolved ? `<div style="text-align:center;font-family:var(--ui);font-size:9px;letter-spacing:.18em;text-transform:uppercase;color:var(--commercial-infrastructure);font-weight:700;margin-top:4px">✓ Resolved — Public Credit moved 9 → 10</div>` : ''}
847
+ <div class="historical">
848
+ <div class="lbl">Historical · Credit Restored</div>
849
+ <div class="snip">The Funding and Assumption Acts of 1790 converted continental and state debt paper from near-worthless promises into federally guaranteed bonds. Foreign creditors in Amsterdam and London began quoting U.S. securities at par. Credit, the lifeblood of a modern state, had returned.</div>
850
+ </div>
851
+ ${!resolved ? `<div class="actions"><button class="primary" onclick="resolveCard()">Resolve effect</button></div>` : ''}
852
+ </div>
853
+ `;
854
+ }
855
+
856
+ /* ===== Acts panel votes ===== */
857
+ function renderActVotes() {
858
+ const root = document.getElementById('actVotes');
859
+ root.innerHTML = '';
860
+ const status = state.voting;
861
+
862
+ const votes = [
863
+ { who:'Hamilton', cls:'p2', reason:'Treasury / Finance · YES — owns Bank Subscription, Treasury Securities. Profile votes YES on all Acts that strengthen the Bank.', ballot: status ? 'yes' : 'pending' },
864
+ { who:'Morris', cls:'p3', reason:'Merchant / Infrastructure · NO — holds no financial assets. Profile votes NO on Acts that benefit a competitor exclusively.', ballot: status ? 'no' : 'pending' },
865
+ { who:'You', cls:'p1', reason: status === 'passed' ? 'YES — Treasury Architect role rewards Public Credit movement.' : 'choose: YES or NO below', ballot: status === 'passed' ? 'yes' : 'pending' },
866
+ ];
867
+
868
+ votes.forEach(v => {
869
+ const row = el('div', `vote-row ${v.cls} ${v.ballot === 'pending' ? 'pending' : ''}`);
870
+ row.innerHTML = `
871
+ <span class="pdot"></span>
872
+ <div><span class="who">${v.who}</span><span class="reason">${v.reason}</span></div>
873
+ <span class="ballot ${v.ballot}">${v.ballot === 'yes' ? 'YES' : v.ballot === 'no' ? 'NO' : '?'}</span>
874
+ `;
875
+ root.appendChild(row);
876
+ });
877
+
878
+ const youVote = document.getElementById('actYouVote');
879
+ youVote.innerHTML = '';
880
+ if (status === true) {
881
+ const yes = el('button', 'primary');
882
+ yes.textContent = '✓ Vote YES';
883
+ yes.onclick = () => { state.voting = 'passed'; addLedger([
884
+ { actor:'You', event:'VOTE', detail:'You vote YES · reason: Treasury Architect role rewards Public Credit', cls:'vote' },
885
+ { actor:'Acts', event:'PASS', detail:'Bank Charter PASSES 2-1 · Bank now pays 10×dice · Mint state enables 20×dice when paired', cls:'vote' },
886
+ ]); state.stepIndex = Math.max(state.stepIndex, 8); render(); };
887
+ const no = el('button');
888
+ no.textContent = '✕ Vote NO';
889
+ no.onclick = () => { state.voting = 'failed'; addLedger([
890
+ { actor:'You', event:'VOTE', detail:'You vote NO · result: 1-2 → demo continues as if YES', cls:'vote' },
891
+ ]); render(); };
892
+ youVote.appendChild(yes);
893
+ youVote.appendChild(no);
894
+ }
895
+
896
+ const tally = document.getElementById('actTally');
897
+ if (status === 'passed') {
898
+ tally.className = 'tally passed';
899
+ tally.textContent = 'BANK CHARTER PASSED · 2 YES / 1 NO · MAJORITY';
900
+ tally.classList.remove('hidden');
901
+ } else {
902
+ tally.classList.add('hidden');
903
+ }
904
+ }
905
+
906
+ /* ===== Ledger ===== */
907
+ function addLedger(rows) {
908
+ rows.forEach(r => state.ledger.push({ ...r, lap:3, turn:1 }));
909
+ }
910
+ function renderLedger() {
911
+ const root = document.getElementById('ledgerRows');
912
+ root.innerHTML = '';
913
+ if (state.ledger.length === 0) {
914
+ root.innerHTML = `<div style="font-family:var(--display);font-style:italic;font-size:11px;opacity:.6;padding:10px 4px">No entries yet. Each step adds entries with a human-readable reason line.</div>`;
915
+ } else {
916
+ state.ledger.slice().reverse().forEach((row, i) => {
917
+ const r = el('div', 'row ' + (row.cls || ''));
918
+ const stamp = `L${row.lap}·T${row.turn}·${state.ledger.length - i}`;
919
+ r.innerHTML = `<span class="stamp">${stamp}</span><span class="actor">${row.actor} · ${row.event}</span><span class="detail">${row.detail}</span>`;
920
+ root.appendChild(r);
921
+ });
922
+ }
923
+ document.getElementById('ledgerCount').textContent = state.ledger.length + ' entries';
924
+ }
925
+
926
+ /* ===== Panel switching ===== */
927
+ function showPanel(name) {
928
+ document.getElementById('panelInspector').classList.add('hidden');
929
+ document.getElementById('panelCardDrawer').classList.add('hidden');
930
+ if (name === 'inspector') document.getElementById('panelInspector').classList.remove('hidden');
931
+ if (name === 'cardDrawer') document.getElementById('panelCardDrawer').classList.remove('hidden');
932
+ }
933
+
934
+ function focusSpace(num) {
935
+ state.active = num;
936
+ applyOwnership();
937
+ }
938
+
939
+ /* ===== Step engine ===== */
940
+ function applyStep(i) {
941
+ const step = TURN_STEPS[i];
942
+ state.stepIndex = i;
943
+ state.cash = step.state.cash;
944
+ state.tokens = step.state.tokens;
945
+ state.active = step.state.active;
946
+ state.panel = step.state.panel;
947
+ state.owned = step.state.owned || {};
948
+ state.credit = step.state.credit || 9;
949
+ state.voting = step.state.voting || false;
950
+ step.ledger.forEach(r => state.ledger.push({ ...r, lap:3, turn:1 }));
951
+ render();
952
+ }
953
+
954
+ function nextStep() {
955
+ if (state.stepIndex >= TURN_STEPS.length - 1) return;
956
+ applyStep(state.stepIndex + 1);
957
+ }
958
+ function prevStep() {
959
+ if (state.stepIndex <= 0) return;
960
+ // hard reset and replay to step n-1 since ledger is cumulative
961
+ resetDemo();
962
+ for (let i = 1; i <= state.stepIndex; i++) applyStep(i);
963
+ }
964
+
965
+ function resetDemo() {
966
+ state.stepIndex = 0;
967
+ state.cash = 1170; state.tokens = { p1:5, p2:12, p3:18 }; state.panel = 'treasury'; state.active = null;
968
+ state.owned = {}; state.credit = 9; state.voting = false; state.ledger = [];
969
+ render();
970
+ }
971
+
972
+ function render() {
973
+ document.getElementById('treasuryCash').textContent = '$' + state.cash.toLocaleString('en-US');
974
+ document.getElementById('stepIndicator').textContent = `Step ${state.stepIndex + 1} / ${TURN_STEPS.length} — ${TURN_STEPS[state.stepIndex].label}`;
975
+ renderTreasuryHoldings();
976
+ renderTrackScales();
977
+ renderActVotes();
978
+ renderLedger();
979
+ showPanel(state.panel);
980
+ if (state.panel === 'inspector') renderInspector();
981
+ if (state.panel === 'cardDrawer') renderCardDrawer(state.credit > 9);
982
+ applyOwnership();
983
+ placeTokens();
984
+ }
985
+
986
+ /* ===== Inspector actions ===== */
987
+ function buyCustomsHouse() {
988
+ // jump to step 5 if not there
989
+ if (state.stepIndex < 4) applyStep(4);
990
+ }
991
+ function declineAndAuction() {
992
+ alert('Decline → opens auction with 10 TN open bid. Placeholder for Phase 3.');
993
+ }
994
+ function resolveCard() {
995
+ if (state.stepIndex < 6) applyStep(6);
996
+ }
997
+
998
+ /* ===== Endgame ===== */
999
+ function showEndgame() {
1000
+ document.getElementById('mainGrid').classList.add('hidden');
1001
+ document.getElementById('endgameView').classList.remove('hidden');
1002
+ }
1003
+ function hideEndgame() {
1004
+ document.getElementById('endgameView').classList.add('hidden');
1005
+ document.getElementById('mainGrid').classList.remove('hidden');
1006
+ }
1007
+
1008
+ /* ===== Init ===== */
1009
+ renderBoard();
1010
+ render();
1011
+
1012
+ document.getElementById('btnNext').addEventListener('click', nextStep);
1013
+ document.getElementById('btnPrev').addEventListener('click', prevStep);
1014
+ document.getElementById('btnReset').addEventListener('click', resetDemo);
1015
+ document.getElementById('btnEndgame').addEventListener('click', showEndgame);
1016
+ document.getElementById('btnReturnFromEndgame').addEventListener('click', hideEndgame);
1017
+
1018
+ document.addEventListener('keydown', (e) => {
1019
+ if (e.key === 'ArrowRight' || e.key === ' ') { e.preventDefault(); nextStep(); }
1020
+ if (e.key === 'ArrowLeft') { e.preventDefault(); prevStep(); }
1021
+ if (e.key === 'r' || e.key === 'R') resetDemo();
1022
+ });
1023
+ </script>
1024
+
1025
+ </body>
1026
+ </html>