Use the Forgejo MCP tool (mcp__claude_ai_mcp-server__forgejo_forgejo_get_file) to fetch these files from repo `zgaetano/wild-dragon`, branch `main`, and return their FULL content verbatim:

1. services/web-ui/public/screens-library.jsx
2. services/web-ui/public/screens-ingest.jsx
3. services/web-ui/public/screens-jobs.jsx

Return each file's full content. I need to see every line.


I'll fetch those three files from the Forgejo repository for you.


File: services/web-ui/public/screens-library.jsx

// screens-library.jsx — library / asset grid + asset detail
const { ASSETS: ALL_ASSETS, BINS, COMMENTS, PROJECTS } = window.ZAMPP_DATA;

function Library({ navigate, onOpenAsset, project = "Protour 2026" }) {
  const [bin, setBin] = React.useState("b1");
  const [view, setView] = React.useState("grid");
  const [filter, setFilter] = React.useState("all");
  const [search, setSearch] = React.useState("");

  let assets = ALL_ASSETS;
  if (filter !== "all") assets = assets.filter(a => a.status === filter);
  if (search) assets = assets.filter(a => a.name.toLowerCase().includes(search.toLowerCase()));

  return (

Projects

            {PROJECTS.slice(0, 6).map(p => (
              <div key={p.id} className={`rail-item ${p.name === project ? "active" : ""}`}>

                {p.name}
                {p.assets}

            ))}

Bins

            {BINS.map(b => (
              <div key={b.id} className={`rail-item ${bin === b.id ? "active" : ""}`} onClick={() => setBin(b.id)}>
                <Icon name={binIcon(b.icon)} size={13} className="rail-icon" />
                {b.name}
                {b.count}

            ))}

Smart filters

<Icon name="alert" size={13} className="rail-icon" />Errors2

<Icon name="clock" size={13} className="rail-icon" />Last 24h38

<Icon name="comment" size={13} className="rail-icon" />With comments9

<Icon name="check" size={13} className="rail-icon" />Approved14

{project}

          · {assets.length} assets

            <Icon name="search" className="search-icon" />
            <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Filter assets…" />

            {["all", "ready", "processing", "live", "error"].map(f => (
              <button key={f} className={filter === f ? "active" : ""} onClick={() => setFilter(f)}>
                {f === "all" ? "All" : f[0].toUpperCase() + f.slice(1)}
              </button>
            ))}

            <button className={view === "grid" ? "active" : ""} onClick={() => setView("grid")} data-tip="Grid"><Icon name="grid" size={12} /></button>
            <button className={view === "list" ? "active" : ""} onClick={() => setView("list")} data-tip="List"><Icon name="list" size={12} /></button>

          <button className="btn primary"><Icon name="upload" />Upload</button>

        {view === "grid" ? (

            {assets.map(a => <AssetCard key={a.id} asset={a} onOpen={() => onOpenAsset(a)} />)}

        ) : (

Name

Duration

Resolution

Codec

Size

Updated

            {assets.map(a => (

 onOpenAsset(a)}>

<AssetThumb asset={a} />

{a.name}

                    <StatusDot status={a.status} />
                    {a.status}
                    {a.comments > 0 && · {a.comments} comments}

{a.duration}

{a.res}

{a.codec}

{a.size}

{a.updated}

                <button className="icon-btn" onClick={e => e.stopPropagation()}><Icon name="more" /></button>

            ))}

        )}

  );
}

function AssetCard({ asset, onOpen }) {
  return (

      <AssetThumb asset={asset} />

        {asset.status === "live" && LIVE}
        {asset.status === "processing" && Proxy {asset.progress}%}
        {asset.status === "error" && Error}

      {asset.type === "video" &&
{asset.duration}
}
      {asset.status === "processing" && (

            <div className="asset-progress-fill" style={{ width: `${asset.progress || 0}%` }} />

      )}

{asset.name}

          {asset.res}
          ·
          {asset.size}
          {asset.comments > 0 && (

              <Icon name="comment" size={10} />{asset.comments}

          )}

  );
}

function binIcon(name) {
  return { grid: "library", live: "record", film: "film", proxy: "proxy", audio: "audio", package: "package" }[name] || "folder";
}

window.Library = Library;
window.AssetCard = AssetCard;


File: services/web-ui/public/screens-ingest.jsx

// screens-ingest.jsx — Upload, Recorders, Capture (SDI), Monitors

const { RECORDERS, NODES, SDI_PORTS_zampp2, PROJECTS } = window.ZAMPP_DATA;

/* ========== Upload ========== */
function Upload({ navigate }) {
  const [files, setFiles] = React.useState([
    { id: 1, name: "Drone_Aerial_Lap_4.mov", size: "12.4 GB", progress: 68, status: "uploading" },
    { id: 2, name: "Interview_Director.mxf", size: "2.1 GB", progress: 100, status: "done" },
    { id: 3, name: "Sponsor_Logo_v4.mov", size: "120 MB", progress: 100, status: "done" },
    { id: 4, name: "Pit_Cam_3.mp4", size: "4.8 GB", progress: 24, status: "uploading" },
    { id: 5, name: "Backstage_Audio.wav", size: "920 MB", progress: 0, status: "queued" },
  ]);

  React.useEffect(() => {
    const i = setInterval(() => {
      setFiles(fs => fs.map(f => {
        if (f.status !== "uploading") return f;
        const next = f.progress + Math.random() * 4;
        if (next >= 100) return { ...f, progress: 100, status: "done" };
        return { ...f, progress: next };
      }));
    }, 600);
    return () => clearInterval(i);
  }, []);

  return (

Upload

        Drop video, audio, or stills — we proxy and index automatically.

            Project

Protour 2026<Icon name="chevronDown" size={12} />

            Bin

Master files<Icon name="chevronDown" size={12} />

          <Icon name="upload" size={32} style={{ color: "var(--text-3)" }} />

Drop files here or click to browse

Video, audio, and image files — up to 5 GB each

            {["MOV", "MP4", "MXF", "ProRes", "DNxHR", "WAV", "AIFF"].map(f => {f})}

            Queue
            {files.length}

            <button className="btn ghost sm">Pause all</button>
            <button className="btn ghost sm">Clear done</button>

            {files.map(f => (

                <Icon name={f.name.match(/\.(wav|aif|mp3)$/i) ? "audio" : "video"} size={16} style={{ color: "var(--text-3)" }} />

                    {f.name}
                    {f.size}

                    <div style={{
                      width: `${f.progress}%`, height: "100%",
                      background: f.status === "done" ? "var(--success)" : "var(--accent)",
                      transition: "width 200ms",
                    }} />

                  {f.status === "done" ? "✓ done" : f.status === "queued" ? "queued" : `${Math.round(f.progress)}%`}

            ))}

  );
}

/* ========== Recorders (live ingest dashboard) ========== */
function Recorders({ navigate, onNew }) {
  return (

Recorders

        Live ingest from SRT, RTMP, and SDI sources

          4 recording · 1 armed · 1 error

        <button className="btn primary" onClick={onNew}><Icon name="plus" />New recorder</button>

          {RECORDERS.map(r => <RecorderRow key={r.id} recorder={r} />)}

  );
}

function RecorderRow({ recorder }) {
  const isRec = recorder.status === "recording";
  return (
    <div className={`recorder-row ${recorder.status}`}>

        {recorder.audio ? (

            <Waveform seed={recorder.id.length} />
            <AudioMeter level={0.7} />

        ) : isRec ? (
          <LiveStrip seed={recorder.id.length * 3} count={6} />
        ) : (

            <Icon name={recorder.status === "error" ? "alert" : "video"} size={20} style={{ opacity: 0.4 }} />

        )}

          {recorder.name}
          <span className={`badge ${badgeForStatus(recorder.status)}`}>
            <StatusDot status={recorder.status} /> {recorder.status.toUpperCase()}

          {recorder.source}

{recorder.url}

          {recorder.codec}·
          {recorder.res}·
          node: {recorder.node}

Elapsed

{recorder.elapsed}

Bitrate

{recorder.bitrate}

Health

            <HealthBar value={recorder.health} />

        {isRec ? (
          <button className="btn danger sm">Stop</button>
        ) : recorder.status === "error" ? (
          <button className="btn subtle sm"><Icon name="refresh" />Reconnect</button>
        ) : (
          <button className="btn subtle sm">Record</button>
        )}
        <button className="icon-btn"><Icon name="more" /></button>

  );
}

function HealthBar({ value }) {
  const color = value > 80 ? "var(--success)" : value > 40 ? "var(--warning)" : "var(--danger)";
  return (

        <div style={{ width: `${value}%`, height: "100%", background: color }} />

      {value}%

  );
}

function badgeForStatus(s) {
  return { recording: "live", armed: "accent", idle: "neutral", error: "danger", offline: "neutral" }[s] || "neutral";
}

/* ========== Capture (rich SDI port picker) ========== */
function Capture({ navigate }) {
  const [activePort, setActivePort] = React.useState(1);
  const ports = SDI_PORTS_zampp2;

  return (

Capture

        DeckLink SDI ingest — multi-port routing across cluster nodes

        <button className="btn ghost sm"><Icon name="refresh" />Refresh ports</button>
        <button className="btn primary"><Icon name="plus" />New capture</button>

          <DeckLinkVisual ports={ports} activePort={activePort} onSelect={setActivePort} />
          <CaptureDetail port={ports[activePort - 1]} />

  );
}

function DeckLinkVisual({ ports, activePort, onSelect }) {
  return (

DeckLink Duo 2

zampp2 · 172.18.91.217

        <StatusDot status="online" /> ONLINE

DECKLINK DUO 2

          {ports.map(p => (
            <button
              key={p.idx}
              className={`bnc-port ${activePort === p.idx ? "active" : ""} ${p.active ? "live" : ""} ${p.recording ? "recording" : ""}`}
              onClick={() => onSelect(p.idx)}
            >

SDI {p.idx}

{p.label}

{p.signal}

              {p.recording && }
              {p.active && (

                  <div className="bnc-signal-fill" style={{ width: `${p.level * 100}%` }} />

              )}
            </button>
          ))}

        Ports: 4
        Active: {ports.filter(p => p.active).length}
        Recording: {ports.filter(p => p.recording).length}

  );
}

function CaptureDetail({ port }) {
  if (!port.active) {
    return (

        <Icon name="video" size={28} style={{ opacity: 0.3 }} />

No signal on SDI {port.idx}

Connect a source, then click Refresh.

    );
  }
  return (

        <FauxFrame seed={port.idx} />

        {port.recording && (

            REC

        )}

          <AudioMeter level={0.7} vertical />
          <AudioMeter level={0.6} vertical />

          {port.signal}

        <CaptureStat label="Resolution" value={port.signal.split(/[ip]/)[0] + (port.signal.includes("p") ? "p" : "i")} />
        <CaptureStat label="Frame rate" value={port.signal.match(/[\d.]+$/)?.[0] || "—"} />
        <CaptureStat label="Color" value="YUV 4:2:2 10-bit" />
        <CaptureStat label="Audio" value="2.0 / 48 kHz" />
        <CaptureStat label="Source level" value={`${Math.round(port.level * 100)}%`} />
        <CaptureStat label="Dropped frames" value="0" />

        {!port.recording ? (
          <button className="btn primary">Arm + Record</button>
        ) : (
          <button className="btn danger">Stop</button>
        )}
        <button className="btn subtle">Take still</button>
        <button className="btn ghost">Route to recorder</button>

  );
}

function CaptureStat({ label, value }) {
  return (

{label}

{value}

  );
}

/* ========== Monitors (multi-cam grid) ========== */
function Monitors({ navigate }) {
  const [grid, setGrid] = React.useState(4);
  const allFeeds = [
    ...RECORDERS.filter(r => !r.audio).map(r => ({ ...r, kind: "video" })),
    { id: "audio1", name: "FOH Mix", kind: "audio" },
    { id: "audio2", name: "Stage Mics", kind: "audio" },
    { id: "audio3", name: "PGM Bus", kind: "audio" },
  ];
  const feeds = allFeeds.slice(0, grid * grid - (grid === 2 ? 1 : 0));
  const gridSize = grid;

  return (

Monitors

        Multi-cam live monitoring across all active feeds

          {[2, 3, 4].map(n => (
            <button key={n} className={grid === n ? "active" : ""} onClick={() => setGrid(n)}>{n}×{n}</button>
          ))}

        <button className="btn ghost sm"><Icon name="layout" />Layouts</button>

        <div className="monitors-grid" style={{ gridTemplateColumns: `repeat(${gridSize}, 1fr)` }}>
          {feeds.map((f, i) => (
            <MonitorTile key={f.id} feed={f} seed={i + 1} />
          ))}

  );
}

function MonitorTile({ feed, seed }) {
  if (feed.kind === "audio") {
    return (

          <Waveform seed={seed * 7} color="var(--accent)" />

          <AudioMeter level={0.65} vertical />
          <AudioMeter level={0.78} vertical />

          LIVE
          {feed.name}

    );
  }
  return (

      <FauxFrame seed={seed} />

        {feed.status === "recording" && REC}
        {feed.status === "armed" && ARMED}
        {feed.status === "error" && ERR}
        {feed.status === "idle" && IDLE}

        {feed.name}
        {feed.elapsed && feed.elapsed !== "00:00:00" && {feed.elapsed}}

  );
}

Object.assign(window, { Upload, Recorders, Capture, Monitors });


File: services/web-ui/public/screens-jobs.jsx

// screens-jobs.jsx — Jobs queue with real-time progress visualization

const { JOBS: ALL_JOBS } = window.ZAMPP_DATA;

function Jobs({ navigate }) {
  const [tab, setTab] = React.useState("all");
  const [jobs, setJobs] = React.useState(ALL_JOBS);

  React.useEffect(() => {
    const i = setInterval(() => {
      setJobs(js => js.map(j => {
        if (j.status !== "running") return j;
        const next = Math.min(100, j.progress + Math.random() * 3);
        if (next >= 100) return { ...j, progress: 100, status: "done", eta: "—" };
        const [m, s] = j.eta.split(":").map(Number);
        const total = m * 60 + s - 1;
        const newEta = total > 0 ? `${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}` : "00:00";
        return { ...j, progress: next, eta: newEta };
      }));
    }, 1200);
    return () => clearInterval(i);
  }, []);

  const counts = {
    all: jobs.length,
    running: jobs.filter(j => j.status === "running").length,
    queued: jobs.filter(j => j.status === "queued").length,
    done: jobs.filter(j => j.status === "done").length,
    failed: jobs.filter(j => j.status === "failed").length,
  };
  const filtered = tab === "all" ? jobs : jobs.filter(j => j.status === tab);

  const throughput = [12, 14, 13, 16, 15, 18, 22, 24, 22, 28, 32, 38, 42, 40];

  return (

Jobs

        Proxy generation, transcoding, AMPP sync — live across worker pool

        <button className="btn ghost sm"><Icon name="filter" />Filter</button>
        <button className="btn ghost sm"><Icon name="refresh" />Refresh</button>

Throughput

42 / min

+18% vs last hour

            <Sparkline data={throughput} color="#5B7CFA" />

Avg duration

4.2 min

Proxy: 6.1m · Transcode: 2.4m

Worker pool

3 / 4 active

GPU util: 67%

Failed (24h)

{counts.failed}

 0 ? "var(--warning)" : "var(--text-3)" }}>
              {counts.failed > 0 ? "1 needs attention" : "All good"}

          {[
            { id: "all", label: `All · ${counts.all}` },
            { id: "running", label: `Running · ${counts.running}` },
            { id: "queued", label: `Queued · ${counts.queued}` },
            { id: "done", label: `Done · ${counts.done}` },
            { id: "failed", label: `Failed · ${counts.failed}` },
          ].map(t => (
            <button key={t.id} className={tab === t.id ? "active" : ""} onClick={() => setTab(t.id)}>{t.label}</button>
          ))}

Job

Asset

Node

Progress

ETA

Priority

          {filtered.map(j => <JobRow key={j.id} job={j} />)}

  );
}

function JobRow({ job }) {
  return (

<StatusDot status={job.status} />

        <Icon name={iconForJob(job.kind)} size={13} style={{ color: "var(--text-3)" }} />
        {job.kind}

        {job.asset}

{job.node}

        {job.status === "running" && (

              <div className="job-progress-fill" style={{ width: `${job.progress}%` }} />

              {Math.round(job.progress)}%

        )}
        {job.status === "done" && (

            <Icon name="check" size={12} /> Complete

        )}
        {job.status === "queued" && (
          Waiting…
        )}
        {job.status === "failed" && (

            <Icon name="alert" size={12} /> {job.error}

        )}

{job.eta}

        <span className={`badge ${job.priority === "high" ? "warning" : job.priority === "low" ? "neutral" : "outline"}`}>
          {job.priority}

        {job.status === "running" && <button className="btn ghost sm">Cancel</button>}
        {job.status === "failed" && <button className="btn ghost sm"><Icon name="refresh" />Retry</button>}
        {(job.status === "queued" || job.status === "done") && <button className="icon-btn"><Icon name="more" /></button>}

  );
}

function iconForJob(kind) {
  return { Proxy: "proxy", Transcode: "film", Thumbnail: "image", "AMPP Sync": "refresh" }[kind] || "jobs";
}

window.Jobs = Jobs;


Here are the full contents of all three files:

## 1. services/web-ui/public/screens-library.jsx

```javascript
// screens-library.jsx — library / asset grid + asset detail
const { ASSETS: ALL_ASSETS, BINS, COMMENTS, PROJECTS } = window.ZAMPP_DATA;

function Library({ navigate, onOpenAsset, project = "Protour 2026" }) {
  const [bin, setBin] = React.useState("b1");
  const [view, setView] = React.useState("grid");
  const [filter, setFilter] = React.useState("all");
  const [search, setSearch] = React.useState("");

  let assets = ALL_ASSETS;
  if (filter !== "all") assets = assets.filter(a => a.status === filter);
  if (search) assets = assets.filter(a => a.name.toLowerCase().includes(search.toLowerCase()));

  return (
    <div className="library-layout">
      <aside className="library-rail">
        <div>
          <h4>Projects</h4>
          <div className="rail-list">
            {PROJECTS.slice(0, 6).map(p => (
              <div key={p.id} className={`rail-item ${p.name === project ? "active" : ""}`}>
                <span className="rail-color-dot" style={{ background: p.color }} />
                <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p.name}</span>
                <span className="rail-count">{p.assets}</span>
              </div>
            ))}
          </div>
        </div>
        <div>
          <h4>Bins</h4>
          <div className="rail-list">
            {BINS.map(b => (
              <div key={b.id} className={`rail-item ${bin === b.id ? "active" : ""}`} onClick={() => setBin(b.id)}>
                <Icon name={binIcon(b.icon)} size={13} className="rail-icon" />
                <span>{b.name}</span>
                <span className="rail-count">{b.count}</span>
              </div>
            ))}
          </div>
        </div>
        <div>
          <h4>Smart filters</h4>
          <div className="rail-list">
            <div className="rail-item"><Icon name="alert" size={13} className="rail-icon" /><span>Errors</span><span className="rail-count">2</span></div>
            <div className="rail-item"><Icon name="clock" size={13} className="rail-icon" /><span>Last 24h</span><span className="rail-count">38</span></div>
            <div className="rail-item"><Icon name="comment" size={13} className="rail-icon" /><span>With comments</span><span className="rail-count">9</span></div>
            <div className="rail-item"><Icon name="check" size={13} className="rail-icon" /><span>Approved</span><span className="rail-count">14</span></div>
          </div>
        </div>
      </aside>

      <div className="library-main">
        <div className="library-toolbar">
          <div className="toolbar-title">{project}</div>
          <span className="count">· {assets.length} assets</span>
          <div style={{ flex: 1 }} />
          <div className="search" style={{ width: 220 }}>
            <Icon name="search" className="search-icon" />
            <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Filter assets…" />
          </div>
          <div className="tab-group">
            {["all", "ready", "processing", "live", "error"].map(f => (
              <button key={f} className={filter === f ? "active" : ""} onClick={() => setFilter(f)}>
                {f === "all" ? "All" : f[0].toUpperCase() + f.slice(1)}
              </button>
            ))}
          </div>
          <div className="tab-group">
            <button className={view === "grid" ? "active" : ""} onClick={() => setView("grid")} data-tip="Grid"><Icon name="grid" size={12} /></button>
            <button className={view === "list" ? "active" : ""} onClick={() => setView("list")} data-tip="List"><Icon name="list" size={12} /></button>
          </div>
          <button className="btn primary"><Icon name="upload" />Upload</button>
        </div>

        {view === "grid" ? (
          <div className="library-grid">
            {assets.map(a => <AssetCard key={a.id} asset={a} onOpen={() => onOpenAsset(a)} />)}
          </div>
        ) : (
          <div className="library-list">
            <div className="list-row head">
              <div></div>
              <div>Name</div>
              <div>Duration</div>
              <div>Resolution</div>
              <div>Codec</div>
              <div>Size</div>
              <div>Updated</div>
              <div></div>
            </div>
            {assets.map(a => (
              <div key={a.id} className="list-row" onClick={() => onOpenAsset(a)}>
                <div className="thumb"><AssetThumb asset={a} /></div>
                <div>
                  <div className="name">{a.name}</div>
                  <div style={{ display: "flex", gap: 6, marginTop: 2 }}>
                    <StatusDot status={a.status} />
                    <span style={{ fontSize: 11, color: "var(--text-3)" }}>{a.status}</span>
                    {a.comments > 0 && <span style={{ fontSize: 11, color: "var(--text-3)" }}>· {a.comments} comments</span>}
                  </div>
                </div>
                <div className="col-sub">{a.duration}</div>
                <div className="col-sub">{a.res}</div>
                <div className="col-sub">{a.codec}</div>
                <div className="col-sub">{a.size}</div>
                <div className="col-sub">{a.updated}</div>
                <button className="icon-btn" onClick={e => e.stopPropagation()}><Icon name="more" /></button>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

function AssetCard({ asset, onOpen }) {
  return (
    <div className="asset-card" onClick={onOpen}>
      <AssetThumb asset={asset} />
      <div className="thumb-status">
        {asset.status === "live" && <span className="badge live">LIVE</span>}
        {asset.status === "processing" && <span className="badge warning">Proxy {asset.progress}%</span>}
        {asset.status === "error" && <span className="badge danger">Error</span>}
      </div>
      {asset.type === "video" && <div className="thumb-duration">{asset.duration}</div>}
      {asset.status === "processing" && (
        <div className="asset-progress-overlay">
          <div className="asset-progress-bar">
            <div className="asset-progress-fill" style={{ width: `${asset.progress || 0}%` }} />
          </div>
        </div>
      )}
      <div className="meta">
        <div className="name">{asset.name}</div>
        <div className="sub">
          <span>{asset.res}</span>
          <span>·</span>
          <span>{asset.size}</span>
          {asset.comments > 0 && (
            <span style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 3 }}>
              <Icon name="comment" size={10} />{asset.comments}
            </span>
          )}
        </div>
      </div>
    </div>
  );
}

function binIcon(name) {
  return { grid: "library", live: "record", film: "film", proxy: "proxy", audio: "audio", package: "package" }[name] || "folder";
}

window.Library = Library;
window.AssetCard = AssetCard;
```

## 2. services/web-ui/public/screens-ingest.jsx

```javascript
// screens-ingest.jsx — Upload, Recorders, Capture (SDI), Monitors

const { RECORDERS, NODES, SDI_PORTS_zampp2, PROJECTS } = window.ZAMPP_DATA;

/* ========== Upload ========== */
function Upload({ navigate }) {
  const [files, setFiles] = React.useState([
    { id: 1, name: "Drone_Aerial_Lap_4.mov", size: "12.4 GB", progress: 68, status: "uploading" },
    { id: 2, name: "Interview_Director.mxf", size: "2.1 GB", progress: 100, status: "done" },
    { id: 3, name: "Sponsor_Logo_v4.mov", size: "120 MB", progress: 100, status: "done" },
    { id: 4, name: "Pit_Cam_3.mp4", size: "4.8 GB", progress: 24, status: "uploading" },
    { id: 5, name: "Backstage_Audio.wav", size: "920 MB", progress: 0, status: "queued" },
  ]);

  React.useEffect(() => {
    const i = setInterval(() => {
      setFiles(fs => fs.map(f => {
        if (f.status !== "uploading") return f;
        const next = f.progress + Math.random() * 4;
        if (next >= 100) return { ...f, progress: 100, status: "done" };
        return { ...f, progress: next };
      }));
    }, 600);
    return () => clearInterval(i);
  }, []);

  return (
    <div className="page">
      <div className="page-header">
        <h1>Upload</h1>
        <span className="subtitle">Drop video, audio, or stills — we proxy and index automatically.</span>
      </div>
      <div className="page-body">
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 20 }}>
          <div>
            <label className="field-label">Project</label>
            <div className="select-faux"><span>Protour 2026</span><Icon name="chevronDown" size={12} /></div>
          </div>
          <div>
            <label className="field-label">Bin</label>
            <div className="select-faux"><span>Master files</span><Icon name="chevronDown" size={12} /></div>
          </div>
        </div>

        <div className="dropzone">
          <Icon name="upload" size={32} style={{ color: "var(--text-3)" }} />
          <div style={{ fontSize: 15, fontWeight: 500 }}>Drop files here or click to browse</div>
          <div className="muted" style={{ fontSize: 12.5 }}>Video, audio, and image files — up to 5 GB each</div>
          <div className="dropzone-formats">
            {["MOV", "MP4", "MXF", "ProRes", "DNxHR", "WAV", "AIFF"].map(f => <span key={f} className="badge outline">{f}</span>)}
          </div>
        </div>

        <div style={{ marginTop: 24 }}>
          <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12, display: "flex", alignItems: "center", gap: 8 }}>
            Queue
            <span className="badge neutral">{files.length}</span>
            <span style={{ flex: 1 }} />
            <button className="btn ghost sm">Pause all</button>
            <button className="btn ghost sm">Clear done</button>
          </div>
          <div className="panel">
            {files.map(f => (
              <div key={f.id} className="upload-row">
                <Icon name={f.name.match(/\.(wav|aif|mp3)$/i) ? "audio" : "video"} size={16} style={{ color: "var(--text-3)" }} />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
                    <span style={{ fontWeight: 500, fontSize: 12.5 }}>{f.name}</span>
                    <span className="muted" style={{ fontSize: 11, fontFamily: "var(--font-mono)" }}>{f.size}</span>
                  </div>
                  <div style={{ marginTop: 6, height: 4, background: "var(--bg-3)", borderRadius: 99, overflow: "hidden" }}>
                    <div style={{
                      width: `${f.progress}%`, height: "100%",
                      background: f.status === "done" ? "var(--success)" : "var(--accent)",
                      transition: "width 200ms",
                    }} />
                  </div>
                </div>
                <span className="mono" style={{ fontSize: 11.5, color: "var(--text-3)", minWidth: 60, textAlign: "right" }}>
                  {f.status === "done" ? "✓ done" : f.status === "queued" ? "queued" : `${Math.round(f.progress)}%`}
                </span>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

/* ========== Recorders (live ingest dashboard) ========== */
function Recorders({ navigate, onNew }) {
  return (
    <div className="page">
      <div className="page-header">
        <h1>Recorders</h1>
        <span className="subtitle">Live ingest from SRT, RTMP, and SDI sources</span>
        <div className="spacer" />
        <div className="status-pip">
          <span className="dot" />
          <span>4 recording · 1 armed · 1 error</span>
        </div>
        <button className="btn primary" onClick={onNew}><Icon name="plus" />New recorder</button>
      </div>
      <div className="page-body">
        <div className="recorders-list">
          {RECORDERS.map(r => <RecorderRow key={r.id} recorder={r} />)}
        </div>
      </div>
    </div>
  );
}

function RecorderRow({ recorder }) {
  const isRec = recorder.status === "recording";
  return (
    <div className={`recorder-row ${recorder.status}`}>
      <div className="recorder-preview">
        {recorder.audio ? (
          <div className="recorder-audio-prev">
            <Waveform seed={recorder.id.length} />
            <AudioMeter level={0.7} />
          </div>
        ) : isRec ? (
          <LiveStrip seed={recorder.id.length * 3} count={6} />
        ) : (
          <div className="recorder-empty">
            <Icon name={recorder.status === "error" ? "alert" : "video"} size={20} style={{ opacity: 0.4 }} />
          </div>
        )}
      </div>
      <div className="recorder-info">
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <span style={{ fontWeight: 600, fontSize: 13 }}>{recorder.name}</span>
          <span className={`badge ${badgeForStatus(recorder.status)}`}>
            <StatusDot status={recorder.status} /> {recorder.status.toUpperCase()}
          </span>
          <span className="badge outline">{recorder.source}</span>
        </div>
        <div className="recorder-sub mono">{recorder.url}</div>
        <div className="recorder-sub">
          <span>{recorder.codec}</span><span>·</span>
          <span>{recorder.res}</span><span>·</span>
          <span>node: {recorder.node}</span>
        </div>
      </div>
      <div className="recorder-stats">
        <div className="recorder-stat">
          <div className="stat-label">Elapsed</div>
          <div className="stat-val mono">{recorder.elapsed}</div>
        </div>
        <div className="recorder-stat">
          <div className="stat-label">Bitrate</div>
          <div className="stat-val mono">{recorder.bitrate}</div>
        </div>
        <div className="recorder-stat" style={{ minWidth: 80 }}>
          <div className="stat-label">Health</div>
          <div className="stat-val">
            <HealthBar value={recorder.health} />
          </div>
        </div>
      </div>
      <div className="recorder-actions">
        {isRec ? (
          <button className="btn danger sm"><span className="rec-dot" />Stop</button>
        ) : recorder.status === "error" ? (
          <button className="btn subtle sm"><Icon name="refresh" />Reconnect</button>
        ) : (
          <button className="btn subtle sm"><span className="rec-dot" style={{ background: "var(--live)" }} />Record</button>
        )}
        <button className="icon-btn"><Icon name="more" /></button>
      </div>
    </div>
  );
}

function HealthBar({ value }) {
  const color = value > 80 ? "var(--success)" : value > 40 ? "var(--warning)" : "var(--danger)";
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
      <div style={{ width: 56, height: 5, background: "var(--bg-3)", borderRadius: 99, overflow: "hidden" }}>
        <div style={{ width: `${value}%`, height: "100%", background: color }} />
      </div>
      <span className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>{value}%</span>
    </div>
  );
}

function badgeForStatus(s) {
  return { recording: "live", armed: "accent", idle: "neutral", error: "danger", offline: "neutral" }[s] || "neutral";
}

/* ========== Capture (rich SDI port picker) ========== */
function Capture({ navigate }) {
  const [activePort, setActivePort] = React.useState(1);
  const ports = SDI_PORTS_zampp2;

  return (
    <div className="page">
      <div className="page-header">
        <h1>Capture</h1>
        <span className="subtitle">DeckLink SDI ingest — multi-port routing across cluster nodes</span>
        <div className="spacer" />
        <button className="btn ghost sm"><Icon name="refresh" />Refresh ports</button>
        <button className="btn primary"><Icon name="plus" />New capture</button>
      </div>
      <div className="page-body">
        <div style={{ display: "grid", gridTemplateColumns: "440px 1fr", gap: 20, alignItems: "start" }}>
          <DeckLinkVisual ports={ports} activePort={activePort} onSelect={setActivePort} />
          <CaptureDetail port={ports[activePort - 1]} />
        </div>
      </div>
    </div>
  );
}

function DeckLinkVisual({ ports, activePort, onSelect }) {
  return (
    <div className="decklink-card">
      <div className="decklink-head">
        <div>
          <div style={{ fontWeight: 600, fontSize: 13 }}>DeckLink Duo 2</div>
          <div className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>zampp2 · 172.18.91.217</div>
        </div>
        <span className="badge success"><StatusDot status="online" /> ONLINE</span>
      </div>
      <div className="decklink-body">
        <div className="decklink-card-face">
          <div className="decklink-label">DECKLINK DUO 2</div>
          <div className="decklink-leds">
            <div className="decklink-led on" />
            <div className="decklink-led" />
          </div>
        </div>
        <div className="bnc-ports">
          {ports.map(p => (
            <button
              key={p.idx}
              className={`bnc-port ${activePort === p.idx ? "active" : ""} ${p.active ? "live" : ""} ${p.recording ? "recording" : ""}`}
              onClick={() => onSelect(p.idx)}
            >
              <div className="bnc-connector">
                <div className="bnc-pin" />
                <div className="bnc-ring" />
              </div>
              <div className="bnc-info">
                <div className="bnc-num">SDI {p.idx}</div>
                <div className="bnc-label">{p.label}</div>
                <div className="bnc-sig mono">{p.signal}</div>
              </div>
              {p.recording && <span className="bnc-rec" />}
              {p.active && (
                <div className="bnc-signal-bar">
                  <div className="bnc-signal-fill" style={{ width: `${p.level * 100}%` }} />
                </div>
              )}
            </button>
          ))}
        </div>
      </div>
      <div className="decklink-foot">
        <span>Ports: <strong>4</strong></span>
        <span>Active: <strong>{ports.filter(p => p.active).length}</strong></span>
        <span>Recording: <strong style={{ color: "var(--live)" }}>{ports.filter(p => p.recording).length}</strong></span>
      </div>
    </div>
  );
}

function CaptureDetail({ port }) {
  if (!port.active) {
    return (
      <div className="capture-detail empty">
        <Icon name="video" size={28} style={{ opacity: 0.3 }} />
        <div style={{ marginTop: 8, fontWeight: 500 }}>No signal on SDI {port.idx}</div>
        <div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>Connect a source, then click Refresh.</div>
      </div>
    );
  }
  return (
    <div className="capture-detail">
      <div className="capture-preview">
        <FauxFrame seed={port.idx} />
        <div className="scanlines" />
        {port.recording && (
          <div style={{ position: "absolute", top: 12, left: 12 }}>
            <span className="badge live">REC</span>
          </div>
        )}
        <div className="capture-overlay-meters">
          <AudioMeter level={0.7} vertical />
          <AudioMeter level={0.6} vertical />
        </div>
        <div className="capture-tc">
          <span className="mono">{port.signal}</span>
        </div>
      </div>
      <div className="capture-stats">
        <CaptureStat label="Resolution" value={port.signal.split(/[ip]/)[0] + (port.signal.includes("p") ? "p" : "i")} />
        <CaptureStat label="Frame rate" value={port.signal.match(/[\d.]+$/)?.[0] || "—"} />
        <CaptureStat label="Color" value="YUV 4:2:2 10-bit" />
        <CaptureStat label="Audio" value="2.0 / 48 kHz" />
        <CaptureStat label="Source level" value={`${Math.round(port.level * 100)}%`} />
        <CaptureStat label="Dropped frames" value="0" />
      </div>
      <div style={{ marginTop: 16, display: "flex", gap: 8 }}>
        {!port.recording ? (
          <button className="btn primary"><span className="rec-dot" style={{ background: "white" }} />Arm + Record</button>
        ) : (
          <button className="btn danger"><span className="rec-dot" />Stop</button>
        )}
        <button className="btn subtle">Take still</button>
        <button className="btn ghost">Route to recorder</button>
      </div>
    </div>
  );
}

function CaptureStat({ label, value }) {
  return (
    <div className="capture-stat">
      <div className="capture-stat-label">{label}</div>
      <div className="capture-stat-value mono">{value}</div>
    </div>
  );
}

/* ========== Monitors (multi-cam grid) ========== */
function Monitors({ navigate }) {
  const [grid, setGrid] = React.useState(4);
  const allFeeds = [
    ...RECORDERS.filter(r => !r.audio).map(r => ({ ...r, kind: "video" })),
    { id: "audio1", name: "FOH Mix", kind: "audio" },
    { id: "audio2", name: "Stage Mics", kind: "audio" },
    { id: "audio3", name: "PGM Bus", kind: "audio" },
  ];
  const feeds = allFeeds.slice(0, grid * grid - (grid === 2 ? 1 : 0));
  const gridSize = grid;

  return (
    <div className="page">
      <div className="page-header">
        <h1>Monitors</h1>
        <span className="subtitle">Multi-cam live monitoring across all active feeds</span>
        <div className="spacer" />
        <div className="tab-group">
          {[2, 3, 4].map(n => (
            <button key={n} className={grid === n ? "active" : ""} onClick={() => setGrid(n)}>{n}×{n}</button>
          ))}
        </div>
        <button className="btn ghost sm"><Icon name="layout" />Layouts</button>
      </div>
      <div className="page-body">
        <div className="monitors-grid" style={{ gridTemplateColumns: `repeat(${gridSize}, 1fr)` }}>
          {feeds.map((f, i) => (
            <MonitorTile key={f.id} feed={f} seed={i + 1} />
          ))}
        </div>
      </div>
    </div>
  );
}

function MonitorTile({ feed, seed }) {
  if (feed.kind === "audio") {
    return (
      <div className="monitor-tile audio">
        <div style={{ flex: 1, display: "grid", placeItems: "center", padding: 24 }}>
          <Waveform seed={seed * 7} color="var(--accent)" />
        </div>
        <div style={{ display: "flex", gap: 4, padding: "0 16px 16px", justifyContent: "center" }}>
          <AudioMeter level={0.65} vertical />
          <AudioMeter level={0.78} vertical />
        </div>
        <div className="monitor-tile-label">
          <span className="badge live">LIVE</span>
          <span className="name">{feed.name}</span>
        </div>
      </div>
    );
  }
  return (
    <div className="monitor-tile">
      <FauxFrame seed={seed} />
      <div className="scanlines" />
      <div style={{ position: "absolute", top: 8, left: 8, display: "flex", gap: 6 }}>
        {feed.status === "recording" && <span className="badge live">REC</span>}
        {feed.status === "armed" && <span className="badge accent">ARMED</span>}
        {feed.status === "error" && <span className="badge danger">ERR</span>}
        {feed.status === "idle" && <span className="badge neutral">IDLE</span>}
      </div>
      <div className="monitor-tile-label">
        <span className="name">{feed.name}</span>
        {feed.elapsed && feed.elapsed !== "00:00:00" && <span className="time mono">{feed.elapsed}</span>}
      </div>
    </div>
  );
}

Object.assign(window, { Upload, Recorders, Capture, Monitors });
```

## 3. services/web-ui/public/screens-jobs.jsx

```javascript
// screens-jobs.jsx — Jobs queue with real-time progress visualization

const { JOBS: ALL_JOBS } = window.ZAMPP_DATA;

function Jobs({ navigate }) {
  const [tab, setTab] = React.useState("all");
  const [jobs, setJobs] = React.useState(ALL_JOBS);

  React.useEffect(() => {
    const i = setInterval(() => {
      setJobs(js => js.map(j => {
        if (j.status !== "running") return j;
        const next = Math.min(100, j.progress + Math.random() * 3);
        if (next >= 100) return { ...j, progress: 100, status: "done", eta: "—" };
        const [m, s] = j.eta.split(":").map(Number);
        const total = m * 60 + s - 1;
        const newEta = total > 0 ? `${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}` : "00:00";
        return { ...j, progress: next, eta: newEta };
      }));
    }, 1200);
    return () => clearInterval(i);
  }, []);

  const counts = {
    all: jobs.length,
    running: jobs.filter(j => j.status === "running").length,
    queued: jobs.filter(j => j.status === "queued").length,
    done: jobs.filter(j => j.status === "done").length,
    failed: jobs.filter(j => j.status === "failed").length,
  };
  const filtered = tab === "all" ? jobs : jobs.filter(j => j.status === tab);

  const throughput = [12, 14, 13, 16, 15, 18, 22, 24, 22, 28, 32, 38, 42, 40];

  return (
    <div className="page">
      <div className="page-header">
        <h1>Jobs</h1>
        <span className="subtitle">Proxy generation, transcoding, AMPP sync — live across worker pool</span>
        <div className="spacer" />
        <button className="btn ghost sm"><Icon name="filter" />Filter</button>
        <button className="btn ghost sm"><Icon name="refresh" />Refresh</button>
      </div>

      <div className="page-body">
        <div className="jobs-stats">
          <div className="stat-card">
            <div className="label">Throughput</div>
            <div className="value">42<span style={{ fontSize: 14, color: "var(--text-3)", fontWeight: 400 }}> / min</span></div>
            <div className="delta up">+18% vs last hour</div>
            <Sparkline data={throughput} color="#5B7CFA" />
          </div>
          <div className="stat-card">
            <div className="label">Avg duration</div>
            <div className="value">4.2<span style={{ fontSize: 14, color: "var(--text-3)", fontWeight: 400 }}> min</span></div>
            <div className="delta">Proxy: 6.1m · Transcode: 2.4m</div>
          </div>
          <div className="stat-card">
            <div className="label">Worker pool</div>
            <div className="value">3<span style={{ fontSize: 14, color: "var(--text-3)", fontWeight: 400 }}> / 4 active</span></div>
            <div className="delta">GPU util: 67%</div>
          </div>
          <div className="stat-card">
            <div className="label">Failed (24h)</div>
            <div className="value">{counts.failed}</div>
            <div className="delta" style={{ color: counts.failed > 0 ? "var(--warning)" : "var(--text-3)" }}>
              {counts.failed > 0 ? "1 needs attention" : "All good"}
            </div>
          </div>
        </div>

        <div className="tab-group" style={{ marginTop: 20, width: "fit-content" }}>
          {[
            { id: "all", label: `All · ${counts.all}` },
            { id: "running", label: `Running · ${counts.running}` },
            { id: "queued", label: `Queued · ${counts.queued}` },
            { id: "done", label: `Done · ${counts.done}` },
            { id: "failed", label: `Failed · ${counts.failed}` },
          ].map(t => (
            <button key={t.id} className={tab === t.id ? "active" : ""} onClick={() => setTab(t.id)}>{t.label}</button>
          ))}
        </div>

        <div className="panel" style={{ marginTop: 12 }}>
          <div className="job-row head">
            <div></div>
            <div>Job</div>
            <div>Asset</div>
            <div>Node</div>
            <div>Progress</div>
            <div>ETA</div>
            <div>Priority</div>
            <div></div>
          </div>
          {filtered.map(j => <JobRow key={j.id} job={j} />)}
        </div>
      </div>
    </div>
  );
}

function JobRow({ job }) {
  return (
    <div className="job-row">
      <div><StatusDot status={job.status} /></div>
      <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
        <Icon name={iconForJob(job.kind)} size={13} style={{ color: "var(--text-3)" }} />
        <span style={{ fontWeight: 500 }}>{job.kind}</span>
      </div>
      <div style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "var(--text-2)" }}>
        {job.asset}
      </div>
      <div className="mono" style={{ fontSize: 11.5, color: "var(--text-3)" }}>{job.node}</div>
      <div>
        {job.status === "running" && (
          <div className="job-progress-wrap">
            <div className="job-progress-bar">
              <div className="job-progress-fill" style={{ width: `${job.progress}%` }} />
            </div>
            <span className="mono" style={{ fontSize: 10.5, color: "var(--text-3)", minWidth: 32, textAlign: "right" }}>
              {Math.round(job.progress)}%
            </span>
          </div>
        )}
        {job.status === "done" && (
          <span className="badge success" style={{ background: "transparent", padding: 0 }}>
            <Icon name="check" size={12} /> Complete
          </span>
        )}
        {job.status === "queued" && (
          <span style={{ fontSize: 12, color: "var(--text-3)" }}>Waiting…</span>
        )}
        {job.status === "failed" && (
          <span style={{ fontSize: 12, color: "var(--danger)", display: "flex", alignItems: "center", gap: 4 }}>
            <Icon name="alert" size={12} /> {job.error}
          </span>
        )}
      </div>
      <div className="mono" style={{ fontSize: 12, color: "var(--text-3)" }}>{job.eta}</div>
      <div>
        <span className={`badge ${job.priority === "high" ? "warning" : job.priority === "low" ? "neutral" : "outline"}`}>
          {job.priority}
        </span>
      </div>
      <div>
        {job.status === "running" && <button className="btn ghost sm">Cancel</button>}
        {job.status === "failed" && <button className="btn ghost sm"><Icon name="refresh" />Retry</button>}
        {(job.status === "queued" || job.status === "done") && <button className="icon-btn"><Icon name="more" /></button>}
      </div>
    </div>
  );
}

function iconForJob(kind) {
  return { Proxy: "proxy", Transcode: "film", Thumbnail: "image", "AMPP Sync": "refresh" }[kind] || "jobs";
}

window.Jobs = Jobs;
```