diff --git a/services/web-ui/public/modal-new-recorder.jsx b/services/web-ui/public/modal-new-recorder.jsx
new file mode 100644
index 0000000..3304ddf
--- /dev/null
+++ b/services/web-ui/public/modal-new-recorder.jsx
@@ -0,0 +1,223 @@
+// modal-new-recorder.jsx — New Recorder dialog (SRT / RTMP / SDI)
+
+const { SDI_PORTS_zampp2, PROJECTS: ALL_PROJECTS } = window.ZAMPP_DATA;
+
+function NewRecorderModal({ open, onClose }) {
+ const [name, setName] = React.useState("");
+ const [sourceType, setSourceType] = React.useState("SRT");
+ const [sdiPort, setSdiPort] = React.useState(1);
+ const [recTab, setRecTab] = React.useState("video");
+ const [proxyTab, setProxyTab] = React.useState("video");
+ const [proxyOn, setProxyOn] = React.useState(true);
+
+ if (!open) return null;
+
+ return (
+
+
e.stopPropagation()}>
+
+
+
New recorder
+
Configure source, codec, and destination
+
+
+
+
+
+
+
+ setName(e.target.value)} />
+
+
+
+
+
+ {[
+ { id: "SRT", label: "SRT", desc: "Secure Reliable Transport — pull caller" },
+ { id: "RTMP", label: "RTMP", desc: "Real-Time Messaging Protocol" },
+ { id: "SDI", label: "SDI", desc: "Blackmagic DeckLink hardware" },
+ ].map(t => (
+
+ ))}
+
+
+
+ {sourceType === "SRT" && (
+
+
+
+
+ The recorder connects out to this URL (caller mode). ?mode=caller is appended automatically.
+
+
+ )}
+
+ {sourceType === "RTMP" && (
+
+
+
+
+ The recorder will pull this RTMP stream. Must be an existing published stream.
+
+
+ )}
+
+ {sourceType === "SDI" && (
+ <>
+
+
+
+ zampp2 · DeckLink Duo 2 · 4 ports
+
+
+
+
+
+
+
+ >
+ )}
+
+
+
+
Master recording
+
+
+ {["video", "audio", "container"].map(t => (
+
+ ))}
+
+
+
+ {recTab === "video" && (
+
+
+
+
+
+
+ )}
+ {recTab === "audio" && (
+
+
+
+
+
+
+ )}
+ {recTab === "container" && (
+
+
+
+
+ )}
+
+
+
+
+
+
+
Generate proxy
+
+ SDI sources record proxy in parallel. Network sources (SRT/RTMP) generate proxy after stop.
+
+
+
+
+ {proxyOn && (
+
+
+
Proxy
+
+
+ {["video", "audio", "container"].map(t => (
+
+ ))}
+
+
+
+ {proxyTab === "video" && (
+
+
+
+
+
+
+ )}
+ {proxyTab === "audio" && (
+
+
+
+
+ )}
+ {proxyTab === "container" && (
+
+ )}
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function SDIPortMini({ ports, selected, onSelect }) {
+ return (
+
+
+
+ {ports.map(p => (
+
+ ))}
+
+
+ );
+}
+
+window.NewRecorderModal = NewRecorderModal;