@dreamboard-games/sdk 0.2.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 (369) hide show
  1. package/LICENSE.md +96 -0
  2. package/README.md +12 -0
  3. package/dist/HandView-ncJIVLhN.d.ts +193 -0
  4. package/dist/ResourceCounter-CTREyF73.d.ts +102 -0
  5. package/dist/ThemeProvider-fy0_QzgO.d.ts +99 -0
  6. package/dist/bundle-TIZcw8LB.d.ts +281 -0
  7. package/dist/cards-Sl3b40Mv.d.ts +13 -0
  8. package/dist/chunk-7YAHLYBR.js +481 -0
  9. package/dist/chunk-7YAHLYBR.js.map +1 -0
  10. package/dist/chunk-FDNZTDD6.js +8085 -0
  11. package/dist/chunk-FDNZTDD6.js.map +1 -0
  12. package/dist/chunk-GKKBPPSW.js +598 -0
  13. package/dist/chunk-GKKBPPSW.js.map +1 -0
  14. package/dist/chunk-I46YJSOD.js +1 -0
  15. package/dist/chunk-I46YJSOD.js.map +1 -0
  16. package/dist/chunk-KAELH4KC.js +104 -0
  17. package/dist/chunk-KAELH4KC.js.map +1 -0
  18. package/dist/chunk-PZ5AY32C.js +10 -0
  19. package/dist/chunk-PZ5AY32C.js.map +1 -0
  20. package/dist/chunk-T3ZKNUZ7.js +1 -0
  21. package/dist/chunk-T3ZKNUZ7.js.map +1 -0
  22. package/dist/chunk-T52J5RMF.js +1 -0
  23. package/dist/chunk-T52J5RMF.js.map +1 -0
  24. package/dist/chunk-TDSWKVZ4.js +5401 -0
  25. package/dist/chunk-TDSWKVZ4.js.map +1 -0
  26. package/dist/chunk-U5C6BONG.js +34 -0
  27. package/dist/chunk-U5C6BONG.js.map +1 -0
  28. package/dist/chunk-VDXOF4FW.js +69 -0
  29. package/dist/chunk-VDXOF4FW.js.map +1 -0
  30. package/dist/chunk-VFTAA4WO.js +115 -0
  31. package/dist/chunk-VFTAA4WO.js.map +1 -0
  32. package/dist/chunk-WN74KVNY.js +17 -0
  33. package/dist/chunk-WN74KVNY.js.map +1 -0
  34. package/dist/chunk-WYPQ3GG5.js +10990 -0
  35. package/dist/chunk-WYPQ3GG5.js.map +1 -0
  36. package/dist/components-D5ZRE2Hl.d.ts +1451 -0
  37. package/dist/generated/runtime/primitives.d.ts +12 -0
  38. package/dist/generated/runtime/primitives.js +180 -0
  39. package/dist/generated/runtime/primitives.js.map +1 -0
  40. package/dist/generated/runtime-api.d.ts +3 -0
  41. package/dist/generated/runtime-api.js +2 -0
  42. package/dist/generated/runtime-api.js.map +1 -0
  43. package/dist/generated/runtime.d.ts +14 -0
  44. package/dist/generated/runtime.js +18 -0
  45. package/dist/generated/runtime.js.map +1 -0
  46. package/dist/generated/workspace-contract.d.ts +14 -0
  47. package/dist/generated/workspace-contract.js +14 -0
  48. package/dist/generated/workspace-contract.js.map +1 -0
  49. package/dist/hex-board-view-D_07hO6O.d.ts +933 -0
  50. package/dist/hex-color-MhOyuY-o.d.ts +8 -0
  51. package/dist/index-BwqPQtBu.d.ts +1433 -0
  52. package/dist/index.d.ts +1 -0
  53. package/dist/index.js +12 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/infrastructure/reducer-bundle-abi.d.ts +1083 -0
  56. package/dist/infrastructure/reducer-bundle-abi.js +14 -0
  57. package/dist/infrastructure/reducer-bundle-abi.js.map +1 -0
  58. package/dist/infrastructure/workspace-codegen.d.ts +53 -0
  59. package/dist/infrastructure/workspace-codegen.js +44 -0
  60. package/dist/infrastructure/workspace-codegen.js.map +1 -0
  61. package/dist/manifest-contract-BNHVGFtU.d.ts +9 -0
  62. package/dist/package-set.d.ts +13 -0
  63. package/dist/package-set.js +12 -0
  64. package/dist/package-set.js.map +1 -0
  65. package/dist/primitive-props-DpKs-GCr.d.ts +11 -0
  66. package/dist/reducer.d.ts +3786 -0
  67. package/dist/reducer.js +8131 -0
  68. package/dist/reducer.js.map +1 -0
  69. package/dist/runtime/primitives.d.ts +226 -0
  70. package/dist/runtime/primitives.js +180 -0
  71. package/dist/runtime/primitives.js.map +1 -0
  72. package/dist/runtime/types/runtime-api.d.ts +1 -0
  73. package/dist/runtime/types/runtime-api.js +2 -0
  74. package/dist/runtime/types/runtime-api.js.map +1 -0
  75. package/dist/runtime/workspace-contract.d.ts +172 -0
  76. package/dist/runtime/workspace-contract.js +14 -0
  77. package/dist/runtime/workspace-contract.js.map +1 -0
  78. package/dist/runtime-api-3dshj6kK.d.ts +101 -0
  79. package/dist/runtime-api-DWxvTr-O.d.ts +379 -0
  80. package/dist/runtime.d.ts +58 -0
  81. package/dist/runtime.js +13 -0
  82. package/dist/runtime.js.map +1 -0
  83. package/dist/slots-1GPGihk8.d.ts +8 -0
  84. package/dist/testing.d.ts +149 -0
  85. package/dist/testing.js +513 -0
  86. package/dist/testing.js.map +1 -0
  87. package/dist/types.d.ts +496 -0
  88. package/dist/types.js +28 -0
  89. package/dist/types.js.map +1 -0
  90. package/dist/ui/components.d.ts +16 -0
  91. package/dist/ui/components.js +192 -0
  92. package/dist/ui/components.js.map +1 -0
  93. package/dist/ui/defaults.d.ts +19 -0
  94. package/dist/ui/defaults.js +104 -0
  95. package/dist/ui/defaults.js.map +1 -0
  96. package/dist/ui/plugin-styles.css +250 -0
  97. package/dist/ui/types/player-state.d.ts +365 -0
  98. package/dist/ui/types/player-state.js +1 -0
  99. package/dist/ui/types/player-state.js.map +1 -0
  100. package/dist/ui-contract-iQfTtUSL.d.ts +1161 -0
  101. package/dist/ui.d.ts +320 -0
  102. package/dist/ui.js +253 -0
  103. package/dist/ui.js.map +1 -0
  104. package/package.json +199 -0
  105. package/src/generated/reducer-contract/builders.ts +90 -0
  106. package/src/generated/reducer-contract/version.ts +9 -0
  107. package/src/generated/reducer-contract/wire.ts +100 -0
  108. package/src/generated/reducer-contract/zod.ts +101 -0
  109. package/src/generated/runtime/primitives.ts +2 -0
  110. package/src/generated/runtime-api.ts +5 -0
  111. package/src/generated/runtime.ts +35 -0
  112. package/src/generated/workspace-contract.ts +2 -0
  113. package/src/index.ts +7 -0
  114. package/src/infrastructure/reducer-bundle-abi.ts +8 -0
  115. package/src/infrastructure/reducer-contract/bundle.ts +37 -0
  116. package/src/infrastructure/workspace-codegen/hex-geometry.ts +69 -0
  117. package/src/infrastructure/workspace-codegen/index.ts +64 -0
  118. package/src/infrastructure/workspace-codegen/manifest-contract.ts +6632 -0
  119. package/src/infrastructure/workspace-codegen/manifest-validation.ts +795 -0
  120. package/src/infrastructure/workspace-codegen/ownership.ts +131 -0
  121. package/src/infrastructure/workspace-codegen/preset-card-sets.ts +169 -0
  122. package/src/infrastructure/workspace-codegen/seeds.ts +1705 -0
  123. package/src/infrastructure/workspace-codegen.ts +1 -0
  124. package/src/package-set.ts +19 -0
  125. package/src/reducer/authoring/contract.ts +157 -0
  126. package/src/reducer/authoring/effect.ts +224 -0
  127. package/src/reducer/authoring/game.ts +23 -0
  128. package/src/reducer/authoring/interaction.ts +98 -0
  129. package/src/reducer/authoring/phase.ts +300 -0
  130. package/src/reducer/authoring/types.ts +70 -0
  131. package/src/reducer/authoring/validation.ts +382 -0
  132. package/src/reducer/authoring/view-stage.ts +68 -0
  133. package/src/reducer/authoring.ts +29 -0
  134. package/src/reducer/bundle/ingress-bundle.ts +491 -0
  135. package/src/reducer/bundle/trusted/engine-instruction-resolver.ts +254 -0
  136. package/src/reducer/bundle/trusted/flow-instruction-resolver.ts +73 -0
  137. package/src/reducer/bundle/trusted/instruction-runner.ts +414 -0
  138. package/src/reducer/bundle/trusted/interaction-authorization.ts +137 -0
  139. package/src/reducer/bundle/trusted/interaction-collectors.ts +859 -0
  140. package/src/reducer/bundle/trusted/interaction-decision.ts +747 -0
  141. package/src/reducer/bundle/trusted/interaction-resolver.ts +95 -0
  142. package/src/reducer/bundle/trusted/interaction-types.ts +171 -0
  143. package/src/reducer/bundle/trusted/lifecycle-runner.ts +427 -0
  144. package/src/reducer/bundle/trusted/projection-builder.ts +356 -0
  145. package/src/reducer/bundle/trusted/projection-context.ts +39 -0
  146. package/src/reducer/bundle/trusted/rng-sampler.ts +150 -0
  147. package/src/reducer/bundle/trusted/runtime-registry.ts +120 -0
  148. package/src/reducer/bundle/trusted/runtime-scope.ts +336 -0
  149. package/src/reducer/bundle/trusted/simultaneous-player.ts +97 -0
  150. package/src/reducer/bundle/trusted/stage-resolver.ts +87 -0
  151. package/src/reducer/bundle/trusted/static-projection.ts +116 -0
  152. package/src/reducer/bundle/trusted/trusted-runtime-args.ts +97 -0
  153. package/src/reducer/bundle/trusted/trusted-runtime-result.ts +39 -0
  154. package/src/reducer/bundle/trusted/trusted-setup-profiles.ts +43 -0
  155. package/src/reducer/bundle/trusted/trusted-state-codec.ts +48 -0
  156. package/src/reducer/bundle/trusted-bundle.ts +97 -0
  157. package/src/reducer/bundle/types.ts +171 -0
  158. package/src/reducer/bundle.ts +2 -0
  159. package/src/reducer/client-param-schemas.ts +57 -0
  160. package/src/reducer/compose.ts +34 -0
  161. package/src/reducer/core/runtime-input.ts +30 -0
  162. package/src/reducer/core/runtime-instruction.ts +59 -0
  163. package/src/reducer/core/types.ts +62 -0
  164. package/src/reducer/definition-index.ts +277 -0
  165. package/src/reducer/derived.ts +106 -0
  166. package/src/reducer/effects.ts +92 -0
  167. package/src/reducer/engine/runtime-instruction-engine.ts +155 -0
  168. package/src/reducer/ingress/decode-runtime-input.ts +7 -0
  169. package/src/reducer/ingress/decode-session-state.ts +9 -0
  170. package/src/reducer/ingress/encode-session-state.ts +6 -0
  171. package/src/reducer/ingress/input-codec.ts +18 -0
  172. package/src/reducer/ingress/phase-schemas.ts +62 -0
  173. package/src/reducer/ingress/raw-types.ts +107 -0
  174. package/src/reducer/ingress/runtime-codec.ts +14 -0
  175. package/src/reducer/ingress/runtime-payload.ts +13 -0
  176. package/src/reducer/ingress/session-codec.ts +392 -0
  177. package/src/reducer/ingress/types.ts +6 -0
  178. package/src/reducer/inputs/boardInput.ts +217 -0
  179. package/src/reducer/inputs/boardTarget.ts +190 -0
  180. package/src/reducer/inputs/cardInput.ts +86 -0
  181. package/src/reducer/inputs/cardTarget.ts +101 -0
  182. package/src/reducer/inputs/choiceTarget.ts +104 -0
  183. package/src/reducer/inputs/defineInputs.ts +71 -0
  184. package/src/reducer/inputs/formInput.ts +809 -0
  185. package/src/reducer/inputs/many.ts +120 -0
  186. package/src/reducer/inputs/promptInput.ts +87 -0
  187. package/src/reducer/inputs/rngInput.ts +58 -0
  188. package/src/reducer/inputs/targetRule.ts +123 -0
  189. package/src/reducer/inputs.ts +41 -0
  190. package/src/reducer/model/definition.ts +1072 -0
  191. package/src/reducer/model/extract.ts +745 -0
  192. package/src/reducer/model/manifest.ts +570 -0
  193. package/src/reducer/model/queries.ts +641 -0
  194. package/src/reducer/model/runtime.ts +264 -0
  195. package/src/reducer/model/spec.ts +1386 -0
  196. package/src/reducer/model/table.ts +260 -0
  197. package/src/reducer/model.ts +7 -0
  198. package/src/reducer/ops.ts +1034 -0
  199. package/src/reducer/parse-utils.ts +28 -0
  200. package/src/reducer/per-player.ts +422 -0
  201. package/src/reducer/rng.ts +69 -0
  202. package/src/reducer/schema-helpers.ts +185 -0
  203. package/src/reducer/setup-bootstrap-helpers.ts +171 -0
  204. package/src/reducer/setup-bootstrap.ts +481 -0
  205. package/src/reducer/table-ops.ts +2671 -0
  206. package/src/reducer/table-queries.ts +372 -0
  207. package/src/reducer/transaction.ts +120 -0
  208. package/src/reducer.ts +314 -0
  209. package/src/runtime/primitives.ts +1 -0
  210. package/src/runtime/types/runtime-api.ts +1 -0
  211. package/src/runtime/workspace-contract.ts +32 -0
  212. package/src/runtime-internal/components/InteractionForm.tsx +1309 -0
  213. package/src/runtime-internal/components/PluginRuntime.tsx +103 -0
  214. package/src/runtime-internal/components/board/target-layer.ts +70 -0
  215. package/src/runtime-internal/context/ClientParamSchemaContext.tsx +44 -0
  216. package/src/runtime-internal/context/InteractionDraftContext.tsx +279 -0
  217. package/src/runtime-internal/context/PluginSessionContext.tsx +47 -0
  218. package/src/runtime-internal/context/PluginStateContext.tsx +262 -0
  219. package/src/runtime-internal/context/RuntimeContext.tsx +96 -0
  220. package/src/runtime-internal/defaults/components.tsx +409 -0
  221. package/src/runtime-internal/defaults/index.ts +11 -0
  222. package/src/runtime-internal/errors/ValidationError.ts +29 -0
  223. package/src/runtime-internal/hooks/useActivePlayers.ts +33 -0
  224. package/src/runtime-internal/hooks/useBoardInteractions.ts +665 -0
  225. package/src/runtime-internal/hooks/useGameSelector.ts +105 -0
  226. package/src/runtime-internal/hooks/useGameView.ts +9 -0
  227. package/src/runtime-internal/hooks/useInteractionByKey.ts +354 -0
  228. package/src/runtime-internal/hooks/useInteractionHandle.ts +438 -0
  229. package/src/runtime-internal/hooks/useIsMyTurn.ts +20 -0
  230. package/src/runtime-internal/hooks/useLobby.ts +76 -0
  231. package/src/runtime-internal/hooks/useMe.ts +48 -0
  232. package/src/runtime-internal/hooks/usePlayerInfo.ts +28 -0
  233. package/src/runtime-internal/hooks/usePlayerTurnOrder.ts +23 -0
  234. package/src/runtime-internal/hooks/usePluginRuntime.ts +147 -0
  235. package/src/runtime-internal/hooks/useSeatInbox.ts +61 -0
  236. package/src/runtime-internal/hooks/useSimultaneousPhase.ts +10 -0
  237. package/src/runtime-internal/index.ts +42 -0
  238. package/src/runtime-internal/internal.ts +43 -0
  239. package/src/runtime-internal/plugin-styles.css +250 -0
  240. package/src/runtime-internal/primitives/board.tsx +459 -0
  241. package/src/runtime-internal/primitives/dialog-lifecycle.ts +58 -0
  242. package/src/runtime-internal/primitives/dice.tsx +79 -0
  243. package/src/runtime-internal/primitives/game-ui-provider.tsx +35 -0
  244. package/src/runtime-internal/primitives/game.tsx +387 -0
  245. package/src/runtime-internal/primitives/hand-intent-adapter.ts +147 -0
  246. package/src/runtime-internal/primitives/hand-surface.tsx +594 -0
  247. package/src/runtime-internal/primitives/index.ts +196 -0
  248. package/src/runtime-internal/primitives/interaction-form-binding.tsx +56 -0
  249. package/src/runtime-internal/primitives/interaction-submit.ts +90 -0
  250. package/src/runtime-internal/primitives/interaction.tsx +987 -0
  251. package/src/runtime-internal/primitives/phase.tsx +43 -0
  252. package/src/runtime-internal/primitives/player-roster.tsx +302 -0
  253. package/src/runtime-internal/primitives/primitive-props.tsx +101 -0
  254. package/src/runtime-internal/primitives/prompt.tsx +255 -0
  255. package/src/runtime-internal/primitives/ui.tsx +60 -0
  256. package/src/runtime-internal/primitives/zone.tsx +791 -0
  257. package/src/runtime-internal/reducer.ts +30 -0
  258. package/src/runtime-internal/runtime/createPluginRuntimeAPI.ts +605 -0
  259. package/src/runtime-internal/types/plugin-state.ts +508 -0
  260. package/src/runtime-internal/types/reducer-state.ts +24 -0
  261. package/src/runtime-internal/types/runtime-api.ts +114 -0
  262. package/src/runtime-internal/ui-contract.ts +519 -0
  263. package/src/runtime-internal/utils/card-intent-adapter.ts +546 -0
  264. package/src/runtime-internal/utils/interaction-inputs.ts +492 -0
  265. package/src/runtime-internal/utils/interaction-labels.ts +23 -0
  266. package/src/runtime-internal/utils/interaction-router.ts +273 -0
  267. package/src/runtime-internal/utils/interaction-status.ts +74 -0
  268. package/src/runtime-internal/workspace-contract.ts +1170 -0
  269. package/src/runtime.ts +34 -0
  270. package/src/testing/create-expect-api.ts +352 -0
  271. package/src/testing/create-test-runtime.ts +381 -0
  272. package/src/testing/definitions.ts +127 -0
  273. package/src/testing/index.ts +3 -0
  274. package/src/testing.ts +1 -0
  275. package/src/type-stubs/manifest-contract.d.ts +42 -0
  276. package/src/type-stubs/manifest-contract.js +72 -0
  277. package/src/type-stubs/ui-contract.d.ts +5 -0
  278. package/src/type-stubs/ui-contract.js +1 -0
  279. package/src/types/authoring-card-properties.type-test.ts +266 -0
  280. package/src/types/authoring.ts +1282 -0
  281. package/src/types/cards.ts +19 -0
  282. package/src/types/contracts.ts +1550 -0
  283. package/src/types/generated-helpers.ts +35 -0
  284. package/src/types/index.ts +147 -0
  285. package/src/types/slots.ts +11 -0
  286. package/src/types.ts +1 -0
  287. package/src/ui/components/ActionButton.tsx +97 -0
  288. package/src/ui/components/ActionPanel.tsx +315 -0
  289. package/src/ui/components/Card.tsx +378 -0
  290. package/src/ui/components/CardDragSurface.tsx +1076 -0
  291. package/src/ui/components/ChromeSuppressionContext.tsx +70 -0
  292. package/src/ui/components/CostDisplay.tsx +145 -0
  293. package/src/ui/components/DiceRoller.tsx +581 -0
  294. package/src/ui/components/Drawer.tsx +180 -0
  295. package/src/ui/components/ErrorBoundary.tsx +275 -0
  296. package/src/ui/components/GameEndDisplay.tsx +398 -0
  297. package/src/ui/components/GameSkeleton.tsx +260 -0
  298. package/src/ui/components/Hand.tsx +468 -0
  299. package/src/ui/components/HandDock.tsx +299 -0
  300. package/src/ui/components/HandView.tsx +441 -0
  301. package/src/ui/components/MobileHandTray.tsx +381 -0
  302. package/src/ui/components/MoreActions.tsx +143 -0
  303. package/src/ui/components/PhaseIndicator.tsx +341 -0
  304. package/src/ui/components/PlayArea.tsx +146 -0
  305. package/src/ui/components/PrimaryActionButton.tsx +336 -0
  306. package/src/ui/components/PrimaryButton.tsx +45 -0
  307. package/src/ui/components/ResourceCounter.tsx +270 -0
  308. package/src/ui/components/StagingZone.tsx +134 -0
  309. package/src/ui/components/ThemedButton.tsx +113 -0
  310. package/src/ui/components/Toast.tsx +264 -0
  311. package/src/ui/components/board/HexGrid.tsx +1294 -0
  312. package/src/ui/components/board/NetworkGraph.tsx +476 -0
  313. package/src/ui/components/board/SlotSystem.tsx +388 -0
  314. package/src/ui/components/board/SquareGrid.tsx +1165 -0
  315. package/src/ui/components/board/TrackBoard.tsx +496 -0
  316. package/src/ui/components/board/ZoneMap.tsx +448 -0
  317. package/src/ui/components/board/hex-board-view.ts +123 -0
  318. package/src/ui/components/board/index.ts +142 -0
  319. package/src/ui/components/board/interaction-accessibility.ts +21 -0
  320. package/src/ui/components/board/target-layer.ts +66 -0
  321. package/src/ui/components/card-render-content.type-test.ts +27 -0
  322. package/src/ui/components/hand-layout-math.ts +163 -0
  323. package/src/ui/components/hand-pointer-engine.ts +413 -0
  324. package/src/ui/components/index.ts +245 -0
  325. package/src/ui/components.ts +1 -0
  326. package/src/ui/defaults/components.tsx +106 -0
  327. package/src/ui/defaults/index.ts +8 -0
  328. package/src/ui/defaults.ts +1 -0
  329. package/src/ui/errors/ValidationError.ts +29 -0
  330. package/src/ui/helpers/cards.ts +19 -0
  331. package/src/ui/helpers/track-board.ts +211 -0
  332. package/src/ui/hooks/useBoardTopology.ts +316 -0
  333. package/src/ui/hooks/useCards.ts +10 -0
  334. package/src/ui/hooks/useHandCardPointer.ts +381 -0
  335. package/src/ui/hooks/useHandLayout.ts +378 -0
  336. package/src/ui/hooks/useHandPresentation.ts +121 -0
  337. package/src/ui/hooks/useHexBoard.ts +74 -0
  338. package/src/ui/hooks/useHexGrid.ts +185 -0
  339. package/src/ui/hooks/useIsMobile.ts +35 -0
  340. package/src/ui/hooks/usePanZoom.ts +278 -0
  341. package/src/ui/hooks/useSquareBoard.ts +124 -0
  342. package/src/ui/hooks/useSquareGrid.ts +328 -0
  343. package/src/ui/index.ts +98 -0
  344. package/src/ui/internal/ui/alert.tsx +51 -0
  345. package/src/ui/internal/ui/button.tsx +58 -0
  346. package/src/ui/internal/ui/dialog.tsx +134 -0
  347. package/src/ui/internal/ui/input.tsx +21 -0
  348. package/src/ui/internal/ui/label.tsx +21 -0
  349. package/src/ui/internal/ui/select.tsx +129 -0
  350. package/src/ui/internal/ui/tooltip.tsx +54 -0
  351. package/src/ui/internal/ui/utils.ts +5 -0
  352. package/src/ui/plugin-styles.css +250 -0
  353. package/src/ui/primitives/dialog-lifecycle.ts +58 -0
  354. package/src/ui/primitives/dice.tsx +79 -0
  355. package/src/ui/primitives/primitive-props.tsx +101 -0
  356. package/src/ui/theme/ThemeProvider.tsx +252 -0
  357. package/src/ui/theme/board.ts +61 -0
  358. package/src/ui/theme/css-vars.ts +105 -0
  359. package/src/ui/theme/derive.ts +240 -0
  360. package/src/ui/theme/index.ts +61 -0
  361. package/src/ui/theme/presets/arcade.ts +261 -0
  362. package/src/ui/theme/presets/studio.ts +261 -0
  363. package/src/ui/theme/presets/tabletop.ts +266 -0
  364. package/src/ui/theme/tokens.ts +392 -0
  365. package/src/ui/types/hex-color.ts +20 -0
  366. package/src/ui/types/player-state.ts +463 -0
  367. package/src/ui/types/tiled-board.ts +785 -0
  368. package/src/ui/types/visual-state.ts +137 -0
  369. package/src/ui.ts +1 -0
@@ -0,0 +1,381 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ type ReactNode,
10
+ } from "react";
11
+ import { useDrag } from "@use-gesture/react";
12
+ import { clsx } from "clsx";
13
+ import { useIsMobile } from "../hooks/useIsMobile.js";
14
+ import { useThemeCssVars } from "../theme/ThemeProvider.js";
15
+
16
+ export type HandRole = "primary" | "auxiliary" | "task";
17
+
18
+ export interface MobileHandRegistration {
19
+ id: string;
20
+ zone: string;
21
+ label: string;
22
+ role: HandRole;
23
+ order?: number;
24
+ version: string;
25
+ count: number;
26
+ active: boolean;
27
+ autoOpen: boolean;
28
+ content: ReactNode;
29
+ }
30
+
31
+ interface MobileHandTrayContextValue {
32
+ registerHand: (hand: MobileHandRegistration) => () => void;
33
+ }
34
+
35
+ const MobileHandTrayContext = createContext<MobileHandTrayContextValue | null>(
36
+ null,
37
+ );
38
+
39
+ // Kept separate from the registration context on purpose: this value changes
40
+ // whenever the tray opens/closes or hands (de)register, whereas `registerHand`
41
+ // must stay referentially stable so `useRegisterMobileHand`'s effect does not
42
+ // re-run and thrash registrations.
43
+ const MobileHandTrayStateContext = createContext<{ active: boolean }>({
44
+ active: false,
45
+ });
46
+
47
+ const ROLE_PRIORITY: Record<HandRole, number> = {
48
+ task: 0,
49
+ primary: 1,
50
+ auxiliary: 2,
51
+ };
52
+
53
+ const TRAY_CONTENT_ID = "dreamboard-mobile-hand-tray";
54
+
55
+ /** Dock snap heights, ordered collapsed → tall. */
56
+ const DOCK_SNAPS = ["peek", "raised", "expanded"] as const;
57
+ type DockSnap = (typeof DOCK_SNAPS)[number];
58
+ const DOCK_SNAP_MAX_HEIGHT: Record<DockSnap, number | string> = {
59
+ peek: 0,
60
+ raised: "min(52vh, 440px)",
61
+ expanded: "82vh",
62
+ };
63
+
64
+ export function MobileHandTrayProvider({ children }: { children: ReactNode }) {
65
+ const [handsById, setHandsById] = useState<
66
+ ReadonlyMap<string, MobileHandRegistration>
67
+ >(() => new Map());
68
+ const isMobile = useIsMobile();
69
+ const registerHand = useCallback((hand: MobileHandRegistration) => {
70
+ setHandsById((current) => {
71
+ const previous = current.get(hand.id);
72
+ if (
73
+ previous &&
74
+ previous.zone === hand.zone &&
75
+ previous.label === hand.label &&
76
+ previous.role === hand.role &&
77
+ previous.order === hand.order &&
78
+ previous.version === hand.version &&
79
+ previous.count === hand.count &&
80
+ previous.active === hand.active &&
81
+ previous.autoOpen === hand.autoOpen
82
+ ) {
83
+ return current;
84
+ }
85
+ const next = new Map(current);
86
+ next.set(hand.id, hand);
87
+ return next;
88
+ });
89
+ return () => {
90
+ setHandsById((current) => {
91
+ if (!current.has(hand.id)) return current;
92
+ const next = new Map(current);
93
+ next.delete(hand.id);
94
+ return next;
95
+ });
96
+ };
97
+ }, []);
98
+ const value = useMemo<MobileHandTrayContextValue>(
99
+ () => ({ registerHand }),
100
+ [registerHand],
101
+ );
102
+ const hands = useMemo(
103
+ () =>
104
+ [...handsById.values()].sort(
105
+ (a, b) =>
106
+ (a.order ?? ROLE_PRIORITY[a.role]) -
107
+ (b.order ?? ROLE_PRIORITY[b.role]) ||
108
+ ROLE_PRIORITY[a.role] - ROLE_PRIORITY[b.role] ||
109
+ a.label.localeCompare(b.label),
110
+ ),
111
+ [handsById],
112
+ );
113
+
114
+ const trayActive = isMobile && hands.length > 0;
115
+ const stateValue = useMemo(() => ({ active: trayActive }), [trayActive]);
116
+
117
+ return (
118
+ <MobileHandTrayContext.Provider value={value}>
119
+ <MobileHandTrayStateContext.Provider value={stateValue}>
120
+ <div
121
+ data-dreamboard-mobile-hand-shell=""
122
+ data-mobile-hand-count={hands.length}
123
+ data-mobile-hand-tray-active={trayActive ? "true" : undefined}
124
+ style={{
125
+ minHeight: "100%",
126
+ paddingBottom: trayActive
127
+ ? "calc(92px + env(safe-area-inset-bottom, 0px))"
128
+ : undefined,
129
+ }}
130
+ >
131
+ {children}
132
+ </div>
133
+ {trayActive ? <MobileHandTray hands={hands} /> : null}
134
+ </MobileHandTrayStateContext.Provider>
135
+ </MobileHandTrayContext.Provider>
136
+ );
137
+ }
138
+
139
+ export function useRegisterMobileHand(hand: MobileHandRegistration): void {
140
+ const context = useContext(MobileHandTrayContext);
141
+ if (!context) {
142
+ throw new Error(
143
+ "Generated hand surfaces must be rendered inside <UI.Root>; mobile hand tray registration is unavailable.",
144
+ );
145
+ }
146
+ useEffect(() => context.registerHand(hand), [context, hand]);
147
+ }
148
+
149
+ /**
150
+ * Whether the mobile hand tray is currently presenting hands — i.e. the
151
+ * viewport is below the mobile breakpoint and at least one primary/auxiliary
152
+ * hand has registered. Authors can use this to drop redundant inline hand
153
+ * chrome (labels, framing) that the tray already provides, instead of guessing
154
+ * the breakpoint with a CSS media query. Returns `false` outside `<UI.Root>`.
155
+ */
156
+ export function useMobileHandTrayActive(): boolean {
157
+ return useContext(MobileHandTrayStateContext).active;
158
+ }
159
+
160
+ function MobileHandTray({
161
+ hands,
162
+ }: {
163
+ hands: readonly MobileHandRegistration[];
164
+ }) {
165
+ // Snap height of the dock. `peek` is the collapsed handle bar; `raised` shows
166
+ // the hand at an actionable height; `expanded` opens it tall for a long hand.
167
+ // The dock is always mounted and never modal — there is no scrim, and only
168
+ // the dock surface captures pointer events, so the board behind it stays
169
+ // visible and interactive.
170
+ const [snap, setSnap] = useState<DockSnap>("peek");
171
+ const open = snap !== "peek";
172
+ const advanceSnap = useCallback((direction: 1 | -1) => {
173
+ setSnap((current) => {
174
+ const index = DOCK_SNAPS.indexOf(current);
175
+ const next = Math.min(
176
+ DOCK_SNAPS.length - 1,
177
+ Math.max(0, index + direction),
178
+ );
179
+ return DOCK_SNAPS[next] ?? current;
180
+ });
181
+ }, []);
182
+ const [activeId, setActiveId] = useState<string | null>(null);
183
+ const themeCssVars = useThemeCssVars();
184
+
185
+ const preferredHand =
186
+ hands.find((hand) => hand.id === activeId) ??
187
+ hands.find((hand) => hand.active) ??
188
+ hands.find((hand) => hand.role === "primary") ??
189
+ hands[0] ??
190
+ null;
191
+ const selectedHand =
192
+ hands.find((hand) => hand.id === activeId) ?? preferredHand;
193
+
194
+ useEffect(() => {
195
+ if (!preferredHand) {
196
+ setActiveId(null);
197
+ return;
198
+ }
199
+ setActiveId((current) =>
200
+ current && hands.some((hand) => hand.id === current)
201
+ ? current
202
+ : preferredHand.id,
203
+ );
204
+ }, [hands, preferredHand]);
205
+
206
+ // Auto-raise to the actionable height when it becomes this seat's turn to
207
+ // act, and settle back to the peek when the turn passes. We only toggle on
208
+ // the active transition, so a manual expand/collapse sticks until the turn
209
+ // changes.
210
+ const active = selectedHand?.active ?? false;
211
+ const prevActiveRef = useRef(false);
212
+ useEffect(() => {
213
+ if (active !== prevActiveRef.current) {
214
+ prevActiveRef.current = active;
215
+ setSnap(active ? "raised" : "peek");
216
+ }
217
+ }, [active]);
218
+
219
+ // Swipe the handle up to expand a step (peek → raised → expanded), down to
220
+ // collapse a step. Taps fall through to the handle's onClick toggle
221
+ // (filterTaps), and velocity lets a quick flick advance even on a short drag.
222
+ const bindDrag = useDrag(
223
+ ({ movement: [, my], velocity: [, vy], last }) => {
224
+ if (!last) return;
225
+ if (my < -24 || (my < 0 && vy > 0.4)) advanceSnap(1);
226
+ else if (my > 24 || (my > 0 && vy > 0.4)) advanceSnap(-1);
227
+ },
228
+ { axis: "y", filterTaps: true },
229
+ );
230
+
231
+ if (!selectedHand) return null;
232
+
233
+ const activeBadges = hands.filter(
234
+ (hand) => hand.id !== selectedHand.id && hand.active,
235
+ );
236
+
237
+ return (
238
+ <div
239
+ data-dreamboard-mobile-hand-tray=""
240
+ data-state={snap}
241
+ data-active-hand={selectedHand.id}
242
+ style={{
243
+ ...themeCssVars,
244
+ position: "fixed",
245
+ left: "env(safe-area-inset-left, 0px)",
246
+ right: "env(safe-area-inset-right, 0px)",
247
+ bottom: 0,
248
+ zIndex: 900,
249
+ display: "flex",
250
+ justifyContent: "center",
251
+ // Non-modal: the wrapper ignores pointers so taps land on the board;
252
+ // only the dock panel below opts back in.
253
+ pointerEvents: "none",
254
+ }}
255
+ >
256
+ <div
257
+ className="flex w-full flex-col overflow-hidden rounded-t-2xl"
258
+ style={{
259
+ pointerEvents: "auto",
260
+ maxWidth: "min(40rem, 100%)",
261
+ background: "var(--background, #fdfbf7)",
262
+ color: "var(--foreground, #2d2d2d)",
263
+ borderTop: "1px solid var(--border, rgba(45,45,45,0.18))",
264
+ borderLeft: "1px solid var(--border, rgba(45,45,45,0.10))",
265
+ borderRight: "1px solid var(--border, rgba(45,45,45,0.10))",
266
+ boxShadow:
267
+ "0 -18px 48px -22px rgba(45,45,45,0.30), 0 -6px 18px -16px rgba(45,45,45,0.20)",
268
+ }}
269
+ >
270
+ {/* Handle + summary bar — drag or tap to toggle peek/open. */}
271
+ <button
272
+ type="button"
273
+ {...bindDrag()}
274
+ onClick={() =>
275
+ setSnap((value) => (value === "peek" ? "raised" : "peek"))
276
+ }
277
+ aria-controls={TRAY_CONTENT_ID}
278
+ aria-expanded={open}
279
+ data-dreamboard-mobile-hand-trigger=""
280
+ data-hand-role={selectedHand.role}
281
+ data-active-badges={activeBadges.length || undefined}
282
+ className="flex w-full flex-col items-stretch gap-1.5 px-4 pb-2 pt-2 text-left"
283
+ style={{
284
+ touchAction: "none",
285
+ background: "transparent",
286
+ border: 0,
287
+ cursor: "pointer",
288
+ }}
289
+ >
290
+ <span
291
+ aria-hidden
292
+ className="mx-auto h-1.5 w-10 rounded-full"
293
+ style={{ background: "rgba(45,45,45,0.28)" }}
294
+ />
295
+ <span className="flex items-center justify-between gap-2">
296
+ <span className="flex min-w-0 items-center gap-2 text-sm font-semibold">
297
+ <span className="truncate">{selectedHand.label}</span>
298
+ <span
299
+ className="rounded-full px-2 py-0.5 text-xs font-bold"
300
+ style={{ background: "rgba(45,45,45,0.08)" }}
301
+ >
302
+ {selectedHand.count}
303
+ </span>
304
+ {active ? (
305
+ <span className="rounded-full bg-amber-400 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-amber-950">
306
+ your turn
307
+ </span>
308
+ ) : null}
309
+ </span>
310
+ {activeBadges.length > 0 ? (
311
+ <span
312
+ aria-label={`${activeBadges.length} other active hand sections`}
313
+ className="inline-flex min-w-5 items-center justify-center rounded-full bg-red-500 px-1.5 text-xs font-bold text-white"
314
+ >
315
+ {activeBadges.length}
316
+ </span>
317
+ ) : null}
318
+ </span>
319
+ </button>
320
+
321
+ {/* Collapsible body — kept mounted at the peek (clipped to zero height
322
+ and made inert) so raising the dock never remounts the hand or
323
+ loses scroll position, and so the auto-raise can animate. The body
324
+ is the scroll container, so the action slot's sticky footer pins to
325
+ its bottom edge. */}
326
+ <div
327
+ id={TRAY_CONTENT_ID}
328
+ role="region"
329
+ aria-label={selectedHand.label}
330
+ data-state={snap}
331
+ inert={open ? undefined : true}
332
+ className="overscroll-contain"
333
+ style={{
334
+ maxHeight: DOCK_SNAP_MAX_HEIGHT[snap],
335
+ overflowY: open ? "auto" : "hidden",
336
+ transition: "max-height 240ms ease",
337
+ }}
338
+ >
339
+ {hands.length > 1 ? (
340
+ <div
341
+ role="tablist"
342
+ aria-label="Hand sections"
343
+ className="flex gap-2 overflow-x-auto px-4 pb-2 pt-1 [scrollbar-width:none]"
344
+ >
345
+ {hands.map((hand) => (
346
+ <button
347
+ key={hand.id}
348
+ type="button"
349
+ role="tab"
350
+ aria-selected={hand.id === selectedHand.id}
351
+ data-active={hand.active || undefined}
352
+ data-hand-role={hand.role}
353
+ onClick={() => setActiveId(hand.id)}
354
+ className={clsx(
355
+ "shrink-0 rounded-full border px-3 py-1 text-sm font-semibold",
356
+ hand.id === selectedHand.id
357
+ ? "border-slate-900 bg-white text-slate-950"
358
+ : "border-slate-300 bg-white/60 text-slate-600",
359
+ hand.active && hand.id !== selectedHand.id
360
+ ? "ring-2 ring-red-400"
361
+ : null,
362
+ )}
363
+ >
364
+ {hand.label} ({hand.count})
365
+ </button>
366
+ ))}
367
+ </div>
368
+ ) : null}
369
+ <div
370
+ className="px-3 pt-2 sm:px-4"
371
+ style={{
372
+ paddingBottom: "calc(20px + env(safe-area-inset-bottom, 0px))",
373
+ }}
374
+ >
375
+ {selectedHand.content}
376
+ </div>
377
+ </div>
378
+ </div>
379
+ </div>
380
+ );
381
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Disclosure that hides a list of low-salience actions behind a single
3
+ * "More" toggle. Sized and styled by the active theme so it looks
4
+ * consistent with neighbouring `<DefaultInteractionButton>` rows.
5
+ *
6
+ * Why not a popover or dropdown? Two reasons:
7
+ *
8
+ * 1. **Layout safety.** The default panel surface lives directly above
9
+ * the hand strip; a floating popover would be obscured by the
10
+ * hand's `overflow-x: auto` clipping. An inline expansion stays
11
+ * inside the panel container and pushes neighbouring rows down.
12
+ * 2. **Discoverability.** Players miss menus that hide behind triple
13
+ * dots / chevrons. An expanded inline list still looks like a row
14
+ * of buttons (Jakob — same affordance as the always-visible row).
15
+ *
16
+ * The toggle reports its open state via `aria-expanded` and labels
17
+ * the disclosed region via `aria-controls` so screen readers announce
18
+ * "Expanded — More actions, region containing 3 buttons" naturally.
19
+ */
20
+
21
+ import { useId, useState, type CSSProperties, type ReactNode } from "react";
22
+ import { ChevronDown, MoreHorizontal } from "lucide-react";
23
+ import { useTheme } from "../theme/ThemeProvider.js";
24
+ import { ThemedButton } from "./ThemedButton.js";
25
+
26
+ export interface MoreActionsProps {
27
+ /**
28
+ * Items rendered inside the disclosure when expanded. Typically
29
+ * `<DefaultInteractionButton>` instances for `salience: "tertiary"`
30
+ * descriptors, but any `ReactNode` works (custom panel cards, etc.).
31
+ */
32
+ children: ReactNode;
33
+ /**
34
+ * Toggle label. Defaults to `"More"`. The descriptor count gets
35
+ * appended automatically when {@link count} is supplied.
36
+ */
37
+ label?: string;
38
+ /**
39
+ * Number of hidden items, used to render the trailing "(N)" badge.
40
+ * Omit when the count is irrelevant or already implied (e.g. when
41
+ * the panel only has a fixed set of disclosed items).
42
+ */
43
+ count?: number;
44
+ /**
45
+ * Initial open state. Defaults to `false` — the disclosure is the
46
+ * point. Authors who want it open by default for a specific seat
47
+ * (e.g. tutorial mode) should pass `true`.
48
+ */
49
+ defaultOpen?: boolean;
50
+ /** Additional inline style merged after the default container. */
51
+ style?: CSSProperties;
52
+ }
53
+
54
+ export function MoreActions({
55
+ children,
56
+ label = "More",
57
+ count,
58
+ defaultOpen = false,
59
+ style,
60
+ }: MoreActionsProps) {
61
+ const theme = useTheme();
62
+ const [open, setOpen] = useState(defaultOpen);
63
+ // `useId` keeps the aria pairing stable across re-renders even when
64
+ // many `<MoreActions>` exist on screen (rare, but easy to break).
65
+ const regionId = `more-actions-${useId().replace(/:/g, "")}`;
66
+
67
+ const showCount = typeof count === "number" && count > 0;
68
+
69
+ return (
70
+ <div
71
+ data-shell-slot="more-actions"
72
+ data-more-actions-open={open ? "true" : "false"}
73
+ style={{
74
+ display: "flex",
75
+ flexDirection: "column",
76
+ gap: theme.space[2],
77
+ alignItems: "stretch",
78
+ // The disclosure is its own block so wrap behaviour upstream
79
+ // doesn't interleave the toggle and the disclosed items.
80
+ flex: "1 1 100%",
81
+ minWidth: 0,
82
+ }}
83
+ >
84
+ <ThemedButton
85
+ type="button"
86
+ variant="secondary"
87
+ size="md"
88
+ aria-expanded={open}
89
+ aria-controls={regionId}
90
+ onClick={() => setOpen((value) => !value)}
91
+ style={{
92
+ display: "inline-flex",
93
+ alignItems: "center",
94
+ gap: theme.space[1],
95
+ ...style,
96
+ }}
97
+ >
98
+ <MoreHorizontal size={16} aria-hidden />
99
+ <span>{label}</span>
100
+ {showCount ? (
101
+ <span
102
+ aria-hidden
103
+ style={{
104
+ fontVariantNumeric: "tabular-nums",
105
+ opacity: 0.78,
106
+ }}
107
+ >
108
+ ({count})
109
+ </span>
110
+ ) : null}
111
+ <ChevronDown
112
+ size={14}
113
+ aria-hidden
114
+ style={{
115
+ transform: open ? "rotate(180deg)" : "rotate(0deg)",
116
+ transition: `transform ${theme.motion.duration.fast} ${theme.motion.easing.out}`,
117
+ }}
118
+ />
119
+ </ThemedButton>
120
+ {open ? (
121
+ <div
122
+ id={regionId}
123
+ role="region"
124
+ aria-label={label}
125
+ style={{
126
+ display: "flex",
127
+ flexDirection: "row",
128
+ flexWrap: "wrap",
129
+ gap: theme.space[2],
130
+ // Inset visually so the disclosed cluster reads as a
131
+ // sub-region rather than a continuation of the main panel.
132
+ paddingInline: theme.space[3],
133
+ paddingBlock: theme.space[2],
134
+ background: theme.semantic.surface.inset,
135
+ borderRadius: theme.radius.lg,
136
+ }}
137
+ >
138
+ {children}
139
+ </div>
140
+ ) : null}
141
+ </div>
142
+ );
143
+ }