-- Table Util
-- Stephen Leitnick
-- September 13, 2017

--[[

	TableUtil.Copy(tbl: table): table
	TableUtil.CopyShallow(tbl: table): table
	TableUtil.Sync(tbl: table, template: table): void
	TableUtil.FastRemove(tbl: table, index: number): void
	TableUtil.FastRemoveFirstValue(tbl: table, value: any): (boolean, number)
	TableUtil.Map(tbl: table, callback: (value: any) -> any): table
	TableUtil.Filter(tbl: table, callback: (value: any) -> boolean): table
	TableUtil.Reduce(tbl: table, callback: (accum: number, value: number) -> number [, initialValue: number]): number
	TableUtil.Assign(target: table, ...sources: table): table
	TableUtil.Extend(tbl: table, extension: table): table
	TableUtil.Reverse(tbl: table): table
	TableUtil.Shuffle(tbl: table [, rng: Random]): table
	TableUtil.Flat(tbl: table [, maxDepth: number = 1]): table
	TableUtil.FlatMap(tbl: callback: (value: any) -> table): table
	TableUtil.Keys(tbl: table): table
	TableUtil.Find(tbl: table, callback: (value: any) -> boolean): (any, number)
	TableUtil.Every(tbl: table, callback: (value: any) -> boolean): boolean
	TableUtil.Some(tbl: table, callback: (value: any) -> boolean): boolean
	TableUtil.IsEmpty(tbl: table): boolean
	TableUtil.EncodeJSON(tbl: table): string
	TableUtil.DecodeJSON(json: string): table

--]]

local TableUtil = {}

local HttpService = game:GetService("HttpService")
local rng = Random.new()

local function CopyTable(t)
	assert(type(t) == "table", "First argument must be a table")
	local function Copy(tbl)
		local tCopy = table.create(#tbl)
		for k,v in pairs(tbl) do
			if (type(v) == "table") then
				tCopy[k] = Copy(v)
			else
				tCopy[k] = v
			end
		end
		return tCopy
	end
	return Copy(t)
end

local function CopyTableShallow(t)
	local tCopy = table.create(#t)
	if (#t > 0) then
		table.move(t, 1, #t, 1, tCopy)
	else
		for k,v in pairs(t) do tCopy[k] = v end
	end
	return tCopy
end

local function Sync(srcTbl, templateTbl)

	assert(type(srcTbl) == "table", "First argument must be a table")
	assert(type(templateTbl) == "table", "Second argument must be a table")

	local tbl = CopyTableShallow(srcTbl)

	-- If 'tbl' has something 'templateTbl' doesn't, then remove it from 'tbl'
	-- If 'tbl' has something of a different type than 'templateTbl', copy from 'templateTbl'
	-- If 'templateTbl' has something 'tbl' doesn't, then add it to 'tbl'
	for k,v in pairs(tbl) do

		local vTemplate = templateTbl[k]

		-- Remove keys not within template:
		if (vTemplate == nil) then
			tbl[k] = nil

		-- Synchronize data types:
		elseif (type(v) ~= type(vTemplate)) then
			if (type(vTemplate) == "table") then
				tbl[k] = CopyTable(vTemplate)
			else
				tbl[k] = vTemplate
			end

		-- Synchronize sub-tables:
		elseif (type(v) == "table") then
			tbl[k] = Sync(v, vTemplate)
		end

	end

	-- Add any missing keys:
	for k,vTemplate in pairs(templateTbl) do

		local v = tbl[k]

		if (v == nil) then
			if (type(vTemplate) == "table") then
				tbl[k] = CopyTable(vTemplate)
			else
				tbl[k] = vTemplate
			end
		end

	end

	return tbl

end

local function FastRemove(t, i)
	local n = #t
	t[i] = t[n]
	t[n] = nil
end

local function FastRemoveFirstValue(t, v)
	local index = table.find(t, v)
	if (index) then
		FastRemove(t, index)
		return true, index
	end
	return false, nil
end

local function Map(t, f)
	assert(type(t) == "table", "First argument must be a table")
	assert(type(f) == "function", "Second argument must be a function")
	local newT = table.create(#t)
	for k,v in pairs(t) do
		newT[k] = f(v, k, t)
	end
	return newT
end

local function Filter(t, f)
	assert(type(t) == "table", "First argument must be a table")
	assert(type(f) == "function", "Second argument must be a function")
	local newT = table.create(#t)
	if (#t > 0) then
		local n = 0
		for i,v in ipairs(t) do
			if (f(v, i, t)) then
				n += 1
				newT[n] = v
			end
		end
	else
		for k,v in pairs(t) do
			if (f(v, k, t)) then
				newT[k] = v
			end
		end
	end
	return newT
end

local function Reduce(t, f, init)
	assert(type(t) == "table", "First argument must be a table")
	assert(type(f) == "function", "Second argument must be a function")
	assert(init == nil or type(init) == "number", "Third argument must be a number or nil")
	local result = (init or 0)
	for k,v in pairs(t) do
		result = f(result, v, k, t)
	end
	return result
end

local function Assign(target, ...)
	local tbl = CopyTableShallow(target)
	for _,src in ipairs({...}) do
		for k,v in pairs(src) do
			tbl[k] = v
		end
	end
	return tbl
end

local function Extend(target, extension)
	local tbl = CopyTableShallow(target)
	for _,v in ipairs(extension) do
		table.insert(tbl, v)
	end
	return tbl
end

local function Reverse(tbl)
	local n = #tbl
	local tblRev = table.create(n)
	for i = 1,n do
		tblRev[i] = tbl[n - i + 1]
	end
	return tblRev
end

local function Shuffle(tbl, rngOverride)
	assert(type(tbl) == "table", "First argument must be a table")
	local shuffled = CopyTableShallow(tbl)
	local random = (rngOverride or rng)
	for i = #tbl, 2, -1 do
		local j = random:NextInteger(1, i)
		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
	end
	return shuffled
end

local function Flat(tbl, depth)
	depth = (depth or 1)
	local flatTbl = table.create(#tbl)
	local function Scan(t, d)
		for _,v in ipairs(t) do
			if (type(v) == "table" and d < depth) then
				Scan(v, d + 1)
			else
				table.insert(flatTbl, v)
			end
		end
	end
	Scan(tbl, 0)
	return flatTbl
end

local function FlatMap(tbl, callback)
	return Flat(Map(tbl, callback))
end

local function Keys(tbl)
	local keys = table.create(#tbl)
	for k in pairs(tbl) do
		table.insert(keys, k)
	end
	return keys
end

local function Find(tbl, callback)
	for k,v in pairs(tbl) do
		if (callback(v, k, tbl)) then
			return v, k
		end
	end
	return nil, nil
end

local function Every(tbl, callback)
	for k,v in pairs(tbl) do
		if (not callback(v, k, tbl)) then
			return false
		end
	end
	return true
end

local function Some(tbl, callback)
	for k,v in pairs(tbl) do
		if (callback(v, k, tbl)) then
			return true
		end
	end
	return false
end

local function IsEmpty(tbl)
	return (next(tbl) == nil)
end

local function EncodeJSON(tbl)
	return HttpService:JSONEncode(tbl)
end

local function DecodeJSON(str)
	return HttpService:JSONDecode(str)
end

TableUtil.Copy = CopyTable
TableUtil.CopyShallow = CopyTableShallow
TableUtil.Sync = Sync
TableUtil.FastRemove = FastRemove
TableUtil.FastRemoveFirstValue = FastRemoveFirstValue
TableUtil.Map = Map
TableUtil.Filter = Filter
TableUtil.Reduce = Reduce
TableUtil.Assign = Assign
TableUtil.Extend = Extend
TableUtil.Reverse = Reverse
TableUtil.Shuffle = Shuffle
TableUtil.Flat = Flat
TableUtil.FlatMap = FlatMap
TableUtil.Keys = Keys
TableUtil.Find = Find
TableUtil.Every = Every
TableUtil.Some = Some
TableUtil.IsEmpty = IsEmpty
TableUtil.EncodeJSON = EncodeJSON
TableUtil.DecodeJSON = DecodeJSON

return TableUtil


local Promise = require(script.Parent.Promise)

local RunService = game:GetService("RunService")

local OUTPUT_PREFIX = "roblox-ts: "
local NODE_MODULES = "node_modules"
local DEFAULT_SCOPE = "@rbxts"

local TS = {}

TS.Promise = Promise

local function isPlugin(context)
	return RunService:IsStudio() and context:FindFirstAncestorWhichIsA("Plugin") ~= nil
end

function TS.getModule(context, scope, moduleName)
	-- legacy call signature
	if moduleName == nil then
		moduleName = scope
		scope = DEFAULT_SCOPE
	end

	-- ensure modules have fully replicated
	if RunService:IsRunning() and RunService:IsClient() and not isPlugin(context) and not game:IsLoaded() then
		game.Loaded:Wait()
	end

	local object = context
	repeat
		local nodeModulesFolder = object:FindFirstChild(NODE_MODULES)
		if nodeModulesFolder then
			local scopeFolder = nodeModulesFolder:FindFirstChild(scope)
			if scopeFolder then
				local module = scopeFolder:FindFirstChild(moduleName)
				if module then
					return module
				end
			end
		end
		object = object.Parent
	until object == nil

	error(OUTPUT_PREFIX .. "Could not find module: " .. moduleName, 2)
end

-- This is a hash which TS.import uses as a kind of linked-list-like history of [Script who Loaded] -> Library
local currentlyLoading = {}
local registeredLibraries = {}

function TS.import(context, module, ...)
	for i = 1, select("#", ...) do
		module = module:WaitForChild((select(i, ...)))
	end

	if module.ClassName ~= "ModuleScript" then
		error(OUTPUT_PREFIX .. "Failed to import! Expected ModuleScript, got " .. module.ClassName, 2)
	end

	currentlyLoading[context] = module

	-- Check to see if a case like this occurs:
	-- module -> Module1 -> Module2 -> module

	-- WHERE currentlyLoading[module] is Module1
	-- and currentlyLoading[Module1] is Module2
	-- and currentlyLoading[Module2] is module

	local currentModule = module
	local depth = 0

	while currentModule do
		depth = depth + 1
		currentModule = currentlyLoading[currentModule]

		if currentModule == module then
			local str = currentModule.Name -- Get the string traceback

			for _ = 1, depth do
				currentModule = currentlyLoading[currentModule]
				str = str .. "  ⇒ " .. currentModule.Name
			end

			error(OUTPUT_PREFIX .. "Failed to import! Detected a circular dependency chain: " .. str, 2)
		end
	end

	if not registeredLibraries[module] then
		if _G[module] then
			error(
				OUTPUT_PREFIX
				.. "Invalid module access! Do you have multiple TS runtimes trying to import this? "
				.. module:GetFullName(),
				2
			)
		end

		_G[module] = TS
		registeredLibraries[module] = true -- register as already loaded for subsequent calls
	end

	local data = require(module)

	if currentlyLoading[context] == module then -- Thread-safe cleanup!
		currentlyLoading[context] = nil
	end

	return data
end

function TS.instanceof(obj, class)
	-- custom Class.instanceof() check
	if type(class) == "table" and type(class.instanceof) == "function" then
		return class.instanceof(obj)
	end

	-- metatable check
	if type(obj) == "table" then
		obj = getmetatable(obj)
		while obj ~= nil do
			if obj == class then
				return true
			end
			local mt = getmetatable(obj)
			if mt then
				obj = mt.__index
			else
				obj = nil
			end
		end
	end

	return false
end

function TS.async(callback)
	return function(...)
		local n = select("#", ...)
		local args = { ... }
		return Promise.new(function(resolve, reject)
			coroutine.wrap(function()
				local ok, result = pcall(callback, unpack(args, 1, n))
				if ok then
					resolve(result)
				else
					reject(result)
				end
			end)()
		end)
	end
end

function TS.await(promise)
	if not Promise.is(promise) then
		return promise
	end

	local status, value = promise:awaitStatus()
	if status == Promise.Status.Resolved then
		return value
	elseif status == Promise.Status.Rejected then
		error(value, 2)
	else
		error("The awaited Promise was cancelled", 2)
	end
end

local SIGN = 2 ^ 31
local COMPLEMENT = 2 ^ 32
local function bit_sign(num)
	-- Restores the sign after an unsigned conversion according to 2s complement.
	if bit32.btest(num, SIGN) then
		return num - COMPLEMENT
	else
		return num
	end
end

function TS.bit_lrsh(a, b)
	return bit_sign(bit32.arshift(a, b))
end

TS.TRY_RETURN = 1
TS.TRY_BREAK = 2
TS.TRY_CONTINUE = 3

function TS.try(func, catch, finally)
	local err, traceback
	local success, exitType, returns = xpcall(
		func,
		function(errInner)
			err = errInner
			traceback = debug.traceback()
		end
	)
	if not success and catch then
		local newExitType, newReturns = catch(err, traceback)
		if newExitType then
			exitType, returns = newExitType, newReturns
		end
	end
	if finally then
		local newExitType, newReturns = finally()
		if newExitType then
			exitType, returns = newExitType, newReturns
		end
	end
	return exitType, returns
end

function TS.generator(callback)
	local co = coroutine.create(callback)
	return {
		next = function(...)
			if coroutine.status(co) == "dead" then
				return { done = true }
			else
				local success, value = coroutine.resume(co, ...)
				if success == false then
					error(value, 2)
				end
				return {
					value = value,
					done = coroutine.status(co) == "dead",
				}
			end
		end,
	}
end

return TS


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
local srcWorkspace = script.Parent.Parent.Parent
local Packages = srcWorkspace.Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local Array = LuauPolyfill.Array
local Object = LuauPolyfill.Object
type Object = LuauPolyfill.Object

local React = require(Packages.React)
local Change = React.Change

local ScrollContentViewNativeComponent = React.Component:extend("ScrollContentViewNativeComponent")

function ScrollContentViewNativeComponent:init(props)
	self.props = props
end

function ScrollContentViewNativeComponent:render()
	local styleProps = if Array.isArray(self.props.style)
		then Array.reduce(self.props.style, function(obj: Object, prop)
			return Object.assign(obj, prop)
		end, {})
		else self.props.style

	local nativeProps = Object.assign({
		Name = "RCTScrollContentView",
		[Change.AbsoluteSize] = self.props.onLayout,
		Size = UDim2.new(1, 0, 1, 0),
	}, if self.props.AutomaticSize then { AutomaticSize = self.props.AutomaticSize } else nil, styleProps)

	return React.createElement("Frame", nativeProps, self.props.children)
end

return ScrollContentViewNativeComponent


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
local srcWorkspace = script.Parent.Parent.Parent
local Packages = srcWorkspace.Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local console = LuauPolyfill.console
local Array = LuauPolyfill.Array
local Object = LuauPolyfill.Object
type Object = LuauPolyfill.Object

local React = require(Packages.React)
local Change = React.Change
local Event = React.Event

local Flipper = require(Packages.flipper.src) -- OverHash deviation: fix flipper rojo path

local DEFAULT_ANIMATION_CONFIG = {
	frequency = 1,
	dampingRatio = 1,
}

local ScrollViewNativeComponent = React.Component:extend("ScrollViewNativeComponent")
function ScrollViewNativeComponent:init(props)
	self.props = props
	self._nativeRef = React.createRef()
	self.lastScrollEventTime = 0

	self.animationConfig = if self.props.animationConfig then self.props.animationConfig else DEFAULT_ANIMATION_CONFIG

	self.motor = Flipper.GroupMotor.new({
		x = 0,
		y = 0,
	})

	self.motorStepConnection = self.motor:onStep(function(canvasPosition)
		self._nativeRef.current.CanvasPosition = Vector2.new(canvasPosition.x, canvasPosition.y)
	end)

	self._nativeRef.animateScrollTo = function(x, y)
		self:_startAnimatedScroll(x, y)
	end
end

function ScrollViewNativeComponent:_validateAnimatedScrollInputs(x: number, y: number)
	if not self._nativeRef.current then
		console.error("scrollTo animation failed: ScrollViewNativeComponent._nativeRef instance is undefined")
	end

	if self._nativeRef.current.ScrollingDirection == Enum.ScrollingDirection.X then
		if x > self._nativeRef.current.AbsoluteCanvasSize.X then
			console.warn(
				"scrollTo animation goal out of bounds, setting X goal to: "
					.. tostring(
						self._nativeRef.current.AbsoluteCanvasSize.X - self._nativeRef.current.AbsoluteWindowSize.X
					)
			)
		elseif x < 0 then
			console.warn("scrollTo animation goal out of bounds, setting X goal to: 0")
		end
	else
		if y > self._nativeRef.current.AbsoluteCanvasSize.Y then
			console.warn(
				"scrollTo animation goal out of bounds, setting Y goal to: "
					.. tostring(
						self._nativeRef.current.AbsoluteCanvasSize.Y - self._nativeRef.current.AbsoluteWindowSize.Y
					)
			)
		elseif y < 0 then
			console.warn("scrollTo animation goal out of bounds, setting Y goal to: 0")
		end
	end
end

function ScrollViewNativeComponent:_startAnimatedScroll(x, y)
	if _G.__DEV__ then
		self:_validateAnimatedScrollInputs(x, y)
	end
	local currentCanvasPosition = self._nativeRef.current.CanvasPosition

	self.motor:setGoal({
		x = Flipper.Instant.new(currentCanvasPosition.X),
		y = Flipper.Instant.new(currentCanvasPosition.Y),
	})

	--[[
		ROBLOX NOTE: Immediately step to the current canvas position. It can take a frame for the
		motor to step to the instant goal if we wait for the heartbeat to call the onStep method.
	]]
	self.motor:step(0)

	self.motor:setGoal({
		x = Flipper.Spring.new(x, self.animationConfig),
		y = Flipper.Spring.new(y, self.animationConfig),
	})
end

function ScrollViewNativeComponent:render()
	local styleProps = if Array.isArray(self.props.style)
		then Array.reduce(self.props.style, function(obj: Object, prop)
			return Object.assign(obj, prop)
		end, {})
		else self.props.style

	local nativeProps = Object.assign({
		Name = "RCTScrollView",
		ScrollingEnabled = if self.props.scrollEnabled ~= nil then self.props.scrollEnabled else true,
		Size = UDim2.new(1, 0, 1, 0),
		-- ROBLOX DEVIATION: For inverted scrolling, manually override the CanvasSize and CanvasPosition props
		CanvasPosition = self.props.CanvasPosition,
		CanvasSize = self.props.CanvasSize or UDim2.new(0, 0, 0, 0),
		AutomaticCanvasSize = self.props.AutomaticCanvasSize or Enum.AutomaticSize.XY,
		ScrollBarThickness = if (self.props.horizontal and self.props.showsHorizontalScrollIndicator == false)
				or (not self.props.horizontal and self.props.showsVerticalScrollIndicator == false)
			then 0
			else nil,
		ref = self._nativeRef,
		[Change.AbsoluteWindowSize] = self.props.onLayout,
		[Change.CanvasPosition] = function(rbx: ScrollingFrame)
			local currentScrollEventTime = os.clock() * 1000
			local minScrollEventThrottleDelta = self.props.scrollEventThrottle or 0
			if currentScrollEventTime - self.lastScrollEventTime > minScrollEventThrottleDelta then
				self.props.onScroll(rbx)
				self.lastScrollEventTime = currentScrollEventTime
			end
		end,
		[Event.InputBegan] = function(rbx, input: InputObject)
			if
				input.UserInputType == Enum.UserInputType.MouseWheel
				or input.UserInputType == Enum.UserInputType.Touch
			then
				if self.motor ~= nil then
					self.motor:stop()
				end
			end

			if input.UserInputType == Enum.UserInputType.Touch then
				self.props.onTouchStart(rbx, input)
			end
		end,
		[Event.InputEnded] = function(rbx, input: InputObject)
			if input.UserInputType == Enum.UserInputType.Touch then
				self.props.onTouchEnd(rbx, input)
			end
		end,
		[Event.InputChanged] = function(rbx, input: InputObject)
			if input.UserInputType == Enum.UserInputType.Touch then
				self.props.onTouchMove(rbx, input)
			end
		end,
	}, styleProps)

	return React.createElement("ScrollingFrame", nativeProps, {
		-- ROBLOX DEVIATION: For inverted scrolling, align the ScrollContentView to the bottom
		Layout = if self.props.inverted and not self.props.getItemLayout
			then React.createElement("UIListLayout", {
				HorizontalAlignment = if self.props.horizontal
					then Enum.HorizontalAlignment.Right
					else Enum.HorizontalAlignment.Center,
				VerticalAlignment = if self.props.horizontal
					then Enum.VerticalAlignment.Center
					else Enum.VerticalAlignment.Bottom,
			})
			else nil,
		[1] = self.props.children,
	})
end

function ScrollViewNativeComponent:willUnmount()
	if self.motor ~= nil then
		self.motor:destroy()
	end

	if self.motorStepConnection ~= nil then
		self.motorStepConnection:disconnect()
	end
end

return ScrollViewNativeComponent


-- ROBLOX note: no upstream
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX comment: in the future we may want to replace this with a full implementation of the View component
local Packages = script.Parent.Parent.Parent.Parent

local LuauPolyfill = require(Packages.LuauPolyfill)
local Array = LuauPolyfill.Array
local Object = LuauPolyfill.Object
type Object = LuauPolyfill.Object

local React = require(Packages.React)
local Change = React.Change

local View = React.Component:extend("View")
function View:init(props)
	self.props = props
	self._nativeRef = self.props.nativeRef or React.createRef()
end

function View:render()
	local styleProps = if Array.isArray(self.props.style)
		then Array.reduce(self.props.style, function(obj: Object, prop)
			return Object.assign(obj, prop)
		end, {})
		else self.props.style

	local props = Object.assign(table.clone(self.props), styleProps)

	self.nativeProps = {
		ref = self._nativeRef,
		-- ROBLOX TODO: We need to handle all valid Frame props / fail
		BackgroundColor3 = props.BackgroundColor3,
		Name = props.Name or "View",
		Size = props.Size or UDim2.new(1, 0, 0, 0),
		AutomaticSize = props.AutomaticSize or Enum.AutomaticSize.Y,
		ZIndex = props.ZIndex or nil,
		LayoutOrder = props.LayoutOrder or nil,
		BorderSizePixel = props.BorderSizePixel or 0,
		BackgroundTransparency = props.BackgroundTransparency or 1,
		[Change.AbsoluteSize] = props.onLayout,
	}
	return React.createElement("Frame", self.nativeProps, self.props.children)
end

return View


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]

local srcWorkspace = script.Parent.Parent
local Packages = srcWorkspace.Parent
local React = require(Packages.React)
local LuauPolyfill = require(Packages.LuauPolyfill)
local Object = LuauPolyfill.Object

local FlatList = require(script.Parent.FlatList)
type FlatListProps<ItemT> = FlatList.Props<ItemT>

local useFocusNavigationScrolling = require(script.Parent.Hooks).useFocusNavigationScrolling

type AnimatedProps<ItemT> = {
	onSelectedIndexChanged: ((item: ItemT) -> ())?,
	viewOffset: number?,
	animated: boolean?,
	cellRendererKey: string?,
}

export type Props<ItemT> = AnimatedProps<ItemT> & FlatListProps<ItemT>

local function AnimatedFlatList<ItemT>(props: Props<ItemT>)
	local listRef = React.useRef(nil)
	local onAnimationScrollFailed = useFocusNavigationScrolling({
		listRef = listRef,
		onSelectedIndexChanged = props.onSelectedIndexChanged,
		initialIndex = props.initialScrollIndex,
		cellRendererKey = props.cellRendererKey,
		viewOffset = props.viewOffset,
		animated = props.animated,
		data = props.data,
	})

	local flatListProps = Object.assign(table.clone(props), {
		viewOffset = Object.None,
		animated = Object.None,
		onSelectedIndexChanged = Object.None,
		ref = listRef,
		onScrollToIndexFailed = if props.getItemLayout then nil else onAnimationScrollFailed,
	})

	return React.createElement(FlatList, flatListProps)
end

return AnimatedFlatList


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]

return {
	useFocusNavigationScrolling = require(script.useFocusNavigationScrolling),
}


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]

local GuiService = game:GetService("GuiService")

local srcWorkspace = script.Parent.Parent.Parent
local Packages = srcWorkspace.Parent

local React = require(Packages.React)

local FlatList = require(script.Parent.Parent.FlatList)
type FlatList<T> = FlatList.FlatList<T>

type ScrollConfig<T> = {
	listRef: React.Ref<FlatList<T>>,
	onSelectedIndexChanged: ((index: number) -> ())?,
	initialIndex: number?,
	cellRendererKey: string?,
	viewOffset: number?,
	animated: boolean?,
	data: { any },
}

type ScrollToIndexFailedInfo = {
	index: number,
	highestMeasuredFrameIndex: number,
	averageItemLength: number,
}

local function useFocusNavigationScrolling<T>(scrollConfig: ScrollConfig<T>)
	local cellRendererKey = if scrollConfig.cellRendererKey then scrollConfig.cellRendererKey else "CellRendererView"
	local initialIndex = if scrollConfig.initialIndex then scrollConfig.initialIndex else 1
	local listRef = scrollConfig.listRef :: { current: FlatList<T>? }
	local onSelectedIndexChanged = scrollConfig.onSelectedIndexChanged
	local viewOffset = scrollConfig.viewOffset
	local animated = if scrollConfig.animated ~= nil then scrollConfig.animated else true
	local focusedIndex = React.useRef(initialIndex)
	local data = scrollConfig.data

	local onScrollToIndexFailed = React.useCallback(function(info: ScrollToIndexFailedInfo)
		local offsetEstimate = info.index * info.averageItemLength
		if listRef and listRef.current then
			listRef.current:scrollToOffset({
				offset = offsetEstimate,
			})
		else
			warn(
				"Animated scrolling failed, the ref to the ScrollView is nil."
					.. "This could indicate that you are selecting focus before the ScrollView has mounted."
			)
		end
	end, { listRef })

	React.useEffect(function()
		local function scrollToFocusSelection(changedValue: string)
			local selectedCoreObject = nil
			if changedValue == "SelectedObject" then
				selectedCoreObject = GuiService.SelectedObject
			elseif changedValue == "SelectedCoreObject" then
				selectedCoreObject = GuiService.SelectedCoreObject
			end

			if not selectedCoreObject then
				return nil
			end

			local parentView = (selectedCoreObject :: GuiObject):FindFirstAncestor(cellRendererKey) :: GuiObject
			if not parentView or not parentView.LayoutOrder then
				return nil
			end

			if
				parentView.LayoutOrder == focusedIndex.current
				or parentView.LayoutOrder > #data
				or parentView.LayoutOrder < 1
			then
				-- FlatList maps index in data to LayoutOrder.
				-- If the index hasn't changed or is out of bounds, we shouldn't animate
				return nil
			end

			focusedIndex.current = parentView.LayoutOrder

			if onSelectedIndexChanged then
				onSelectedIndexChanged(focusedIndex.current :: number)
			end

			if not listRef or not listRef.current then
				return nil
			end

			(listRef.current :: FlatList<T>):scrollToIndex({
				index = focusedIndex.current :: number,
				animated = animated,
				viewOffset = viewOffset,
			})
			return nil
		end

		local connection = GuiService.Changed:Connect(scrollToFocusSelection)

		return function()
			if connection then
				connection:Disconnect()
			end
		end
	end, { listRef, focusedIndex, cellRendererKey, onSelectedIndexChanged, animated, viewOffset } :: { any })

	return onScrollToIndexFailed
end

return useFocusNavigationScrolling


-- ROBLOX note: no upstream
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
local Components = script.Components
local Lists = script.Lists
local ScrollView = require(Components.ScrollView.ScrollView)
local VirtualizedList = require(Lists.VirtualizedList)
local SectionList = require(Lists.SectionList).default
local FlatList = require(Lists.FlatList)
local View = require(Components.View.View)
local ViewabilityHelper = require(Lists.ViewabilityHelper)
local Hooks = require(Lists.Hooks)
local AnimatedFlatList = require(Lists.AnimatedFlatList)

export type ViewabilityConfigCallbackPair = ViewabilityHelper.ViewabilityConfigCallbackPair
export type ViewabilityConfig = ViewabilityHelper.ViewabilityConfig
export type AnimatedFlatListProps<T> = AnimatedFlatList.Props<T>

return {
	ScrollView = ScrollView,
	VirtualizedList = VirtualizedList,
	SectionList = SectionList,
	FlatList = FlatList,
	View = View,
	AnimatedFlatList = AnimatedFlatList,
	Hooks = Hooks,
}


local fs = require("@lune/fs")
local net = require("@lune/net")
local serde = require("@lune/serde")

print("Fetching client version...")

local versionRes = net.request("https://clientsettings.roblox.com/v2/client-version/WindowsStudio64")
local version = serde.decode("json", versionRes.body).clientVersionUpload

print("Fetching api dump...")

local dumpRes = net.request(`https://setup.rbxcdn.com/{version}-API-Dump.json`)
local dump = serde.decode("json", dumpRes.body)

print("Processing api dump...")

local nameIndexMap = {}
for i, class in dump.Classes do
	nameIndexMap[class.Name] = i
end

local function testSecurity(security)
	return security == "None" or security == "PluginSecurity"
end

local function getAllNonInstanceMembers(className)
	local members = {}
	while className and className ~= "Instance" do
		local class = dump.Classes[nameIndexMap[className]]
		for _, member in class.Members do
			table.insert(members, member)
		end
		className = class.Superclass
	end
	return members
end

local blacklist = {
	"VoiceChatInternal", -- not for use
	"RomarkService", -- locked
	"CommerceService", -- not enabled
	"HttpRbxApiService", -- only RequestLimitedAsync, which is not enabled
	"Studio", -- acquired via settings()
	"RenderSettings", -- as above (NB: other subsettings aren't even tagged as services)
}

local whitelist = {
	"ServerScriptService",
	"ServerStorage",
	"ReplicatedStorage",
	"StarterGui",
	"StarterPack",
	"StarterPlayer",
}

local pluginOnlyOverride = {
	["ServerScriptService"] = false,
	["ServerStorage"] = false,
	["ReplicatedStorage"] = false,
	["StarterPlayer"] = false,
	["StarterPack"] = false,
	["CoreGui"] = true,
	["Selection"] = true,
}

local services = {}

for _, class in dump.Classes do
	if table.find(blacklist, class.Name) then
		continue
	end

	local tags = class.Tags
	if not tags then
		continue
	elseif not table.find(tags, "Service") then
		continue
	elseif table.find(tags, "Deprecated") then
		continue
	end

	local anyValidSecurity = false
	local pluginOnly = true

	for _, member in getAllNonInstanceMembers(class.Name) do
		if member.Tags and table.find(member.Tags, "Deprecated") then
			continue
		end

		local lowestSecurity
		if member.Type == "Property" then
			local readSecurity = member.Security.Read
			local writeSecurity = member.Security.Write
			if testSecurity(readSecurity) or testSecurity(writeSecurity) then
				if readSecurity == writeSecurity then
					lowestSecurity = readSecurity
				else
					-- if they are not the same, one must be None
					lowestSecurity = "None"
				end
			end
		else
			if testSecurity(member.Security) then
				lowestSecurity = member.Security
			end
		end
		if lowestSecurity ~= nil then
			anyValidSecurity = true
		end
		if lowestSecurity == "None" then
			pluginOnly = false
		end
	end

	local name = class.Name
	if pluginOnlyOverride[name] ~= nil then
		anyValidSecurity = true
		pluginOnly = pluginOnlyOverride[name]
	end

	if anyValidSecurity or table.find(whitelist, name) then
		table.insert(services, {
			name = name,
			pluginOnly = pluginOnly,
		})
	end
end

fs.writeFile("services.json", serde.encode("json", services))


local moduleServices = require(script.Parent.moduleServices)

local excludedRoots = {
	"_Index", -- wally
	"node_modules", -- npm/yarn
}

local function isImportable(module: ModuleScript)
	local parent = module.Parent
	while parent and parent ~= game do
		if parent:IsA("Folder") and table.find(excludedRoots, parent.Name) then
			return false
		end
		parent = parent.Parent
	end

	return true
end

type Context = "server" | "shared" | "client"

local serverDirectories = {
	game:GetService("ServerScriptService"),
	game:GetService("ServerStorage"),
}

local clientDirectories = {
	game:GetService("StarterPlayer"),
	game:GetService("StarterGui"),
	game:GetService("StarterPack"),
}

local function getContext(container: LuaSourceContainer): Context
	if container:IsA("LocalScript") then -- do this check first as localscript inherits script
		return "client"
	elseif container:IsA("Script") then
		if container.RunContext == Enum.RunContext.Client then
			return "client"
		end
		return "server"
	end
	for _, dir in serverDirectories do
		if container:IsDescendantOf(dir) then
			return "server"
		end
	end
	for _, dir in clientDirectories do
		if container:IsDescendantOf(dir) then
			return "client"
		end
	end
	return "shared"
end

local function canAccess(origin: LuaSourceContainer, target: ModuleScript)
	if origin == target then
		return false
	end

	local originContext = getContext(origin)
	local targetContext = getContext(target)
	if originContext == "server" then
		return targetContext == "server" or targetContext == "shared"
	elseif originContext == "client" then
		return targetContext == "client" or targetContext == "shared"
	end

	return true
end

local latestModules: { ModuleScript } = {}

local function getModules(origin: LuaSourceContainer): { ModuleScript }
	local modules = {}
	for _, module in latestModules do
		if canAccess(origin, module) then
			table.insert(modules, module)
		end
	end

	return modules
end

for _, service in moduleServices do
	for _, inst in service:GetDescendants() do
		if inst:IsA("ModuleScript") and isImportable(inst) then
			table.insert(latestModules, inst)
		end
	end
	service.DescendantAdded:Connect(function(inst)
		if inst:IsA("ModuleScript") and isImportable(inst) then
			table.insert(latestModules, inst)
		end
	end)
	service.DescendantRemoving:Connect(function(inst)
		local index = table.find(latestModules, inst)
		if index ~= nil then
			table.remove(latestModules, index)
		end
	end)
end

return getModules


return {
	game:GetService("Workspace"),
	game:GetService("ReplicatedFirst"),
	game:GetService("ReplicatedStorage"),
	game:GetService("ServerScriptService"),
	game:GetService("ServerStorage"),
	game:GetService("StarterGui"),
	game:GetService("StarterPack"),
	game:GetService("StarterPlayer"),
}


--// Administer
--// CodingAgent 2026

--// This file exists to stop circular imports
local Root = script:FindFirstAncestor("Loader")

local Admins = require(Root.Modules.AdminRunner)
local Apps = require(Root.Modules.Apps)
local Utils = require(Root.Modules.Utilities)
local Var = require(Root.Core.Variables)

return function(): ()
	Utils.Logging.Print("[-] Performing first-time setup...")

	local Owner = Utils.GetGameOwner()

	Var.IsFirstBoot = true
	Admins.Ranks.New({
		AdmRankVersion = 3,

		Name = "Administrator",
		Protected = true,
		Members = {
			{
				ID = Owner.ID,
				MemberType = Owner.MemberType,
				GroupRank = 255,
			},
		},

		Apps = {
			SuperAdmin = true,
		},

		CreationReason = "Added by System for first-time setup",
		ActingUser = 1,

		Color = "23ff74",
	})

	Var.PrimaryAppServer = Var.DefaultAppServer

	task.spawn(function()
		local ok, err = pcall(function()
			Apps.ServerAPI.New(Var.DefaultAppServer, -1)
			Apps.ServerAPI.RegisterPrimary(Var.DefaultAppServer)
			Apps.ServerAPI.InstallFromServer(Var.DefaultAppServer, "org.admsoftware.player-management.Main")
		end)
		if not ok then
			Utils.Logging.SWarn("[BootstrapGame] AOS registration failed (gateway unreachable?): " .. tostring(err))
		end
	end)

	Utils.Logging.Print("[✓] Welcome to Administer!")
	Utils.Logging.SWarn(
		"[!] Is Administer making too many print messages? To silence prints, go to Settings and set General.Verbosity to `error`!"
	)
end


--!strict

--// Administer
--// CodingAgent 2026

local Types = require(script.Parent.Types)
local Locales: { Default: string, Value: string, Items: { string } } = {
	Default = "en",
	Value = "en",
	Items = {},
}
for _, Locale in script.Parent.Locales:GetChildren() do
	table.insert(Locales.Items, Locale.Name)
end

type SettingsType = Types.Configuration

local Settings: SettingsType = {
	Name = "Administer",

	VersData = {
		Service = {
			Name = "Administer",
			Icon = `rbxassetid://{require(script.Parent.Icons).logos["administer-blue"]}`,
		},

		Major = 2,
		Minor = 1,
		Tweak = 1,

		Extra = "",
		String = "1.0.0",
		GitRevision = "a13118d",

		NoExtraData = "1.0.0",
		APIRevision = "2.0.0",

		ID = "org.admsoftware.Core",
	},

	Admins = {

		Groups = {
			[0] = {
				RankNumber = 255,
				AdminRankID = 1,
			},
		},

		Players = {
			[0] = {
				AdminRankID = 1,
			},
		},
	},

	Contributors = {
		{
			UserID = 1457766451,
			Name = "theblackwither4",
			Job = "Project Owner",
		},
		{
			UserID = 133017837,
			Name = "pyxfluff",
			Job = "previous owner, AOS, frontend/backend",
		},
		{
			UserID = 938429178,
			Name = "AjaxTiba",
			Job = "core features",
		},
		{
			UserID = 3610087630,
			Name = "dogo8me2",
			Job = "core assistance, apps",
		},
		{
			UserID = 630064214,
			Name = "DullerKill7r",
			Job = "app development",
		},
		{
			UserID = 3239844753,
			Name = "CDot_NightHawk",
			Job = "hosting infrastructure",
		},
		{
			UserID = 1381321740,
			Name = "apexblue1705",
			Job = "product testing, translation (vi)",
		},
		{
			UserID = 1556153247,
			Name = "gdr",
			Job = "translation (ru)",
		},
		{
			UserID = 215537737,
			Name = "SKrun",
			Job = "translation (pt)",
		},
		{
			UserID = 1936557762,
			Name = "WarriorOfLC",
			Job = "translation (many languages)",
		},
		{
			UserID = 1119454702,
			Name = "CheekyPine",
			Job = "translation (es)",
		},
		{
			UserID = 365438147,
			Name = "pixe_ated",
			Job = "QuickBlur",
		},
	},

	Settings = {
		_SettingsMeta = {
			Name = "Administer",
		},

		Admins = {
			_CategoryOpts = {
				DisplayName = "Admins",
				Color = "#2b32e2",
				Description = "Change how Administer manages ranks and admins",
				Icon = "user-clip",
			},

			RefreshTime = {
				DisplayName = "Admin check time",
				Description = "How often to check if admins are still admins, and take away the panel if they are not.",
				SettingType = "NumberRange",
				SettingDisplayType = "Slider",
				RequiresRestart = false,

				Value = {
					Value = 120,
					Default = 120,
					Min = 15,
					Max = 1000,
					Step = 1,
				},
			},

			LiveUpdate = {
				DisplayName = "Live rank changes",
				Description = "If enabled, when a rank is made or edited, Administer will automatically check for new admins.",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = false,

				Value = {
					Value = true,
					Default = true,
				},
			},

			AllowProtectedEdits = {
				DisplayName = "Edit Protected Ranks",
				Description = "Allows editing protected ranks (superadmin). Also allows the creation of them. Not recommended as you could potentially break your installation.",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = false,

				Value = {
					Value = false,
					Default = false,
				},
			},

			LoadDelay = {
				Description = "Enables a 3s delay before loading any admins. Use this to solve apps not initializing.",
				SettingType = "Enum",
				SettingDisplayType = "Dropdown",
				RequiresRestart = false,

				Value = {
					Value = "None",
					Default = "None",
					Items = {
						"None",
						"InStudio",
						"All",
					},
				},
			},

			sudo = {
				Description = "Enables free admin for everybody in Studio. Use this as an alternative to `EnableFreeAdmin` if you only need it for configuration within Studio.",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = false,

				Value = {
					Value = true,
					Default = true,
				},
			},
		},

		Interface = {
			_CategoryOpts = {
				DisplayName = "Interface",
				Color = "#2be274",
				Description = "Customize the user interface.",
				Icon = "sidebar-menu",
			},

			Language = {
				DisplayName = "Interface Language (BETA)",
				Description = "This option will override your default Roblox preference. This is a work in progress and will NOT be supported fully by Administer or apps yet.",
				SettingType = "Enum",
				SettingDisplayType = "Dropdown",

				Value = Locales :: Types.SettingValue,
			},

			AnimationSpeed = {
				DisplayName = "Animation Speed",
				Description = "Controls the speed of client-side animations. Higher is slower. Set to 0 to disable animations.",
				SettingType = "float",
				SettingDisplayType = "Slider",
				RequiresRestart = false,

				Value = {
					Value = 1,
					Default = 1,
					Min = 0,
					Max = 5,
					Step = 0.1,
				},
			},

			AllowThemeScriptHooks = {
				DisplayName = "Allow themes to run Lua",
				Description = "If enabled, all themes will be allowed to run external Luau code. This is useful for more advanced themes or other modifications.",
				SettingType = "boolean",
				SettingDisplayType = "Toggle",

				Value = {
					Value = true,
					Default = true,
				},
			},

			EnableEditableImages = {
				DisplayName = "Enable EditableImages",
				Description = "Enables EditableImage features such as app card backgrounds, reflections, and other blurring effects. Requires the game permission and ID verification due to Roblox limitations.",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = false,

				Value = {
					Value = true,
					Default = true,
				},
			},

			HomepageGreeting = {
				DisplayName = "Homepage Greeting",
				Description = 'This is the text that displays after the "Hello, username!" text on the homepage.',
				SettingType = "String",
				SettingDisplayType = "InputBox",
				RequiresRestart = false,

				Value = {
					Value = "Welcome to Administer!",
					Default = "Welcome to Administer!",
				},
			},

			PanelKeybind = {
				DisplayName = "Panel Keybind",
				Description = "The key used to open the panel on keyboard-enabled devices.",
				SettingType = "KeyCode",
				SettingDisplayType = "KeybindSelector",
				RequiresRestart = false,

				Value = {
					Value = Enum.KeyCode.F2.Name,
					Default = Enum.KeyCode.F2.Name,
				},
			},

			PanelFullscreenKeybind = {
				DisplayName = "Panel Fullscreen Keybind",
				Description = "The key used to expand the panel to be fullscreen on keyboard-enabled devices.",
				SettingType = "KeyCode",
				SettingDisplayType = "KeybindSelector",
				RequiresRestart = false,

				Value = {
					Value = "LeftControl,Space",
					Default = "LeftControl,Space",
				},
			},

			AppDrawerKeybind = {
				Description = "Opens the App Drawer when you push this button",
				SettingType = "KeyCode",
				SettingDisplayType = "KeybindSelector",
				RequiresRestart = false,

				Value = {
					Value = "Equals",
					Default = "Equals",
				},
			},

			EnableClickEffects = {
				DisplayName = "Enable button effects",
				Description = "Adds a ripple & hover effect and some noise.",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = true,

				Value = {
					Value = true,
					Default = true,
				},
			},
		},

		Apps = {
			_CategoryOpts = {
				DisplayName = "Apps",
				Color = "#2be2e1",
				Description = "Change how Administer treats Apps.",
				Icon = "apps",
			},

			DisableAOS = {
				Description = "Prohibits AOS from being reached for the marketplace.",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = false,

				Value = {
					Value = false,
					Default = false,
				},
			},

			LoadApps = {
				Description = "Enables the ability for Administer to load apps.",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = true,

				Value = {
					Value = true,
					Default = true,
				},
			},
		},

		Accessibility = {
			_CategoryOpts = {
				DisplayName = "Accessibility",
				Color = "#e4e47f",
				Description = "Make Administer easier to use.",
				Icon = "accessibility",
			},

			ChatCommand = {
				Description = "Enables an /adm command to open Administer.",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = false,

				Value = {
					Value = true,
					Default = true,
				},
			},

			AssistiveClose = {
				Description = "Enables a close button in the top left for desktop. Always enabled on mobile. Does not impact the Control Bar",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = true,

				Value = {
					Value = false,
					Default = false,
				},
			},

			MobileOpenGesture = {
				Description = "Controls how big the mobile open gesture is from the right side.",
				SettingType = "Enum",
				SettingDisplayType = "Dropdown",
				RequiresRestart = false,

				Value = {
					Items = {
						"disabled",
						"small",
						"regular",
						"medium",
						"large",
					},

					Value = "regular",
					Default = "regular",
				},
			},

			GripperIdleOpacity = {
				Description = "Controls the visibility of the gripper in its idle state.",
				SettingType = "Enum",
				SettingDisplayType = "Dropdown",
				RequiresRestart = false,

				Value = {
					Items = {
						"opaque",
						"faint",
						"visible",
					},

					Value = "opaque",
					Default = "opaque",
				},
			},
		},

		General = {
			_CategoryOpts = {
				DisplayName = "General",
				Color = "#e22b4d",
				Description = "General Administer settings.",
				Icon = "gear",
			},

			Verbosity = {
				Description = "The amount of debugging prints Administer makes. To disable remote logging (<-- Remote [1], --> Done (20ms)), set to `warning`.",
				SettingType = "Enum",
				SettingDisplayType = "Dropdown",
				RequiresRestart = false,

				Value = {
					Items = {
						"debug",
						"log",
						"warning",
						"error",
					},

					Value = "warning",
					Default = "warning",
				},
			},

			KeybindRegisterTime = {
				Description = "Changes how soon Administer will listen for the keybind to open the panel. Optimistic = Early but quick, some things may not be finished, Safe = registers when ready",
				SettingType = "Enum",
				SettingDisplayType = "Dropdown",
				RequiresRestart = false,

				Value = {
					Items = {
						"optimistic",
						"safe",
					},

					Value = "optimistic",
					Default = "optimistic",
				},
			},

			ShortNumberDecimals = {
				Description = "How many decimals to display after a short number (54.123k)",
				SettingType = "Integer",
				SettingDisplayType = "InputBox",
				RequiresRestart = false,

				Value = {
					Value = 2,
					Default = 2,
				},
			},
		},

		BetaFeatures = {
			_CategoryOpts = {
				DisplayName = "Developer",
				Color = "#daff00",
				Description = "Development flags and beta features",
				Icon = "hammer-idle",
			},

			MobileModeBeta = {
				Description = "Beta Mobile-first interface",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = true,

				Value = {
					Value = false,
					Default = false,
				},
			},

			ForceMobile = {
				Description = "Forces Vars.Mobile to be true",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = true,

				Value = {
					Value = false,
					Default = false,
				},
			},

			ForceAcrylic = {
				Description = "Forces Vars.UseAcrylic to be true",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = true,

				Value = {
					Value = false,
					Default = false,
				},
			},

			ThemeDebug = {
				Description = "Enabled test labels to see what Administer auto-classifies GuiObjects as",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = true,

				Value = {
					Value = false,
					Default = false,
				},
			},

			ThemeForceSimilarity = {
				Description = "If enabled, colors will be snapped to the nearest theme color unless it is super different",
				SettingType = "Boolean",
				SettingDisplayType = "Toggle",
				RequiresRestart = true,

				Value = {
					Value = true,
					Default = true,
				},
			},

			ThemeSimilarityThreshold = {
				Description = "How similar a color has to be to be overridden with a theme color",
				SettingType = "Integer",
				SettingDisplayType = "InputBox",
				RequiresRestart = true,

				Value = {
					Value = 12,
					Default = 12,
				},
			},
		},
	},
}

return Settings


--// # THIS IS AN AUTOGENERATED FILE - DO NOT EDIT
--// Compilation Date: Wed Oct 01 2026 18:40:37 GMT-0400 (Eastern Daylight Time)
--// Source file: /mnt/other/Projects/Administer/administer/src/Server/Core/Locales/th.luau
return {}


--// Administer
--// CodingAgent 2026

local Debugging = {}

local Root = script:FindFirstAncestor("Loader")
local AdminRunner = require(Root.Modules.AdminRunner)
local Var = require(Root.Core.Variables)

Debugging.text = [[
///// ADMINISTER DEBUG OUTPUT \\\\\

* VERSION: 1.0
* GENERATION TIMESTAMP: 1727997091
* GENERATION TIME: 5.000000000s
* SUBMITTED TO APP SERVER: YES

// ENVIORNMENT \\

* LOCATION: RobloxStudio
* IDENTITY: 4
* ROBLOX VERSION: 0.642.0.6420636
* GAME: https://www.roblox.com/games/82284720720514
* PLACEID: 82284720720514
* HTTP ENABLED: YES
* STUDIO APIS ENABLED: YES
* UPTIME: 38548563.324873842s

// ADMINISTER \\

* VERSION: 1.0.1
* APP API VERSION: 1.0.0
* BRANCH: STABLE
* UP TO DATE: YES (check time: 1727997091)
* DB INITIALIZED: 1727997091
* SERVER ERRORS 0
* SERVER WARNINGS: 0
* SERVER ERROR LOG: []
* VERBOSE LOGS: 0
* INIT TIME: 4.7375452518463135s
* INIT JSON: {"FunctionDefinition":"0.0017399","RegisterStartupEvents":"0.7171185","Services":"0.0000703","AppsBootstrap":"3.3675901","Constants":"0.0000147","DataStoreService":"0.5957047","ConstructRemotes":"0.4938125","BootstrapAdmins":"0.4935016","FullExecute":4.737712383270264}

// APPS \\

* APPS INSTALLED: 3
* LOCAL APPS INSTALLED: 0
* DISABLED APPS: 0
* ENABLED APPS: [{"ID":18116194237,"InstallSource":"https://administer.notpyx.me","InstallDate":1727125991,"Name":"Moderation+"},null,{"ID":104329212775273,"InstallSource":"https://administer.notpyx.me","InstallDate":1727637186,"Name":"Team Manager"},{"ID":89624237695477,"InstallSource":"https://administer.notpyx.me","InstallDate":1727641979,"Name":"Global Announcements"},{"ID":116009468305526,"InstallSource":"https://administer.notpyx.me","InstallDate":1727899707,"Name":"Player Management"}]
* ERRORS IN INIT: YES (1 FAILED)
* INIT TIME: 2.435835982435
* SERVER COUNT: 1
* SERVERS: [{"URL": "https://administer.notpyx.me", "Source": "_setup", "Timestamp": "0000"}]

// ADMINS \\

* TOTAL RANKS: 8
* TOTAL ADMINS: 82
* DOES USE LEGACY MODULE: YES

// CONFIGURATION \\

{"ShortNumberDecimals":{"Name":"ShortNumberDecimals","Value":2},"DisplayHours":{"Name":"DisplayHours","Value":false},"SettingsCheckTime":{"Name":"SettingsCheckTime","Value":30},"AppLoadDelay":{"Name":"AppLoadDelay","Value":"None"},"NotificationCloseTimer":{"Name":"NotificationCloseTimer","Value":15},"PanelKeybind":{"Name":"PanelKeybind","Value":"Z"},"AnimationSpeed":{"Name":"AnimationSpeed","Value":1},"SandboxMode":{"Name":"SandboxMode","Value":true},"Verbose":{"Name":"Verbose","Value":true},"RequireShift":{"Name":"RequireShift","Value":true},"HomepageGreeting":{"Name":"HomepageGreeting","Value":"Welcome to Administer!"},"DisableAppServerFetch":{"Name":"DisableAppServerFetch","Value":false},"TopbarPlus":{"Name":"TopbarPlus","Value":false},"EditableImageRenderingDelay":{"Name":"EditableImageRenderingDelay","Value":false},"EnableEditableImages":{"Name":"EnableEditableImages","Value":true},"AdminCheck":{"Name":"AdminCheck","Value":120},"DisableUnofficialAppWarning":{"Name":"DisableUnofficialAppWarning","Value":false},"DisableApps":{"Name":"DisableApps","Value":false}}

]]

Debugging.Table = {
	["AdministerEnv"] = {
		["Version"] = "1.2",
		["AppAPIVersion"] = "1.0",
		["Branch"] = "LIVE",

		["ParentedTo"] = "game.ServerScriptService",
		["GeneratedAt"] = os.time(),
		["GenerationTime"] = "2.0000000s",
		["Location"] = "RobloxStudio",

		["RobloxVersion"] = "0.642.0.6420636",
		["GameURL"] = "https://www.roblox.com/games/82284720720514",
		["PlaceID"] = "82284720720514",
	},

	["Permissions"] = {
		["HttpEnabled"] = true,
		["ConnectedToAPIs"] = true,
		["AOSDidPing"] = true,
	},
}

return Debugging


--// Administer
--// CodingAgent 2026

--// Administer - PanelRelocator
--// Patches AdministerMainPanel's script Sources from the Rojo-managed
--// PanelScripts folder at server startup, before any panel is cloned.
--//
--// HOW IT WORKS:
--//   PanelScripts/ mirrors the script hierarchy inside AdministerMainPanel.
--//   At startup this module walks every Script/LocalScript/ModuleScript in
--//   PanelScripts and resolves the matching instance inside the Panel template
--//   using the same relative path. It then overwrites .Source so players
--//   always get the latest code even though the UI lives in a .rbxm.
--//
--// TO ADD A NEW SCRIPT TO THE SYNC:
--//   Just create the .luau file in PanelScripts/ at the correct path.
--//   No config changes needed — paths are resolved automatically.
--//
--// PATH RULES (PanelScripts/ folder mirrors AdministerMainPanel children):
--//   PanelScripts/Client.client.luau
--//     → AdministerMainPanel.Client
--//   PanelScripts/Client/Modules/Frontend.luau
--//     → AdministerMainPanel.Client.Modules.Frontend
--//   PanelScripts/Client/Modules/Helpers/FirstTimeSetup/init.luau
--//     → AdministerMainPanel.Client.Modules.Helpers.FirstTimeSetup
--//   PanelScripts/Client/Modules/Helpers/FirstTimeSetup/Tour.luau
--//     → AdministerMainPanel.Client.Modules.Helpers.FirstTimeSetup.Tour

local SCRIPT_CLASSES = {
	LocalScript = true,
	ModuleScript = true,
	Script = true,
}

local function resolvePath(root: Instance, parts: { string }): Instance?
	local cur: Instance? = root
	for _, part in parts do
		if cur == nil then
			return nil
		end
		cur = (cur :: Instance):FindFirstChild(part)
	end
	return cur
end

local function getPanelPathParts(src: Instance, psRoot: Instance): { string }
	local parts: { string } = {}
	local cur: Instance? = src
	while cur ~= nil and cur ~= psRoot do
		table.insert(parts, 1, (cur :: Instance).Name)
		cur = (cur :: Instance).Parent
	end
	if parts[#parts] == "init" then
		table.remove(parts)
	end
	return parts
end

return function(panelTemplate: Instance, panelScriptsFolder: Instance): number
	local patched = 0
	local missed = 0

	for _, src in panelScriptsFolder:GetDescendants() do
		if not SCRIPT_CLASSES[src.ClassName] then
			continue
		end

		local parts = getPanelPathParts(src, panelScriptsFolder)
		local target = resolvePath(panelTemplate, parts)

		if target and SCRIPT_CLASSES[target.ClassName] then
			(target :: Script).Source = (src :: Script).Source
			patched += 1
		else
			warn(
				string.format(
					"[PanelRelocator] No match for '%s' (looked for '%s' in %s)",
					src:GetFullName(),
					table.concat(parts, "."),
					panelTemplate:GetFullName()
				)
			)
			missed += 1
		end
	end

	print(string.format("[PanelRelocator] Done — %d scripts patched, %d unmatched.", patched, missed))

	return patched
end


--// Administer
--// CodingAgent 2026
--// This code sucks, hopefully it will never have to be seen

return function(Input: string): { Command: string, Flags: {} }
	local Options: {} = {}
	local Split = {}
	local Quotes = false
	local Buff = ""

	for char in string.gmatch(Input .. " ", ".") do
		if char == "'" or char == '"' then
			Quotes = not Quotes
		elseif char == " " and not Quotes then
			if Buff ~= "" then
				table.insert(Split, Buff)
				Buff = ""
			end
		else
			Buff = Buff .. char
		end
	end

	local i = 1
	while i <= #Split do
		local Phrase = Split[i]

		if string.sub(Phrase, 1, 2) == "--" then
			local key = string.sub(Phrase, 3)
			if i < #Split and not string.match(Split[i + 1], "^%-") then
				Options[key] = Split[i + 1]
				i = i + 1
			else
				Options[key] = true
			end
		elseif string.sub(Phrase, 1, 1) == "-" then
			local key = string.sub(Phrase, 2)
			if i < #Split and not string.match(Split[i + 1], "^%-") then
				Options[key] = Split[i + 1]
				i = i + 1
			else
				Options[key] = true
			end
		end

		i = i + 1
	end

	return {
		Command = Split[1],
		Flags = Options,
	}
end


--// Administer
--// CodingAgent 2026

local Importer = {}

local Utils = require(script.Parent.Utilities)

Importer.Systems = {
	"EXE_5",
	"EXE_6",
	"HD_ADMIN",
	"ADONIS",
	"BASIC_ADMIN_ESSENTIALS",
	"KOHLS_ADMIN",
	"KOHLS_ADMIN_2",
}

function Importer.GetData(SystemName)
	if SystemName == "EXE_5" then
		local Exe5Elements = require(game.ServerScriptService:FindFirstAncestor("ELEMENTS"))
		local ExeFoundSanitizedAdmins = {}

		if Exe5Elements == nil then
			return { false, "Exe 5 is not installed." }
		end

		local ExeGroup = Exe5Elements.FETCH_GROUP()
		local ExeGroupRanks = Exe5Elements.FETCH_RANKS()
		local ExePlayers = Exe5Elements.FETCH_PLAYERS()

		for UserID, Data in ExePlayers do
			table.insert(ExeFoundSanitizedAdmins, {
				MemberType = "User",
				ID = UserID,
			})
		end

		for _, Rank in ExeGroupRanks do
			table.insert(ExeFoundSanitizedAdmins, {
				MemberType = "Group",
				RankName = "Administrator",

				ID = ExeGroup,
				GroupRank = Rank,
			})
		end

		return {
			true,
			{
				Commands = {},
				Ranks = {
					"Administrator",
				},
				Admins = ExeFoundSanitizedAdmins,
				Logs = {},
			},
		}
	elseif SystemName == "HD_ADMIN" then
		local HDRoot = game:FindFirstAncestor("HD Admin")
		local HDSanitizedAdmins = {}
		local HDRankNames = {}

		if HDRoot == nil then
			return { false, "HD Admin is not installed." }
		end

		local HDSettings = require(HDRoot.Settings)

		for GroupID, RankData in HDSettings.Groups do
			for GroupRank, RankName in RankData do
				table.insert(HDSanitizedAdmins, {
					MemberType = "Group",
					RankName = RankName,

					ID = GroupID,
					GroupRank = GroupRank,
				})
			end
		end

		for _, Rank in HDSettings.Ranks do
			for _, Child in Rank do
				if typeof(Child) ~= "table" then
					continue
				end

				table.insert(HDRankNames)
				table.insert(HDSanitizedAdmins, {
					MemberType = "User",
					RankName = Rank[1],

					ID = Child[1],
				})
			end
		end

		return {
			true,
			{
				Commands = {},
				Ranks = {
					"Administrator",
				},
				Admins = HDSanitizedAdmins,
				Logs = {},
			},
		}
	else
		return {
			false,
			`{SystemName} is not installed`,
		}
	end
end

function Importer.ListFoundSystems()
	local Final = {}

	for _, System in Importer.Systems do
		xpcall(function()
			local Data = Importer.GetData(System)

			Utils.Logging.Print(Data)

			if Data[1] then
				Data = Data[2]

				Final[System] = {
					Commands = #Data.Commands,
					Ranks = #Data.Ranks,
					Admins = #Data.Admins,
				}
			end
		end, function(e)
			Utils.Logging.Print(`System not found: {e}`)
			Final[System] = {
				Error = true,
				Data = e,

				Commands = -1,
				Ranks = -1,
				Admins = -1,
			}
		end)
	end

	return {
		EXE_5 = {
			Commands = 3,
			Ranks = 1,
			Admins = 4,
		},

		HD_ADMIN = {
			Commands = 0,
			Ranks = 1,
			Admins = 7,
		},

		_AdmSystemsNotFound = {
			"EXE_6",
			"ADONIS",
			"BASIC_ADMIN_ESSENTIALS",
			"KOHLS_ADMIN_2",
			"KOHLS_ADMIN",
		},
	}
end

function Importer.Import(System)
	if not table.find() then
		return { false, "This system is not supported for importing." }
	end
end

return Importer


local Connection = {}
Connection.__index = Connection

function Connection.new(callback)
	local newConnection = {}
	newConnection.callback = function(...)
		callback(...)
	end
	return newConnection
end

function Connection:_Fire()
	if self.callback then
		task.spawn(self.callback)
	end
end

function Connection:Disconnect()
	self.callback = nil
end

return Connection


local Signal = {}
Signal.__index = Signal

local Connection = require(script.Connection)

local HttpService = game:GetService("HttpService")

function Signal.new()
	local connectionSignal = {}
	connectionSignal.Connections = {}
	function connectionSignal:Connect(setting: string, callback)
		local connection = Connection.new(callback)
		self.Connections[connection] = setting
		return connection
	end
	return connectionSignal
end

function Signal:Fire(setting: string)
	for connection, connectionSetting in self.Connections do
		if connectionSetting == setting then
			connection:_Fire()
		end
	end
end

return Signal


-- dogo8me2 2024

local SettingsAPI = {}

local Types = require(script.Parent.Parent.Core.Types)
local Signal = require(script.Signal)

local DataStoreService = game:GetService("DataStoreService")
local HttpService = game:GetService("HttpService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local _dsAvailable = pcall(function()
	DataStoreService:GetDataStore("__adm_probe"):GetAsync("__probe")
end)

local SettingsDataStore = _dsAvailable and DataStoreService:GetDataStore("Administer_SettingPresets")
	or {
		GetAsync = function()
			return nil
		end,
		SetAsync = function() end,
		UpdateAsync = function() end,
		RemoveAsync = function() end,
	}

type Setting = Types.Setting
type SettingsGroup = Types.SettingsGroup
type SettingsApi = Types.SettingsAPI
type MainSettings = Types.MainSettings

local function saveDataStore(key: string, serverKey: string, data: {}): boolean
	local clientData = {}
	local serverData = {}

	for categoryName, config in data do
		clientData[categoryName] = {}
		serverData[categoryName] = {}

		for settingName, data in config do
			if data["SaveOnServer"] then
				serverData[categoryName][settingName] = data
			end
			if data["SaveOnClient"] or data["SaveOnClient"] == nil then
				clientData[categoryName][settingName] = data
			end
		end
	end

	local success, err = pcall(function()
		SettingsDataStore:SetAsync(key, HttpService:JSONEncode(clientData))
		SettingsDataStore:SetAsync(serverKey, HttpService:JSONEncode(serverData))
	end)
	return success
end

local function loadDataStore(key: string, serverKey: string, template: {}?): {}
	local success, clientData, serverData = pcall(function()
		if key and serverKey then
			return SettingsDataStore:GetAsync(key), SettingsDataStore:GetAsync(serverKey)
		elseif key then
			return SettingsDataStore:GetAsync(key), "{}"
		elseif serverKey then
			return "{}", SettingsDataStore:GetAsync(serverKey)
		else
			warn("A key must be provided")
			return nil, nil
		end
	end)

	if success and clientData and serverData then
		local successDecode, decodedClientData, decodedServerData = pcall(function()
			return HttpService:JSONDecode(clientData), HttpService:JSONDecode(serverData)
		end)

		if successDecode then
			local data = {}

			for categoryName, config in decodedClientData do
				data[categoryName] = {}

				for settingName, loadedData in config do
					data[categoryName][settingName] = loadedData
				end
			end

			for categoryName, config in decodedServerData do
				if data[categoryName] == nil then
					data[categoryName] = {}
				end
				for settingName, loadedData in config do
					data[categoryName][settingName] = loadedData
				end
			end

			if template then
				local function mergeTemplate(dataTable, templateTable)
					local result = {}
					for key, value in templateTable do
						if typeof(value) == "table" then
							result[key] = mergeTemplate(dataTable and dataTable[key] or {}, value)
						else
							result[key] = dataTable and dataTable[key] ~= nil and dataTable[key] or value
						end
					end
					return result
				end

				return mergeTemplate(data, template)
			else
				return data
			end
		end
	end

	if template then
		return template
	end
	return {}
end

local function settingFromString(
	settingName: string,
	settingsGroup: SettingsGroup,
	categoryName: string?,
	returnAsString: boolean?
)
	if categoryName then
		return settingsGroup[categoryName][settingName]
	else
		for i, category in settingsGroup do
			local setting: Setting = category[settingName]
			if setting then
				return setting
			end
		end
	end
end

local function categoryFromString(settingName: string, settingsGroup: SettingsGroup)
	for i, category in settingsGroup do
		local setting: Setting = category[settingName]
		if setting and i ~= "_CategoryOpts" then
			return i
		end
	end
end

SettingsAPI.SettingsList = {}

function SettingsAPI.GetController(name: string)
	for i, v in pairs(SettingsAPI.SettingsList) do
		local data = v.GetData()["_SettingsMeta"]
		if data.Name == name then
			return v
		end
	end

	return nil
end

function SettingsAPI.new(rawSettings)
	local remotes = {}

	local function cloneTable(list)
		local newTable = {}
		for i, v in list do
			if typeof(v) == "table" then
				newTable[i] = cloneTable(v)
			else
				newTable[i] = v
			end
		end

		return newTable
	end
	local settingsGroup = cloneTable(rawSettings)

	local isServer = RunService:IsServer()
	local APIController = {}
	local dataStoreKey = settingsGroup["_SettingsMeta"].Name
	local changedSignal = Signal.new()

	function APIController.Changed(names: { settingName: string }, callback)
		for i, name in names do
			changedSignal:Connect(name, function()
				callback(settingsGroup)
			end)
			return changedSignal
		end
	end

	function APIController.with(names: { string }, callback)
		return function(...)
			for i, name in names do
				local retrievedSetting: Setting = settingFromString(name, settingsGroup)
				callback(retrievedSetting, ...)
			end
		end
	end

	function APIController.Get(names: any): any
		if typeof(names) == "table" then
			local returnTable = {}
			for i, name in names do
				local retrievedSetting: Setting = settingFromString(name, settingsGroup)
				returnTable[name] = retrievedSetting["Value"]["Value"]
			end
			return returnTable
		else
			local retrievedSetting: Setting = settingFromString(names, settingsGroup)

			local _, Value = xpcall(function()
				return retrievedSetting["Value"]["Value"]
			end, function(e)
				return false
			end)

			return Value
		end
	end

	function APIController.GetRemote(names: any)
		if typeof(names) == "table" then
			local returnTable = {}
			for i, name in names do
				local retrievedRemote = remotes[name]
				if retrievedRemote == nil then
					retrievedRemote = Instance.new("RemoteEvent")
					remotes[name] = retrievedRemote
				end
				returnTable[name] = retrievedRemote
			end
			return returnTable
		else
			local retrievedRemote = remotes[names]
			if retrievedRemote == nil then
				retrievedRemote = Instance.new("RemoteEvent")
				retrievedRemote.Parent = game.ReplicatedStorage:WaitForChild("Administer")
				remotes[names] = retrievedRemote
				return retrievedRemote
			end
			return retrievedRemote
		end
	end

	function APIController.GetData(): any
		pcall(function()
			if settingsGroup._SettingsMeta.Name ~= "Administer" then
				return
			end

			for GroupName, Group in settingsGroup do
				Group._CategoryOpts = require(script.Parent.Parent.Core.Configuration).Settings[GroupName]._CategoryOpts
			end
		end)

		return settingsGroup
	end

	function APIController.GetCategory(name: string)
		local retrievedCategory: SettingsGroup = settingsGroup[name]
		return retrievedCategory
	end

	function APIController.Set(name: string, value: any?): boolean
		if not value then
			value = ""
		end

		local success = pcall(function()
			local setting: Setting = settingFromString(name, settingsGroup)
			setting.Value.Value = value
		end)

		pcall(function()
			if success then
				changedSignal:Fire(name)
			end
		end)

		if remotes[name] then
			local remote: RemoteEvent = remotes[name]
			remote:FireAllClients(value)
		end

		return success
	end

	function APIController.Save(UserId: number?): boolean
		if isServer then
			local success, err = pcall(function()
				saveDataStore(dataStoreKey .. "#" .. UserId, dataStoreKey .. "#1", settingsGroup)
			end)
			return success
		else
			local localPlayer = Players.LocalPlayer
			local remoteFolder = ReplicatedStorage:FindFirstChild("Administer")
			local saveRemote: RemoteFunction = remoteFolder:FindFirstChild("SaveSettings")

			return saveRemote:InvokeServer(settingsGroup["_SettingsMeta"].Name, UserId)
		end
	end

	function APIController.Load(UserId: number?, customTemplate: SettingsGroup?): boolean
		if isServer then
			local success, err = pcall(function()
				settingsGroup =
					loadDataStore(dataStoreKey .. "#" .. UserId, dataStoreKey .. "#1", customTemplate or rawSettings)
			end)

			if success then
				local remoteFolder = ReplicatedStorage:FindFirstChild("Administer")
				pcall(function()
					local loaded: RemoteEvent = remoteFolder:WaitForChild("SettingsLoaded", 0.5)

					loaded:FireAllClients()
				end)
			end
			return success
		else
			local localPlayer = Players.LocalPlayer
			local remoteFolder = ReplicatedStorage:FindFirstChild("Administer")
			local loadRemote: RemoteFunction = remoteFolder:FindFirstChild("LoadSettings")

			return loadRemote:InvokeServer(settingsGroup["_SettingsMeta"].Name, UserId, customTemplate)
		end
	end

	table.insert(SettingsAPI.SettingsList, APIController)

	return APIController
end

return SettingsAPI


local SettingsAPI = require(game.ServerScriptService.Administer.Loader.Modules.SettingsAPI)

local Settings = SettingsAPI.new({
	["_SettingsMeta"] = {
		["Name"] = "Administertesttesttest",
	},

	["General"] = {
		["_CategoryOpts"] = {
			["DisplayName"] = "General",
			["Color"] = "#fff",
			["Description"] = "Configure general settings.",
		},

		["AnimationSpeed"] = {
			["DisplayName"] = "Animation Speed",
			["Description"] = "Controls the speed of animations.",

			["SettingType"] = "float",
			["SettingDisplayType"] = "InputBox",

			["Value"] = {
				["Value"] = 1,
				["Default"] = 1,
			},
		},
		["Language"] = {
			["Description"] = "Administer's UI language.",
			["SettingType"] = "Enum",

			["InputDisplayType"] = "Dropdown",
			["Value"] = {
				"English",
				"Spanish",
				"French",
				["Value"] = "English",
				["Default"] = "English",
			},
		},
	},

	["Interface"] = {
		["_CategoryOpts"] = {
			["DisplayName"] = "Interface options",
			["Color"] = "#000",
			["Description"] = "Change the interface.",
		},

		["InterfaceScale"] = {
			["Description"] = "The scale of the interface.",
			["SettingType"] = "float",
			["InputDisplayType"] = "Slider",

			["Value"] = {
				["Value"] = 1,
				["Default"] = 1,
				["Min"] = 0.25,
				["Max"] = 5,
				["Step"] = 1,
			},
		},
	},
})

Settings.Changed({ "AnimationSpeed", "InterfaceScale" }, function(Setting)
	print(`{Setting.Name} has changed and is now {Setting.Value}!`)
end)

local LotsOfValues = Settings.Get({ "Language", "InterfaceScale" })
print(LotsOfValues)

print(Settings.Get("AnimationSpeed"))

local Category = Settings.GetCategory("Interface")

print(Category)


return function(UIInstance, Mouse, Duration, Color)
	if UIInstance.ClipsDescendants == false then
		UIInstance.ClipsDescendants = true
	end
	if UIInstance.AutoButtonColor == true then
		UIInstance.AutoButtonColor = false
	end

	local ASX, ASY = UIInstance.AbsoluteSize.X, UIInstance.AbsoluteSize.Y
	local APX, APY = UIInstance.AbsolutePosition.X, UIInstance.AbsolutePosition.Y
	local MX, MY = Mouse.X, Mouse.Y

	local UI = Instance.new("Frame", UIInstance)
	UI.BackgroundColor3 = Color
	UI.Name = "Ripple"
	UI.ZIndex = math.huge

	local Corner = Instance.new("UICorner", UI)
	Corner.CornerRadius = UDim.new(1, 0)

	UI.AnchorPoint = Vector2.new(0.5, 0.5)
	UI.Position = UDim2.new(0, Mouse.X - APX, 0, Mouse.Y - APY)

	UI:TweenSize(
		UDim2.fromOffset(math.max(ASX, ASY), math.max(ASX, ASY)),
		Enum.EasingDirection.Out,
		Enum.EasingStyle.Sine,
		Duration
	)

	game:GetService("TweenService"):Create(UI, TweenInfo.new(Duration), { BackgroundTransparency = 1 }):Play()

	task.wait(Duration)
	UI:Destroy()
end


local gui = script.Parent.Parent.Parent

local types = script:WaitForChild("ConfettiTypes")

local deleteConfettiAfter = 10

local rnd = Random.new()

function CreateOne(origin, colors, minSize, maxSize)
	local allTypes = types:GetChildren()

	local confetti = allTypes[rnd:NextInteger(1, #allTypes)]:Clone()

	confetti.Position = origin

	local size = rnd:NextInteger(minSize, maxSize) * (gui.AbsoluteSize.X / 1920)
	local sizeScaleX = size / gui.AbsoluteSize.X
	local sizeScaleY = size / gui.AbsoluteSize.Y
	local sizeUDim = UDim2.new(sizeScaleX, 0, sizeScaleY, 0)

	confetti.Size = sizeUDim

	local color = colors[rnd:NextInteger(1, #colors)]
	if confetti:IsA("ImageLabel") then
		confetti.ImageColor3 = color
	end
	confetti.BackgroundColor3 = color

	confetti.Parent = gui

	game:GetService("Debris"):AddItem(confetti, deleteConfettiAfter)

	return confetti
end

function MoveOne(confetti, upwardVelocity, horizontalVelocity, downwardAcceleration, angularVelocity)
	local origin = Vector2.new(confetti.AbsolutePosition.X, confetti.AbsolutePosition.Y)

	local sx = 0
	local sy = 0

	local u = -upwardVelocity

	local a = downwardAcceleration

	local t = 0

	local function GetS_Y()
		local S_Y = u * t + 1 / 2 * (a * t) ^ 2
		return S_Y
	end

	local loopStart = tick()

	while confetti and confetti.Parent == gui do
		t = tick() - loopStart

		sx += horizontalVelocity
		sy = GetS_Y()

		local newX = origin.X + sx
		local newY = origin.Y + sy

		local newPos = UDim2.new(newX / gui.AbsoluteSize.X, 0, newY / gui.AbsoluteSize.Y, 0)

		local newRot = confetti.Rotation + angularVelocity

		confetti.Position = newPos
		confetti.Rotation = newRot

		if confetti.Position.X.Scale < 0 or confetti.Position.X.Scale > 1 or confetti.Position.Y.Scale > 1 then
			confetti:Destroy()
			break
		end

		game:GetService("RunService").Heartbeat:Wait()
	end
end

function CreateConfetti(
	origin: UDim2?,

	count: number?,

	colors: { Color3 }?,

	minSize: number?,
	maxSize: number?,

	minUpwardVelocity: number?,
	maxUpwardVelocity: number?,

	minHorizontalSpeed: number?,
	maxHorizontalSpeed: number?,

	minDownwardAcceleration: number?,
	maxDownwardAcceleration: number?,

	minAngularSpeed: number?,
	maxAngularSpeed: number?
)
	origin = origin or UDim2.fromScale(0.5, -0.5) :: UDim2
	count = count or 200
	colors = colors
		or {
			Color3.fromRGB(255, 0, 0),
			Color3.fromRGB(255, 119, 0),
			Color3.fromRGB(255, 234, 0),
			Color3.fromRGB(47, 255, 0),
			Color3.fromRGB(0, 255, 247),
			Color3.fromRGB(0, 4, 255),
			Color3.fromRGB(140, 0, 255),
			Color3.fromRGB(255, 0, 212),
		}
	minSize = minSize or 10
	maxSize = maxSize or 20
	minUpwardVelocity = minUpwardVelocity or 200
	maxUpwardVelocity = maxUpwardVelocity or 750
	minHorizontalSpeed = minHorizontalSpeed or 0
	maxHorizontalSpeed = maxHorizontalSpeed or 5
	minDownwardAcceleration = minDownwardAcceleration or 25
	maxDownwardAcceleration = maxDownwardAcceleration or 50
	minAngularSpeed = minAngularSpeed or 0
	maxAngularSpeed = maxAngularSpeed or 7

	if not count then
		return
	end

	for i = 1, count do
		task.wait(rnd:NextNumber(0, 0.05))

		local confetti = CreateOne(origin, colors, minSize, maxSize)

		local upwardVelocity = rnd:NextNumber(minUpwardVelocity, maxUpwardVelocity)

		local horizontalVelocity = rnd:NextNumber(minHorizontalSpeed, maxHorizontalSpeed)
		if rnd:NextNumber() > 0.5 then
			horizontalVelocity = -horizontalVelocity
		end

		local downwardAcceleration = rnd:NextNumber(minDownwardAcceleration, maxDownwardAcceleration)

		local angularVelocity = rnd:NextNumber(minAngularSpeed, maxAngularSpeed)
		if horizontalVelocity == 0 then
			angularVelocity = 0
		elseif horizontalVelocity < 0 then
			angularVelocity = -angularVelocity
		end

		upwardVelocity = upwardVelocity * (gui.AbsoluteSize.Y / 1080)
		downwardAcceleration = downwardAcceleration * (gui.AbsoluteSize.Y / 1080)
		horizontalVelocity = horizontalVelocity * (gui.AbsoluteSize.X / 1920)

		task.spawn(MoveOne, confetti, upwardVelocity, horizontalVelocity, downwardAcceleration, angularVelocity)
	end
end

return CreateConfetti


-- fractality

local module = {}

local RunService = game:GetService("RunService")
local camera = workspace.CurrentCamera

do
	local function IsNotNaN(x)
		return x == x
	end
	local continue = IsNotNaN(camera:ScreenPointToRay(0, 0).Origin.x)
	while not continue do
		RunService.RenderStepped:wait()
		continue = IsNotNaN(camera:ScreenPointToRay(0, 0).Origin.x)
	end
end

local binds = {}
local root = Instance.new("Folder", camera)
root.Name = "neon"

local GenUid
do
	local id = 0
	function GenUid()
		id = id + 1
		return "neon::" .. tostring(id)
	end
end

local DrawQuad
do
	local acos, max, pi, sqrt = math.acos, math.max, math.pi, math.sqrt
	local sz = 0.2

	function DrawTriangle(v1, v2, v3, p0, p1)
		local s1 = (v1 - v2).magnitude
		local s2 = (v2 - v3).magnitude
		local s3 = (v3 - v1).magnitude
		local smax = max(s1, s2, s3)
		local A, B, C
		if s1 == smax then
			A, B, C = v1, v2, v3
		elseif s2 == smax then
			A, B, C = v2, v3, v1
		elseif s3 == smax then
			A, B, C = v3, v1, v2
		end

		local para = ((B - A).x * (C - A).x + (B - A).y * (C - A).y + (B - A).z * (C - A).z) / (A - B).magnitude
		local perp = sqrt((C - A).magnitude ^ 2 - para * para)
		local dif_para = (A - B).magnitude - para

		local st = CFrame.new(B, A)
		local za = CFrame.Angles(pi / 2, 0, 0)

		local cf0 = st

		local Top_Look = (cf0 * za).lookVector
		local Mid_Point = A + CFrame.new(A, B).lookVector * para
		local Needed_Look = CFrame.new(Mid_Point, C).lookVector
		local dot = Top_Look.x * Needed_Look.x + Top_Look.y * Needed_Look.y + Top_Look.z * Needed_Look.z

		local ac = CFrame.Angles(0, 0, acos(dot))

		cf0 = cf0 * ac
		if ((cf0 * za).lookVector - Needed_Look).magnitude > 0.01 then
			cf0 = cf0 * CFrame.Angles(0, 0, -2 * acos(dot))
		end
		cf0 = cf0 * CFrame.new(0, perp / 2, -(dif_para + para / 2))

		local cf1 = st * ac * CFrame.Angles(0, pi, 0)
		if ((cf1 * za).lookVector - Needed_Look).magnitude > 0.01 then
			cf1 = cf1 * CFrame.Angles(0, 0, 2 * acos(dot))
		end
		cf1 = cf1 * CFrame.new(0, perp / 2, dif_para / 2)

		if not p0 then
			p0 = Instance.new("Part")
			p0.FormFactor = "Custom"
			p0.TopSurface = 0
			p0.BottomSurface = 0
			p0.Anchored = true
			p0.CanCollide = false
			p0.Material = "Glass"
			p0.Size = Vector3.new(sz, sz, sz)
			local mesh = Instance.new("SpecialMesh", p0)
			mesh.MeshType = 2
			mesh.Name = "WedgeMesh"
		end
		p0.WedgeMesh.Scale = Vector3.new(0, perp / sz, para / sz)
		p0.CFrame = cf0

		if not p1 then
			p1 = p0:clone()
		end
		p1.WedgeMesh.Scale = Vector3.new(0, perp / sz, dif_para / sz)
		p1.CFrame = cf1

		return p0, p1
	end

	function DrawQuad(v1, v2, v3, v4, parts)
		parts[1], parts[2] = DrawTriangle(v1, v2, v3, parts[1], parts[2])
		parts[3], parts[4] = DrawTriangle(v3, v2, v4, parts[3], parts[4])
	end
end

function module:BindFrame(frame, properties)
	if binds[frame] then
		return binds[frame].parts
	end

	local uid = GenUid()
	local parts = {}
	local f = Instance.new("Folder", root)
	f.Name = frame.Name

	local parents = {}
	do
		local function add(child)
			if child:IsA("GuiObject") then
				parents[#parents + 1] = child
				add(child.Parent)
			end
		end
		add(frame)
	end

	local function UpdateOrientation(fetchProps)
		local zIndex = 1 - 0.05 * frame.ZIndex
		local tl, br = frame.AbsolutePosition, frame.AbsolutePosition + frame.AbsoluteSize
		local tr, bl = Vector2.new(br.x, tl.y), Vector2.new(tl.x, br.y)
		do
			local rot = 0
			for _, v in ipairs(parents) do
				rot = rot + v.Rotation
			end
			if rot ~= 0 and rot % 180 ~= 0 then
				local mid = tl:lerp(br, 0.5)
				local s, c = math.sin(math.rad(rot)), math.cos(math.rad(rot))
				local vec = tl
				tl = Vector2.new(c * (tl.x - mid.x) - s * (tl.y - mid.y), s * (tl.x - mid.x) + c * (tl.y - mid.y)) + mid
				tr = Vector2.new(c * (tr.x - mid.x) - s * (tr.y - mid.y), s * (tr.x - mid.x) + c * (tr.y - mid.y)) + mid
				bl = Vector2.new(c * (bl.x - mid.x) - s * (bl.y - mid.y), s * (bl.x - mid.x) + c * (bl.y - mid.y)) + mid
				br = Vector2.new(c * (br.x - mid.x) - s * (br.y - mid.y), s * (br.x - mid.x) + c * (br.y - mid.y)) + mid
			end
		end
		DrawQuad(
			camera:ScreenPointToRay(tl.x, tl.y, zIndex).Origin,
			camera:ScreenPointToRay(tr.x, tr.y, zIndex).Origin,
			camera:ScreenPointToRay(bl.x, bl.y, zIndex).Origin,
			camera:ScreenPointToRay(br.x, br.y, zIndex).Origin,
			parts
		)
		if fetchProps then
			for _, pt in pairs(parts) do
				pt.Parent = f
			end
			for propName, propValue in pairs(properties) do
				for _, pt in pairs(parts) do
					pt[propName] = propValue
				end
			end
		end
	end

	UpdateOrientation(true)
	RunService:BindToRenderStep(uid, 2000, UpdateOrientation)

	binds[frame] = {
		uid = uid,
		parts = parts,
	}
	return binds[frame].parts
end

function module:Modify(frame, properties)
	local parts = module:GetBoundParts(frame)
	if parts then
		for propName, propValue in pairs(properties) do
			for _, pt in pairs(parts) do
				pt[propName] = propValue
			end
		end
	else
	end
end

function module:UnbindFrame(frame)
	local cb = binds[frame]
	if cb then
		RunService:UnbindFromRenderStep(cb.uid)
		for _, v in pairs(cb.parts) do
			v:Destroy()
		end
		binds[frame] = nil
	else
	end
end

function module:HasBinding(frame)
	return binds[frame] ~= nil
end

function module:GetBoundParts(frame)
	return binds[frame] and binds[frame].parts
end

return module


local QuickBlur = {} :: QuickBlurImpl

type QuickBlurImpl = {
	Blur: (self: QuickBlurImpl, Image: EditableImage, Size: number?, Desample: number?) -> EditableImage,
}

function QuickBlur:Blur(Image, Size, Desample)
	Size = Size or 3
	Desample = Desample or 12

	local ImageWidth = Image.Size.X // Desample
	local ImageHeight = Image.Size.Y // Desample
	local OrginalSize = Image.Size
	local TotalRGBA = ImageWidth * ImageHeight * 4

	local ImageData = Image:ReadPixelsBuffer(Vector2.zero, Vector2.new(ImageWidth, ImageHeight))
	local rpx = {}

	for i = 0, TotalRGBA - 1 do
		table.insert(rpx, buffer.readu8(ImageData, i))
	end

	ImageData = rpx

	for pass = 1, Size do
		for y = 0, ImageHeight - 1 do
			for x = 0, ImageWidth - 1 do
				local function clamp(val, min, max)
					return math.max(min, math.min(max, val))
				end

				local top_left_index = clamp((y + 1) * ImageWidth * 4 + (x - 1) * 4 + 1, 1, TotalRGBA)
				local top_right_index = clamp((y + 1) * ImageWidth * 4 + (x + 1) * 4 + 1, 1, TotalRGBA)
				local low_left_index = clamp((y - 1) * ImageWidth * 4 + (x - 1) * 4 + 1, 1, TotalRGBA)
				local low_right_index = clamp((y - 1) * ImageWidth * 4 + (x + 1) * 4 + 1, 1, TotalRGBA)
				local buffer_index = clamp(y * ImageWidth * 4 + x * 4 + 1, 1, TotalRGBA)

				for offset = 0, 2 do
					local pixel = ImageData[buffer_index + offset]

					local top_left = ImageData[top_left_index + offset] or pixel
					local top_right = ImageData[top_right_index + offset] or pixel
					local low_left = ImageData[low_left_index + offset] or pixel
					local low_right = ImageData[low_right_index + offset] or pixel

					local top_centre = (top_right + top_left) / 2
					local low_centre = (low_right + low_left) / 2
					local mid_left = (low_left + top_left) / 2
					local mid_right = (low_right + top_right) / 2

					ImageData[buffer_index + offset] = (
						top_left
						+ top_right
						+ low_left
						+ low_right
						+ top_centre
						+ low_centre
						+ mid_left
						+ mid_right
						+ pixel
					) / 9
				end
			end
		end
	end

	local FinalBuffer = buffer.create(TotalRGBA)
	for i = 1, #ImageData do
		buffer.writeu8(FinalBuffer, i - 1, ImageData[i])
	end

	Image:WritePixelsBuffer(Vector2.zero, Vector2.new(ImageWidth, ImageHeight), FinalBuffer)

	return Image
end

return QuickBlur