CCCPaste

b4a569f8fad8c

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

local LP        = Players.LocalPlayer
local PlayerGui = LP:WaitForChild("PlayerGui")

local WavesFolder   = ReplicatedStorage:WaitForChild("Waves")
local WavesEnabled  = ReplicatedStorage:WaitForChild("WavesEnabled")
local WaveControl   = ReplicatedStorage:WaitForChild("WaveControl")

local WaterStreamer = script.Parent:FindFirstChild("WaterStreamer")
if not WaterStreamer then
	for _, s in ipairs(script.Parent:GetChildren()) do
		if s:IsA("LocalScript") and s.Name:lower():find("waterstreamer") then
			WaterStreamer = s; break
		end
	end
end

local C = {
	BG   = Color3.fromRGB(24,24,30),
	FG2  = Color3.fromRGB(30,30,38),
	ACC  = Color3.fromRGB(40,40,52),
	SEL  = Color3.fromRGB(90,100,255),
	HI   = Color3.fromRGB(100,120,255),
	T1   = Color3.fromRGB(240,240,255),
	T2   = Color3.fromRGB(230,230,236),
	TM   = Color3.fromRGB(200,200,210),
	BRD  = Color3.fromRGB(64,64,86),
	KN   = Color3.fromRGB(180,180,255),
	KNS  = Color3.fromRGB(220,220,255),
	ERR  = Color3.fromRGB(220,140,140),
	G1   = Color3.fromRGB(58,58,76),
	G2   = Color3.fromRGB(54,54,70),
	G3   = Color3.fromRGB(60,60,80),
}

local function ui(className, props, parent)
	local o = Instance.new(className)
	if props then for k,v in pairs(props) do o[k]=v end end
	if parent then o.Parent = parent end
	return o
end

local function roundBox(inst, r, strokeColor, t)
	ui("UICorner", {CornerRadius = UDim.new(0, r or 10)}, inst)
	if strokeColor then ui("UIStroke", {Color = strokeColor, Thickness = t or 1}, inst) end
end

local function btn(text, w)
	local b = ui("TextButton", {
		Text = text, AutoButtonColor = true,
		Size = UDim2.new(0, w or 100, 0, 28),
		BackgroundColor3 = C.ACC, TextColor3 = C.T1,
		Font = Enum.Font.GothamSemibold, TextSize = 14
	})
	roundBox(b, 10, Color3.fromRGB(65,65,85), 1)
	return b
end

local function makeThrottledSender(sendFn, hz)
	local pending, last = nil, 0
	local interval = 1/(hz or 12.5)
	RunService.RenderStepped:Connect(function()
		if not pending then return end
		local t = os.clock()
		if t - last >= interval then
			last = t
			local v = pending; pending = nil
			sendFn(v)
		end
	end)
	return function(v) pending = v end
end

local function switch(parent, labelText)
	local row = ui("Frame", {Size = UDim2.new(1,0,0,34), BackgroundTransparency=1}, parent)
	ui("TextLabel", {
		Text = labelText, BackgroundTransparency = 1,
		TextXAlignment = Enum.TextXAlignment.Left, TextColor3 = C.T2,
		Font = Enum.Font.Gotham, TextSize = 14,
		Size = UDim2.new(1,-60,1,0)
	}, row)
	local box = ui("TextButton", {
		AutoButtonColor=false, Text="",
		AnchorPoint=Vector2.new(1,0.5), Position=UDim2.new(1,0,0.5,0),
		Size = UDim2.new(0,52,0,22), BackgroundColor3=C.G1
	}, row)
	roundBox(box, 999)
	local knob = ui("Frame", {Size=UDim2.new(0,20,0,20), Position=UDim2.new(0,1,0,1), BackgroundColor3=C.KN}, box)
	roundBox(knob, 999)

	local on=false
	local function set(v)
		on=v
		box.BackgroundColor3 = v and C.HI or C.G1
		knob:TweenPosition(UDim2.new(v and 1 or 0, v and -21 or 1, 0,1), "Out","Quad",0.12,true)
	end
	box.MouseButton1Click:Connect(function() set(not on) end)
	return {Instance=row, Set=set, Get=function() return on end, Button=box}
end

local function slider(parent, label, min, max, step, getFn, setFn)
	local row = ui("Frame", {Size=UDim2.new(1,0,0,58), BackgroundColor3=C.FG2}, parent)
	roundBox(row, 10, Color3.fromRGB(60,60,78), 1)
	ui("UIPadding", {PaddingLeft=UDim.new(0,10),PaddingRight=UDim.new(0,10),PaddingTop=UDim.new(0,8),PaddingBottom=UDim.new(0,8)}, row)
	ui("TextLabel", {
		Text=label, BackgroundTransparency=1, TextColor3=Color3.fromRGB(232,232,240),
		Font=Enum.Font.Gotham, TextSize=14, TextXAlignment=Enum.TextXAlignment.Left, Size=UDim2.new(1,-64,0,18)
	}, row)

	local valBtn = ui("TextButton", {Text="", BackgroundTransparency=1, TextColor3=C.TM, Font=Enum.Font.Gotham, TextSize=14,
		TextXAlignment=Enum.TextXAlignment.Right, AnchorPoint=Vector2.new(1,0), Position=UDim2.new(1,0,0,0), Size=UDim2.new(0,64,0,18)}, row)

	local bar = ui("Frame", {Size=UDim2.new(1,0,0,10), Position=UDim2.new(0,0,0,30), BackgroundColor3=C.G2}, row)
	roundBox(bar,5)
	local fill = ui("Frame", {Size=UDim2.new(0,0,1,0), BackgroundColor3=Color3.fromRGB(120,130,255)}, bar)
	roundBox(fill,5)
	local knob = ui("Frame", {Size=UDim2.new(0,14,0,14), AnchorPoint=Vector2.new(0.5,0.5), Position=UDim2.new(0,0,0.5,0), BackgroundColor3=C.KN}, bar)
	roundBox(knob,999); ui("UIStroke",{Color=C.KNS,Thickness=1},knob)

	local function snap(x)
		if step and step>0 then
			local k = math.floor((x-min)/step+0.5)
			return math.clamp(min + k*step, min, max)
		end
		return math.clamp(x, min, max)
	end

	local send = makeThrottledSender(function(v) setFn(v) end, 12.5)

	local function setVisual(v)
		v = snap(v)
		valBtn.Text = string.format("%.3f", v)
		local a = (v - min) / (max - min)
		fill.Size = UDim2.new(a,0,1,0)
		knob.Position = UDim2.new(a,0,0.5,0)
		return v
	end

	local function refresh() setVisual(getFn()) end
	refresh()

	local dragging=false
	local function fromX(x)
		local a = (x - bar.AbsolutePosition.X) / math.max(1, bar.AbsoluteSize.X)
		send(setVisual(min + math.clamp(a,0,1)*(max-min)))
	end

	bar.InputBegan:Connect(function(i)
		if i.UserInputType==Enum.UserInputType.MouseButton1 or i.UserInputType==Enum.UserInputType.Touch then
			dragging=true; fromX(i.Position.X)
		end
	end)
	bar.InputChanged:Connect(function(i)
		if dragging and (i.UserInputType==Enum.UserInputType.MouseMovement or i.UserInputType==Enum.UserInputType.Touch) then
			fromX(i.Position.X)
		end
	end)
	bar.InputEnded:Connect(function(i)
		if i.UserInputType==Enum.UserInputType.MouseButton1 or i.UserInputType==Enum.UserInputType.Touch then dragging=false end
	end)

	valBtn.MouseButton1Click:Connect(function()
		valBtn.AutoButtonColor=false; valBtn.Text=""
		local tb = ui("TextBox", {
			Text=tostring(getFn()), ClearTextOnFocus=false, BackgroundTransparency=0.2, BackgroundColor3=Color3.fromRGB(25,25,33),
			TextColor3=C.T1, Font=Enum.Font.Gotham, TextSize=14, AnchorPoint=Vector2.new(1,0), Position=UDim2.new(1,0,0,0),
			Size=UDim2.new(0,64,0,18)
		}, row)
		tb:CaptureFocus()
		tb.FocusLost:Connect(function()
			local nv = tonumber(tb.Text); if nv then send(setVisual(nv)) end
			tb:Destroy(); valBtn.AutoButtonColor=true; refresh()
		end)
	end)

	return {Refresh=refresh}
end

local function dirPad(parent, getX, getY, setX, setY)
	local root = ui("Frame", {Size=UDim2.new(1,0,0,120), BackgroundTransparency=1}, parent)
	ui("TextLabel", {Text="Direction (X,Y)", BackgroundTransparency=1, TextColor3=Color3.fromRGB(232,232,240),
		Font=Enum.Font.Gotham, TextSize=14, TextXAlignment=Enum.TextXAlignment.Left, Size=UDim2.new(1,0,0,18)}, root)

	local pad = ui("Frame", {Size=UDim2.new(0,120,0,120), Position=UDim2.new(0,0,0,24), BackgroundColor3=Color3.fromRGB(30,30,40)}, root)
	roundBox(pad,60, Color3.fromRGB(64,64,84), 1)
	ui("Frame",{Size=UDim2.new(0,2,1,-12), Position=UDim2.new(0.5,-1,0,6), BackgroundColor3=Color3.fromRGB(70,70,96)}, pad)
	ui("Frame",{Size=UDim2.new(1,-12,0,2), Position=UDim2.new(0,6,0.5,-1), BackgroundColor3=Color3.fromRGB(70,70,96)}, pad)
	local knob = ui("Frame",{Size=UDim2.new(0,16,0,16), AnchorPoint=Vector2.new(0.5,0.5), Position=UDim2.new(0.5,0,0.5,0), BackgroundColor3=C.KN}, pad)
	roundBox(knob,999); ui("UIStroke",{Color=C.KNS,Thickness=1},knob)

	local coord = ui("TextLabel",{Text="", BackgroundTransparency=1, TextColor3=C.TM, Font=Enum.Font.Code, TextSize=13,
		AnchorPoint=Vector2.new(1,0), Position=UDim2.new(1,0,0,2), Size=UDim2.new(0,140,0,16), TextXAlignment=Enum.TextXAlignment.Right}, root)

	local function setVisual(dx,dy)
		local m = math.sqrt(dx*dx+dy*dy); if m>1 then dx,dy=dx/m,dy/m end
		local r = math.min(pad.AbsoluteSize.X, pad.AbsoluteSize.Y)*0.5 - 8
		if r<=0 then knob.Position = UDim2.new(0.5,0,0.5,0) else
			local cx,cy=pad.AbsolutePosition.X+pad.AbsoluteSize.X*0.5, pad.AbsolutePosition.Y+pad.AbsoluteSize.Y*0.5
			local kx,ky=cx+dx*r, cy-dy*r
			local rx = (kx-pad.AbsolutePosition.X)/math.max(1,pad.AbsoluteSize.X)
			local ry = (ky-pad.AbsolutePosition.Y)/math.max(1,pad.AbsoluteSize.Y)
			knob.Position = UDim2.new(rx,0,ry,0)
		end
		coord.Text = string.format("(%.3f, %.3f)", dx, dy)
	end

	local dragging=false
	local function applyFrom(px,py)
		local ax,ay = pad.AbsolutePosition.X, pad.AbsolutePosition.Y
		local cx,cy  = ax+pad.AbsoluteSize.X*0.5, ay+pad.AbsoluteSize.Y*0.5
		local dx,dy  = (px-cx), -(py-cy)
		local r      = math.min(pad.AbsoluteSize.X, pad.AbsoluteSize.Y)*0.5 - 8
		if r<=0 then return end
		local vx,vy = dx/r, dy/r
		local l = math.sqrt(vx*vx+vy*vy); if l>1 then vx,vy=vx/l,vy/l end
		setX(vx); setY(vy); setVisual(vx,vy)
	end

	pad.InputBegan:Connect(function(i)
		if i.UserInputType==Enum.UserInputType.MouseButton1 or i.UserInputType==Enum.UserInputType.Touch then
			dragging=true; applyFrom(i.Position.X,i.Position.Y)
		end
	end)
	pad.InputChanged:Connect(function(i)
		if dragging and (i.UserInputType==Enum.UserInputType.MouseMovement or i.UserInputType==Enum.UserInputType.Touch) then
			applyFrom(i.Position.X,i.Position.Y)
		end
	end)
	pad.InputEnded:Connect(function(i)
		if i.UserInputType==Enum.UserInputType.MouseButton1 or i.UserInputType==Enum.UserInputType.Touch then dragging=false end
	end)

	RunService.Heartbeat:Once(function() setVisual(getX(), getY()) end)
	return {Refresh=function() setVisual(getX(), getY()) end}
end

local function waveCount()
	local n=0; while WavesFolder:FindFirstChild("Wave"..(n+1)) do n+=1 end; return n
end

local function setServerWave(i, prop, v)
	WaveControl:FireServer("set", i, prop, v)
end

local gui = ui("ScreenGui", {Name="GerstnerPanel", ResetOnSpawn=false, IgnoreGuiInset=true}, PlayerGui)
local panel = ui("Frame", {Size=UDim2.new(0,420,0,520), Position=UDim2.new(0,24,0,120),
	BackgroundColor3=C.BG, BorderSizePixel=0}, gui)
roundBox(panel,14, C.BRD, 1)

local header = ui("Frame",{Size=UDim2.new(1,0,0,40), BackgroundTransparency=1}, panel)
local title  = ui("TextLabel", {
	Text="Gerstner Waves — Sandbox", BackgroundTransparency=1, TextColor3=C.T1, Font=Enum.Font.GothamBold,
	TextSize=18, TextXAlignment=Enum.TextXAlignment.Left, Position=UDim2.new(0,16,0,0), Size=UDim2.new(1,-160,1,0)
}, header)
local bMin = btn("—", 36); bMin.Parent=header; bMin.Position=UDim2.new(1,-96,0,6)
local bX   = btn("X", 36);  bX.Parent=header;   bX.Position=UDim2.new(1,-52,0,6)

do
	local dragging=false; local off=Vector2.new()
	local function mouse() local p=UIS:GetMouseLocation(); return Vector2.new(p.X,p.Y) end
	header.InputBegan:Connect(function(i) if i.UserInputType==Enum.UserInputType.MouseButton1 then dragging=true; off=mouse()-panel.AbsolutePosition end end)
	header.InputEnded:Connect(function(i) if i.UserInputType==Enum.UserInputType.MouseButton1 then dragging=false end end)
	UIS.InputChanged:Connect(function(i)
		if dragging and (i.UserInputType==Enum.UserInputType.MouseMovement or i.UserInputType==Enum.UserInputType.Touch) then
			local m=mouse(); panel.Position = UDim2.fromOffset(m.X-off.X, m.Y-off.Y)
		end
	end)
end

bMin.MouseButton1Click:Connect(function()
	local h=panel.Size.Y.Offset
	panel:TweenSize(UDim2.new(0,420,0,(h<=50) and 520 or 48), "Out","Quad",0.15,true)
end)
bX.MouseButton1Click:Connect(function() panel.Visible=false end)
UIS.InputBegan:Connect(function(i,gp) if not gp and i.KeyCode==Enum.KeyCode.F6 then panel.Visible = not panel.Visible end end)

local topBar = ui("Frame",{BackgroundTransparency=1, Size=UDim2.new(1,-16,0,32), Position=UDim2.new(0,8,0,46)}, panel)
local bToggle = btn("On/Off"); bToggle.Parent=topBar; bToggle.Position=UDim2.new(0,0,0,0)
local bReset  = btn("Réinitialiser"); bReset.Parent=topBar; bReset.Position=UDim2.new(0,112,0,0)
bToggle.MouseButton1Click:Connect(function() WaveControl:FireServer("toggle") end)
bReset.MouseButton1Click:Connect(function() WaveControl:FireServer("reset") end)

local tabBar = ui("Frame",{BackgroundTransparency=1, Size=UDim2.new(1,-16,0,32), Position=UDim2.new(0,8,0,86)}, panel)
local TABS = {"Global","Vagues","Presets","Paramètres"}
local tabBtn, current = {}, "Vagues"
for i,name in ipairs(TABS) do
	local b=btn(name,100); b.Parent=tabBar; b.Position=UDim2.new(0,(i-1)*106,0,0); tabBtn[name]=b
end
local pages = ui("Frame",{Size=UDim2.new(1,-16,1,-140), Position=UDim2.new(0,8,0,124), BackgroundTransparency=1}, panel)

local function clearPages()
	for _,c in ipairs(pages:GetChildren()) do if c:IsA("Frame") then c:Destroy() end end
end

local function pageGlobal()
	local root = ui("Frame",{BackgroundTransparency=1, Size=UDim2.fromScale(1,1)}, pages)
	local sc = ui("ScrollingFrame",{BackgroundTransparency=1, Size=UDim2.fromScale(1,1), ScrollBarThickness=8, AutomaticCanvasSize=Enum.AutomaticSize.Y}, root)
	ui("UIListLayout",{Padding=UDim.new(0,10), SortOrder=Enum.SortOrder.LayoutOrder}, sc)

	ui("TextLabel",{
		Text=("Vagues actives : %d"):format(waveCount()),
		TextColor3=C.T2, BackgroundTransparency=1, Font=Enum.Font.GothamSemibold, TextSize=16, Size=UDim2.new(1,0,0,24)
	}, sc)

	local sw = switch(sc, "Activer les vagues")
	sw.Set(WavesEnabled.Value)
	WavesEnabled.Changed:Connect(function() sw.Set(WavesEnabled.Value) end)
	sw.Button.MouseButton1Click:Connect(function() WaveControl:FireServer("toggle") end)

	local br = btn("Réinitialiser toutes les vagues", 240); br.Parent=sc
	br.MouseButton1Click:Connect(function() WaveControl:FireServer("reset") end)
	return root
end

local function makeWaveSliders(parent, idx, waveFolder)
	local function gv(p) return waveFolder[p].Value end
	local function sv(p,v) setServerWave(idx,p,v) end
	local rf = {}
	table.insert(rf, slider(parent,"Amplitude", 0,100,0.1, function() return gv("Amplitude") end, function(v) sv("Amplitude",v) end ).Refresh)
	table.insert(rf, slider(parent,"Longueur d'onde", 0.1,1000,0.1, function() return gv("Wavelength") end, function(v) sv("Wavelength",v) end ).Refresh)
	table.insert(rf, slider(parent,"Vitesse", 0,50,0.01, function() return gv("Speed") end, function(v) sv("Speed",v) end ).Refresh)
	table.insert(rf, slider(parent,"Raideur (Steepness)", 0,1,0.001, function() return gv("Steepness") end, function(v) sv("Steepness",v) end ).Refresh)
	local dp = dirPad(parent, function() return gv("DirectionX") end, function() return gv("DirectionY") end,
	function(v) sv("DirectionX",v) end, function(v) sv("DirectionY",v) end)
	table.insert(rf, dp.Refresh)

	local bar = ui("Frame",{Size=UDim2.new(1,0,0,32), BackgroundTransparency=1}, parent)
	local bN = btn("Norm. direction", 150); bN.Parent=bar
	bN.MouseButton1Click:Connect(function()
		local dx,dy=gv("DirectionX"),gv("DirectionY"); local m=math.sqrt(dx*dx+dy*dy)
		if m<1e-6 then dx,dy=1,0 else dx,dy=dx/m,dy/m end
		sv("DirectionX",dx); sv("DirectionY",dy)
	end)
	local bR = btn("Randomiser",120); bR.Parent=bar; bR.Position=UDim2.new(0,160,0,0)
	bR.MouseButton1Click:Connect(function()
		local function rnd(a,b) return a + math.random()*(b-a) end
		sv("Amplitude", rnd(0.1,5)); sv("Wavelength", rnd(6,24)); sv("Speed", rnd(0.5,3.2)); sv("Steepness", rnd(0.05,0.5))
		local a=rnd(0, math.pi*2); sv("DirectionX", math.cos(a)); sv("DirectionY", math.sin(a))
	end)

	local conns={}
	for _,v in ipairs(waveFolder:GetChildren()) do
		if v:IsA("NumberValue") then
			table.insert(conns, v.Changed:Connect(function() for _,f in ipairs(rf) do f() end end))
		end
	end
	return function()
		for _,c in ipairs(conns) do if c.Disconnect then c:Disconnect() end end
	end
end

local function pageWaves()
	local root = ui("Frame",{BackgroundTransparency=1, Size=UDim2.fromScale(1,1)}, pages)
	local left = ui("Frame",{BackgroundTransparency=1, Size=UDim2.new(0,112,1,0)}, root)
	local right= ui("ScrollingFrame",{BackgroundTransparency=1, Size=UDim2.new(1,-120,1,0), Position=UDim2.new(0,120,0,0),
		ScrollBarThickness=8, AutomaticCanvasSize=Enum.AutomaticSize.Y}, root)
	ui("UIListLayout",{Padding=UDim.new(0,10), SortOrder=Enum.SortOrder.LayoutOrder}, right)

	local selected=1; local buttons={}; local cleanup=function()end
	local function select(i)
		selected=i
		for k,b in pairs(buttons) do b.BackgroundColor3 = (k==i) and C.SEL or C.ACC end
		for _,c in ipairs(right:GetChildren()) do if c:IsA("Frame") then c:Destroy() end end
		cleanup()

		local wf=WavesFolder:FindFirstChild("Wave"..i)
		if not wf then return end
		cleanup = makeWaveSliders(right, i, wf)
	end

	for i=1, waveCount() do
		local b=btn(("Vague %d"):format(i),100); b.Parent=left; b.Position=UDim2.new(0,6,0,(i-1)*36)
		buttons[i]=b; b.MouseButton1Click:Connect(function() select(i) end)
	end
	select(1)
	return root
end

local PRESETS = {
	["Calme"] = {
		{A=0.2, L=18, S=1.0, Q=0.15, dir=0.0},
		{A=0.15,L=24, S=0.9, Q=0.12, dir=0.7},
		{A=0.12,L=12, S=1.4, Q=0.18, dir=2.1},
	},
	["Clapot"] = {
		{A=0.6, L=10, S=2.0, Q=0.35, dir=0.2},
		{A=0.3, L=7,  S=2.6, Q=0.45, dir=1.3},
		{A=0.25,L=6,  S=2.8, Q=0.40, dir=2.6},
	},
	["Swell diagonal"] = {
		{A=0.8, L=22, S=1.3, Q=0.25, dir=math.rad(35)},
		{A=0.3, L=12, S=2.4, Q=0.35, dir=math.rad(205)},
		{A=0.25,L=18, S=1.9, Q=0.28, dir=math.rad(110)},
	},
	["Verre (glass)"] = {
		{A=0.05,L=28, S=0.7, Q=0.10, dir=0.0},
		{A=0.04,L=32, S=0.6, Q=0.08, dir=1.4},
		{A=0.03,L=20, S=1.1, Q=0.10, dir=2.7},
	},
	["Tempête"] = {
		{A=1.8, L=14, S=2.6, Q=0.45, dir=0.3},
		{A=1.1, L=10, S=3.0, Q=0.50, dir=1.6},
		{A=0.7, L=7,  S=3.3, Q=0.50, dir=2.9},
	},
}

local function applyPreset(name)
	local p = PRESETS[name]; if not p then return end
	local n = math.min(#p, waveCount())
	for i=1,n do
		local w=p[i]
		task.defer(function() setServerWave(i,"Amplitude", w.A) end)
		task.defer(function() setServerWave(i,"Wavelength",w.L) end)
		task.defer(function() setServerWave(i,"Speed",     w.S) end)
		task.defer(function() setServerWave(i,"Steepness", w.Q) end)
		task.defer(function()
			setServerWave(i,"DirectionX", math.cos(w.dir))
			setServerWave(i,"DirectionY", math.sin(w.dir))
		end)
	end
end

local function wavesToJSON()
	local arr={}
	for i=1, waveCount() do
		local wf=WavesFolder:FindFirstChild("Wave"..i)
		arr[i] = {
			Amplitude = wf.Amplitude.Value,
			Wavelength= wf.Wavelength.Value,
			Speed     = wf.Speed.Value,
			Steepness = wf.Steepness.Value,
			DirectionX= wf.DirectionX.Value,
			DirectionY= wf.DirectionY.Value,
		}
	end
	return HttpService:JSONEncode(arr)
end

local function importJSON(txt)
	local ok, arr = pcall(function() return HttpService:JSONDecode(txt) end)
	if not ok or type(arr)~="table" then return false end
	local n = math.min(#arr, waveCount())
	for i=1,n do
		local w=arr[i]; if type(w)=="table" then
			local need={"Amplitude","Wavelength","Speed","Steepness","DirectionX","DirectionY"}
			for _,k in ipairs(need) do if w[k]==nil then return false end end
			for k,v in pairs(w) do setServerWave(i,k, tonumber(v)) end
			local dx,dy= w.DirectionX, w.DirectionY
			local m = math.sqrt(dx*dx+dy*dy)
			if m>1e-6 then
				setServerWave(i,"DirectionX", dx/m)
				setServerWave(i,"DirectionY", dy/m)
			end
		end
	end
	return true
end

local function pagePresets()
	local root = ui("Frame",{BackgroundTransparency=1, Size=UDim2.fromScale(1,1)}, pages)
	local grid = ui("Frame",{BackgroundTransparency=1, Size=UDim2.new(1,0,0,140)}, root)
	ui("UIGridLayout",{CellPadding=UDim2.new(0,10,0,10), CellSize=UDim2.new(0,150,0,34), FillDirectionMaxCells=3}, grid)
	for name in pairs(PRESETS) do
		local b=btn(name,150); b.Parent=grid; b.MouseButton1Click:Connect(function() applyPreset(name) end)
	end

	local tb = ui("TextBox",{
		ClearTextOnFocus=false, MultiLine=true, Text="",
		PlaceholderText="Colle ici le JSON des vagues…",
		TextXAlignment=Enum.TextXAlignment.Left, TextYAlignment=Enum.TextYAlignment.Top,
		Font=Enum.Font.Code, TextSize=14, TextColor3=C.T1, BackgroundColor3=Color3.fromRGB(22,22,28),
		Size=UDim2.new(1,0,1,-160), Position=UDim2.new(0,0,0,150)
	}, root)
	roundBox(tb,10, C.G3,1)
	ui("UIPadding",{PaddingLeft=UDim.new(0,8),PaddingRight=UDim.new(0,8),PaddingTop=UDim.new(0,8),PaddingBottom=UDim.new(0,8)}, tb)

	local bar = ui("Frame",{BackgroundTransparency=1, Size=UDim2.new(1,0,0,34), Position=UDim2.new(0,0,1,-34)}, root)
	local bE = btn("Exporter JSON",150); bE.Parent=bar; bE.MouseButton1Click:Connect(function() tb.Text = wavesToJSON() end)
	local bI = btn("Importer JSON",150); bI.Parent=bar; bI.Position=UDim2.new(0,160,0,0)
	bI.MouseButton1Click:Connect(function() tb.Text = importJSON(tb.Text) and "-- Import OK --" or "-- JSON invalide --" end)
	return root
end

local RMIN, RMAX, RSTEP = 50, 2000, 10
local function getRD()
	local d=300
	if WaterStreamer and WaterStreamer:GetAttribute("RenderDistance") then d = WaterStreamer:GetAttribute("RenderDistance") end
	return d
end
local function setRD(v)
	v = math.clamp(math.floor(v/RSTEP+0.5)*RSTEP, RMIN, RMAX)
	if WaterStreamer then WaterStreamer:SetAttribute("RenderDistance", v) end
end

local function pageSettings()
	local root = ui("Frame",{BackgroundTransparency=1, Size=UDim2.fromScale(1,1)}, pages)
	local sc = ui("ScrollingFrame",{BackgroundTransparency=1, Size=UDim2.fromScale(1,1), ScrollBarThickness=8, AutomaticCanvasSize=Enum.AutomaticSize.Y}, root)
	ui("UIListLayout",{Padding=UDim.new(0,10), SortOrder=Enum.SortOrder.LayoutOrder}, sc)
	slider(sc, "Distance d'affichage (studs)", RMIN, RMAX, RSTEP, getRD, setRD)
	if WaterStreamer then WaterStreamer:GetAttributeChangedSignal("RenderDistance"):Connect(function() end) end
	return root
end

local builders = {["Global"]=pageGlobal, ["Vagues"]=pageWaves, ["Presets"]=pagePresets, ["Paramètres"]=pageSettings}

local function openTab(name)
	current=name
	for n,b in pairs(tabBtn) do b.BackgroundColor3 = (n==name) and C.SEL or C.ACC end
	clearPages()
	builders[name]()
end
for n,b in pairs(tabBtn) do b.MouseButton1Click:Connect(function() openTab(n) end) end
openTab(current)

local function syncEnabled()
	local on=WavesEnabled.Value
	title.Text = on and "Gerstner Waves — Sandbox (ON)" or "Gerstner Waves — Sandbox (OFF)"
	title.TextColor3 = on and C.T1 or C.ERR
end
syncEnabled(); WavesEnabled.Changed:Connect(syncEnabled)