<roblox version="4">
  <Item class="HttpService" referent="0">
    <Properties>
      <string name="Name">HttpService</string>
      <bool name="HttpEnabled">true</bool>
    </Properties>
  </Item>
  <Item class="ReplicatedStorage" referent="1">
    <Properties>
      <string name="Name">ReplicatedStorage</string>
    </Properties>
    <Item class="Folder" referent="2">
      <Properties>
        <string name="Name">Shared</string>
      </Properties>
      <Item class="ModuleScript" referent="3">
        <Properties>
          <string name="Name">Config</string>
          <string name="Source"><![CDATA[local Config = {}

Config.StartingSheckles = 20
Config.PlotSize = 8           -- 8x8 grid of tiles
Config.TileStud = 4           -- studs per tile
Config.MaxPlayersPerServer = 6

Config.ShopRefreshSeconds = 300       -- 5 minutes
Config.WeatherTickSeconds = 60        -- roll for new weather every minute
Config.WeatherMinDuration = 60
Config.WeatherMaxDuration = 240

Config.PetHungerDrainPerSec = 0.05    -- 0..100
Config.PetAgeXpPerSec = 1
Config.PetMaxAge = 100

Config.AutoSaveSeconds = 60

return Config
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="4">
        <Properties>
          <string name="Name">Gear</string>
          <string name="Source"><![CDATA[-- Sprinklers / tools. Effects are applied while gear is placed on a plot.

local Gear = {
	BasicSprinkler = {
		name = "Basic Sprinkler", price = 1500,
		radius = 3, growthMult = 1.10, mutationChance = 0.01,
	},
	AdvancedSprinkler = {
		name = "Advanced Sprinkler", price = 12000,
		radius = 4, growthMult = 1.25, mutationChance = 0.03,
	},
	GodlySprinkler = {
		name = "Godly Sprinkler", price = 80000,
		radius = 5, growthMult = 1.50, mutationChance = 0.06,
	},
	WateringCan = {
		name = "Watering Can", price = 500,
		usable = true, action = "water",   -- click crop to apply Wet
	},
	GoldenWateringCan = {
		name = "Golden Watering Can", price = 75000,
		usable = true, action = "golden",  -- 1% chance Golden per use
	},
}

return Gear
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="5">
        <Properties>
          <string name="Name">Mutations</string>
          <string name="Source"><![CDATA[-- Mutations stack MULTIPLICATIVELY when applied to a crop.
-- finalValue = sellBase * product(multipliers)

local Mutations = {
	Wet        = { name = "Wet",       mult = 2,    color = Color3.fromRGB(120, 180, 255) },
	Chilled    = { name = "Chilled",   mult = 2,    color = Color3.fromRGB(180, 220, 255) },
	Frozen     = { name = "Frozen",    mult = 10,   color = Color3.fromRGB(140, 220, 255) },
	Shocked    = { name = "Shocked",   mult = 100,  color = Color3.fromRGB(255, 240, 60) },
	Moonlit    = { name = "Moonlit",   mult = 2,    color = Color3.fromRGB(180, 180, 255) },
	Bloodlit   = { name = "Bloodlit",  mult = 4,    color = Color3.fromRGB(200, 40, 40) },
	Burnt      = { name = "Burnt",     mult = 4,    color = Color3.fromRGB(80, 60, 50) },
	Golden     = { name = "Golden",    mult = 20,   color = Color3.fromRGB(255, 215, 0) },
	Rainbow    = { name = "Rainbow",   mult = 50,   color = Color3.fromRGB(255, 100, 200) },
	Celestial  = { name = "Celestial", mult = 120,  color = Color3.fromRGB(220, 200, 255) },
	Zombified  = { name = "Zombified", mult = 25,   color = Color3.fromRGB(120, 200, 80) },
	Disco      = { name = "Disco",     mult = 125,  color = Color3.fromRGB(255, 80, 220) },
}

function Mutations.computeMultiplier(applied)
	local m = 1
	if applied then
		for id, _ in pairs(applied) do
			local def = Mutations[id]
			if def then m = m * def.mult end
		end
	end
	return m
end

function Mutations.tryApply(crop, id, chance)
	if not crop.mutations then crop.mutations = {} end
	if crop.mutations[id] then return false end
	if math.random() <= chance then
		crop.mutations[id] = true
		return true
	end
	return false
end

return Mutations
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="6">
        <Properties>
          <string name="Name">Pets</string>
          <string name="Source"><![CDATA[-- Pet egg / pet catalog.
-- traitKind drives the passive behavior PetService applies each second.

local Pets = {}

Pets.Eggs = {
	CommonEgg = {
		name = "Common Egg", price = 200, hatchSec = 60,
		pool = { { id = "Dog", w = 50 }, { id = "Cat", w = 50 } },
	},
	RareEgg = {
		name = "Rare Egg", price = 2500, hatchSec = 180,
		pool = { { id = "Bee", w = 50 }, { id = "Bunny", w = 35 }, { id = "Dragonfly", w = 15 } },
	},
	LegendaryEgg = {
		name = "Legendary Egg", price = 50000, hatchSec = 600,
		pool = { { id = "PolarBear", w = 60 }, { id = "Phoenix", w = 30 }, { id = "Dragon", w = 10 } },
	},
}

Pets.Defs = {
	Dog      = { name = "Dog",       traitKind = "growthMult",  value = 1.05, color = Color3.fromRGB(200,160,100) },
	Cat      = { name = "Cat",       traitKind = "shopWeight",  value = 1.10, color = Color3.fromRGB(180,180,180) },
	Bee      = { name = "Bee",       traitKind = "mutationChance", value = 0.05, color = Color3.fromRGB(255,210,60) },
	Bunny    = { name = "Bunny",     traitKind = "regrowMult",  value = 1.25, color = Color3.fromRGB(240,220,200) },
	Dragonfly= { name = "Dragonfly", traitKind = "rareSeedFind", value = 0.02, color = Color3.fromRGB(120,220,200) },
	PolarBear= { name = "Polar Bear",traitKind = "mutationChance", value = 0.15, color = Color3.fromRGB(240,250,255) },
	Phoenix  = { name = "Phoenix",   traitKind = "growthMult",  value = 1.50, color = Color3.fromRGB(255,120,60) },
	Dragon   = { name = "Dragon",    traitKind = "sellMult",    value = 1.25, color = Color3.fromRGB(120,60,220) },
}

function Pets.rollFromPool(pool)
	local total = 0
	for _, e in ipairs(pool) do total = total + e.w end
	local r = math.random() * total
	local acc = 0
	for _, e in ipairs(pool) do
		acc = acc + e.w
		if r <= acc then return e.id end
	end
	return pool[1].id
end

return Pets
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="7">
        <Properties>
          <string name="Name">Remotes</string>
          <string name="Source"><![CDATA[local ReplicatedStorage = game:GetService("ReplicatedStorage")

local function getOrCreate(parent, className, name)
	local existing = parent:FindFirstChild(name)
	if existing then return existing end
	local inst = Instance.new(className)
	inst.Name = name
	inst.Parent = parent
	return inst
end

local folder = getOrCreate(ReplicatedStorage, "Folder", "Remotes")

local Remotes = {
	-- client -> server
	BuySeed       = getOrCreate(folder, "RemoteEvent", "BuySeed"),
	PlantSeed     = getOrCreate(folder, "RemoteEvent", "PlantSeed"),
	HarvestCrop   = getOrCreate(folder, "RemoteEvent", "HarvestCrop"),
	SellAll       = getOrCreate(folder, "RemoteEvent", "SellAll"),
	BuyEgg        = getOrCreate(folder, "RemoteEvent", "BuyEgg"),
	HatchEgg      = getOrCreate(folder, "RemoteEvent", "HatchEgg"),
	EquipPet      = getOrCreate(folder, "RemoteEvent", "EquipPet"),
	FeedPet       = getOrCreate(folder, "RemoteEvent", "FeedPet"),
	BuyGear       = getOrCreate(folder, "RemoteEvent", "BuyGear"),
	PlaceGear     = getOrCreate(folder, "RemoteEvent", "PlaceGear"),

	-- server -> client
	DataChanged   = getOrCreate(folder, "RemoteEvent", "DataChanged"),
	ShopChanged   = getOrCreate(folder, "RemoteEvent", "ShopChanged"),
	WeatherChanged= getOrCreate(folder, "RemoteEvent", "WeatherChanged"),
	Notify        = getOrCreate(folder, "RemoteEvent", "Notify"),

	-- request/response
	GetState      = getOrCreate(folder, "RemoteFunction", "GetState"),
}

return Remotes
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="8">
        <Properties>
          <string name="Name">Seeds</string>
          <string name="Source"><![CDATA[-- Seed / crop catalog.
-- harvestType: "single" disappears after one pick; "multi" regrows on regrowSec.
-- shopWeight: relative odds of appearing in shop stock per refresh.

local Seeds = {
	Carrot = {
		name = "Carrot",
		price = 10, sellBase = 18,
		rarity = "Common", shopWeight = 60,
		growSec = 30, harvestType = "single",
		color = Color3.fromRGB(255, 140, 60),
	},
	Strawberry = {
		name = "Strawberry",
		price = 50, sellBase = 14,
		rarity = "Common", shopWeight = 55,
		growSec = 45, harvestType = "multi", regrowSec = 35,
		color = Color3.fromRGB(220, 50, 80),
	},
	Blueberry = {
		name = "Blueberry",
		price = 400, sellBase = 25,
		rarity = "Uncommon", shopWeight = 30,
		growSec = 90, harvestType = "multi", regrowSec = 60,
		color = Color3.fromRGB(60, 100, 220),
	},
	Tomato = {
		name = "Tomato",
		price = 800, sellBase = 35,
		rarity = "Uncommon", shopWeight = 25,
		growSec = 120, harvestType = "multi", regrowSec = 80,
		color = Color3.fromRGB(220, 60, 60),
	},
	Watermelon = {
		name = "Watermelon",
		price = 2500, sellBase = 280,
		rarity = "Rare", shopWeight = 12,
		growSec = 300, harvestType = "single",
		color = Color3.fromRGB(50, 160, 80),
	},
	Pumpkin = {
		name = "Pumpkin",
		price = 6000, sellBase = 700,
		rarity = "Rare", shopWeight = 8,
		growSec = 420, harvestType = "single",
		color = Color3.fromRGB(230, 130, 30),
	},
	DragonFruit = {
		name = "Dragon Fruit",
		price = 50000, sellBase = 4500,
		rarity = "Legendary", shopWeight = 3,
		growSec = 900, harvestType = "multi", regrowSec = 600,
		color = Color3.fromRGB(230, 60, 160),
	},
	Mango = {
		name = "Mango",
		price = 100000, sellBase = 7500,
		rarity = "Legendary", shopWeight = 2,
		growSec = 1200, harvestType = "multi", regrowSec = 700,
		color = Color3.fromRGB(250, 180, 40),
	},
	Cactus = {
		name = "Cactus",
		price = 250000, sellBase = 18000,
		rarity = "Mythic", shopWeight = 1,
		growSec = 1800, harvestType = "single",
		color = Color3.fromRGB(80, 180, 100),
	},
	Beanstalk = {
		name = "Beanstalk",
		price = 1000000, sellBase = 75000,
		rarity = "Mythic", shopWeight = 1,
		growSec = 3600, harvestType = "multi", regrowSec = 1800,
		color = Color3.fromRGB(120, 200, 90),
	},
}

function Seeds.list()
	local arr = {}
	for id, s in pairs(Seeds) do
		if type(s) == "table" then
			table.insert(arr, { id = id, def = s })
		end
	end
	return arr
end

function Seeds.get(id)
	local s = rawget(Seeds, id)
	if type(s) == "table" then return s end
	return nil
end

return Seeds
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="9">
        <Properties>
          <string name="Name">Weather</string>
          <string name="Source"><![CDATA[-- Server-wide weather events. Each rolls per WeatherTickSeconds.
-- effect(crop) is called for every growing/ready crop on every plot during the event.

local Weather = {
	Clear = {
		name = "Clear", duration = 60, weight = 50,
		appliesMutation = nil, growthMult = 1,
	},
	Rain = {
		name = "Rain", duration = 120, weight = 30,
		appliesMutation = "Wet", mutationChancePerTick = 0.5,
		growthMult = 1.25,
	},
	Thunderstorm = {
		name = "Thunderstorm", duration = 60, weight = 6,
		appliesMutation = "Shocked", mutationChancePerTick = 0.15,
		secondaryMutation = "Wet", secondaryChance = 0.5,
		growthMult = 1.25,
	},
	Snow = {
		name = "Snow", duration = 180, weight = 12,
		appliesMutation = "Chilled", mutationChancePerTick = 0.4,
		growthMult = 0.8,
	},
	Blizzard = {
		name = "Blizzard", duration = 120, weight = 4,
		appliesMutation = "Frozen", mutationChancePerTick = 0.2,
		growthMult = 0.6,
	},
	Heatwave = {
		name = "Heatwave", duration = 120, weight = 8,
		appliesMutation = "Burnt", mutationChancePerTick = 0.25,
		growthMult = 1.5,
	},
	Moonlight = {
		name = "Moonlight", duration = 180, weight = 8,
		appliesMutation = "Moonlit", mutationChancePerTick = 0.35,
		growthMult = 1.2,
	},
	BloodMoon = {
		name = "Blood Moon", duration = 180, weight = 3,
		appliesMutation = "Bloodlit", mutationChancePerTick = 0.4,
		growthMult = 1.3,
	},
	MeteorShower = {
		name = "Meteor Shower", duration = 90, weight = 2,
		appliesMutation = "Celestial", mutationChancePerTick = 0.1,
		growthMult = 1.0,
	},
	SolarEclipse = {
		name = "Solar Eclipse", duration = 120, weight = 2,
		appliesMutation = "Golden", mutationChancePerTick = 0.05,
		growthMult = 1.0,
	},
	Disco = {
		name = "Disco", duration = 90, weight = 1,
		appliesMutation = "Disco", mutationChancePerTick = 0.08,
		growthMult = 1.5,
	},
}

function Weather.roll()
	local total = 0
	for _, def in pairs(Weather) do
		if type(def) == "table" then total = total + (def.weight or 0) end
	end
	local r = math.random() * total
	local acc = 0
	for id, def in pairs(Weather) do
		if type(def) == "table" then
			acc = acc + def.weight
			if r <= acc then return id, def end
		end
	end
	return "Clear", Weather.Clear
end

return Weather
]]></string>
        </Properties>
      </Item>
    </Item>
  </Item>
  <Item class="ServerScriptService" referent="10">
    <Properties>
      <string name="Name">ServerScriptService</string>
    </Properties>
    <Item class="Folder" referent="11">
      <Properties>
        <string name="Name">Server</string>
      </Properties>
      <Item class="ModuleScript" referent="12">
        <Properties>
          <string name="Name">CropService</string>
          <string name="Source"><![CDATA[-- Visual + lifecycle for crops. Server-authoritative.

local RS        = game:GetService("ReplicatedStorage")
local Seeds     = require(RS.Shared.Seeds)
local Mutations = require(RS.Shared.Mutations)

local CropService = {}

local function cropPart(seedDef, scale)
	local p = Instance.new("Part")
	p.Anchored = true
	p.CanCollide = false
	p.Size = Vector3.new(scale, scale * 1.5, scale)
	p.Material = Enum.Material.Neon
	p.Color = seedDef.color
	p.Shape = Enum.PartType.Ball
	return p
end

function CropService.spawnVisual(plot, x, z, seedId)
	local seedDef = Seeds.get(seedId)
	local tile = plot.model.Tiles:FindFirstChild(("Tile_%d_%d"):format(x, z))
	if not tile then return nil end

	local model = Instance.new("Model")
	model.Name = ("Crop_%d_%d"):format(x, z)

	local body = cropPart(seedDef, 0.4)
	body.CFrame = tile.CFrame * CFrame.new(0, 1, 0)
	body.Parent = model
	model.PrimaryPart = body

	local click = Instance.new("ClickDetector")
	click.MaxActivationDistance = 64
	click.Parent = body

	local bb = Instance.new("BillboardGui")
	bb.Size = UDim2.fromScale(4, 1.2)
	bb.StudsOffset = Vector3.new(0, 2, 0)
	bb.AlwaysOnTop = true
	bb.Parent = body
	local label = Instance.new("TextLabel")
	label.BackgroundTransparency = 1
	label.Size = UDim2.fromScale(1, 1)
	label.TextScaled = true
	label.TextStrokeTransparency = 0.5
	label.TextColor3 = Color3.new(1,1,1)
	label.Font = Enum.Font.GothamBold
	label.Text = ""
	label.Parent = bb

	model.Parent = plot.model
	return model, label, body
end

function CropService.updateVisual(crop)
	if not crop.visual then return end
	local body = crop.visual.PrimaryPart
	if not body then return end

	local seedDef = Seeds.get(crop.seedId)
	local growthPct = math.clamp(crop.growth / seedDef.growSec, 0, 1)
	local scale = 0.3 + growthPct * 1.5
	body.Size = Vector3.new(scale, scale * 1.2, scale)

	-- mutation tint: blend mutation colors
	local r, g, b, n = 0, 0, 0, 0
	for id in pairs(crop.mutations or {}) do
		local m = Mutations[id]
		if m then r += m.color.R; g += m.color.G; b += m.color.B; n += 1 end
	end
	if n > 0 then
		body.Color = Color3.new(r/n, g/n, b/n)
		body.Material = Enum.Material.Neon
	else
		body.Color = seedDef.color
	end

	local label = body:FindFirstChildOfClass("BillboardGui")
	if label then
		local txt = body.BillboardGui.TextLabel
		if crop.ready then
			local mults = {}
			for id in pairs(crop.mutations or {}) do table.insert(mults, id) end
			txt.Text = (#mults > 0) and ("READY " .. table.concat(mults, "+")) or "READY"
		else
			txt.Text = ("%d%%"):format(math.floor(growthPct * 100))
		end
	end
end

function CropService.removeVisual(crop)
	if crop.visual then crop.visual:Destroy(); crop.visual = nil end
end

function CropService.computeValue(crop, sellMult)
	local seedDef = Seeds.get(crop.seedId)
	local base = seedDef.sellBase
	local mult = Mutations.computeMultiplier(crop.mutations)
	return math.floor(base * mult * (sellMult or 1))
end

return CropService
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="13">
        <Properties>
          <string name="Name">GearService</string>
          <string name="Source"><![CDATA[local RS      = game:GetService("ReplicatedStorage")
local Remotes = require(RS.Shared.Remotes)
local Gear    = require(RS.Shared.Gear)

local PlayerData  = require(script.Parent.PlayerData)
local PlotService = require(script.Parent.PlotService)

local GearService = {}

function GearService.place(player, gearId, x, z)
	local data = PlayerData.get(player); if not data then return end
	local plot = PlotService.get(player); if not plot then return end
	if (data.gear[gearId] or 0) <= 0 then return end
	if not Gear[gearId] then return end

	local key = PlotService.tileKey(math.floor(x), math.floor(z))
	if plot.gear[key] then Remotes.Notify:FireClient(player, "Gear already placed there"); return end

	plot.gear[key] = { id = gearId, x = x, z = z }
	data.gear[gearId] -= 1
	if data.gear[gearId] <= 0 then data.gear[gearId] = nil end
	PlayerData.push(player)

	-- visual marker
	local tile = PlotService.findTile(plot, x, z)
	if tile then
		local marker = Instance.new("Part")
		marker.Anchored = true
		marker.CanCollide = false
		marker.Size = Vector3.new(1.2, 1.2, 1.2)
		marker.Material = Enum.Material.Neon
		marker.Color = Color3.fromRGB(120, 200, 255)
		marker.CFrame = tile.CFrame * CFrame.new(0, 1.5, 0)
		marker.Name = "Gear_" .. key
		marker.Parent = plot.model
	end
end

function GearService.bind()
	Remotes.PlaceGear.OnServerEvent:Connect(GearService.place)
end

return GearService
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="14">
        <Properties>
          <string name="Name">GrowthService</string>
          <string name="Source"><![CDATA[-- Per-tick growth, regrow, weather mutation rolls, sprinkler effects.

local RS        = game:GetService("ReplicatedStorage")
local Seeds     = require(RS.Shared.Seeds)
local Mutations = require(RS.Shared.Mutations)
local Gear      = require(RS.Shared.Gear)

local PlotService = require(script.Parent.PlotService)
local CropService = require(script.Parent.CropService)
local PetService  -- lazy require

local GrowthService = {}
local currentWeather = nil

function GrowthService.setWeather(weatherDef)
	currentWeather = weatherDef
end

local function gearGrowthMult(plot)
	local m = 1
	for _, g in pairs(plot.gear or {}) do
		local def = Gear[g.id]
		if def and def.growthMult then m = m * def.growthMult end
	end
	return m
end

local function gearMutationChance(plot)
	local c = 0
	for _, g in pairs(plot.gear or {}) do
		local def = Gear[g.id]
		if def and def.mutationChance then c = c + def.mutationChance end
	end
	return c
end

local function tickCrop(plot, key, crop, dt, petBonuses)
	local seedDef = Seeds.get(crop.seedId)
	if not seedDef then return end

	local weatherGrowth = (currentWeather and currentWeather.growthMult) or 1
	local growthMult = weatherGrowth * gearGrowthMult(plot) * (petBonuses.growthMult or 1)

	if not crop.ready then
		crop.growth = math.min(seedDef.growSec, crop.growth + dt * growthMult)
		if crop.growth >= seedDef.growSec then
			crop.ready = true
		end
	elseif seedDef.harvestType == "multi" and crop.regrowTimer then
		local regrowMult = (petBonuses.regrowMult or 1) * growthMult
		crop.regrowTimer = math.max(0, crop.regrowTimer - dt * regrowMult)
		if crop.regrowTimer <= 0 then
			crop.ready = true
			crop.regrowTimer = nil
		end
	end

	-- weather mutation rolls (only on growing/ready crops, not on regrow-cooldown)
	if currentWeather and currentWeather.appliesMutation and crop.ready or (currentWeather and currentWeather.appliesMutation and not crop.ready) then
		local mc = (currentWeather.mutationChancePerTick or 0) * (dt / 5) -- approximate per-tick scale
		mc = mc + gearMutationChance(plot) * (dt / 5)
		mc = mc + (petBonuses.mutationChance or 0) * (dt / 5)
		if math.random() < mc then
			crop.mutations = crop.mutations or {}
			crop.mutations[currentWeather.appliesMutation] = true
		end
		if currentWeather.secondaryMutation and math.random() < (currentWeather.secondaryChance or 0) * (dt / 5) then
			crop.mutations = crop.mutations or {}
			crop.mutations[currentWeather.secondaryMutation] = true
		end
	end

	CropService.updateVisual(crop)
end

function GrowthService.start()
	PetService = PetService or require(script.Parent.PetService)
	local last = tick()
	game:GetService("RunService").Heartbeat:Connect(function()
		local now = tick()
		local dt = now - last
		if dt < 0.5 then return end
		last = now

		for uid, plot in pairs(PlotService.all()) do
			local petBonuses = PetService.bonusesFor(uid)
			for key, crop in pairs(plot.crops) do
				tickCrop(plot, key, crop, dt, petBonuses)
			end
		end
	end)
end

return GrowthService
]]></string>
        </Properties>
      </Item>
      <Item class="Script" referent="15">
        <Properties>
          <string name="Name">Main</string>
          <token name="RunContext">0</token>
          <string name="Source"><![CDATA[-- Boot script. Wires every service together and handles join/leave.

local Players = game:GetService("Players")
local RS      = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

local Remotes     = require(RS.Shared.Remotes)
local PlayerData  = require(script.Parent.PlayerData)
local PlotService = require(script.Parent.PlotService)
local PlantingService = require(script.Parent.PlantingService)
local GrowthService   = require(script.Parent.GrowthService)
local ShopService     = require(script.Parent.ShopService)
local WeatherService  = require(script.Parent.WeatherService)
local PetService      = require(script.Parent.PetService)
local SellService     = require(script.Parent.SellService)
local GearService     = require(script.Parent.GearService)

-- spawn baseplate if workspace is empty
if not Workspace:FindFirstChildWhichIsA("BasePart") then
	local baseplate = Instance.new("Part")
	baseplate.Name = "Baseplate"
	baseplate.Anchored = true
	baseplate.Size = Vector3.new(2048, 1, 2048)
	baseplate.Position = Vector3.new(0, -0.5, 0)
	baseplate.Material = Enum.Material.Grass
	baseplate.Color = Color3.fromRGB(80, 140, 70)
	baseplate.Parent = Workspace
end

local function onPlayerAdded(player)
	PlayerData.load(player)
	local plot = PlotService.assign(player)

	player.CharacterAdded:Connect(function(char)
		task.wait(0.25)
		local hrp = char:FindFirstChild("HumanoidRootPart")
		if hrp and plot then
			hrp.CFrame = plot.origin + Vector3.new(0, 5, 8)
		end
	end)

	-- click-to-plant/harvest binding per tile (server-side ClickDetector)
	for _, tile in ipairs(plot.model.Tiles:GetChildren()) do
		local click = tile:FindFirstChildOfClass("ClickDetector")
		if click then
			click.MouseClick:Connect(function(plr)
				if plr ~= player then return end -- only owner can plant on their plot
				local x = tile:GetAttribute("X")
				local z = tile:GetAttribute("Z")
				local key = PlotService.tileKey(x, z)
				local crop = plot.crops[key]
				if crop and crop.ready then
					PlantingService.harvest(player, x, z)
				else
					-- ask client to pick a seed via remote; client UI sends PlantSeed
					Remotes.Notify:FireClient(player, ("Use inventory to plant on (%d,%d)"):format(x, z))
				end
			end)
		end
	end

	PlayerData.push(player)
	Remotes.ShopChanged:FireClient(player, ShopService.state)
	Remotes.WeatherChanged:FireClient(player, {
		id = WeatherService.current.id,
		name = WeatherService.current.def.name,
		endsAt = WeatherService.current.endsAt,
	})
end

Remotes.GetState.OnServerInvoke = function(player)
	return {
		data = PlayerData.get(player),
		shop = ShopService.state,
		weather = {
			id = WeatherService.current.id,
			name = WeatherService.current.def.name,
			endsAt = WeatherService.current.endsAt,
		},
	}
end

Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(function(plr) PlotService.release(plr) end)
for _, p in ipairs(Players:GetPlayers()) do onPlayerAdded(p) end

PlantingService.bind()
SellService.bind()
GearService.bind()
ShopService.start()
WeatherService.start()
PetService.start()
GrowthService.start()

print("[GrowAGarden] Server started.")
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="16">
        <Properties>
          <string name="Name">PetService</string>
          <string name="Source"><![CDATA[local RS      = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Config  = require(RS.Shared.Config)
local Pets    = require(RS.Shared.Pets)
local Remotes = require(RS.Shared.Remotes)
local Seeds   = require(RS.Shared.Seeds)

local PlayerData = require(script.Parent.PlayerData)

local PetService = {}

local function uuid()
	return tostring(os.time()) .. "_" .. tostring(math.random(100000, 999999))
end

function PetService.hatch(player, eggId)
	local data = PlayerData.get(player); if not data then return end
	if (data.eggs[eggId] or 0) <= 0 then return end
	local egg = Pets.Eggs[eggId]; if not egg then return end

	-- (skipping hatch timer — instant for simplicity; add cooldown if you want)
	local petId = Pets.rollFromPool(egg.pool)
	local uid = uuid()
	data.pets[uid] = {
		id = petId, age = 1, hunger = 100,
		mutation = nil, equipped = false,
	}
	PlayerData.addEgg(player, eggId, -1)
	PlayerData.push(player)
	Remotes.Notify:FireClient(player, "Hatched " .. Pets.Defs[petId].name .. "!")
end

function PetService.equip(player, petUid)
	local data = PlayerData.get(player); if not data then return end
	-- max 3 equipped
	local equipCount = 0
	for _, p in pairs(data.pets) do if p.equipped then equipCount += 1 end end
	local pet = data.pets[petUid]; if not pet then return end
	if pet.equipped then
		pet.equipped = false
	else
		if equipCount >= 3 then Remotes.Notify:FireClient(player, "Max 3 equipped"); return end
		pet.equipped = true
	end
	PlayerData.push(player)
end

function PetService.feed(player, petUid)
	local data = PlayerData.get(player); if not data then return end
	local pet = data.pets[petUid]; if not pet then return end
	-- consume one cheapest harvested crop
	if #data.harvest == 0 then Remotes.Notify:FireClient(player, "No food (harvest first)"); return end
	table.remove(data.harvest, 1)
	pet.hunger = math.min(100, pet.hunger + 25)
	PlayerData.push(player)
end

function PetService.bonusesFor(userId)
	local bonuses = { growthMult = 1, mutationChance = 0, regrowMult = 1, sellMult = 1, shopWeight = 1 }
	local data = PlayerData._cache[userId]; if not data then return bonuses end
	for _, p in pairs(data.pets) do
		if p.equipped and p.hunger > 0 then
			local def = Pets.Defs[p.id]; if def then
				if def.traitKind == "growthMult" then
					bonuses.growthMult = bonuses.growthMult * def.value
				elseif def.traitKind == "mutationChance" then
					bonuses.mutationChance = bonuses.mutationChance + def.value
				elseif def.traitKind == "regrowMult" then
					bonuses.regrowMult = bonuses.regrowMult * def.value
				elseif def.traitKind == "sellMult" then
					bonuses.sellMult = bonuses.sellMult * def.value
				elseif def.traitKind == "shopWeight" then
					bonuses.shopWeight = bonuses.shopWeight * def.value
				end
			end
		end
	end
	return bonuses
end

function PetService.start()
	-- hunger/age tick
	task.spawn(function()
		while true do
			task.wait(1)
			for _, plr in ipairs(Players:GetPlayers()) do
				local data = PlayerData.get(plr); if data then
					local changed = false
					for _, p in pairs(data.pets) do
						if p.equipped then
							local before = p.hunger
							p.hunger = math.max(0, p.hunger - Config.PetHungerDrainPerSec)
							if p.hunger > 0 and p.age < Config.PetMaxAge then
								p.age = math.min(Config.PetMaxAge, p.age + Config.PetAgeXpPerSec / 10)
							end
							if math.floor(before) ~= math.floor(p.hunger) then changed = true end
						end
					end
					if changed then PlayerData.push(plr) end
				end
			end
		end
	end)

	Remotes.HatchEgg.OnServerEvent:Connect(PetService.hatch)
	Remotes.EquipPet.OnServerEvent:Connect(PetService.equip)
	Remotes.FeedPet.OnServerEvent:Connect(PetService.feed)
end

return PetService
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="17">
        <Properties>
          <string name="Name">PlantingService</string>
          <string name="Source"><![CDATA[local Players = game:GetService("Players")
local RS      = game:GetService("ReplicatedStorage")
local Remotes = require(RS.Shared.Remotes)
local Seeds   = require(RS.Shared.Seeds)

local PlayerData  = require(script.Parent.PlayerData)
local PlotService = require(script.Parent.PlotService)
local CropService = require(script.Parent.CropService)

local PlantingService = {}

local function notify(player, msg)
	Remotes.Notify:FireClient(player, msg)
end

function PlantingService.plant(player, seedId, x, z)
	local data = PlayerData.get(player); if not data then return end
	local plot = PlotService.get(player); if not plot then return end
	if not Seeds.get(seedId) then return end
	if (data.seeds[seedId] or 0) <= 0 then notify(player, "No seeds of that kind."); return end

	x = math.floor(x); z = math.floor(z)
	if x < 1 or z < 1 or x > plot.size or z > plot.size then return end

	local key = PlotService.tileKey(x, z)
	if plot.crops[key] then notify(player, "Tile occupied."); return end

	local crop = {
		seedId = seedId,
		growth = 0,
		ready = false,
		mutations = {},
		x = x, z = z,
	}
	local model = CropService.spawnVisual(plot, x, z, seedId)
	crop.visual = model

	plot.crops[key] = crop
	PlayerData.addSeed(player, seedId, -1)
end

function PlantingService.harvest(player, x, z)
	local plot = PlotService.get(player); if not plot then return end
	local key = PlotService.tileKey(x, z)
	local crop = plot.crops[key]
	if not crop or not crop.ready then return end

	local seedDef = Seeds.get(crop.seedId)
	local value = CropService.computeValue(crop, 1)

	PlayerData.addHarvest(player, {
		seedId = crop.seedId,
		value = value,
		mutations = crop.mutations,
		ts = os.time(),
	})

	if seedDef.harvestType == "multi" then
		crop.ready = false
		crop.regrowTimer = seedDef.regrowSec
		crop.mutations = {}
		CropService.updateVisual(crop)
	else
		CropService.removeVisual(crop)
		plot.crops[key] = nil
	end
end

function PlantingService.bind()
	Remotes.PlantSeed.OnServerEvent:Connect(function(plr, seedId, x, z)
		PlantingService.plant(plr, seedId, x, z)
	end)
	Remotes.HarvestCrop.OnServerEvent:Connect(function(plr, x, z)
		PlantingService.harvest(plr, x, z)
	end)
end

return PlantingService
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="18">
        <Properties>
          <string name="Name">PlayerData</string>
          <string name="Source"><![CDATA[-- DataStore-backed player profile. Holds currency, inventory, pets, gear.

local DataStoreService = game:GetService("DataStoreService")
local Players          = game:GetService("Players")
local RS               = game:GetService("ReplicatedStorage")

local Config  = require(RS.Shared.Config)
local Remotes = require(RS.Shared.Remotes)

local Store = DataStoreService:GetDataStore("GAG_PlayerData_v1")

local PlayerData = {}
PlayerData._cache = {}    -- [userId] = profile

local function newProfile()
	return {
		sheckles = Config.StartingSheckles,
		seeds    = {},        -- [seedId] = count
		eggs     = {},        -- [eggId]  = count
		pets     = {},        -- [petUid] = { id, age, hunger, mutation, equipped }
		gear     = {},        -- [gearId] = count (owned, unplaced)
		harvest  = {},        -- inventory of harvested crops { seedId, value, mutations }
		stats    = { totalHarvested = 0, totalSold = 0 },
	}
end

function PlayerData.get(player)
	return PlayerData._cache[player.UserId]
end

function PlayerData.load(player)
	local key = "u_" .. player.UserId
	local ok, data = pcall(function() return Store:GetAsync(key) end)
	if not ok or type(data) ~= "table" then data = newProfile() end
	-- migrate missing fields
	local def = newProfile()
	for k, v in pairs(def) do if data[k] == nil then data[k] = v end end
	PlayerData._cache[player.UserId] = data
	return data
end

function PlayerData.save(player)
	local data = PlayerData._cache[player.UserId]
	if not data then return end
	local key = "u_" .. player.UserId
	pcall(function() Store:SetAsync(key, data) end)
end

function PlayerData.unload(player)
	PlayerData.save(player)
	PlayerData._cache[player.UserId] = nil
end

function PlayerData.push(player)
	local data = PlayerData._cache[player.UserId]
	if data then Remotes.DataChanged:FireClient(player, data) end
end

function PlayerData.addSheckles(player, amount)
	local d = PlayerData.get(player); if not d then return end
	d.sheckles = math.max(0, d.sheckles + amount)
	PlayerData.push(player)
end

function PlayerData.spend(player, amount)
	local d = PlayerData.get(player); if not d then return false end
	if d.sheckles < amount then return false end
	d.sheckles -= amount
	PlayerData.push(player)
	return true
end

function PlayerData.addSeed(player, seedId, n)
	local d = PlayerData.get(player); if not d then return end
	d.seeds[seedId] = (d.seeds[seedId] or 0) + n
	if d.seeds[seedId] <= 0 then d.seeds[seedId] = nil end
	PlayerData.push(player)
end

function PlayerData.addEgg(player, eggId, n)
	local d = PlayerData.get(player); if not d then return end
	d.eggs[eggId] = (d.eggs[eggId] or 0) + n
	if d.eggs[eggId] <= 0 then d.eggs[eggId] = nil end
	PlayerData.push(player)
end

function PlayerData.addHarvest(player, entry)
	local d = PlayerData.get(player); if not d then return end
	table.insert(d.harvest, entry)
	d.stats.totalHarvested += 1
	PlayerData.push(player)
end

-- autosave loop
task.spawn(function()
	while true do
		task.wait(Config.AutoSaveSeconds)
		for _, plr in ipairs(Players:GetPlayers()) do
			PlayerData.save(plr)
		end
	end
end)

Players.PlayerRemoving:Connect(function(plr) PlayerData.unload(plr) end)
game:BindToClose(function()
	for _, plr in ipairs(Players:GetPlayers()) do PlayerData.save(plr) end
end)

return PlayerData
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="19">
        <Properties>
          <string name="Name">PlotService</string>
          <string name="Source"><![CDATA[-- Owns plot models and crop state. One plot per player.

local RS        = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local Config    = require(RS.Shared.Config)

local PlotService = {}
PlotService._plots = {}    -- [userId] = plotState

local plotsFolder = Workspace:FindFirstChild("Plots") or Instance.new("Folder")
plotsFolder.Name = "Plots"
plotsFolder.Parent = Workspace

local function buildPlotModel(originCFrame)
	local model = Instance.new("Model")
	model.Name = "Plot"
	local size = Config.PlotSize
	local stud = Config.TileStud

	local base = Instance.new("Part")
	base.Anchored = true
	base.Size = Vector3.new(size * stud + 2, 1, size * stud + 2)
	base.Material = Enum.Material.Grass
	base.Color = Color3.fromRGB(80, 140, 70)
	base.CFrame = originCFrame
	base.Parent = model
	model.PrimaryPart = base

	local tiles = Instance.new("Folder")
	tiles.Name = "Tiles"
	tiles.Parent = model

	for x = 1, size do
		for z = 1, size do
			local tile = Instance.new("Part")
			tile.Anchored = true
			tile.Size = Vector3.new(stud - 0.2, 0.2, stud - 0.2)
			tile.Material = Enum.Material.Ground
			tile.Color = Color3.fromRGB(110, 80, 50)
			local offX = (x - (size + 1) / 2) * stud
			local offZ = (z - (size + 1) / 2) * stud
			tile.CFrame = originCFrame * CFrame.new(offX, 0.6, offZ)
			tile.Name = ("Tile_%d_%d"):format(x, z)
			tile:SetAttribute("X", x)
			tile:SetAttribute("Z", z)
			tile.Parent = tiles

			local click = Instance.new("ClickDetector")
			click.MaxActivationDistance = 64
			click.Parent = tile
		end
	end

	model.Parent = plotsFolder
	return model
end

function PlotService.assign(player)
	local index = #plotsFolder:GetChildren()
	local origin = CFrame.new(index * 60, 1, 0)
	local model = buildPlotModel(origin)
	model.Name = "Plot_" .. player.UserId
	model:SetAttribute("OwnerUserId", player.UserId)

	local state = {
		owner = player.UserId,
		model = model,
		origin = origin,
		size = Config.PlotSize,
		crops = {},   -- [ "x_z" ] = crop
		gear  = {},   -- [ "x_z" ] = { id = gearId }
	}
	PlotService._plots[player.UserId] = state
	return state
end

function PlotService.release(player)
	local p = PlotService._plots[player.UserId]
	if not p then return end
	if p.model then p.model:Destroy() end
	PlotService._plots[player.UserId] = nil
end

function PlotService.get(player) return PlotService._plots[player.UserId] end
function PlotService.getByUserId(uid) return PlotService._plots[uid] end
function PlotService.all() return PlotService._plots end

function PlotService.tileKey(x, z) return ("%d_%d"):format(x, z) end

function PlotService.findTile(plot, x, z)
	local tiles = plot.model:FindFirstChild("Tiles")
	return tiles and tiles:FindFirstChild(("Tile_%d_%d"):format(x, z))
end

return PlotService
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="20">
        <Properties>
          <string name="Name">SellService</string>
          <string name="Source"><![CDATA[local RS      = game:GetService("ReplicatedStorage")
local Remotes = require(RS.Shared.Remotes)

local PlayerData = require(script.Parent.PlayerData)
local PetService = require(script.Parent.PetService)

local SellService = {}

function SellService.sellAll(player)
	local data = PlayerData.get(player); if not data then return end
	local bonuses = PetService.bonusesFor(player.UserId)
	local total = 0
	for _, h in ipairs(data.harvest) do
		total = total + math.floor((h.value or 0) * (bonuses.sellMult or 1))
	end
	data.harvest = {}
	data.stats.totalSold = (data.stats.totalSold or 0) + total
	data.sheckles = data.sheckles + total
	PlayerData.push(player)
	Remotes.Notify:FireClient(player, ("Sold for %d sheckles"):format(total))
end

function SellService.bind()
	Remotes.SellAll.OnServerEvent:Connect(SellService.sellAll)
end

return SellService
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="21">
        <Properties>
          <string name="Name">ShopService</string>
          <string name="Source"><![CDATA[-- Rotating seed shop, plus pet egg and gear shops (fixed stock).

local RS      = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Config  = require(RS.Shared.Config)
local Seeds   = require(RS.Shared.Seeds)
local Pets    = require(RS.Shared.Pets)
local Gear    = require(RS.Shared.Gear)
local Remotes = require(RS.Shared.Remotes)

local PlayerData = require(script.Parent.PlayerData)

local ShopService = {}

ShopService.state = {
	seeds = {},   -- { { id, stock } }
	eggs  = {},
	gear  = {},
	nextRefresh = 0,
}

local function rollSeedStock()
	local picks = {}
	local list = Seeds.list()
	-- weighted with replacement; cap stock per pick
	local slots = 6
	for _ = 1, slots do
		local total = 0
		for _, e in ipairs(list) do total = total + (e.def.shopWeight or 0) end
		local r = math.random() * total
		local acc = 0
		for _, e in ipairs(list) do
			acc = acc + (e.def.shopWeight or 0)
			if r <= acc then
				local rarity = e.def.rarity
				local stock = (rarity == "Mythic") and 1
					or (rarity == "Legendary") and 2
					or (rarity == "Rare") and 3
					or 5
				table.insert(picks, { id = e.id, stock = stock })
				break
			end
		end
	end
	return picks
end

function ShopService.refresh()
	ShopService.state.seeds = rollSeedStock()
	ShopService.state.eggs = {
		{ id = "CommonEgg", stock = 99 },
		{ id = "RareEgg",   stock = 10 },
		{ id = "LegendaryEgg", stock = 2 },
	}
	ShopService.state.gear = {
		{ id = "WateringCan", stock = 99 },
		{ id = "BasicSprinkler", stock = 20 },
		{ id = "AdvancedSprinkler", stock = 5 },
		{ id = "GodlySprinkler", stock = 1 },
		{ id = "GoldenWateringCan", stock = 1 },
	}
	ShopService.state.nextRefresh = os.time() + Config.ShopRefreshSeconds
	for _, plr in ipairs(Players:GetPlayers()) do
		Remotes.ShopChanged:FireClient(plr, ShopService.state)
	end
end

function ShopService.buySeed(player, seedId)
	local def = Seeds.get(seedId); if not def then return end
	local row
	for _, r in ipairs(ShopService.state.seeds) do
		if r.id == seedId and r.stock > 0 then row = r; break end
	end
	if not row then Remotes.Notify:FireClient(player, "Out of stock"); return end
	if not PlayerData.spend(player, def.price) then
		Remotes.Notify:FireClient(player, "Not enough sheckles"); return
	end
	row.stock -= 1
	PlayerData.addSeed(player, seedId, 1)
	Remotes.ShopChanged:FireClient(player, ShopService.state)
end

function ShopService.buyEgg(player, eggId)
	local def = Pets.Eggs[eggId]; if not def then return end
	if not PlayerData.spend(player, def.price) then
		Remotes.Notify:FireClient(player, "Not enough sheckles"); return
	end
	PlayerData.addEgg(player, eggId, 1)
end

function ShopService.buyGear(player, gearId)
	local def = Gear[gearId]; if not def then return end
	if not PlayerData.spend(player, def.price) then
		Remotes.Notify:FireClient(player, "Not enough sheckles"); return
	end
	local data = PlayerData.get(player)
	data.gear[gearId] = (data.gear[gearId] or 0) + 1
	PlayerData.push(player)
end

function ShopService.start()
	ShopService.refresh()
	task.spawn(function()
		while true do
			task.wait(Config.ShopRefreshSeconds)
			ShopService.refresh()
		end
	end)
	Remotes.BuySeed.OnServerEvent:Connect(ShopService.buySeed)
	Remotes.BuyEgg.OnServerEvent:Connect(ShopService.buyEgg)
	Remotes.BuyGear.OnServerEvent:Connect(ShopService.buyGear)
end

return ShopService
]]></string>
        </Properties>
      </Item>
      <Item class="ModuleScript" referent="22">
        <Properties>
          <string name="Name">WeatherService</string>
          <string name="Source"><![CDATA[local RS      = game:GetService("ReplicatedStorage")
local Lighting= game:GetService("Lighting")
local Players = game:GetService("Players")
local Config  = require(RS.Shared.Config)
local Weather = require(RS.Shared.Weather)
local Remotes = require(RS.Shared.Remotes)

local GrowthService = require(script.Parent.GrowthService)

local WeatherService = {}
WeatherService.current = { id = "Clear", def = Weather.Clear, endsAt = os.time() + 60 }

local function applyAtmosphere(id)
	-- Simple lighting tints — pretty enough until you swap in real VFX.
	local color = ColorSequenceKeypoint
	if id == "Rain" or id == "Thunderstorm" then
		Lighting.ClockTime = 14; Lighting.FogEnd = 200; Lighting.OutdoorAmbient = Color3.fromRGB(80, 90, 110)
	elseif id == "Snow" or id == "Blizzard" then
		Lighting.ClockTime = 12; Lighting.FogEnd = 150; Lighting.OutdoorAmbient = Color3.fromRGB(160, 170, 200)
	elseif id == "Heatwave" then
		Lighting.ClockTime = 13; Lighting.FogEnd = 400; Lighting.OutdoorAmbient = Color3.fromRGB(220, 170, 110)
	elseif id == "Moonlight" then
		Lighting.ClockTime = 0;  Lighting.FogEnd = 300; Lighting.OutdoorAmbient = Color3.fromRGB(120, 120, 180)
	elseif id == "BloodMoon" then
		Lighting.ClockTime = 0;  Lighting.FogEnd = 200; Lighting.OutdoorAmbient = Color3.fromRGB(120, 30, 30)
	elseif id == "MeteorShower" then
		Lighting.ClockTime = 1;  Lighting.FogEnd = 250; Lighting.OutdoorAmbient = Color3.fromRGB(140, 120, 180)
	elseif id == "SolarEclipse" then
		Lighting.ClockTime = 12; Lighting.FogEnd = 250; Lighting.OutdoorAmbient = Color3.fromRGB(40, 40, 60)
	elseif id == "Disco" then
		Lighting.ClockTime = 22; Lighting.FogEnd = 300; Lighting.OutdoorAmbient = Color3.fromRGB(200, 100, 220)
	else
		Lighting.ClockTime = 12; Lighting.FogEnd = 1000; Lighting.OutdoorAmbient = Color3.fromRGB(140, 140, 140)
	end
end

function WeatherService.broadcast()
	for _, plr in ipairs(Players:GetPlayers()) do
		Remotes.WeatherChanged:FireClient(plr, {
			id = WeatherService.current.id,
			name = WeatherService.current.def.name,
			endsAt = WeatherService.current.endsAt,
		})
	end
end

function WeatherService.start()
	task.spawn(function()
		while true do
			local id, def = Weather.roll()
			WeatherService.current = {
				id = id, def = def,
				endsAt = os.time() + def.duration,
			}
			GrowthService.setWeather(def)
			applyAtmosphere(id)
			WeatherService.broadcast()
			task.wait(def.duration)
		end
	end)
end

return WeatherService
]]></string>
        </Properties>
      </Item>
    </Item>
  </Item>
  <Item class="StarterPlayer" referent="23">
    <Properties>
      <string name="Name">StarterPlayer</string>
    </Properties>
    <Item class="StarterPlayerScripts" referent="24">
      <Properties>
        <string name="Name">StarterPlayerScripts</string>
      </Properties>
      <Item class="Folder" referent="25">
        <Properties>
          <string name="Name">Client</string>
        </Properties>
        <Item class="LocalScript" referent="26">
          <Properties>
            <string name="Name">Main</string>
            <string name="Source"><![CDATA[-- Minimal but functional client UI for Grow a Garden.

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

local Remotes = require(RS.Shared.Remotes)
local Seeds   = require(RS.Shared.Seeds)
local Pets    = require(RS.Shared.Pets)
local Gear    = require(RS.Shared.Gear)

local player = Players.LocalPlayer
local pg = player:WaitForChild("PlayerGui")

local state = { data = nil, shop = nil, weather = nil, selectedSeed = nil }

----------------------------------------------------------------
-- UI scaffolding
----------------------------------------------------------------
local screen = Instance.new("ScreenGui")
screen.Name = "GrowAGardenUI"
screen.ResetOnSpawn = false
screen.IgnoreGuiInset = true
screen.Parent = pg

local function makeFrame(name, size, pos, color)
	local f = Instance.new("Frame")
	f.Name = name
	f.Size = size
	f.Position = pos
	f.BackgroundColor3 = color or Color3.fromRGB(30,30,40)
	f.BackgroundTransparency = 0.1
	f.BorderSizePixel = 0
	f.Parent = screen
	local corner = Instance.new("UICorner", f); corner.CornerRadius = UDim.new(0, 8)
	return f
end

local function makeLabel(parent, text, size, pos)
	local l = Instance.new("TextLabel")
	l.Size = size
	l.Position = pos
	l.BackgroundTransparency = 1
	l.TextColor3 = Color3.new(1,1,1)
	l.Font = Enum.Font.GothamBold
	l.TextScaled = true
	l.Text = text
	l.Parent = parent
	return l
end

local function makeButton(parent, text, size, pos, color)
	local b = Instance.new("TextButton")
	b.Size = size
	b.Position = pos
	b.BackgroundColor3 = color or Color3.fromRGB(70,130,180)
	b.BorderSizePixel = 0
	b.TextColor3 = Color3.new(1,1,1)
	b.Font = Enum.Font.GothamBold
	b.TextScaled = true
	b.Text = text
	b.Parent = parent
	local c = Instance.new("UICorner", b); c.CornerRadius = UDim.new(0,6)
	return b
end

-- top bar
local topBar = makeFrame("TopBar", UDim2.new(1,0,0,50), UDim2.new(0,0,0,0), Color3.fromRGB(20,25,35))
local moneyLabel = makeLabel(topBar, "Sheckles: 0", UDim2.new(0,250,1,-10), UDim2.new(0,10,0,5))
moneyLabel.TextXAlignment = Enum.TextXAlignment.Left
local weatherLabel = makeLabel(topBar, "Weather: ...", UDim2.new(0,400,1,-10), UDim2.new(0.5,-200,0,5))
local sellBtn = makeButton(topBar, "Sell All", UDim2.new(0,120,0,36), UDim2.new(1,-130,0,7), Color3.fromRGB(220,150,50))
sellBtn.MouseButton1Click:Connect(function() Remotes.SellAll:FireServer() end)

-- toast
local toast = makeLabel(screen, "", UDim2.new(0,500,0,40), UDim2.new(0.5,-250,0,60))
toast.TextColor3 = Color3.fromRGB(255,230,120)
toast.BackgroundTransparency = 1
toast.Visible = false

local function showToast(msg)
	toast.Text = msg
	toast.Visible = true
	task.delay(2.5, function() toast.Visible = false end)
end

Remotes.Notify.OnClientEvent:Connect(showToast)

----------------------------------------------------------------
-- Inventory + shop panel (left)
----------------------------------------------------------------
local panel = makeFrame("Panel", UDim2.new(0,340,1,-60), UDim2.new(0,10,0,55))
local tabs = makeFrame("Tabs", UDim2.new(1,-20,0,32), UDim2.new(0,10,0,8), Color3.fromRGB(0,0,0))
tabs.BackgroundTransparency = 1
local list = Instance.new("ScrollingFrame")
list.Size = UDim2.new(1,-20,1,-50)
list.Position = UDim2.new(0,10,0,46)
list.BackgroundTransparency = 1
list.BorderSizePixel = 0
list.CanvasSize = UDim2.new(0,0,0,0)
list.AutomaticCanvasSize = Enum.AutomaticSize.Y
list.ScrollBarThickness = 4
list.Parent = panel
local layout = Instance.new("UIListLayout", list)
layout.Padding = UDim.new(0, 4)

local currentTab = "Seeds"  -- Seeds | Shop | Pets | Gear

local tabBtns = {}
local function rebuildTabs()
	for _, t in ipairs(tabs:GetChildren()) do if t:IsA("GuiObject") then t:Destroy() end end
	local names = { "Seeds", "Shop", "Pets", "Gear" }
	for i, n in ipairs(names) do
		local b = makeButton(tabs, n,
			UDim2.new(0.25, -4, 1, 0),
			UDim2.new((i-1)*0.25, 2, 0, 0),
			currentTab == n and Color3.fromRGB(80,170,90) or Color3.fromRGB(60,60,80))
		b.MouseButton1Click:Connect(function()
			currentTab = n
			rebuildTabs()
			refreshList()
		end)
		tabBtns[n] = b
	end
end

----------------------------------------------------------------
-- List rebuilders
----------------------------------------------------------------
local function clearList()
	for _, c in ipairs(list:GetChildren()) do
		if c:IsA("GuiObject") then c:Destroy() end
	end
end

local function addRow(text, btnText, btnColor, onClick)
	local row = Instance.new("Frame")
	row.Size = UDim2.new(1, -4, 0, 44)
	row.BackgroundColor3 = Color3.fromRGB(45, 45, 60)
	row.BorderSizePixel = 0
	row.Parent = list
	local c = Instance.new("UICorner", row); c.CornerRadius = UDim.new(0,6)

	local lbl = makeLabel(row, text, UDim2.new(1,-100,1,-8), UDim2.new(0,8,0,4))
	lbl.TextXAlignment = Enum.TextXAlignment.Left

	if btnText then
		local b = makeButton(row, btnText, UDim2.new(0,84,0,32), UDim2.new(1,-92,0,6), btnColor)
		b.MouseButton1Click:Connect(onClick)
	end
	return row
end

function refreshList()
	clearList()
	if not state.data then return end

	if currentTab == "Seeds" then
		for seedId, count in pairs(state.data.seeds) do
			local def = Seeds.get(seedId); if def then
				local picked = state.selectedSeed == seedId and " [SELECTED]" or ""
				addRow(("%s x%d%s"):format(def.name, count, picked), "Select", Color3.fromRGB(80,170,90), function()
					state.selectedSeed = seedId
					refreshList()
					showToast("Selected " .. def.name .. ". Press F over a tile to plant.")
				end)
			end
		end
		if next(state.data.seeds) == nil then
			addRow("No seeds yet. Open Shop tab.", nil)
		end

	elseif currentTab == "Shop" then
		if state.shop then
			for _, r in ipairs(state.shop.seeds) do
				local def = Seeds.get(r.id); if def then
					addRow(("%s — %d¢ (stock %d)"):format(def.name, def.price, r.stock),
						"Buy", Color3.fromRGB(70,130,200), function()
							Remotes.BuySeed:FireServer(r.id)
						end)
				end
			end
			for _, r in ipairs(state.shop.eggs or {}) do
				local def = Pets.Eggs[r.id]
				if def then
					addRow(("Egg: %s — %d¢"):format(def.name, def.price),
						"Buy", Color3.fromRGB(200,130,200), function()
							Remotes.BuyEgg:FireServer(r.id)
						end)
				end
			end
			for _, r in ipairs(state.shop.gear or {}) do
				local def = Gear[r.id]
				if def then
					addRow(("Gear: %s — %d¢"):format(def.name, def.price),
						"Buy", Color3.fromRGB(130,180,200), function()
							Remotes.BuyGear:FireServer(r.id)
						end)
				end
			end
		end

	elseif currentTab == "Pets" then
		for eggId, n in pairs(state.data.eggs) do
			local def = Pets.Eggs[eggId]; if def then
				addRow(("Egg: %s x%d"):format(def.name, n), "Hatch",
					Color3.fromRGB(200,130,200), function()
						Remotes.HatchEgg:FireServer(eggId)
					end)
			end
		end
		for uid, pet in pairs(state.data.pets) do
			local def = Pets.Defs[pet.id]; if def then
				addRow(("%s | age %d | hunger %d %s"):format(def.name,
						math.floor(pet.age), math.floor(pet.hunger),
						pet.equipped and "[E]" or ""),
					pet.equipped and "Unequip" or "Equip",
					Color3.fromRGB(80,170,90), function()
						Remotes.EquipPet:FireServer(uid)
					end)
			end
		end

	elseif currentTab == "Gear" then
		for gearId, n in pairs(state.data.gear) do
			local def = Gear[gearId]; if def then
				addRow(("%s x%d"):format(def.name, n), "Place",
					Color3.fromRGB(130,180,200), function()
						state.selectedGear = gearId
						showToast("Selected " .. def.name .. ". Press G over a tile to place.")
					end)
			end
		end
	end
end

----------------------------------------------------------------
-- Keypress: F to plant, G to place gear (uses mouse target tile)
----------------------------------------------------------------
local mouse = player:GetMouse()
local function tileUnderMouse()
	local t = mouse.Target
	if not t then return nil end
	if t.Name:sub(1,5) ~= "Tile_" then return nil end
	return t, t:GetAttribute("X"), t:GetAttribute("Z")
end

UIS.InputBegan:Connect(function(input, gpe)
	if gpe then return end
	if input.KeyCode == Enum.KeyCode.F then
		if not state.selectedSeed then showToast("Select a seed first."); return end
		local _, x, z = tileUnderMouse()
		if x then Remotes.PlantSeed:FireServer(state.selectedSeed, x, z) end
	elseif input.KeyCode == Enum.KeyCode.G then
		if not state.selectedGear then showToast("Select gear first."); return end
		local _, x, z = tileUnderMouse()
		if x then Remotes.PlaceGear:FireServer(state.selectedGear, x, z) end
	end
end)

----------------------------------------------------------------
-- Wiring
----------------------------------------------------------------
local function refreshHud()
	if state.data then
		moneyLabel.Text = ("Sheckles: %d  |  Harvest: %d"):format(state.data.sheckles, #state.data.harvest)
	end
	if state.weather then
		weatherLabel.Text = ("Weather: %s  (ends in %ds)"):format(state.weather.name, math.max(0, state.weather.endsAt - os.time()))
	end
end

Remotes.DataChanged.OnClientEvent:Connect(function(data)
	state.data = data
	refreshHud(); refreshList()
end)
Remotes.ShopChanged.OnClientEvent:Connect(function(shop)
	state.shop = shop
	refreshList()
end)
Remotes.WeatherChanged.OnClientEvent:Connect(function(w)
	state.weather = w
	refreshHud()
end)

-- initial pull
local s = Remotes.GetState:InvokeServer()
state.data = s.data; state.shop = s.shop; state.weather = s.weather
rebuildTabs()
refreshHud()
refreshList()

task.spawn(function()
	while true do task.wait(1); refreshHud() end
end)

print("[GrowAGarden] Client UI ready.")
]]></string>
          </Properties>
        </Item>
      </Item>
    </Item>
  </Item>
  <Item class="Workspace" referent="27">
    <Properties>
      <string name="Name">Workspace</string>
      <bool name="FilteringEnabled">true</bool>
      <bool name="NeedsPivotMigration">false</bool>
    </Properties>
  </Item>
</roblox>