pulse-rb 1.4.3 → 1.4.4

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.
@@ -59,6 +59,7 @@ local _toggleKeyName = "RightControl"
59
59
  local _windowVisible = true
60
60
 
61
61
  local _isMobile = _UIS.TouchEnabled and not _UIS.KeyboardEnabled
62
+ local _PULSE_PREMIUM = false -- set true during key validation if key grants premium
62
63
 
63
64
  -- ── Adapter ───────────────────────────────────────────────────────────────────
64
65
 
@@ -140,7 +141,7 @@ function _UIAdapter:CreateWindow(title, w, h, opts)
140
141
  local openBtnMobileOnly = opts.open_btn_mobile_only ~= false
141
142
  local openBtnIcon = (opts.open_btn_icon and opts.open_btn_icon ~= "") and opts.open_btn_icon or nil
142
143
 
143
- _windWindow = WindUI:CreateWindow({
144
+ local _winCfg = {
144
145
  Title = title,
145
146
  Icon = icon,
146
147
  Author = author,
@@ -151,7 +152,6 @@ function _UIAdapter:CreateWindow(title, w, h, opts)
151
152
  Transparent = acrylic or (transparency > 0),
152
153
  BackgroundImageTransparency = acrylic and 0 or transparency,
153
154
  Acrylic = acrylic,
154
-
155
155
  OpenButton = {
156
156
  Enabled = true,
157
157
  Draggable = true,
@@ -161,7 +161,62 @@ function _UIAdapter:CreateWindow(title, w, h, opts)
161
161
  CornerRadius = UDim.new(1, 0),
162
162
  StrokeThickness = 3,
163
163
  },
164
- })
164
+ }
165
+
166
+ -- Key system — configured via layout.keySystem
167
+ local _ks = _PULSE_LAYOUT.keySystem
168
+ if _ks then
169
+ local function _checkPremium(key)
170
+ local _pr = _PULSE_LAYOUT.premium
171
+ if not _pr then return end
172
+ if _pr.keys then
173
+ for _, k in ipairs(_pr.keys) do
174
+ if k == key then _PULSE_PREMIUM = true; return end
175
+ end
176
+ end
177
+ if _pr.validatorUrl and _pr.validatorUrl ~= "" then
178
+ local ok, resp = pcall(function() return game:HttpGet(_pr.validatorUrl .. key) end)
179
+ if ok and resp and resp:lower():match("^true") then _PULSE_PREMIUM = true end
180
+ end
181
+ end
182
+ local _ksCfg = {
183
+ Title = _ks.title or "Key Required",
184
+ Note = (_ks.note and _ks.note ~= "") and _ks.note or nil,
185
+ SaveKey = _ks.saveKey == true,
186
+ URL = (_ks.getKeyUrl and _ks.getKeyUrl ~= "") and _ks.getKeyUrl or nil,
187
+ KeyValidator = function(key)
188
+ local passed = false
189
+ -- Check against static keys list
190
+ if _ks.keys then
191
+ for _, k in ipairs(_ks.keys) do
192
+ if k == key then passed = true; break end
193
+ end
194
+ end
195
+ -- HTTP validation (server-side) when no static list or key not found
196
+ if not passed and _ks.validatorUrl and _ks.validatorUrl ~= "" then
197
+ local ok, resp = pcall(function() return game:HttpGet(_ks.validatorUrl .. key) end)
198
+ if ok and resp and resp:lower():match("^true") then passed = true end
199
+ end
200
+ -- No validation configured → any non-empty key passes (dev mode)
201
+ if not passed and (not _ks.keys or #_ks.keys == 0)
202
+ and (not _ks.validatorUrl or _ks.validatorUrl == "") then
203
+ passed = key ~= nil and key ~= ""
204
+ end
205
+ _checkPremium(key)
206
+ return passed or _PULSE_PREMIUM -- premium key also grants basic access
207
+ end,
208
+ }
209
+ if _ks.thumbnail then
210
+ _ksCfg.Thumbnail = {
211
+ Image = _ks.thumbnail.image or "",
212
+ Title = _ks.thumbnail.title or "",
213
+ Width = _ks.thumbnail.width or 100,
214
+ }
215
+ end
216
+ _winCfg.KeySystem = _ksCfg
217
+ end
218
+
219
+ _windWindow = WindUI:CreateWindow(_winCfg)
165
220
 
166
221
  pcall(function()
167
222
  _windConfig = _windWindow.ConfigManager:Config(_folder)
@@ -552,6 +607,28 @@ task.spawn(function()
552
607
 
553
608
  local L = _PULSE_LAYOUT
554
609
 
610
+ -- Premium dynamic unlock system
611
+ -- Each entry is a function that destroys the locked UI and mounts the real component.
612
+ local _premiumPendingUnlocks = {}
613
+ local _premiumStatusPara = nil
614
+
615
+ local function _activatePremium()
616
+ _PULSE_PREMIUM = true
617
+ -- Update settings status paragraph if it exists
618
+ pcall(function()
619
+ if _premiumStatusPara then
620
+ _premiumStatusPara:SetTitle("Premium Active")
621
+ _premiumStatusPara:SetDesc("All premium features are now unlocked.")
622
+ end
623
+ end)
624
+ -- Swap locked UI → real components for every registered premium groupbox
625
+ for _, fn in ipairs(_premiumPendingUnlocks) do
626
+ task.spawn(function() pcall(fn) end)
627
+ end
628
+ _premiumPendingUnlocks = {}
629
+ pcall(function() _PulseNotify("Premium unlocked!", 3) end)
630
+ end
631
+
555
632
  -- Resolve toggle key
556
633
  local _tkName = (L.toggleKey and L.toggleKey ~= "") and L.toggleKey or "RightControl"
557
634
  local _tk = pcall(function() return Enum.KeyCode[_tkName] end) and Enum.KeyCode[_tkName]
@@ -778,7 +855,107 @@ task.spawn(function()
778
855
  end
779
856
  if gb.mount then
780
857
  local comp = _BundleComponents[gb.mount]
781
- if comp then
858
+ -- Premium gate: show inline key-entry UI; dynamically replaced on unlock
859
+ if gb.premium and not _PULSE_PREMIUM then
860
+ local _mountComp = comp
861
+ local _mountCont = container
862
+ local _destroyFns = {}
863
+
864
+ pcall(function()
865
+ local _keyValue = ""
866
+ local _statusPara
867
+
868
+ local function _setStatus(msg)
869
+ pcall(function() if _statusPara then _statusPara:SetDesc(msg) end end)
870
+ end
871
+
872
+ -- Header
873
+ local _header = _mountCont._container:Paragraph({
874
+ Title = "Premium Feature",
875
+ Desc = "Enter your key below to unlock this feature.",
876
+ })
877
+ table.insert(_destroyFns, function() pcall(function() _header:Destroy() end) end)
878
+
879
+ -- Key input (wrapped in pcall — Input may not exist on all Section types)
880
+ local _inputElem
881
+ pcall(function()
882
+ _inputElem = _mountCont._container:Input({
883
+ Title = "Premium Key",
884
+ Placeholder = "Paste your key here…",
885
+ Flag = (gb.mount or "prem") .. "_premKey",
886
+ Callback = function(v) _keyValue = v end,
887
+ })
888
+ table.insert(_destroyFns, function() pcall(function() _inputElem:Destroy() end) end)
889
+ end)
890
+
891
+ -- Validate + unlock
892
+ local function _doCheck()
893
+ local key = (_keyValue or ""):match("^%s*(.-)%s*$")
894
+ if key == "" then _setStatus("Enter a key first."); return end
895
+ local valid = false
896
+ local _pr = _PULSE_LAYOUT.premium
897
+ if _pr then
898
+ if _pr.keys then
899
+ for _, k in ipairs(_pr.keys) do
900
+ if k == key then valid = true; break end
901
+ end
902
+ end
903
+ if not valid and _pr.validatorUrl and _pr.validatorUrl ~= "" then
904
+ _setStatus("Checking…")
905
+ local ok, resp = pcall(function()
906
+ return game:HttpGet(_pr.validatorUrl .. key)
907
+ end)
908
+ if ok and resp and resp:lower():match("^true") then valid = true end
909
+ end
910
+ end
911
+ if valid then
912
+ _setStatus("Valid! Unlocking…")
913
+ task.delay(0.4, _activatePremium)
914
+ else
915
+ _setStatus("Invalid key.")
916
+ end
917
+ end
918
+
919
+ -- "Copy Link" button
920
+ local _pKeyUrl = (_PULSE_LAYOUT.premium and _PULSE_LAYOUT.premium.getKeyUrl)
921
+ or (_PULSE_LAYOUT.keySystem and _PULSE_LAYOUT.keySystem.getKeyUrl) or ""
922
+ local _copyBtn = _mountCont._container:Button({
923
+ Title = "Copy Link",
924
+ Desc = "Get your premium key",
925
+ Callback = function()
926
+ if _pKeyUrl == "" then _setStatus("No link configured."); return end
927
+ local ok = false
928
+ pcall(function() setclipboard(_pKeyUrl); ok = true end)
929
+ pcall(function() if not ok then toclipboard(_pKeyUrl); ok = true end end)
930
+ _setStatus(ok and "Link copied!" or "No clipboard API.")
931
+ end,
932
+ })
933
+ table.insert(_destroyFns, function() pcall(function() _copyBtn:Destroy() end) end)
934
+
935
+ -- "Check Key" button
936
+ local _checkBtn = _mountCont._container:Button({
937
+ Title = "Check Key",
938
+ Callback = _doCheck,
939
+ })
940
+ table.insert(_destroyFns, function() pcall(function() _checkBtn:Destroy() end) end)
941
+
942
+ -- Status line
943
+ _statusPara = _mountCont._container:Paragraph({
944
+ Title = "Status",
945
+ Desc = "—",
946
+ })
947
+ table.insert(_destroyFns, function() pcall(function() _statusPara:Destroy() end) end)
948
+ end)
949
+
950
+ -- Register: when premium activates, destroy locked UI and mount real component
951
+ table.insert(_premiumPendingUnlocks, function()
952
+ for _, fn in ipairs(_destroyFns) do fn() end
953
+ task.wait(0.05)
954
+ if _mountComp then
955
+ pcall(function() _UIAdapter:mount(_mountComp, _mountCont) end)
956
+ end
957
+ end)
958
+ elseif comp then
782
959
  local ok, err = pcall(function() _UIAdapter:mount(comp, container) end)
783
960
  if not ok then
784
961
  warn("[Pulse] mount '" .. gb.mount .. "' error: " .. tostring(err))
@@ -852,6 +1029,43 @@ task.spawn(function()
852
1029
  end,
853
1030
  })
854
1031
 
1032
+ -- Premium status groupbox — shown only when premium tier is configured
1033
+ if _PULSE_LAYOUT.premium then
1034
+ local gb_prem = _settingsTab:AddRightGroupbox("Premium")
1035
+ if _PULSE_PREMIUM then
1036
+ pcall(function()
1037
+ gb_prem._container:Paragraph({
1038
+ Title = "Premium Active",
1039
+ Desc = "All premium features are unlocked.",
1040
+ })
1041
+ end)
1042
+ else
1043
+ pcall(function()
1044
+ -- Store reference so _activatePremium can update it live
1045
+ _premiumStatusPara = gb_prem._container:Paragraph({
1046
+ Title = "Premium Locked",
1047
+ Desc = "Enter a key in any locked feature to unlock.",
1048
+ })
1049
+ end)
1050
+ local _pKeyUrl = (_PULSE_LAYOUT.premium and _PULSE_LAYOUT.premium.getKeyUrl)
1051
+ or (_PULSE_LAYOUT.keySystem and _PULSE_LAYOUT.keySystem.getKeyUrl)
1052
+ if _pKeyUrl and _pKeyUrl ~= "" then
1053
+ pcall(function()
1054
+ gb_prem._container:Button({
1055
+ Title = "Get Premium Key",
1056
+ Desc = "Copy the link to your key page",
1057
+ Callback = function()
1058
+ local ok = false
1059
+ pcall(function() setclipboard(_pKeyUrl); ok = true end)
1060
+ pcall(function() if not ok then toclipboard(_pKeyUrl); ok = true end end)
1061
+ _PulseNotify(ok and "Link copied!" or _pKeyUrl, 4)
1062
+ end,
1063
+ })
1064
+ end)
1065
+ end
1066
+ end
1067
+ end
1068
+
855
1069
  -- Menu settings groupbox
856
1070
  local gb_menu = _settingsTab:AddRightGroupbox("Menu Settings")
857
1071
  _UIAdapter:addButton(gb_menu, {
package/dist/index.js CHANGED
@@ -56,7 +56,7 @@ var RB_VERSION, RB_HOME, IRONBREW2_DIR, IRONBREW2_REPO, ALWAYS_EXCLUDE, VALID_UI
56
56
  var init_constants = __esm({
57
57
  "src/constants.ts"() {
58
58
  init_cjs_shims();
59
- RB_VERSION = "1.4.3";
59
+ RB_VERSION = "1.4.4";
60
60
  RB_HOME = path.join(os.homedir(), ".rb");
61
61
  path.join(RB_HOME, "bin");
62
62
  IRONBREW2_DIR = path.join(RB_HOME, "ironbrew2");
@@ -1156,6 +1156,52 @@ _G.__AOT_R_DESTROY = _PulseDestroy
1156
1156
  }
1157
1157
  return "windui";
1158
1158
  }
1159
+ // Extract content between matching braces for a named TS field.
1160
+ // Returns null if field is absent or commented out.
1161
+ extractLayoutBlock(src, field) {
1162
+ const rx = new RegExp(`\\b${field}\\s*:\\s*\\{`);
1163
+ const m = src.match(rx);
1164
+ if (!m || m.index === void 0) return null;
1165
+ const before = src.slice(0, m.index);
1166
+ const lineStart = before.lastIndexOf("\n") + 1;
1167
+ if (before.slice(lineStart).trimStart().startsWith("//")) return null;
1168
+ let depth = 0, i = m.index + m[0].length - 1;
1169
+ const start = i + 1;
1170
+ for (; i < src.length; i++) {
1171
+ if (src[i] === "{") depth++;
1172
+ else if (src[i] === "}") {
1173
+ depth--;
1174
+ if (depth === 0) break;
1175
+ }
1176
+ }
1177
+ return src.slice(start, i);
1178
+ }
1179
+ // Convert an extracted TS block body to a Lua table literal string.
1180
+ blockToLuaTable(block) {
1181
+ const str = (k) => block.match(new RegExp(`\\b${k}\\s*:\\s*['"]([^'"]+)['"]`))?.at(1) ?? null;
1182
+ const bool = (k) => {
1183
+ const m = block.match(new RegExp(`\\b${k}\\s*:\\s*(true|false)\\b`));
1184
+ return m ? m[1] === "true" : null;
1185
+ };
1186
+ const arr = (k) => {
1187
+ const m = block.match(new RegExp(`\\b${k}\\s*:\\s*\\[([^\\]]*)\\]`));
1188
+ return m ? [...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]) : null;
1189
+ };
1190
+ const parts = [];
1191
+ const title = str("title");
1192
+ if (title !== null) parts.push(`title=${JSON.stringify(title)}`);
1193
+ const note = str("note");
1194
+ if (note !== null) parts.push(`note=${JSON.stringify(note)}`);
1195
+ const save = bool("saveKey");
1196
+ if (save !== null) parts.push(`saveKey=${save}`);
1197
+ const gkUrl = str("getKeyUrl");
1198
+ if (gkUrl !== null) parts.push(`getKeyUrl=${JSON.stringify(gkUrl)}`);
1199
+ const valUrl = str("validatorUrl");
1200
+ if (valUrl !== null) parts.push(`validatorUrl=${JSON.stringify(valUrl)}`);
1201
+ const keys = arr("keys");
1202
+ if (keys && keys.length) parts.push(`keys={${keys.map((k) => JSON.stringify(k)).join(",")}}`);
1203
+ return parts.length ? `{${parts.join(",")}}` : null;
1204
+ }
1159
1205
  readLayoutConfig() {
1160
1206
  const layoutTs = path.join(this.srcDir, "layout.ts");
1161
1207
  const cfg = {
@@ -1324,6 +1370,17 @@ _G.__AOT_R_DESTROY = _PulseDestroy
1324
1370
  if (lc["discord"]) parts.push(`,discord=${JSON.stringify(lc["discord"])}`);
1325
1371
  if (lc["version"]) parts.push(`,version=${JSON.stringify(lc["version"])}`);
1326
1372
  if (opts.dev) parts.push(`,dev=true`);
1373
+ const layoutSrc = fs.existsSync(path.join(this.srcDir, "layout.ts")) ? fs.readFileSync(path.join(this.srcDir, "layout.ts"), "utf8") : "";
1374
+ const ksBlock = this.extractLayoutBlock(layoutSrc, "keySystem");
1375
+ const prBlock = this.extractLayoutBlock(layoutSrc, "premium");
1376
+ if (ksBlock) {
1377
+ const lua = this.blockToLuaTable(ksBlock);
1378
+ if (lua) parts.push(`,keySystem=${lua}`);
1379
+ }
1380
+ if (prBlock) {
1381
+ const lua = this.blockToLuaTable(prBlock);
1382
+ if (lua) parts.push(`,premium=${lua}`);
1383
+ }
1327
1384
  parts.push(`}
1328
1385
  `);
1329
1386
  parts.push(`local _P=loadstring(game:HttpGet("${base}/pulse.lua"))(_PULSE_LAYOUT)
@@ -1835,7 +1892,7 @@ function makeClaudeMd(name) {
1835
1892
  function makeAgentsMd(name) {
1836
1893
  return read("AGENTS.md").replace(/{NAME}/g, name);
1837
1894
  }
1838
- var DIR, TEMPLATE_GLOBALS, TEMPLATE_REMOTES, MODULE_BOILERPLATE, REMOTE_BOILERPLATE, TEMPLATE_GITIGNORE, TEMPLATE_DEPLOY_EXAMPLE, TEMPLATE_LAYOUT_TS, TEMPLATE_PAGE_HOME_TS, TEMPLATE_COMPONENT_TS, TEMPLATE_EX_SPEED_TS, TEMPLATE_EX_FOV_TS, TEMPLATE_EX_ESP_TS;
1895
+ var DIR, TEMPLATE_GLOBALS, TEMPLATE_REMOTES, MODULE_BOILERPLATE, REMOTE_BOILERPLATE, TEMPLATE_GITIGNORE, TEMPLATE_DEPLOY_EXAMPLE, TEMPLATE_LAYOUT_TS, TEMPLATE_PAGE_COMBAT_TS, TEMPLATE_COMPONENT_TS, TEMPLATE_EX_SPEED_TS, TEMPLATE_EX_FOV_TS, TEMPLATE_EX_ESP_TS, TEMPLATE_EX_AIMBOT_TS;
1839
1896
  var init_templates = __esm({
1840
1897
  "src/templates.ts"() {
1841
1898
  init_cjs_shims();
@@ -1847,11 +1904,13 @@ var init_templates = __esm({
1847
1904
  TEMPLATE_GITIGNORE = read("gitignore");
1848
1905
  TEMPLATE_DEPLOY_EXAMPLE = read("deploy_config.example");
1849
1906
  TEMPLATE_LAYOUT_TS = read("layout.ts");
1850
- TEMPLATE_PAGE_HOME_TS = read("page_home.ts");
1907
+ read("page_home.ts");
1908
+ TEMPLATE_PAGE_COMBAT_TS = read("page_combat.ts");
1851
1909
  TEMPLATE_COMPONENT_TS = read("component.ts");
1852
1910
  TEMPLATE_EX_SPEED_TS = read("component_speed.ts");
1853
1911
  TEMPLATE_EX_FOV_TS = read("component_fov.ts");
1854
1912
  TEMPLATE_EX_ESP_TS = read("component_esp.ts");
1913
+ TEMPLATE_EX_AIMBOT_TS = read("component_aimbot.ts");
1855
1914
  }
1856
1915
  });
1857
1916
 
@@ -1940,9 +1999,10 @@ async function cmdInit(args) {
1940
1999
  ["src/misc/globals.lua", TEMPLATE_GLOBALS.replace(/{NAME}/g, name)],
1941
2000
  ["src/misc/remotes.lua", TEMPLATE_REMOTES],
1942
2001
  ["src/layout.ts", TEMPLATE_LAYOUT_TS.replace(/{NAME}/g, name)],
1943
- ["src/pages/1_Home.ts", TEMPLATE_PAGE_HOME_TS],
2002
+ ["src/pages/1_Combat.ts", TEMPLATE_PAGE_COMBAT_TS],
1944
2003
  ["src/combat/SpeedHack.ts", TEMPLATE_EX_SPEED_TS],
1945
2004
  ["src/combat/FOVChanger.ts", TEMPLATE_EX_FOV_TS],
2005
+ ["src/combat/Aimbot.ts", TEMPLATE_EX_AIMBOT_TS],
1946
2006
  ["src/visuals/PlayerESP.ts", TEMPLATE_EX_ESP_TS],
1947
2007
  ["tsconfig.json", TSCONFIG],
1948
2008
  [".rb-deploy.example", TEMPLATE_DEPLOY_EXAMPLE],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-rb",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "description": "rb CLI — Pulse framework build tool for Roblox script projects",
5
5
  "bin": {
6
6
  "rb": "./bin/rb.js"
package/pulse/runtime.lua CHANGED
@@ -544,6 +544,7 @@ local function _groupbox(side, title, opts)
544
544
  icon=(opts and opts.icon) or "",
545
545
  mount=(opts and opts.mount) or nil,
546
546
  plain=(opts and opts.plain) or false,
547
+ premium=(opts and opts.premium) or false,
547
548
  widgets=(opts and opts.widgets) or {} }
548
549
  end
549
550
 
@@ -0,0 +1,18 @@
1
+ // Example premium component — mark the groupbox as { premium: true } in your page.
2
+ // Users see an inline key-entry form until they unlock with a valid premium key.
3
+ defineComponent('Aimbot', (): WidgetDef[] => {
4
+ const enabled = signal<boolean>(false)
5
+ const fovRadius = signal<number>(90)
6
+ const smoothing = signal<number>(0.15)
7
+
8
+ on.heartbeat({ when: enabled, every: 0.016 }, (): void => {
9
+ // Aimbot logic — find nearest enemy and aim toward them.
10
+ // Use Pulse.World helpers and _PulseGetHRP() for the local root part.
11
+ })
12
+
13
+ return [
14
+ toggle('Aimbot').bind(enabled),
15
+ slider('FOV Radius', { min: 10, max: 360, suffix: '°' }).bind(fovRadius),
16
+ slider('Smoothing', { min: 0, max: 1 }).bind(smoothing),
17
+ ]
18
+ })
@@ -1,9 +1,12 @@
1
- // Pulse layout — configures the script window.
2
- // Based on Next.js root layout convention.
1
+ // Pulse layout — configures the script window and optional key/premium systems.
2
+ // This file is never compiled into the output; the rb CLI reads it at build time.
3
3
 
4
4
  export default {
5
5
  title: '{NAME}',
6
6
  author: '',
7
+ version: '1.0.0',
8
+ description: 'A powerful script for {NAME}. Enable features from the tabs above.',
9
+ discord: '', // shown as "Join Discord" button in Home tab
7
10
  toggleKey: 'RightControl',
8
11
  size: [850, 560] as [number, number],
9
12
  uiLibrary: 'windui' as const,
@@ -16,4 +19,34 @@ export default {
16
19
  openButtonIcon: 'code-2',
17
20
  themes: [] as LayoutConfig['themes'],
18
21
  compatExclude: [] as string[],
22
+
23
+ // ── Key system (optional) ────────────────────────────────────────────────────
24
+ // Blocks the UI until the user enters a valid key.
25
+ // Remove this block entirely to let anyone use the script without a key.
26
+ //
27
+ // keySystem: {
28
+ // title: 'Key Required',
29
+ // note: 'Get your free key from our Discord',
30
+ // saveKey: true, // persist key to executor filesystem
31
+ // getKeyUrl: 'https://discord.gg/example',
32
+ //
33
+ // // Option A — static list (client-side):
34
+ // keys: ['FREE_KEY_1', 'FREE_KEY_2'],
35
+ //
36
+ // // Option B — server-side validator (more secure, any string "true" = valid):
37
+ // // validatorUrl: 'https://yoursite.com/validate?key=',
38
+ // },
39
+
40
+ // ── Premium tier (optional) ──────────────────────────────────────────────────
41
+ // A second key layer on top of the free tier.
42
+ // Mark groupboxes with { premium: true } in your pages to lock them.
43
+ // Users see an inline key-entry UI in each locked groupbox; entering a valid
44
+ // premium key unlocks all of them live — no re-injection needed.
45
+ // Premium keys also grant basic (keySystem) access automatically.
46
+ //
47
+ // premium: {
48
+ // keys: ['PREMIUM_KEY_1'],
49
+ // // OR: validatorUrl: 'https://yoursite.com/premium?key=',
50
+ // getKeyUrl: 'https://yoursite.com/premium', // shown in "Copy Link" / "Get Premium Key" buttons
51
+ // },
19
52
  } satisfies LayoutConfig
@@ -0,0 +1,9 @@
1
+ // Page names must not clash with the built-in "Home" and "Settings" tabs.
2
+ // Rename this page and add more pages as needed.
3
+ definePage('Combat', { icon: 'swords' }, () => [
4
+ groupbox('left', 'Movement', { icon: 'person', mount: 'SpeedHack' }),
5
+ groupbox('left', 'Camera', { icon: 'eye', mount: 'FOVChanger' }),
6
+ groupbox('right', 'Visuals', { icon: 'scan-eye', mount: 'PlayerESP' }),
7
+ // premium: true — shows inline key-entry form until the user unlocks with a premium key.
8
+ groupbox('right', 'Aimbot', { icon: 'crosshair', mount: 'Aimbot', premium: true }),
9
+ ])