@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,328 @@
1
+ /**
2
+ * useSquareGrid hook - Utilities for square grid operations
3
+ *
4
+ * Provides basic grid utilities and neighbor finding.
5
+ * Game-specific logic (pathfinding, movement rules) should be implemented by the parent.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const gridApi = useSquareGrid({ rows: 8, cols: 8, pieces });
10
+ *
11
+ * // Get neighbors
12
+ * const neighbors = gridApi.getNeighbors(3, 4);
13
+ *
14
+ * // Get piece at position
15
+ * const piece = gridApi.getPieceAt(0, 0);
16
+ *
17
+ * // Calculate distance
18
+ * const dist = gridApi.getDistance(0, 0, 7, 7);
19
+ * ```
20
+ */
21
+
22
+ import { useMemo, useCallback } from "react";
23
+
24
+ // ============================================================================
25
+ // Types
26
+ // ============================================================================
27
+
28
+ export interface GridPieceData {
29
+ /** Unique piece identifier */
30
+ id: string;
31
+ /** Row position (0-based) */
32
+ row: number;
33
+ /** Column position (0-based) */
34
+ col: number;
35
+ /** Piece type */
36
+ type: string;
37
+ /** Owner player ID */
38
+ owner?: string;
39
+ /** Additional data */
40
+ data?: Record<string, unknown>;
41
+ }
42
+
43
+ export interface CellData {
44
+ /** Row index */
45
+ row: number;
46
+ /** Column index */
47
+ col: number;
48
+ /** Whether the cell is blocked */
49
+ blocked?: boolean;
50
+ /** Cell type */
51
+ type?: string;
52
+ /** Additional data */
53
+ data?: Record<string, unknown>;
54
+ }
55
+
56
+ export type DistanceType = "manhattan" | "chebyshev" | "euclidean";
57
+ export type NeighborType = "orthogonal" | "diagonal" | "all";
58
+
59
+ export interface UseSquareGridOptions {
60
+ /** Number of rows */
61
+ rows: number;
62
+ /** Number of columns */
63
+ cols: number;
64
+ /** Pieces on the grid */
65
+ pieces?: GridPieceData[];
66
+ /** Blocked cells */
67
+ blockedCells?: Array<{ row: number; col: number }>;
68
+ /** Neighbor type for movement */
69
+ neighborType?: NeighborType;
70
+ }
71
+
72
+ export interface UseSquareGridReturn {
73
+ /** Get piece at a position */
74
+ getPieceAt: (row: number, col: number) => GridPieceData | undefined;
75
+
76
+ /** Get pieces by owner */
77
+ getPiecesByOwner: (owner: string) => GridPieceData[];
78
+
79
+ /** Get neighboring cells */
80
+ getNeighbors: (
81
+ row: number,
82
+ col: number,
83
+ type?: NeighborType,
84
+ ) => Array<{ row: number; col: number }>;
85
+
86
+ /** Calculate distance between two cells */
87
+ getDistance: (
88
+ r1: number,
89
+ c1: number,
90
+ r2: number,
91
+ c2: number,
92
+ type?: DistanceType,
93
+ ) => number;
94
+
95
+ /** Check if a cell is valid (within bounds) */
96
+ isValidCell: (row: number, col: number) => boolean;
97
+
98
+ /** Check if a cell is blocked */
99
+ isBlocked: (row: number, col: number) => boolean;
100
+
101
+ /** Check if a cell is occupied by a piece */
102
+ isOccupied: (row: number, col: number) => boolean;
103
+
104
+ /** Get cells in a rectangle area */
105
+ getCellsInRect: (
106
+ topRow: number,
107
+ leftCol: number,
108
+ bottomRow: number,
109
+ rightCol: number,
110
+ ) => Array<{ row: number; col: number }>;
111
+
112
+ /** Convert row/col to algebraic notation */
113
+ toAlgebraic: (row: number, col: number) => string;
114
+
115
+ /** Convert algebraic notation to row/col */
116
+ fromAlgebraic: (notation: string) => { row: number; col: number } | null;
117
+ }
118
+
119
+ // ============================================================================
120
+ // Direction vectors
121
+ // ============================================================================
122
+
123
+ const ORTHOGONAL_DIRS = [
124
+ { dr: -1, dc: 0 }, // up
125
+ { dr: 1, dc: 0 }, // down
126
+ { dr: 0, dc: -1 }, // left
127
+ { dr: 0, dc: 1 }, // right
128
+ ];
129
+
130
+ const DIAGONAL_DIRS = [
131
+ { dr: -1, dc: -1 }, // up-left
132
+ { dr: -1, dc: 1 }, // up-right
133
+ { dr: 1, dc: -1 }, // down-left
134
+ { dr: 1, dc: 1 }, // down-right
135
+ ];
136
+
137
+ const ALL_DIRS = [...ORTHOGONAL_DIRS, ...DIAGONAL_DIRS];
138
+
139
+ // ============================================================================
140
+ // Hook Implementation
141
+ // ============================================================================
142
+
143
+ export function useSquareGrid(
144
+ options: UseSquareGridOptions,
145
+ ): UseSquareGridReturn {
146
+ const {
147
+ rows,
148
+ cols,
149
+ pieces = [],
150
+ blockedCells = [],
151
+ neighborType = "orthogonal",
152
+ } = options;
153
+
154
+ // Create lookup maps
155
+ const pieceMap = useMemo(() => {
156
+ const map = new Map<string, GridPieceData>();
157
+ pieces.forEach((p) => {
158
+ map.set(`${p.row},${p.col}`, p);
159
+ });
160
+ return map;
161
+ }, [pieces]);
162
+
163
+ const blockedSet = useMemo(() => {
164
+ return new Set(blockedCells.map((c) => `${c.row},${c.col}`));
165
+ }, [blockedCells]);
166
+
167
+ // Get piece at position
168
+ const getPieceAt = useCallback(
169
+ (row: number, col: number): GridPieceData | undefined => {
170
+ return pieceMap.get(`${row},${col}`);
171
+ },
172
+ [pieceMap],
173
+ );
174
+
175
+ // Get pieces by owner
176
+ const getPiecesByOwner = useCallback(
177
+ (owner: string): GridPieceData[] => {
178
+ return pieces.filter((p) => p.owner === owner);
179
+ },
180
+ [pieces],
181
+ );
182
+
183
+ // Check if cell is valid
184
+ const isValidCell = useCallback(
185
+ (row: number, col: number): boolean => {
186
+ return row >= 0 && row < rows && col >= 0 && col < cols;
187
+ },
188
+ [rows, cols],
189
+ );
190
+
191
+ // Check if cell is blocked
192
+ const isBlocked = useCallback(
193
+ (row: number, col: number): boolean => {
194
+ return blockedSet.has(`${row},${col}`);
195
+ },
196
+ [blockedSet],
197
+ );
198
+
199
+ // Check if cell is occupied
200
+ const isOccupied = useCallback(
201
+ (row: number, col: number): boolean => {
202
+ return pieceMap.has(`${row},${col}`);
203
+ },
204
+ [pieceMap],
205
+ );
206
+
207
+ // Get neighboring cells
208
+ const getNeighbors = useCallback(
209
+ (
210
+ row: number,
211
+ col: number,
212
+ type: NeighborType = neighborType,
213
+ ): Array<{ row: number; col: number }> => {
214
+ const dirs =
215
+ type === "orthogonal"
216
+ ? ORTHOGONAL_DIRS
217
+ : type === "diagonal"
218
+ ? DIAGONAL_DIRS
219
+ : ALL_DIRS;
220
+
221
+ const neighbors: Array<{ row: number; col: number }> = [];
222
+ for (const { dr, dc } of dirs) {
223
+ const newRow = row + dr;
224
+ const newCol = col + dc;
225
+ if (isValidCell(newRow, newCol)) {
226
+ neighbors.push({ row: newRow, col: newCol });
227
+ }
228
+ }
229
+ return neighbors;
230
+ },
231
+ [neighborType, isValidCell],
232
+ );
233
+
234
+ // Calculate distance
235
+ const getDistance = useCallback(
236
+ (
237
+ r1: number,
238
+ c1: number,
239
+ r2: number,
240
+ c2: number,
241
+ type: DistanceType = "manhattan",
242
+ ): number => {
243
+ const dr = Math.abs(r2 - r1);
244
+ const dc = Math.abs(c2 - c1);
245
+
246
+ switch (type) {
247
+ case "manhattan":
248
+ return dr + dc;
249
+ case "chebyshev":
250
+ return Math.max(dr, dc);
251
+ case "euclidean":
252
+ return Math.sqrt(dr * dr + dc * dc);
253
+ default:
254
+ return dr + dc;
255
+ }
256
+ },
257
+ [],
258
+ );
259
+
260
+ // Get cells in rectangle
261
+ const getCellsInRect = useCallback(
262
+ (
263
+ topRow: number,
264
+ leftCol: number,
265
+ bottomRow: number,
266
+ rightCol: number,
267
+ ): Array<{ row: number; col: number }> => {
268
+ const cells: Array<{ row: number; col: number }> = [];
269
+
270
+ const minRow = Math.max(0, Math.min(topRow, bottomRow));
271
+ const maxRow = Math.min(rows - 1, Math.max(topRow, bottomRow));
272
+ const minCol = Math.max(0, Math.min(leftCol, rightCol));
273
+ const maxCol = Math.min(cols - 1, Math.max(leftCol, rightCol));
274
+
275
+ for (let r = minRow; r <= maxRow; r++) {
276
+ for (let c = minCol; c <= maxCol; c++) {
277
+ cells.push({ row: r, col: c });
278
+ }
279
+ }
280
+
281
+ return cells;
282
+ },
283
+ [rows, cols],
284
+ );
285
+
286
+ // Convert to algebraic notation
287
+ const toAlgebraic = useCallback(
288
+ (row: number, col: number): string => {
289
+ const file = String.fromCharCode(97 + col);
290
+ const rank = rows - row;
291
+ return `${file}${rank}`;
292
+ },
293
+ [rows],
294
+ );
295
+
296
+ // Convert from algebraic notation
297
+ const fromAlgebraic = useCallback(
298
+ (notation: string): { row: number; col: number } | null => {
299
+ if (notation.length < 2) return null;
300
+
301
+ const file = notation.charCodeAt(0) - 97;
302
+ const rank = parseInt(notation.slice(1), 10);
303
+
304
+ if (isNaN(rank)) return null;
305
+
306
+ const row = rows - rank;
307
+ const col = file;
308
+
309
+ if (!isValidCell(row, col)) return null;
310
+
311
+ return { row, col };
312
+ },
313
+ [rows, isValidCell],
314
+ );
315
+
316
+ return {
317
+ getPieceAt,
318
+ getPiecesByOwner,
319
+ getNeighbors,
320
+ getDistance,
321
+ isValidCell,
322
+ isBlocked,
323
+ isOccupied,
324
+ getCellsInRect,
325
+ toAlgebraic,
326
+ fromAlgebraic,
327
+ };
328
+ }
@@ -0,0 +1,98 @@
1
+ export { calculateViewBox } from "./hooks/usePanZoom.js";
2
+ export { toTrackBoardData } from "./helpers/track-board.js";
3
+ export { useIsMobile } from "./hooks/useIsMobile.js";
4
+ export {
5
+ ThemeProvider,
6
+ arcadeTheme,
7
+ buttonStyle,
8
+ chipStyle,
9
+ cssVar,
10
+ cssVarOr,
11
+ deriveBoardTheme,
12
+ getThemePreset,
13
+ intentForVariant,
14
+ mergeTheme,
15
+ motionDuration,
16
+ playerColor,
17
+ resolveTheme,
18
+ studioTheme,
19
+ surfaceStyle,
20
+ tabletopTheme,
21
+ themeToCssVars,
22
+ useBoardTheme,
23
+ useTheme,
24
+ useThemeCssVars,
25
+ type BoardTheme,
26
+ type ButtonSize,
27
+ type ButtonVariant,
28
+ type ColorRamp,
29
+ type ComponentTokens,
30
+ type Elevation,
31
+ type FoundationColor,
32
+ type IntentColor,
33
+ type Motion,
34
+ type PlayerColor,
35
+ type Radius,
36
+ type SemanticColor,
37
+ type Space,
38
+ type Theme,
39
+ type ThemeContextValue,
40
+ type ThemeMeta,
41
+ type ThemeOverride,
42
+ type ThemePresetId,
43
+ type ThemeProviderProps,
44
+ type Typography,
45
+ } from "./theme/index.js";
46
+ export {
47
+ normalizeHexBoardInput,
48
+ normalizeSquareBoardInput,
49
+ } from "./types/tiled-board.js";
50
+ export * from "./components/index.js";
51
+ export {
52
+ dropTargetVisualStateDataAttributes,
53
+ visualStateDataAttributes,
54
+ type CardDropTargetVisualState,
55
+ type CardIntent,
56
+ type InteractionVisualState,
57
+ type TargetIntent,
58
+ } from "./types/visual-state.js";
59
+ export type * from "./hooks/useHandLayout.js";
60
+ export type * from "./hooks/usePanZoom.js";
61
+ export {
62
+ hexColor,
63
+ isHexColor,
64
+ parseHexColor,
65
+ type HexColor,
66
+ } from "./types/hex-color.js";
67
+ export type * from "./types/player-state.js";
68
+ export type {
69
+ CardCollection,
70
+ ViewCard,
71
+ ViewSlotOccupant,
72
+ } from "../types/index.js";
73
+ export type {
74
+ AnyHexBoardInput,
75
+ AnySquareBoardInput,
76
+ BoardEdgeIdOf,
77
+ BoardSpaceIdOf,
78
+ BoardVertexIdOf,
79
+ AuthoredHexBoardInput,
80
+ AuthoredSquareBoardInput,
81
+ GeneratedHexBoardInput,
82
+ GeneratedHexSpaceStateLike,
83
+ GeneratedSquareBoardInput,
84
+ GeneratedSquareSpaceStateLike,
85
+ GeneratedTiledEdgeStateLike,
86
+ GeneratedTiledVertexStateLike,
87
+ HexBoardInput,
88
+ NormalizedHexBoard,
89
+ NormalizedHexEdgeOf,
90
+ NormalizedHexTileOf,
91
+ NormalizedHexVertexOf,
92
+ NormalizedSquareBoard,
93
+ NormalizedSquareCellOf,
94
+ NormalizedSquareEdgeOf,
95
+ NormalizedSquarePieceOf,
96
+ NormalizedSquareVertexOf,
97
+ SquareBoardInput,
98
+ } from "./types/tiled-board.js";
@@ -0,0 +1,51 @@
1
+ import * as React from "react";
2
+ import { cn } from "./utils.js";
3
+
4
+ export interface AlertProps extends React.ComponentProps<"div"> {
5
+ variant?: "default" | "destructive";
6
+ }
7
+
8
+ export function Alert({
9
+ className,
10
+ variant = "default",
11
+ ...props
12
+ }: AlertProps) {
13
+ return (
14
+ <div
15
+ data-slot="alert"
16
+ role="alert"
17
+ className={cn(
18
+ "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[1rem_1fr] has-[>svg]:gap-x-3 [&>svg]:translate-y-0.5",
19
+ variant === "destructive" && "border-red-300 text-red-800",
20
+ className,
21
+ )}
22
+ {...props}
23
+ />
24
+ );
25
+ }
26
+
27
+ export function AlertTitle({
28
+ className,
29
+ ...props
30
+ }: React.ComponentProps<"div">) {
31
+ return (
32
+ <div
33
+ data-slot="alert-title"
34
+ className={cn("col-start-2 font-medium tracking-tight", className)}
35
+ {...props}
36
+ />
37
+ );
38
+ }
39
+
40
+ export function AlertDescription({
41
+ className,
42
+ ...props
43
+ }: React.ComponentProps<"div">) {
44
+ return (
45
+ <div
46
+ data-slot="alert-description"
47
+ className={cn("col-start-2 text-sm opacity-90", className)}
48
+ {...props}
49
+ />
50
+ );
51
+ }
@@ -0,0 +1,58 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cn } from "./utils.js";
4
+
5
+ export type ButtonVariant =
6
+ | "default"
7
+ | "destructive"
8
+ | "outline"
9
+ | "secondary"
10
+ | "ghost"
11
+ | "link";
12
+
13
+ export type ButtonSize = "default" | "sm" | "lg" | "icon";
14
+
15
+ export interface ButtonProps extends React.ComponentProps<"button"> {
16
+ variant?: ButtonVariant;
17
+ size?: ButtonSize;
18
+ asChild?: boolean;
19
+ }
20
+
21
+ const variantClass: Record<ButtonVariant, string> = {
22
+ default: "border-slate-900 bg-white text-slate-950 hover:bg-slate-100",
23
+ destructive: "border-red-900 bg-red-600 text-white hover:bg-red-700",
24
+ outline: "border-slate-900 bg-transparent hover:bg-slate-100",
25
+ secondary: "border-slate-700 bg-slate-100 text-slate-950 hover:bg-slate-200",
26
+ ghost: "border-transparent bg-transparent hover:bg-slate-100",
27
+ link: "border-transparent bg-transparent text-blue-700 underline-offset-4 hover:underline",
28
+ };
29
+
30
+ const sizeClass: Record<ButtonSize, string> = {
31
+ default: "h-10 px-4 py-2",
32
+ sm: "h-9 px-3",
33
+ lg: "h-11 px-8",
34
+ icon: "size-10",
35
+ };
36
+
37
+ export function Button({
38
+ className,
39
+ variant = "default",
40
+ size = "default",
41
+ asChild = false,
42
+ ...props
43
+ }: ButtonProps) {
44
+ const Comp = asChild ? Slot : "button";
45
+ return (
46
+ <Comp
47
+ data-slot="button"
48
+ className={cn(
49
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50",
50
+ "focus-visible:ring-2 focus-visible:ring-slate-400 focus-visible:ring-offset-2",
51
+ variantClass[variant],
52
+ sizeClass[size],
53
+ className,
54
+ )}
55
+ {...props}
56
+ />
57
+ );
58
+ }
@@ -0,0 +1,134 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
5
+ import { XIcon } from "lucide-react";
6
+ import { cn } from "./utils.js";
7
+
8
+ export function Dialog(
9
+ props: React.ComponentProps<typeof DialogPrimitive.Root>,
10
+ ) {
11
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
12
+ }
13
+
14
+ export function DialogTrigger(
15
+ props: React.ComponentProps<typeof DialogPrimitive.Trigger>,
16
+ ) {
17
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
18
+ }
19
+
20
+ export function DialogPortal(
21
+ props: React.ComponentProps<typeof DialogPrimitive.Portal>,
22
+ ) {
23
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
24
+ }
25
+
26
+ export function DialogClose(
27
+ props: React.ComponentProps<typeof DialogPrimitive.Close>,
28
+ ) {
29
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
30
+ }
31
+
32
+ export function DialogOverlay({
33
+ className,
34
+ ...props
35
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
36
+ return (
37
+ <DialogPrimitive.Overlay
38
+ data-slot="dialog-overlay"
39
+ className={cn("fixed inset-0 z-50 bg-black/50", className)}
40
+ {...props}
41
+ />
42
+ );
43
+ }
44
+
45
+ export function DialogContent({
46
+ className,
47
+ children,
48
+ showCloseButton = true,
49
+ overlayClassName,
50
+ ...props
51
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & {
52
+ showCloseButton?: boolean;
53
+ overlayClassName?: string;
54
+ }) {
55
+ return (
56
+ <DialogPortal>
57
+ <DialogOverlay className={overlayClassName} />
58
+ <DialogPrimitive.Content
59
+ data-slot="dialog-content"
60
+ className={cn(
61
+ "fixed left-1/2 top-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border bg-white p-6 shadow-lg sm:max-w-lg",
62
+ className,
63
+ )}
64
+ {...props}
65
+ >
66
+ {children}
67
+ {showCloseButton ? (
68
+ <DialogPrimitive.Close
69
+ data-slot="dialog-close"
70
+ className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400"
71
+ >
72
+ <XIcon aria-hidden="true" className="size-4" />
73
+ <span className="sr-only">Close</span>
74
+ </DialogPrimitive.Close>
75
+ ) : null}
76
+ </DialogPrimitive.Content>
77
+ </DialogPortal>
78
+ );
79
+ }
80
+
81
+ export function DialogHeader({
82
+ className,
83
+ ...props
84
+ }: React.ComponentProps<"div">) {
85
+ return (
86
+ <div
87
+ data-slot="dialog-header"
88
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
89
+ {...props}
90
+ />
91
+ );
92
+ }
93
+
94
+ export function DialogFooter({
95
+ className,
96
+ ...props
97
+ }: React.ComponentProps<"div">) {
98
+ return (
99
+ <div
100
+ data-slot="dialog-footer"
101
+ className={cn(
102
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
103
+ className,
104
+ )}
105
+ {...props}
106
+ />
107
+ );
108
+ }
109
+
110
+ export function DialogTitle({
111
+ className,
112
+ ...props
113
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
114
+ return (
115
+ <DialogPrimitive.Title
116
+ data-slot="dialog-title"
117
+ className={cn("text-lg font-semibold leading-none", className)}
118
+ {...props}
119
+ />
120
+ );
121
+ }
122
+
123
+ export function DialogDescription({
124
+ className,
125
+ ...props
126
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
127
+ return (
128
+ <DialogPrimitive.Description
129
+ data-slot="dialog-description"
130
+ className={cn("text-sm text-slate-600", className)}
131
+ {...props}
132
+ />
133
+ );
134
+ }
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+ import { cn } from "./utils.js";
3
+
4
+ export function Input({
5
+ className,
6
+ type,
7
+ ...props
8
+ }: React.ComponentProps<"input">) {
9
+ return (
10
+ <input
11
+ type={type}
12
+ data-slot="input"
13
+ className={cn(
14
+ "h-10 w-full min-w-0 rounded-md border border-slate-300 bg-white px-3 py-2 text-base outline-none disabled:cursor-not-allowed disabled:opacity-50",
15
+ "focus-visible:border-slate-600 focus-visible:ring-2 focus-visible:ring-slate-300",
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ );
21
+ }
@@ -0,0 +1,21 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as LabelPrimitive from "@radix-ui/react-label";
5
+ import { cn } from "./utils.js";
6
+
7
+ export function Label({
8
+ className,
9
+ ...props
10
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
11
+ return (
12
+ <LabelPrimitive.Root
13
+ data-slot="label"
14
+ className={cn(
15
+ "flex items-center gap-2 text-sm font-medium leading-none",
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ );
21
+ }