roblox-opencode 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 (248) hide show
  1. package/README.md +122 -0
  2. package/commands/setup-game.md +108 -0
  3. package/commands/sync-check.md +53 -0
  4. package/core/roblox-core.md +93 -0
  5. package/dist/server.js +167 -0
  6. package/package.json +35 -0
  7. package/skills/roblox-analytics/SKILL.md +277 -0
  8. package/skills/roblox-analytics/references/event-batcher.luau +75 -0
  9. package/skills/roblox-animation-vfx/SKILL.md +1325 -0
  10. package/skills/roblox-architecture/SKILL.md +863 -0
  11. package/skills/roblox-architecture/references/combat-systems.md +1381 -0
  12. package/skills/roblox-code-review/SKILL.md +687 -0
  13. package/skills/roblox-data/SKILL.md +889 -0
  14. package/skills/roblox-data/references/inventory-systems.md +1729 -0
  15. package/skills/roblox-debug/SKILL.md +99 -0
  16. package/skills/roblox-gui/SKILL.md +1103 -0
  17. package/skills/roblox-gui-fusion/SKILL.md +150 -0
  18. package/skills/roblox-gui-fusion/references/inventory.luau +427 -0
  19. package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -0
  20. package/skills/roblox-gui-fusion/references/shop.luau +411 -0
  21. package/skills/roblox-luau-mastery/SKILL.md +1519 -0
  22. package/skills/roblox-monetization/SKILL.md +1084 -0
  23. package/skills/roblox-monetization/references/process-receipt.luau +131 -0
  24. package/skills/roblox-networking/SKILL.md +669 -0
  25. package/skills/roblox-networking/references/remote-validator.luau +193 -0
  26. package/skills/roblox-publish-checklist/SKILL.md +128 -0
  27. package/skills/roblox-runtime/SKILL.md +753 -0
  28. package/skills/roblox-sharp-edges/SKILL.md +295 -0
  29. package/skills/roblox-sync/SKILL.md +126 -0
  30. package/skills/roblox-testing/SKILL.md +943 -0
  31. package/skills/roblox-tooling/SKILL.md +150 -0
  32. package/vendor/LICENSES/ProfileStore-LICENSE +201 -0
  33. package/vendor/LICENSES/RbxUtil-LICENSE +7 -0
  34. package/vendor/LICENSES/promise-LICENSE +21 -0
  35. package/vendor/LICENSES/t-LICENSE +21 -0
  36. package/vendor/LICENSES/testez-LICENSE +201 -0
  37. package/vendor/README.md +84 -0
  38. package/vendor/fusion/Animation/ExternalTime.luau +84 -0
  39. package/vendor/fusion/Animation/Spring.luau +322 -0
  40. package/vendor/fusion/Animation/Stopwatch.luau +128 -0
  41. package/vendor/fusion/Animation/Tween.luau +187 -0
  42. package/vendor/fusion/Animation/getTweenDuration.luau +27 -0
  43. package/vendor/fusion/Animation/getTweenRatio.luau +47 -0
  44. package/vendor/fusion/Animation/lerpType.luau +164 -0
  45. package/vendor/fusion/Animation/packType.luau +100 -0
  46. package/vendor/fusion/Animation/springCoefficients.luau +80 -0
  47. package/vendor/fusion/Animation/unpackType.luau +103 -0
  48. package/vendor/fusion/Colour/Oklab.luau +70 -0
  49. package/vendor/fusion/Colour/sRGB.luau +55 -0
  50. package/vendor/fusion/External.luau +168 -0
  51. package/vendor/fusion/ExternalDebug.luau +70 -0
  52. package/vendor/fusion/Graph/Observer.luau +114 -0
  53. package/vendor/fusion/Graph/castToGraph.luau +29 -0
  54. package/vendor/fusion/Graph/change.luau +81 -0
  55. package/vendor/fusion/Graph/depend.luau +33 -0
  56. package/vendor/fusion/Graph/evaluate.luau +56 -0
  57. package/vendor/fusion/Instances/Attribute.luau +58 -0
  58. package/vendor/fusion/Instances/AttributeChange.luau +47 -0
  59. package/vendor/fusion/Instances/AttributeOut.luau +63 -0
  60. package/vendor/fusion/Instances/Child.luau +21 -0
  61. package/vendor/fusion/Instances/Children.luau +148 -0
  62. package/vendor/fusion/Instances/Hydrate.luau +33 -0
  63. package/vendor/fusion/Instances/New.luau +53 -0
  64. package/vendor/fusion/Instances/OnChange.luau +50 -0
  65. package/vendor/fusion/Instances/OnEvent.luau +54 -0
  66. package/vendor/fusion/Instances/Out.luau +69 -0
  67. package/vendor/fusion/Instances/applyInstanceProps.luau +149 -0
  68. package/vendor/fusion/Instances/defaultProps.luau +194 -0
  69. package/vendor/fusion/LICENSE +21 -0
  70. package/vendor/fusion/Logging/formatError.luau +49 -0
  71. package/vendor/fusion/Logging/messages.luau +52 -0
  72. package/vendor/fusion/Logging/parseError.luau +25 -0
  73. package/vendor/fusion/Memory/checkLifetime.luau +134 -0
  74. package/vendor/fusion/Memory/deriveScope.luau +24 -0
  75. package/vendor/fusion/Memory/deriveScopeImpl.luau +45 -0
  76. package/vendor/fusion/Memory/doCleanup.luau +79 -0
  77. package/vendor/fusion/Memory/innerScope.luau +34 -0
  78. package/vendor/fusion/Memory/legacyCleanup.luau +18 -0
  79. package/vendor/fusion/Memory/needsDestruction.luau +17 -0
  80. package/vendor/fusion/Memory/poisonScope.luau +34 -0
  81. package/vendor/fusion/Memory/scopePool.luau +55 -0
  82. package/vendor/fusion/Memory/scoped.luau +27 -0
  83. package/vendor/fusion/Memory/whichLivesLonger.luau +75 -0
  84. package/vendor/fusion/RobloxExternal.luau +98 -0
  85. package/vendor/fusion/State/Computed.luau +139 -0
  86. package/vendor/fusion/State/For/Disassembly.luau +211 -0
  87. package/vendor/fusion/State/For/ForTypes.luau +30 -0
  88. package/vendor/fusion/State/For/init.luau +110 -0
  89. package/vendor/fusion/State/ForKeys.luau +94 -0
  90. package/vendor/fusion/State/ForPairs.luau +97 -0
  91. package/vendor/fusion/State/ForValues.luau +94 -0
  92. package/vendor/fusion/State/Value.luau +88 -0
  93. package/vendor/fusion/State/castToState.luau +26 -0
  94. package/vendor/fusion/State/peek.luau +31 -0
  95. package/vendor/fusion/State/updateAll.luau +1 -0
  96. package/vendor/fusion/Types.luau +314 -0
  97. package/vendor/fusion/Utility/Contextual.luau +91 -0
  98. package/vendor/fusion/Utility/Safe.luau +23 -0
  99. package/vendor/fusion/Utility/isSimilar.luau +29 -0
  100. package/vendor/fusion/Utility/merge.luau +35 -0
  101. package/vendor/fusion/Utility/nameOf.luau +35 -0
  102. package/vendor/fusion/Utility/never.luau +14 -0
  103. package/vendor/fusion/Utility/nicknames.luau +11 -0
  104. package/vendor/fusion/Utility/xtypeof.luau +27 -0
  105. package/vendor/fusion/init.luau +82 -0
  106. package/vendor/profilestore/init.luau +2243 -0
  107. package/vendor/promise/init.luau +1982 -0
  108. package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -0
  109. package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -0
  110. package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -0
  111. package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -0
  112. package/vendor/rbxutil/buffer-util/Types.luau +60 -0
  113. package/vendor/rbxutil/buffer-util/index.d.ts +153 -0
  114. package/vendor/rbxutil/buffer-util/init.luau +41 -0
  115. package/vendor/rbxutil/buffer-util/package.json +16 -0
  116. package/vendor/rbxutil/buffer-util/wally.toml +9 -0
  117. package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -0
  118. package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -0
  119. package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -0
  120. package/vendor/rbxutil/comm/Client/init.luau +135 -0
  121. package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -0
  122. package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -0
  123. package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -0
  124. package/vendor/rbxutil/comm/Server/init.luau +140 -0
  125. package/vendor/rbxutil/comm/Types.luau +18 -0
  126. package/vendor/rbxutil/comm/Util.luau +27 -0
  127. package/vendor/rbxutil/comm/init.luau +35 -0
  128. package/vendor/rbxutil/comm/wally.toml +13 -0
  129. package/vendor/rbxutil/component/init.luau +759 -0
  130. package/vendor/rbxutil/component/init.test.luau +311 -0
  131. package/vendor/rbxutil/component/wally.toml +14 -0
  132. package/vendor/rbxutil/concur/init.luau +542 -0
  133. package/vendor/rbxutil/concur/init.test.luau +364 -0
  134. package/vendor/rbxutil/concur/wally.toml +8 -0
  135. package/vendor/rbxutil/enum-list/init.luau +101 -0
  136. package/vendor/rbxutil/enum-list/init.test.luau +91 -0
  137. package/vendor/rbxutil/enum-list/wally.toml +8 -0
  138. package/vendor/rbxutil/find/index.d.ts +20 -0
  139. package/vendor/rbxutil/find/init.luau +44 -0
  140. package/vendor/rbxutil/find/package.json +17 -0
  141. package/vendor/rbxutil/find/wally.toml +8 -0
  142. package/vendor/rbxutil/input/Gamepad.luau +559 -0
  143. package/vendor/rbxutil/input/Keyboard.luau +124 -0
  144. package/vendor/rbxutil/input/Mouse.luau +278 -0
  145. package/vendor/rbxutil/input/PreferredInput.luau +91 -0
  146. package/vendor/rbxutil/input/Touch.luau +120 -0
  147. package/vendor/rbxutil/input/init.luau +33 -0
  148. package/vendor/rbxutil/input/wally.toml +12 -0
  149. package/vendor/rbxutil/loader/index.d.ts +15 -0
  150. package/vendor/rbxutil/loader/init.luau +137 -0
  151. package/vendor/rbxutil/loader/wally.toml +8 -0
  152. package/vendor/rbxutil/log/index.d.ts +38 -0
  153. package/vendor/rbxutil/log/init.luau +746 -0
  154. package/vendor/rbxutil/log/wally.toml +8 -0
  155. package/vendor/rbxutil/net/init.luau +190 -0
  156. package/vendor/rbxutil/net/wally.toml +8 -0
  157. package/vendor/rbxutil/option/index.d.ts +44 -0
  158. package/vendor/rbxutil/option/init.luau +489 -0
  159. package/vendor/rbxutil/option/init.test.luau +342 -0
  160. package/vendor/rbxutil/option/wally.toml +8 -0
  161. package/vendor/rbxutil/pid/index.d.ts +53 -0
  162. package/vendor/rbxutil/pid/init.luau +195 -0
  163. package/vendor/rbxutil/pid/package.json +16 -0
  164. package/vendor/rbxutil/pid/wally.toml +9 -0
  165. package/vendor/rbxutil/quaternion/index.d.ts +117 -0
  166. package/vendor/rbxutil/quaternion/init.luau +570 -0
  167. package/vendor/rbxutil/quaternion/package.json +16 -0
  168. package/vendor/rbxutil/quaternion/wally.toml +9 -0
  169. package/vendor/rbxutil/query/index.d.ts +43 -0
  170. package/vendor/rbxutil/query/init.luau +117 -0
  171. package/vendor/rbxutil/query/package.json +18 -0
  172. package/vendor/rbxutil/query/wally.toml +9 -0
  173. package/vendor/rbxutil/sequent/index.d.ts +28 -0
  174. package/vendor/rbxutil/sequent/init.luau +340 -0
  175. package/vendor/rbxutil/sequent/package.json +16 -0
  176. package/vendor/rbxutil/sequent/wally.toml +9 -0
  177. package/vendor/rbxutil/ser/init.luau +175 -0
  178. package/vendor/rbxutil/ser/init.test.luau +50 -0
  179. package/vendor/rbxutil/ser/wally.toml +11 -0
  180. package/vendor/rbxutil/shake/index.d.ts +36 -0
  181. package/vendor/rbxutil/shake/init.luau +532 -0
  182. package/vendor/rbxutil/shake/init.test.luau +267 -0
  183. package/vendor/rbxutil/shake/package.json +16 -0
  184. package/vendor/rbxutil/shake/wally.toml +9 -0
  185. package/vendor/rbxutil/signal/index.d.ts +100 -0
  186. package/vendor/rbxutil/signal/init.luau +432 -0
  187. package/vendor/rbxutil/signal/init.test.luau +190 -0
  188. package/vendor/rbxutil/signal/package.json +17 -0
  189. package/vendor/rbxutil/signal/wally.toml +9 -0
  190. package/vendor/rbxutil/silo/TableWatcher.luau +65 -0
  191. package/vendor/rbxutil/silo/Util.luau +55 -0
  192. package/vendor/rbxutil/silo/init.luau +338 -0
  193. package/vendor/rbxutil/silo/init.test.luau +215 -0
  194. package/vendor/rbxutil/silo/wally.toml +8 -0
  195. package/vendor/rbxutil/spring/index.d.ts +40 -0
  196. package/vendor/rbxutil/spring/init.luau +97 -0
  197. package/vendor/rbxutil/spring/package.json +17 -0
  198. package/vendor/rbxutil/spring/wally.toml +8 -0
  199. package/vendor/rbxutil/stream/index.d.ts +88 -0
  200. package/vendor/rbxutil/stream/init.luau +597 -0
  201. package/vendor/rbxutil/stream/package.json +18 -0
  202. package/vendor/rbxutil/stream/wally.toml +9 -0
  203. package/vendor/rbxutil/streamable/Streamable.luau +202 -0
  204. package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -0
  205. package/vendor/rbxutil/streamable/init.luau +8 -0
  206. package/vendor/rbxutil/streamable/wally.toml +12 -0
  207. package/vendor/rbxutil/symbol/init.luau +56 -0
  208. package/vendor/rbxutil/symbol/init.test.luau +37 -0
  209. package/vendor/rbxutil/symbol/wally.toml +8 -0
  210. package/vendor/rbxutil/table-util/init.luau +938 -0
  211. package/vendor/rbxutil/table-util/init.test.luau +439 -0
  212. package/vendor/rbxutil/table-util/wally.toml +8 -0
  213. package/vendor/rbxutil/task-queue/index.d.ts +27 -0
  214. package/vendor/rbxutil/task-queue/init.luau +97 -0
  215. package/vendor/rbxutil/task-queue/wally.toml +8 -0
  216. package/vendor/rbxutil/timer/index.d.ts +81 -0
  217. package/vendor/rbxutil/timer/init.luau +249 -0
  218. package/vendor/rbxutil/timer/init.test.luau +73 -0
  219. package/vendor/rbxutil/timer/wally.toml +11 -0
  220. package/vendor/rbxutil/tree/index.d.ts +15 -0
  221. package/vendor/rbxutil/tree/init.luau +137 -0
  222. package/vendor/rbxutil/tree/wally.toml +8 -0
  223. package/vendor/rbxutil/trove/index.d.ts +46 -0
  224. package/vendor/rbxutil/trove/init.luau +787 -0
  225. package/vendor/rbxutil/trove/init.test.luau +203 -0
  226. package/vendor/rbxutil/trove/wally.toml +8 -0
  227. package/vendor/rbxutil/typed-remote/init.luau +196 -0
  228. package/vendor/rbxutil/typed-remote/wally.toml +8 -0
  229. package/vendor/rbxutil/wait-for/index.d.ts +17 -0
  230. package/vendor/rbxutil/wait-for/init.luau +257 -0
  231. package/vendor/rbxutil/wait-for/init.test.luau +182 -0
  232. package/vendor/rbxutil/wait-for/wally.toml +11 -0
  233. package/vendor/t/t.lua +1350 -0
  234. package/vendor/testez/Context.lua +26 -0
  235. package/vendor/testez/Expectation.lua +311 -0
  236. package/vendor/testez/ExpectationContext.lua +38 -0
  237. package/vendor/testez/LifecycleHooks.lua +89 -0
  238. package/vendor/testez/Reporters/TeamCityReporter.lua +102 -0
  239. package/vendor/testez/Reporters/TextReporter.lua +106 -0
  240. package/vendor/testez/Reporters/TextReporterQuiet.lua +97 -0
  241. package/vendor/testez/TestBootstrap.lua +147 -0
  242. package/vendor/testez/TestEnum.lua +28 -0
  243. package/vendor/testez/TestPlan.lua +304 -0
  244. package/vendor/testez/TestPlanner.lua +40 -0
  245. package/vendor/testez/TestResults.lua +112 -0
  246. package/vendor/testez/TestRunner.lua +188 -0
  247. package/vendor/testez/TestSession.lua +243 -0
  248. package/vendor/testez/init.lua +40 -0
@@ -0,0 +1,411 @@
1
+ --[[
2
+ Shop Screen — Fusion 0.3
3
+ Source patterns: VirtualButFake/fusion-components (MIT), dphfox/Fusion docs
4
+ Ported to Fusion 0.3 scoped syntax.
5
+
6
+ A complete shop interface with:
7
+ - Scrollable item grid (UIGridLayout)
8
+ - Item cards with icon, name, price
9
+ - Purchase confirmation modal
10
+ - Currency display header
11
+ - Server-validated purchase flow via RemoteFunction
12
+
13
+ ADAPT THIS:
14
+ - Replace ITEMS table with your game's item data
15
+ - Replace purchaseRemote with your RemoteFunction
16
+ - Adjust grid CellSize and colors to match your theme
17
+ - Add categories/tabs by wrapping the grid in a tab component
18
+
19
+ Requires: Fusion 0.3 (wally: elttob/fusion@0.3.0)
20
+ ]]
21
+
22
+ local ReplicatedStorage = game:GetService("ReplicatedStorage")
23
+ local Players = game:GetService("Players")
24
+
25
+ -- Adjust this path to match your project structure:
26
+ -- Wally: require(ReplicatedStorage.Packages.Fusion)
27
+ -- Custom: require(game.ReplicatedStorage.Fusion)
28
+ -- Vendored: (default, from roblox-opencode setup)
29
+ local Fusion = require(ReplicatedStorage[".opencode"].vendor.fusion)
30
+ local scoped = Fusion.scoped
31
+ local peek = Fusion.peek
32
+ local Children = Fusion.Children
33
+ local OnEvent = Fusion.OnEvent
34
+
35
+ --------------------------------------------------------------------------------
36
+ -- Types
37
+ --------------------------------------------------------------------------------
38
+
39
+ export type ShopItem = {
40
+ Id: string,
41
+ Name: string,
42
+ Price: number,
43
+ Icon: string?, -- rbxassetid:// or placeholder color
44
+ Owned: boolean?,
45
+ }
46
+
47
+ type ShopProps = {
48
+ Items: Fusion.Value<{ ShopItem }>,
49
+ Currency: Fusion.Value<number>,
50
+ OnPurchase: (itemId: string) -> boolean, -- returns success
51
+ Visible: Fusion.Value<boolean>,
52
+ }
53
+
54
+ --------------------------------------------------------------------------------
55
+ -- Theme constants
56
+ --------------------------------------------------------------------------------
57
+
58
+ local COLORS = {
59
+ Background = Color3.fromRGB(20, 20, 28),
60
+ Surface = Color3.fromRGB(30, 30, 42),
61
+ SurfaceHover = Color3.fromRGB(40, 40, 55),
62
+ Primary = Color3.fromRGB(80, 140, 255),
63
+ PrimaryHover = Color3.fromRGB(100, 160, 255),
64
+ Danger = Color3.fromRGB(220, 60, 60),
65
+ Text = Color3.fromRGB(240, 240, 245),
66
+ TextSecondary = Color3.fromRGB(160, 160, 175),
67
+ TextMuted = Color3.fromRGB(100, 100, 115),
68
+ Accent = Color3.fromRGB(255, 200, 50),
69
+ Owned = Color3.fromRGB(60, 180, 80),
70
+ Backdrop = Color3.fromRGB(0, 0, 0),
71
+ }
72
+
73
+ local FONT = {
74
+ Title = Font.new("rbxasset://fonts/families/GothamSSm.json", Enum.FontWeight.Bold),
75
+ Body = Font.new("rbxasset://fonts/families/GothamSSm.json", Enum.FontWeight.Medium),
76
+ Small = Font.new("rbxasset://fonts/families/GothamSSm.json", Enum.FontWeight.Regular),
77
+ }
78
+
79
+ --------------------------------------------------------------------------------
80
+ -- Item Card component
81
+ --------------------------------------------------------------------------------
82
+
83
+ local function ItemCard(scope: Fusion.Scope, item: ShopItem, onClick: () -> ())
84
+ local isHovering = scope:Value(false)
85
+
86
+ return scope:New "TextButton" {
87
+ Name = "ItemCard_" .. item.Id,
88
+ BackgroundColor3 = scope:Spring(scope:Computed(function(use)
89
+ return if use(isHovering) then COLORS.SurfaceHover else COLORS.Surface
90
+ end), 30),
91
+ AutoButtonColor = false,
92
+ Text = "",
93
+ LayoutOrder = 0,
94
+
95
+ [OnEvent "Activated"] = onClick,
96
+ [OnEvent "MouseEnter"] = function()
97
+ isHovering:set(true)
98
+ end,
99
+ [OnEvent "MouseLeave"] = function()
100
+ isHovering:set(false)
101
+ end,
102
+
103
+ [Children] = {
104
+ scope:New "UICorner" { CornerRadius = UDim.new(0, 8) },
105
+ scope:New "UIPadding" {
106
+ PaddingTop = UDim.new(0, 8),
107
+ PaddingBottom = UDim.new(0, 8),
108
+ PaddingLeft = UDim.new(0, 8),
109
+ PaddingRight = UDim.new(0, 8),
110
+ },
111
+ scope:New "UIListLayout" {
112
+ FillDirection = Enum.FillDirection.Vertical,
113
+ HorizontalAlignment = Enum.HorizontalAlignment.Center,
114
+ Padding = UDim.new(0, 6),
115
+ SortOrder = Enum.SortOrder.LayoutOrder,
116
+ },
117
+
118
+ -- Icon placeholder (colored frame or ImageLabel)
119
+ scope:New "Frame" {
120
+ Name = "IconFrame",
121
+ BackgroundColor3 = COLORS.Background,
122
+ Size = UDim2.new(1, 0, 0, 60),
123
+ LayoutOrder = 1,
124
+ [Children] = {
125
+ scope:New "UICorner" { CornerRadius = UDim.new(0, 6) },
126
+ if item.Icon then scope:New "ImageLabel" {
127
+ Image = item.Icon,
128
+ BackgroundTransparency = 1,
129
+ Size = UDim2.new(1, -8, 1, -8),
130
+ Position = UDim2.fromOffset(4, 4),
131
+ ScaleType = Enum.ScaleType.Fit,
132
+ } else nil,
133
+ },
134
+ },
135
+
136
+ -- Item name
137
+ scope:New "TextLabel" {
138
+ Name = "ItemName",
139
+ BackgroundTransparency = 1,
140
+ Size = UDim2.new(1, 0, 0, 16),
141
+ FontFace = FONT.Body,
142
+ Text = item.Name,
143
+ TextColor3 = COLORS.Text,
144
+ TextSize = 13,
145
+ TextTruncate = Enum.TextTruncate.AtEnd,
146
+ TextXAlignment = Enum.TextXAlignment.Center,
147
+ LayoutOrder = 2,
148
+ },
149
+
150
+ -- Price or "Owned" badge
151
+ scope:New "TextLabel" {
152
+ Name = "Price",
153
+ BackgroundTransparency = 1,
154
+ Size = UDim2.new(1, 0, 0, 14),
155
+ FontFace = FONT.Small,
156
+ Text = if item.Owned then "Owned" else tostring(item.Price),
157
+ TextColor3 = if item.Owned then COLORS.Owned else COLORS.Accent,
158
+ TextSize = 12,
159
+ TextXAlignment = Enum.TextXAlignment.Center,
160
+ LayoutOrder = 3,
161
+ },
162
+ },
163
+ }
164
+ end
165
+
166
+ --------------------------------------------------------------------------------
167
+ -- Purchase Modal component
168
+ --------------------------------------------------------------------------------
169
+
170
+ local function PurchaseModal(
171
+ scope: Fusion.Scope,
172
+ visible: Fusion.Value<boolean>,
173
+ selectedItem: Fusion.Value<ShopItem?>,
174
+ onConfirm: () -> (),
175
+ onCancel: () -> ()
176
+ )
177
+ return scope:New "Frame" {
178
+ Name = "PurchaseModal",
179
+ BackgroundColor3 = COLORS.Backdrop,
180
+ BackgroundTransparency = scope:Spring(scope:Computed(function(use)
181
+ return if use(visible) then 0.4 else 1
182
+ end), 25),
183
+ Size = UDim2.new(1, 0, 1, 0),
184
+ Visible = scope:Computed(function(use)
185
+ return use(visible)
186
+ end),
187
+ ZIndex = 100,
188
+
189
+ [Children] = {
190
+ -- Centered dialog card
191
+ scope:New "Frame" {
192
+ Name = "DialogCard",
193
+ AnchorPoint = Vector2.new(0.5, 0.5),
194
+ Position = UDim2.fromScale(0.5, 0.5),
195
+ Size = UDim2.new(0, 280, 0, 160),
196
+ BackgroundColor3 = COLORS.Surface,
197
+
198
+ [Children] = {
199
+ scope:New "UICorner" { CornerRadius = UDim.new(0, 12) },
200
+ scope:New "UIPadding" {
201
+ PaddingTop = UDim.new(0, 16),
202
+ PaddingBottom = UDim.new(0, 16),
203
+ PaddingLeft = UDim.new(0, 16),
204
+ PaddingRight = UDim.new(0, 16),
205
+ },
206
+ scope:New "UIListLayout" {
207
+ FillDirection = Enum.FillDirection.Vertical,
208
+ HorizontalAlignment = Enum.HorizontalAlignment.Center,
209
+ Padding = UDim.new(0, 12),
210
+ SortOrder = Enum.SortOrder.LayoutOrder,
211
+ },
212
+
213
+ -- Title
214
+ scope:New "TextLabel" {
215
+ BackgroundTransparency = 1,
216
+ Size = UDim2.new(1, 0, 0, 20),
217
+ FontFace = FONT.Title,
218
+ Text = "Confirm Purchase",
219
+ TextColor3 = COLORS.Text,
220
+ TextSize = 16,
221
+ LayoutOrder = 1,
222
+ },
223
+
224
+ -- Description
225
+ scope:New "TextLabel" {
226
+ BackgroundTransparency = 1,
227
+ Size = UDim2.new(1, 0, 0, 32),
228
+ FontFace = FONT.Small,
229
+ Text = scope:Computed(function(use)
230
+ local item = use(selectedItem)
231
+ if not item then return "" end
232
+ return `Buy {item.Name} for {item.Price} coins?`
233
+ end),
234
+ TextColor3 = COLORS.TextSecondary,
235
+ TextSize = 14,
236
+ TextWrapped = true,
237
+ LayoutOrder = 2,
238
+ },
239
+
240
+ -- Button row
241
+ scope:New "Frame" {
242
+ BackgroundTransparency = 1,
243
+ Size = UDim2.new(1, 0, 0, 36),
244
+ LayoutOrder = 3,
245
+ [Children] = {
246
+ scope:New "UIListLayout" {
247
+ FillDirection = Enum.FillDirection.Horizontal,
248
+ HorizontalAlignment = Enum.HorizontalAlignment.Center,
249
+ Padding = UDim.new(0, 12),
250
+ VerticalAlignment = Enum.VerticalAlignment.Center,
251
+ },
252
+
253
+ -- Cancel
254
+ scope:New "TextButton" {
255
+ Name = "CancelBtn",
256
+ BackgroundColor3 = COLORS.Background,
257
+ Size = UDim2.new(0, 100, 0, 32),
258
+ FontFace = FONT.Body,
259
+ Text = "Cancel",
260
+ TextColor3 = COLORS.TextSecondary,
261
+ TextSize = 14,
262
+ AutoButtonColor = false,
263
+ [OnEvent "Activated"] = onCancel,
264
+ [Children] = { scope:New "UICorner" { CornerRadius = UDim.new(0, 6) } },
265
+ },
266
+
267
+ -- Confirm
268
+ scope:New "TextButton" {
269
+ Name = "ConfirmBtn",
270
+ BackgroundColor3 = COLORS.Primary,
271
+ Size = UDim2.new(0, 100, 0, 32),
272
+ FontFace = FONT.Body,
273
+ Text = "Buy",
274
+ TextColor3 = COLORS.Text,
275
+ TextSize = 14,
276
+ AutoButtonColor = false,
277
+ [OnEvent "Activated"] = onConfirm,
278
+ [Children] = { scope:New "UICorner" { CornerRadius = UDim.new(0, 6) } },
279
+ },
280
+ },
281
+ },
282
+ },
283
+ },
284
+ },
285
+ }
286
+ end
287
+
288
+ --------------------------------------------------------------------------------
289
+ -- Shop Screen (main export)
290
+ --------------------------------------------------------------------------------
291
+
292
+ local function ShopScreen(props: ShopProps)
293
+ local scope = scoped(Fusion)
294
+
295
+ local selectedItem: Fusion.Value<ShopItem?> = scope:Value(nil)
296
+ local modalVisible = scope:Value(false)
297
+
298
+ local screenGui = scope:New "ScreenGui" {
299
+ Name = "ShopScreen",
300
+ ResetOnSpawn = false,
301
+ IgnoreGuiInset = true,
302
+ Enabled = props.Visible,
303
+ DisplayOrder = 10,
304
+ Parent = Players.LocalPlayer:WaitForChild("PlayerGui"),
305
+
306
+ [Children] = {
307
+ -- Main container
308
+ scope:New "Frame" {
309
+ Name = "Container",
310
+ BackgroundColor3 = COLORS.Background,
311
+ Size = UDim2.new(1, 0, 1, 0),
312
+
313
+ [Children] = {
314
+ scope:New "UIPadding" {
315
+ PaddingTop = UDim.new(0, 60), -- below top bar
316
+ PaddingBottom = UDim.new(0, 12),
317
+ PaddingLeft = UDim.new(0, 16),
318
+ PaddingRight = UDim.new(0, 16),
319
+ },
320
+ scope:New "UIListLayout" {
321
+ FillDirection = Enum.FillDirection.Vertical,
322
+ Padding = UDim.new(0, 12),
323
+ SortOrder = Enum.SortOrder.LayoutOrder,
324
+ },
325
+
326
+ -- Header: title + currency
327
+ scope:New "Frame" {
328
+ Name = "Header",
329
+ BackgroundTransparency = 1,
330
+ Size = UDim2.new(1, 0, 0, 32),
331
+ LayoutOrder = 1,
332
+ [Children] = {
333
+ scope:New "TextLabel" {
334
+ BackgroundTransparency = 1,
335
+ Size = UDim2.new(0.5, 0, 1, 0),
336
+ FontFace = FONT.Title,
337
+ Text = "Shop",
338
+ TextColor3 = COLORS.Text,
339
+ TextSize = 22,
340
+ TextXAlignment = Enum.TextXAlignment.Left,
341
+ },
342
+ scope:New "TextLabel" {
343
+ BackgroundTransparency = 1,
344
+ AnchorPoint = Vector2.new(1, 0),
345
+ Position = UDim2.fromScale(1, 0),
346
+ Size = UDim2.new(0.5, 0, 1, 0),
347
+ FontFace = FONT.Body,
348
+ Text = scope:Computed(function(use)
349
+ return tostring(use(props.Currency)) .. " coins"
350
+ end),
351
+ TextColor3 = COLORS.Accent,
352
+ TextSize = 16,
353
+ TextXAlignment = Enum.TextXAlignment.Right,
354
+ },
355
+ },
356
+ },
357
+
358
+ -- Scrollable item grid
359
+ scope:New "ScrollingFrame" {
360
+ Name = "ItemGrid",
361
+ BackgroundTransparency = 1,
362
+ Size = UDim2.new(1, 0, 1, -44),
363
+ LayoutOrder = 2,
364
+ AutomaticCanvasSize = Enum.AutomaticSize.Y,
365
+ ScrollingDirection = Enum.ScrollingDirection.Y,
366
+ ScrollBarThickness = 4,
367
+ ScrollBarImageColor3 = COLORS.TextMuted,
368
+ CanvasSize = UDim2.new(0, 0, 0, 0),
369
+
370
+ [Children] = {
371
+ scope:New "UIGridLayout" {
372
+ CellSize = UDim2.new(0, 110, 0, 130),
373
+ CellPadding = UDim2.new(0, 10, 0, 10),
374
+ FillDirection = Enum.FillDirection.Horizontal,
375
+ FillDirectionMaxCells = 0,
376
+ HorizontalAlignment = Enum.HorizontalAlignment.Center,
377
+ SortOrder = Enum.SortOrder.LayoutOrder,
378
+ },
379
+
380
+ scope:ForPairs(props.Items, function(use, itemScope, index, item)
381
+ return index, ItemCard(itemScope, item, function()
382
+ if not item.Owned then
383
+ selectedItem:set(item)
384
+ modalVisible:set(true)
385
+ end
386
+ end)
387
+ end),
388
+ },
389
+ },
390
+ },
391
+ },
392
+
393
+ -- Purchase confirmation modal
394
+ PurchaseModal(scope, modalVisible, selectedItem, function()
395
+ local item = peek(selectedItem)
396
+ if item then
397
+ props.OnPurchase(item.Id)
398
+ end
399
+ modalVisible:set(false)
400
+ selectedItem:set(nil)
401
+ end, function()
402
+ modalVisible:set(false)
403
+ selectedItem:set(nil)
404
+ end),
405
+ },
406
+ }
407
+
408
+ return screenGui, scope
409
+ end
410
+
411
+ return ShopScreen