--!strict -- PORTFOLIO SAMPLE / ROBLOX STUDIO VALIDATION REQUIRED -- Put this LocalScript in StarterPlayer > StarterPlayerScripts. -- Requires RewardCore.server.luau in ServerScriptService. local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local player = Players.LocalPlayer local remote = ReplicatedStorage:WaitForChild("NeoPortfolioRewardsAction") :: RemoteFunction local gui = Instance.new("ScreenGui") gui.Name = "NeoDemoRewardsGUI" gui.ResetOnSpawn = false gui.IgnoreGuiInset = false gui.Parent = player:WaitForChild("PlayerGui") local root = Instance.new("Frame") root.Name = "Panel" root.Size = UDim2.fromOffset(340, 355) root.AnchorPoint = Vector2.new(1, 0) root.Position = UDim2.new(1, -18, 0, 18) root.BackgroundColor3 = Color3.fromRGB(25, 29, 54) root.BorderSizePixel = 0 root.Parent = gui Instance.new("UICorner", root).CornerRadius = UDim.new(0, 17) local stroke = Instance.new("UIStroke") stroke.Color = Color3.fromRGB(112, 101, 164) stroke.Thickness = 1 stroke.Transparency = 0.25 stroke.Parent = root local padding = Instance.new("UIPadding") padding.PaddingTop = UDim.new(0, 20) padding.PaddingBottom = UDim.new(0, 20) padding.PaddingLeft = UDim.new(0, 20) padding.PaddingRight = UDim.new(0, 20) padding.Parent = root local layout = Instance.new("UIListLayout") layout.Padding = UDim.new(0, 10) layout.SortOrder = Enum.SortOrder.LayoutOrder layout.Parent = root local function label(text: string, size: number, height: number, order: number, color: Color3): TextLabel local item = Instance.new("TextLabel") item.BackgroundTransparency = 1 item.Size = UDim2.new(1, 0, 0, height) item.Text = text item.TextColor3 = color item.TextSize = size item.Font = Enum.Font.Gotham item.TextXAlignment = Enum.TextXAlignment.Left item.TextWrapped = true item.LayoutOrder = order item.Parent = root return item end local function button(text: string, order: number): TextButton local item = Instance.new("TextButton") item.Size = UDim2.new(1, 0, 0, 41) item.Text = text item.Font = Enum.Font.GothamBold item.TextSize = 14 item.BackgroundColor3 = Color3.fromRGB(172, 245, 218) item.TextColor3 = Color3.fromRGB(24, 62, 52) item.BorderSizePixel = 0 item.LayoutOrder = order item.Parent = root Instance.new("UICorner", item).CornerRadius = UDim.new(0, 9) return item end local heading = label("DAILY REWARDS", 21, 29, 1, Color3.fromRGB(242, 242, 255)) heading.Font = Enum.Font.GothamBold local balance = label("Coins: loading...", 13, 19, 2, Color3.fromRGB(255, 226, 171)) local timer = label("Checking reward status...", 12, 20, 3, Color3.fromRGB(199, 193, 247)) local dailyButton = button("Claim +150 coins", 4) local promoHeading = label("PROMO CODES", 14, 23, 5, Color3.fromRGB(228, 223, 255)) promoHeading.Font = Enum.Font.GothamBold local input = Instance.new("TextBox") input.Name = "PromoCode" input.Size = UDim2.new(1, 0, 0, 36) input.BackgroundColor3 = Color3.fromRGB(35, 40, 71) input.TextColor3 = Color3.fromRGB(249, 247, 255) input.PlaceholderColor3 = Color3.fromRGB(153, 158, 186) input.PlaceholderText = "Enter code (try WELCOME100)" input.Text = "" input.Font = Enum.Font.Gotham input.TextSize = 12 input.ClearTextOnFocus = false input.LayoutOrder = 6 input.Parent = root Instance.new("UICorner", input).CornerRadius = UDim.new(0, 9) local redeemButton = button("Redeem code", 7) local feedback = label("Sample project — test in a separate experience.", 11, 32, 8, Color3.fromRGB(167, 176, 203)) feedback.TextWrapped = true local scale = Instance.new("UIScale") scale.Parent = root local function updateScale() local viewport = workspace.CurrentCamera and workspace.CurrentCamera.ViewportSize if viewport then scale.Scale = math.min(1, math.max(0.7, (viewport.X - 26) / 340)) end end updateScale() if workspace.CurrentCamera then workspace.CurrentCamera:GetPropertyChangedSignal("ViewportSize"):Connect(updateScale) end local busy = false local remaining = 0 local reward = 150 local available = false local function showState(state: any) if type(state) ~= "table" then return end balance.Text = "Coins: " .. tostring(state.coins or "?") remaining = math.max(0, tonumber(state.secondsLeft) or 0) reward = tonumber(state.reward) or 150 available = state.canClaim == true end local function call(action: string, argument: string?): any local success, result = pcall(function() return remote:InvokeServer(action, argument) end) if not success then feedback.Text = "Network / server error: " .. tostring(result) return nil end if type(result) == "table" then feedback.Text = tostring(result.message or "") showState(result.state) return result end feedback.Text = "Unexpected server response" return nil end local function formatSeconds(seconds: number): string local h = math.floor(seconds / 3600) local m = math.floor((seconds % 3600) / 60) local s = math.floor(seconds % 60) return string.format("%02d:%02d:%02d", h, m, s) end local function refreshTimer() if available and remaining == 0 then timer.Text = "Ready to claim" dailyButton.Text = "Claim +" .. tostring(reward) .. " coins" else timer.Text = "Next reward: " .. formatSeconds(remaining) dailyButton.Text = "On cooldown" end end dailyButton.MouseButton1Click:Connect(function() if busy or not available or remaining > 0 then return end busy = true call("daily", nil) busy = false refreshTimer() end) redeemButton.MouseButton1Click:Connect(function() if busy then return end busy = true call("promo", input.Text) busy = false refreshTimer() end) local initial = call("status", nil) if not initial or not initial.ok then -- Initial GetAsync may still be running. Retry status for a short period. task.spawn(function() for _ = 1, 8 do task.wait(2) local result = call("status", nil) if result and result.ok then refreshTimer() break end end end) end refreshTimer() task.spawn(function() while gui.Parent do task.wait(1) if remaining > 0 then remaining -= 1 if remaining == 0 then available = true end end refreshTimer() end end)