diff --git a/docs.json b/docs.json
index c544ab6..1f19859 100644
--- a/docs.json
+++ b/docs.json
@@ -100,7 +100,10 @@
"rayfield-gen2/elements/inputs",
"rayfield-gen2/elements/keybinds",
"rayfield-gen2/elements/color-pickers",
- "rayfield-gen2/elements/stats"
+ "rayfield-gen2/elements/stats",
+ "rayfield-gen2/elements/labels",
+ "rayfield-gen2/elements/paragraphs",
+ "rayfield-gen2/elements/dividers"
]
},
{
diff --git a/images/gen2/colorpicker-gradient.png b/images/gen2/colorpicker-gradient.png
new file mode 100644
index 0000000..fa490ae
Binary files /dev/null and b/images/gen2/colorpicker-gradient.png differ
diff --git a/images/gen2/divider.png b/images/gen2/divider.png
new file mode 100644
index 0000000..b4f8914
Binary files /dev/null and b/images/gen2/divider.png differ
diff --git a/images/gen2/text.png b/images/gen2/text.png
new file mode 100644
index 0000000..30190d4
Binary files /dev/null and b/images/gen2/text.png differ
diff --git a/rayfield-gen2/elements/color-pickers.mdx b/rayfield-gen2/elements/color-pickers.mdx
index e6b5871..71502c5 100644
--- a/rayfield-gen2/elements/color-pickers.mdx
+++ b/rayfield-gen2/elements/color-pickers.mdx
@@ -28,6 +28,48 @@ tab:CreateColorPicker({
Color pickers are full-width and live at the tab level. Create them directly on the tab, not inside a group.
+## Gradients
+
+Pass `gradient = true` and the picker edits a `ColorSequence` instead of one colour. A bar above the map holds the stops. Click the bar to add one where you clicked, drag a stop to move it, and the button beside the bar removes the one you have selected. The two ends stay put.
+
+
+
+
+
+```lua
+tab:CreateColorPicker({
+ name = "Trail",
+ gradient = true,
+ color = ColorSequence.new({
+ ColorSequenceKeypoint.new(0, Color3.fromRGB(255, 0, 128)),
+ ColorSequenceKeypoint.new(1, Color3.fromRGB(0, 200, 255)),
+ }),
+ callback = function(sequence, alpha)
+ trail.Color = sequence
+ end,
+})
+```
+
+The map and bars below edit whichever stop is selected, so the picker works the same way once you have one in hand.
+
+
+ Edit a `ColorSequence` rather than a single colour.
+
+
+A gradient picker hands its callback a `ColorSequence`, and `GetValue()` returns the same. `.value` is still the colour of the stop being edited, so read `GetValue()` when you want the whole thing.
+
+```lua
+local picker = tab:CreateColorPicker({ name = "Trail", gradient = true })
+
+picker:GetValue() -- ColorSequence
+picker.value -- Color3, the selected stop
+
+picker:Set(ColorSequence.new(Color3.new(1, 0, 0), Color3.new(0, 0, 1))) -- the whole gradient
+picker:Set(Color3.fromRGB(255, 255, 0)) -- just the selected stop
+```
+
+A single colour passed as `color` is taken as a flat gradient of it, which is also how a configuration saved before the picker was a gradient loads back in.
+
## Properties
diff --git a/rayfield-gen2/elements/dividers.mdx b/rayfield-gen2/elements/dividers.mdx
new file mode 100644
index 0000000..9c2cdfe
--- /dev/null
+++ b/rayfield-gen2/elements/dividers.mdx
@@ -0,0 +1,64 @@
+---
+title: Divider
+description: A rule across the page, or room with nothing in it.
+icon: minus
+---
+
+import SentivelBanner from '/snippets/sentivel-banner.mdx'
+
+
+
+A divider breaks a tab into parts. It draws a rule across the page, with an optional word in the middle of it.
+
+
+
+
+
+```lua
+tab:CreateToggle({ name = "Auto Sprint" })
+tab:CreateToggle({ name = "Reduced Motion" })
+
+tab:CreateDivider({ text = "Advanced" })
+
+tab:CreateToggle({ name = "Verbose Logging" })
+```
+
+Pass `line = false` for room and no rule, when two groups need separating but a line would be too much.
+
+```lua
+tab:CreateDivider({ line = false, spacing = 24 })
+```
+
+
+ A divider holds no value, so it never saves and takes no `flag`. For a heading with a name that belongs to what follows it, use a [section](/rayfield-gen2/elements/overview).
+
+
+## Properties
+
+
+ A word in the middle of the rule. Optional. Without one the rule runs the full width.
+
+
+
+ Room above and below, in pixels.
+
+
+
+ `false` draws no rule, leaving only the room.
+
+
+## Handle
+
+
+ The current word, or an empty string.
+
+
+
+ Replace the word. Pass an empty string to clear it, and the rule closes back up into a plain one.
+
+
+```lua
+local stage = tab:CreateDivider({ text = "Loading" })
+stage:Set("Ready")
+stage:Set("") -- back to a plain rule
+```
diff --git a/rayfield-gen2/elements/labels.mdx b/rayfield-gen2/elements/labels.mdx
new file mode 100644
index 0000000..e6769c1
--- /dev/null
+++ b/rayfield-gen2/elements/labels.mdx
@@ -0,0 +1,48 @@
+---
+title: Label
+description: A line of text with no control on it.
+icon: type
+---
+
+import SentivelBanner from '/snippets/sentivel-banner.mdx'
+
+
+
+A label is one line of text on a card. Nothing to press, nothing to read back. Use it for a note beside the controls it belongs to.
+
+
+
+
+
+```lua
+local status = tab:CreateLabel({
+ text = "Waiting for a match",
+ icon = 93364949241311,
+})
+
+status:Set("In a match")
+```
+
+
+ A label holds no value, so it never saves and takes no `flag`. For a heading over a run of elements, use a [section](/rayfield-gen2/elements/overview). For a title with body text under it, use a [paragraph](/rayfield-gen2/elements/paragraphs).
+
+
+## Properties
+
+
+ The line of text. `name` works in its place.
+
+
+
+ An icon shown beside the text. Optional.
+
+
+## Handle
+
+
+ The current text.
+
+
+
+ Replace the text. The card grows or shrinks to fit it.
+
diff --git a/rayfield-gen2/elements/paragraphs.mdx b/rayfield-gen2/elements/paragraphs.mdx
new file mode 100644
index 0000000..1c0c2aa
--- /dev/null
+++ b/rayfield-gen2/elements/paragraphs.mdx
@@ -0,0 +1,68 @@
+---
+title: Paragraph
+description: A title with body text under it.
+icon: text-align-left
+---
+
+import SentivelBanner from '/snippets/sentivel-banner.mdx'
+
+
+
+A paragraph is a title with body text under it, on one card. The body wraps and the card grows to fit. Use it for anything too long to sit on a label.
+
+
+
+
+
+```lua
+local notes = tab:CreateParagraph({
+ name = "Aimbot",
+ text = "Holds on the closest visible player. Walls block it, so it drops the target when they go behind cover.",
+ icon = 93364949241311,
+})
+
+notes:Set("Now tracking the lowest health target instead.")
+```
+
+Either half can be left out. A paragraph with no `name` is body text on its own, and one with no `text` is a title on its own.
+
+```lua
+tab:CreateParagraph({ text = "Read this before you turn anything on." })
+```
+
+
+ A paragraph holds no value, so it never saves and takes no `flag`.
+
+
+## Properties
+
+
+ The title. Optional.
+
+
+
+ The body under the title. Optional.
+
+
+
+ An icon shown beside the title. Optional.
+
+
+## Handle
+
+
+ The current title.
+
+
+
+ The current body.
+
+
+
+ Replace the body, and the title as well if you pass one. Pass `nil` for the body to leave it as it is.
+
+
+```lua
+notes:Set(nil, "Aimbot (legit)") -- title only
+notes:Set("Smoothing raised to 0.4") -- body only
+```
diff --git a/rayfield-gen2/windows.mdx b/rayfield-gen2/windows.mdx
index ec79b24..527766a 100644
--- a/rayfield-gen2/windows.mdx
+++ b/rayfield-gen2/windows.mdx
@@ -141,6 +141,20 @@ The window handle exposes everything you need at runtime.
`true` once `Unload` has run. The handle is spent after that.
+### Resizing
+
+The window carries a grip in its bottom-right corner. Drag it to resize, and double-click it to drop back to the size the window fits itself to. A hand-picked size is held through screen changes, down to whatever the screen can carry, and comes back in full once there is room for it again.
+
+Players can turn the grip off under **Resizable window** in the settings tab, and hold its mark still under **Animated resize grip**. Both persist with the rest of the window's settings.
+
+
+ Show or hide the grip from code. Same setting the tab toggles.
+
+
+```lua
+window.resize:SetEnabled(false) -- no grip, no resizing
+```
+
### Theme and language