diff --git a/se5-plugins.json b/se5-plugins.json
new file mode 100644
index 0000000..f75e10c
--- /dev/null
+++ b/se5-plugins.json
@@ -0,0 +1,14 @@
+{
+ "plugins": [
+ {
+ "name": "Haxor",
+ "description": "Translates the text of the selected lines (or all lines) to haxor.",
+ "version": "1.0.0",
+ "author": "Subtitle Edit",
+ "url": "https://github.com/SubtitleEdit/plugins/tree/master/se5/Haxor",
+ "date": "2026-05-14",
+ "minSeVersion": "5.0.0",
+ "downloadUrl": "https://github.com/SubtitleEdit/plugins/releases/download/se5-v1.0/Haxor.zip"
+ }
+ ]
+}
diff --git a/se5/Haxor/Haxor.csproj b/se5/Haxor/Haxor.csproj
new file mode 100644
index 0000000..ba47e41
--- /dev/null
+++ b/se5/Haxor/Haxor.csproj
@@ -0,0 +1,13 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ Haxor
+ SubtitleEdit.Plugins.Haxor
+ true
+
+
+
diff --git a/se5/Haxor/HaxorTranslator.cs b/se5/Haxor/HaxorTranslator.cs
new file mode 100644
index 0000000..f0e8a59
--- /dev/null
+++ b/se5/Haxor/HaxorTranslator.cs
@@ -0,0 +1,35 @@
+using System.Text;
+
+namespace SubtitleEdit.Plugins.Haxor;
+
+/// Lower-cases text and swaps a handful of letters for their "haxor" look-alikes.
+public static class HaxorTranslator
+{
+ private static readonly Dictionary Map = new()
+ {
+ ['a'] = '4',
+ ['c'] = '©',
+ ['e'] = '3',
+ ['h'] = 'H',
+ ['i'] = '!',
+ ['k'] = 'K',
+ ['n'] = 'ñ',
+ ['o'] = '0',
+ ['s'] = '$',
+ ['y'] = '¥',
+ };
+
+ public static string Translate(string text)
+ {
+ var sb = new StringBuilder(text.ToLowerInvariant());
+ for (var i = 0; i < sb.Length; i++)
+ {
+ if (Map.TryGetValue(sb[i], out var mapped))
+ {
+ sb[i] = mapped;
+ }
+ }
+
+ return sb.ToString();
+ }
+}
diff --git a/se5/Haxor/PluginContract.cs b/se5/Haxor/PluginContract.cs
new file mode 100644
index 0000000..3608724
--- /dev/null
+++ b/se5/Haxor/PluginContract.cs
@@ -0,0 +1,66 @@
+using System.Text.Json;
+
+namespace SubtitleEdit.Plugins.Haxor;
+
+// These types mirror the Subtitle Edit 5 plugin JSON contract.
+// See https://github.com/SubtitleEdit/subtitleedit/blob/main/docs/plugin.md
+
+/// Written by Subtitle Edit; its path is passed as the first command-line argument.
+public sealed class PluginRequest
+{
+ public int ApiVersion { get; set; } = 1;
+ public string RequestType { get; set; } = "run";
+
+ /// Where this plugin must write its .
+ public string ResponseFilePath { get; set; } = string.Empty;
+
+ /// A scratch directory; deleted by Subtitle Edit after the run.
+ public string TempDirectory { get; set; } = string.Empty;
+
+ public PluginSubtitle Subtitle { get; set; } = new();
+
+ /// Zero-based indices of the lines selected in the grid (empty if none).
+ public List SelectedIndices { get; set; } = new();
+
+ public string VideoFileName { get; set; } = string.Empty;
+ public double FrameRate { get; set; }
+ public string UiLanguage { get; set; } = string.Empty;
+ public string Theme { get; set; } = string.Empty;
+ public string SeVersion { get; set; } = string.Empty;
+
+ /// This plugin's settings as last persisted by Subtitle Edit (null on first run).
+ public JsonElement? Settings { get; set; }
+}
+
+public sealed class PluginSubtitle
+{
+ /// Friendly format name, e.g. "SubRip".
+ public string Format { get; set; } = string.Empty;
+
+ public string FileName { get; set; } = string.Empty;
+
+ /// Full subtitle text in .
+ public string Native { get; set; } = string.Empty;
+
+ /// Full subtitle text as SubRip (.srt) - always provided in requests.
+ public string SubRip { get; set; } = string.Empty;
+}
+
+/// Written by this plugin to .
+public sealed class PluginResponse
+{
+ public int ApiVersion { get; set; } = 1;
+
+ /// "ok": apply the subtitle; "cancelled": do nothing; "error": show the message.
+ public string Status { get; set; } = "cancelled";
+
+ public string? Message { get; set; }
+
+ /// The modified subtitle. Only and are read.
+ public PluginSubtitle? Subtitle { get; set; }
+
+ /// Settings to persist; handed back unchanged in the next request.
+ public JsonElement? Settings { get; set; }
+
+ public string? UndoDescription { get; set; }
+}
diff --git a/se5/Haxor/Program.cs b/se5/Haxor/Program.cs
new file mode 100644
index 0000000..79f79c1
--- /dev/null
+++ b/se5/Haxor/Program.cs
@@ -0,0 +1,86 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using SubtitleEdit.Plugins.Haxor;
+
+// A Subtitle Edit 5 plugin is just an executable:
+// 1. read the request file (its path is the first command-line argument),
+// 2. transform the subtitle,
+// 3. write the response file (path is given in the request),
+// 4. exit with code 0.
+
+if (args.Length < 1)
+{
+ Console.Error.WriteLine("Usage: Haxor ");
+ return 1;
+}
+
+var jsonOptions = new JsonSerializerOptions
+{
+ PropertyNameCaseInsensitive = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ WriteIndented = true,
+};
+
+PluginRequest? request;
+try
+{
+ request = JsonSerializer.Deserialize(File.ReadAllText(args[0]), jsonOptions);
+}
+catch (Exception exception)
+{
+ Console.Error.WriteLine("Could not read request: " + exception.Message);
+ return 1;
+}
+
+if (request is null || string.IsNullOrEmpty(request.ResponseFilePath))
+{
+ Console.Error.WriteLine("Invalid request.");
+ return 1;
+}
+
+// Work on the SubRip representation - it is always provided in the request.
+var srt = request.Subtitle.SubRip;
+var selected = new HashSet(request.SelectedIndices);
+
+var count = 0;
+var blocks = Regex.Split(srt.Replace("\r\n", "\n").Trim('\n'), @"\n[ \t]*\n");
+for (var i = 0; i < blocks.Length; i++)
+{
+ // An empty SelectedIndices means "apply to every line".
+ if (selected.Count > 0 && !selected.Contains(i))
+ {
+ continue;
+ }
+
+ // A SubRip block is: number line, timecode line, then one or more text lines.
+ var lines = blocks[i].Split('\n');
+ if (lines.Length < 3)
+ {
+ continue;
+ }
+
+ for (var t = 2; t < lines.Length; t++)
+ {
+ lines[t] = HaxorTranslator.Translate(lines[t]);
+ }
+
+ blocks[i] = string.Join('\n', lines);
+ count++;
+}
+
+var response = new PluginResponse
+{
+ Status = "ok",
+ Message = count == 0 ? "No lines changed." : $"Translated {count} line(s) to haxor.",
+ UndoDescription = "Haxor 1.0.0",
+ Subtitle = new PluginSubtitle
+ {
+ Format = "SubRip",
+ Native = (string.Join("\n\n", blocks) + "\n").Replace("\n", "\r\n"),
+ },
+};
+
+File.WriteAllText(request.ResponseFilePath, JsonSerializer.Serialize(response, jsonOptions));
+return 0;
diff --git a/se5/Haxor/README.md b/se5/Haxor/README.md
new file mode 100644
index 0000000..38e6ab9
--- /dev/null
+++ b/se5/Haxor/README.md
@@ -0,0 +1,49 @@
+# Haxor (Subtitle Edit 5 sample plugin)
+
+Translates subtitle text to "haxor" — lower-cases it and swaps a few letters for
+look-alikes (`a→4`, `e→3`, `o→0`, `s→$`, `i→!`, `c→©`, `h→H`, `k→K`, `n→ñ`, `y→¥`).
+If lines are selected in the grid only those are changed, otherwise every line is.
+
+This is a reference plugin for the Subtitle Edit 5 plugin system. It shows the whole
+contract: read the request JSON, transform the subtitle, write the response JSON.
+See [docs/plugin.md](https://github.com/SubtitleEdit/subtitleedit/blob/main/docs/plugin.md)
+for the full specification.
+
+## Files
+
+| File | Purpose |
+|------|---------|
+| `plugin.json` | Manifest — lets Subtitle Edit list the plugin without launching it. |
+| `PluginContract.cs` | The request/response DTOs (the JSON contract). |
+| `Program.cs` | Entry point: read request → transform → write response → exit 0. |
+| `HaxorTranslator.cs` | The actual text transformation. |
+
+## Build
+
+```
+dotnet publish Haxor.csproj -c Release -o publish
+```
+
+## Install for testing
+
+Copy the build output plus `plugin.json` into a folder under Subtitle Edit's
+`Plugins` directory:
+
+```
+Plugins/
+ Haxor/
+ plugin.json
+ Haxor.dll
+ Haxor.runtimeconfig.json
+ ... (other files from publish/)
+```
+
+The manifest uses `"runtime": "dotnet"`, so Subtitle Edit launches it as
+`dotnet Haxor.dll ` — the .NET 8 runtime must be installed. To ship a
+self-contained build instead, publish with `-r --self-contained` and replace
+the `runtime`/`entry` fields in `plugin.json` with an `executables` block.
+
+## Package for the plugin index
+
+Zip the plugin folder (so the zip contains a single top-level `Haxor/` folder),
+attach it to a GitHub release, and point `downloadUrl` in `se5-plugins.json` at it.
diff --git a/se5/Haxor/plugin.json b/se5/Haxor/plugin.json
new file mode 100644
index 0000000..2844f75
--- /dev/null
+++ b/se5/Haxor/plugin.json
@@ -0,0 +1,12 @@
+{
+ "apiVersion": 1,
+ "name": "Haxor",
+ "description": "Translates the text of the selected lines (or all lines) to haxor.",
+ "version": "1.0.0",
+ "author": "Subtitle Edit",
+ "url": "https://github.com/SubtitleEdit/plugins/tree/master/se5/Haxor",
+ "menu": "Translate",
+ "minSeVersion": "5.0.0",
+ "runtime": "dotnet",
+ "entry": "Haxor.dll"
+}