// Settings screen — left sub-nav rail (8 sections) + focused form panes, one pane
// per settings group (mirrors /api/settings/*). Dense "system settings" rows: label
// + help text left, control right, inside flush Cards. A floating save bar appears
// once anything is dirty.
(function () {
  const NS = window.ChicoryTVDesignSystem_eb3b61;
  const { Card, Button, IconButton, Badge, Tag, Input, Select, Switch, Tooltip } = NS;
  const Ico = window.Ico;
  const mono = { fontFamily: "var(--font-mono)", fontVariantNumeric: "tabular-nums" };

  const SECTIONS = [
    { id: "general", label: "General", icon: "SlidersHorizontal", hint: "Theme & language" },
    { id: "streaming", label: "Streaming", icon: "Clapperboard", hint: "FFmpeg & transcoding" },
    { id: "playout", label: "Playout", icon: "ListVideo", hint: "Build defaults" },
    { id: "xmltv", label: "Guide (XMLTV)", icon: "CalendarDays", hint: "EPG output" },
    { id: "iptv", label: "IPTV", icon: "Radio", hint: "M3U & stream URLs" },
    { id: "scanner", label: "Scanner", icon: "Radar", hint: "Library refresh" },
    { id: "logging", label: "Logging", icon: "ScrollText", hint: "Log levels" },
    { id: "system", label: "System", icon: "Server", hint: "HDHR, sources, about" },
  ];

  const PROFILES = ["1080p H.264", "1080p HEVC", "720p H.264", "480p H.264"];
  const LOG_LEVELS = ["Verbose", "Debug", "Information", "Warning", "Error", "Fatal"];
  const RESOLUTIONS = [
    { id: 1, w: 1280, h: 720, tag: "720p" },
    { id: 2, w: 1920, h: 1080, tag: "1080p" },
    { id: 3, w: 1920, h: 820, tag: "custom" },
    { id: 4, w: 2560, h: 1080, tag: "custom" },
  ];
  const SOURCES = [
    { id: 1, kind: "Local", name: "Movies", address: "local", lastScan: "Last scan Jul 17, 9:12 AM" },
    { id: 2, kind: "Jellyfin", name: "Jellyfin — Main", address: "192.168.1.50:8096", lastScan: "Last scan Jul 16, 11:40 PM" },
    { id: 3, kind: "Local", name: "Anime Library", address: "local", lastScan: null },
  ];

  const DEFAULTS = {
    theme: "Dark", language: "en-US",
    ffmpegPath: "/usr/bin/ffmpeg", ffprobePath: "/usr/bin/ffprobe",
    defaultProfile: "1080p H.264", preferredAudioLanguage: "eng",
    useEmbeddedSubtitles: true, extractEmbeddedSubtitles: false,
    probeInterlaced: true, saveReports: false,
    globalWatermark: "(none)", globalFallbackFiller: "Retro Bumpers",
    hlsIdleTimeout: "60", hlsWorkAhead: "1", hlsInitialSegments: "1",
    hlsDirectFormat: "MPEG-TS", mpegTsScript: "",
    playoutDays: "2", skipMissing: true, scriptedTimeout: "30",
    xmltvDays: "2", xmltvTimeZone: "Local", xmltvBlockBehavior: "Split time evenly",
    iptvBaseUrl: "http://192.168.1.99:8409",
    refreshHours: "6",
    logDefault: "Information", logScanning: "Information", logScheduling: "Information",
    logSearching: "Information", logStreaming: "Information", logHttp: "Warning",
    tunerCount: "2",
  };

  /* ---------- shared row primitives ---------- */

  function Pane({ title, subtitle, children }) {
    return (
      <div style={{ maxWidth: 760, display: "flex", flexDirection: "column", gap: 16, animation: "ctv-fade-in 240ms cubic-bezier(0.16,1,0.3,1)" }}>
        <div>
          <div style={{ font: "var(--weight-semibold) var(--text-lg)/1.2 var(--font-sans)", color: "var(--text-primary)" }}>{title}</div>
          {subtitle && <div style={{ marginTop: 5, font: "var(--text-sm)/1.45 var(--font-sans)", color: "var(--text-secondary)" }}>{subtitle}</div>}
        </div>
        {children}
      </div>
    );
  }

  // One setting: label + help left, control right. Flush rows inside Card padded={false}.
  function Row({ label, help, children, first, control = 260 }) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 20, padding: "13px 16px", borderTop: first ? "none" : "1px solid var(--border-hairline)" }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ font: "var(--weight-medium) var(--text-sm)/1.3 var(--font-sans)", color: "var(--text-primary)" }}>{label}</div>
          {help && <div style={{ marginTop: 3, font: "var(--text-xs)/1.45 var(--font-sans)", color: "var(--text-disabled)" }}>{help}</div>}
        </div>
        <div style={{ flex: `0 0 ${control}px`, display: "flex", justifyContent: "flex-end" }}>{children}</div>
      </div>
    );
  }

  // Flush non-Row line inside a Card — icon + main content + trailing bits.
  function FlushRow({ children, first }) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "11px 16px", borderTop: first ? "none" : "1px solid var(--border-hairline)" }}>
        {children}
      </div>
    );
  }

  function LegacyCallout({ children }) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 14px", borderRadius: "var(--radius-sm)", background: "var(--ctv-surface-2)", border: "1px dashed var(--border-control)" }}>
        <Ico n="ExternalLink" s={14} color="var(--text-disabled)" />
        <span style={{ flex: 1, font: "var(--text-xs)/1.4 var(--font-sans)", color: "var(--text-secondary)" }}>{children}</span>
        <Badge tone="neutral">Legacy UI</Badge>
      </div>
    );
  }

  function PathInput({ value, onChange }) {
    const ok = value.startsWith("/");
    return (
      <Input size="sm" fullWidth value={value} onChange={(e) => onChange(e.target.value)}
        style={mono}
        trailing={ok
          ? <Ico n="CircleCheck" s={14} color="var(--status-ok)" />
          : <Tooltip label="File not found"><Ico n="CircleAlert" s={14} color="var(--status-error)" /></Tooltip>} />
    );
  }

  function NumInput({ value, onChange, unit, w = 120 }) {
    return (
      <Input size="sm" type="number" value={value} onChange={(e) => onChange(e.target.value)}
        style={{ width: w, ...mono }}
        trailing={unit && <span style={{ font: "var(--text-2xs)/1 var(--font-sans)", color: "var(--text-disabled)" }}>{unit}</span>} />
    );
  }

  /* ---------- panes ---------- */

  function GeneralPane({ v, set }) {
    return (
      <Pane title="General" subtitle="Interface preferences. These apply to the legacy web UI; ChicoryTV theming lives in the theme switcher.">
        <Card padded={false}>
          <Row first label="Theme" help="Legacy UI color scheme.">
            <Select size="sm" fullWidth value={v.theme} onChange={(e) => set("theme", e.target.value)} options={["Dark", "Light"]} />
          </Row>
          <Row label="Language" help="Display language for the legacy UI, as a culture code (e.g. en-US).">
            <Input size="sm" fullWidth value={v.language} onChange={(e) => set("language", e.target.value)} placeholder="en-US" />
          </Row>
        </Card>
      </Pane>
    );
  }

  function StreamingPane({ v, set }) {
    const [resW, setResW] = React.useState("");
    const [resH, setResH] = React.useState("");
    const [resolutions, setResolutions] = React.useState(RESOLUTIONS);
    return (
      <Pane title="Streaming" subtitle="FFmpeg engine, transcoding defaults and HLS session tuning.">
        <Card title="FFmpeg" padded={false}>
          <Row first label="FFmpeg path" help="Binary used for all transcoding." control={320}>
            <PathInput value={v.ffmpegPath} onChange={(x) => set("ffmpegPath", x)} />
          </Row>
          <Row label="FFprobe path" help="Binary used to inspect media files." control={320}>
            <PathInput value={v.ffprobePath} onChange={(x) => set("ffprobePath", x)} />
          </Row>
          <Row label="Default profile" help="Transcoding profile for new channels.">
            <Select size="sm" fullWidth value={v.defaultProfile} onChange={(e) => set("defaultProfile", e.target.value)} options={PROFILES} />
          </Row>
          <Row label="Preferred audio language" help="ISO 639-2 code of the track picked when media has multiple audio languages (e.g. eng).">
            <Input size="sm" fullWidth value={v.preferredAudioLanguage} onChange={(e) => set("preferredAudioLanguage", e.target.value)} placeholder="eng" style={mono} />
          </Row>
          <Row label="Use embedded subtitles" help="Burn in / pass through subtitles found in media files.">
            <Switch size="sm" checked={v.useEmbeddedSubtitles} onChange={(x) => set("useEmbeddedSubtitles", x)} />
          </Row>
          <Row label="Extract embedded subtitles" help="Copy text subtitles out ahead of playback (slower scans).">
            <Switch size="sm" checked={v.extractEmbeddedSubtitles} onChange={(x) => set("extractEmbeddedSubtitles", x)} />
          </Row>
          <Row label="Probe for interlaced frames" help="Deeper scan to auto-apply deinterlacing.">
            <Switch size="sm" checked={v.probeInterlaced} onChange={(x) => set("probeInterlaced", x)} />
          </Row>
          <Row label="Save troubleshooting reports" help="Write a report per transcode session to the config folder.">
            <Switch size="sm" checked={v.saveReports} onChange={(x) => set("saveReports", x)} />
          </Row>
        </Card>

        <Card title="Global defaults" padded={false}>
          <Row first label="Watermark" help="Applied to every channel without its own watermark.">
            <Select size="sm" fullWidth value={v.globalWatermark} onChange={(e) => set("globalWatermark", e.target.value)}
              options={["(none)", "Station bug — corner", "Station bug — large"]} />
          </Row>
          <Row label="Fallback filler" help="Plays when a playout has nothing scheduled.">
            <Select size="sm" fullWidth value={v.globalFallbackFiller} onChange={(e) => set("globalFallbackFiller", e.target.value)}
              options={["(none)", "Retro Bumpers", "Ad Break Pool", "Test Pattern"]} />
          </Row>
        </Card>

        <Card title="HLS sessions" padded={false}>
          <Row first label="Idle timeout" help="Stop a segmenter after no client has asked for segments.">
            <NumInput value={v.hlsIdleTimeout} onChange={(x) => set("hlsIdleTimeout", x)} unit="sec" />
          </Row>
          <Row label="Work-ahead limit" help="Max segmenters transcoding ahead of live at once.">
            <NumInput value={v.hlsWorkAhead} onChange={(x) => set("hlsWorkAhead", x)} unit="sessions" />
          </Row>
          <Row label="Initial segments" help="Segments ready before playback starts.">
            <NumInput value={v.hlsInitialSegments} onChange={(x) => set("hlsInitialSegments", x)} unit="segments" />
          </Row>
          <Row label="HLS Direct output format" help="Container for HLS Direct channels.">
            <Select size="sm" fullWidth value={v.hlsDirectFormat} onChange={(e) => set("hlsDirectFormat", e.target.value)} options={["MPEG-TS", "MP4", "MKV"]} />
          </Row>
          <Row label="Default MPEG-TS script" help="Channels in MPEG-TS mode use this script unless overridden.">
            <Input size="sm" fullWidth value={v.mpegTsScript} onChange={(e) => set("mpegTsScript", e.target.value)} placeholder="(default template)" style={mono} />
          </Row>
        </Card>

        <Card title="Custom resolutions" subtitle="Extra target resolutions selectable in FFmpeg profiles" padded={false}>
          {resolutions.map((r, i) => (
            <FlushRow key={r.id} first={i === 0}>
              <Ico n="Proportions" s={15} color="var(--text-disabled)" />
              <span style={{ flex: 1, ...mono, font: "var(--text-sm)/1 var(--font-mono)", color: "var(--text-primary)" }}>{r.w} × {r.h}</span>
              <Tag>{r.tag}</Tag>
              {r.tag === "custom" && (
                <IconButton size="sm" variant="ghost" title={`Delete ${r.w}×${r.h}`} onClick={() => setResolutions(resolutions.filter((x) => x.id !== r.id))}>
                  <Ico n="Trash2" s={14} />
                </IconButton>
              )}
            </FlushRow>
          ))}
          <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "10px 16px", borderTop: "1px solid var(--border-hairline)", background: "var(--ctv-bg-sunken)" }}>
            <Input size="sm" type="number" placeholder="width" value={resW} onChange={(e) => setResW(e.target.value)} style={{ width: 96, ...mono }} />
            <span style={{ color: "var(--text-disabled)" }}>×</span>
            <Input size="sm" type="number" placeholder="height" value={resH} onChange={(e) => setResH(e.target.value)} style={{ width: 96, ...mono }} />
            <span style={{ flex: 1 }} />
            <Button size="sm" variant="secondary" disabled={!resW || !resH}
              onClick={() => { setResolutions([...resolutions, { id: Date.now(), w: +resW, h: +resH, tag: "custom" }]); setResW(""); setResH(""); }}>
              Add resolution
            </Button>
          </div>
        </Card>

        <Card title="FFmpeg profiles" subtitle="Fine-grained transcoding presets" padded={false}
          actions={<Badge tone="neutral">{PROFILES.length} profiles</Badge>}>
          {PROFILES.map((p, i) => (
            <FlushRow key={p} first={i === 0}>
              <Ico n="Cpu" s={15} color="var(--ctv-accent)" />
              <span style={{ flex: 1, font: "var(--weight-medium) var(--text-sm)/1 var(--font-sans)", color: "var(--text-primary)" }}>{p}</span>
              {p === v.defaultProfile && <Badge tone="accent">Default</Badge>}
            </FlushRow>
          ))}
          <div style={{ display: "flex", justifyContent: "flex-end", padding: "10px 16px", borderTop: "1px solid var(--border-hairline)" }}>
            <Button size="sm" variant="secondary" endIcon={<Ico n="ArrowRight" s={13} />}>Manage FFmpeg profiles</Button>
          </div>
        </Card>
      </Pane>
    );
  }

  function PlayoutPane({ v, set }) {
    return (
      <Pane title="Playout" subtitle="Defaults for how far ahead playouts are built and how gaps are handled.">
        <Card padded={false}>
          <Row first label="Days to build" help="How many days of programming each playout keeps ahead.">
            <NumInput value={v.playoutDays} onChange={(x) => set("playoutDays", x)} unit="days" />
          </Row>
          <Row label="Skip missing items" help="Skip items whose files are missing instead of failing the build.">
            <Switch size="sm" checked={v.skipMissing} onChange={(x) => set("skipMissing", x)} />
          </Row>
          <Row label="Scripted schedule timeout" help="Max run time for external scripted-schedule processes.">
            <NumInput value={v.scriptedTimeout} onChange={(x) => set("scriptedTimeout", x)} unit="sec" />
          </Row>
        </Card>
      </Pane>
    );
  }

  function XmltvPane({ v, set }) {
    return (
      <Pane title="Guide (XMLTV)" subtitle="Shape of the XMLTV guide data served to Jellyfin and other clients.">
        <Card padded={false}>
          <Row first label="Days to build" help="Guide horizon exported per channel.">
            <NumInput value={v.xmltvDays} onChange={(x) => set("xmltvDays", x)} unit="days" />
          </Row>
          <Row label="Time zone" help="Timestamps written into the XMLTV file.">
            <Select size="sm" fullWidth value={v.xmltvTimeZone} onChange={(e) => set("xmltvTimeZone", e.target.value)} options={["Local", "UTC"]} />
          </Row>
          <Row label="Block behavior" help="How block-schedule playouts are represented in the guide.">
            <Select size="sm" fullWidth value={v.xmltvBlockBehavior} onChange={(e) => set("xmltvBlockBehavior", e.target.value)} options={["Split time evenly", "Use actual times"]} />
          </Row>
        </Card>
      </Pane>
    );
  }

  function IptvPane({ v, set }) {
    return (
      <Pane title="IPTV" subtitle="How ErsatzTV advertises itself to IPTV clients in M3U playlists and stream URLs.">
        <Card padded={false}>
          <Row first control={340} label="Advertised base URL"
            help="Absolute base URL clients should use to reach this server (e.g. http://192.168.1.99:8409 or https://tv.example.com/etv). Leave empty to use the address of each incoming request.">
            <Input size="sm" fullWidth value={v.iptvBaseUrl} onChange={(e) => set("iptvBaseUrl", e.target.value)} placeholder="(use request origin)" style={mono} />
          </Row>
        </Card>
      </Pane>
    );
  }

  function ScannerPane({ v, set }) {
    return (
      <Pane title="Scanner" subtitle="Background scanning of local libraries.">
        <Card padded={false}>
          <Row first label="Library refresh interval" help="Hours between automatic local-library scans. 0 disables automatic scanning.">
            <NumInput value={v.refreshHours} onChange={(x) => set("refreshHours", x)} unit="hours" />
          </Row>
        </Card>
        {v.refreshHours === "0" && (
          <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 14px", borderRadius: "var(--radius-sm)", background: "var(--ctv-warn-soft)", border: "1px solid transparent" }}>
            <Ico n="TriangleAlert" s={14} color="var(--status-warn)" />
            <span style={{ font: "var(--text-xs)/1.4 var(--font-sans)", color: "var(--text-secondary)" }}>
              Automatic scanning is disabled — libraries only update when scanned manually.
            </span>
          </div>
        )}
      </Pane>
    );
  }

  function LoggingPane({ v, set }) {
    const CATS = [
      { key: "logDefault", label: "Default", help: "Everything without a more specific category." },
      { key: "logScanning", label: "Scanning", help: "Library scans and metadata refresh." },
      { key: "logScheduling", label: "Scheduling", help: "Playout builds and schedule evaluation." },
      { key: "logSearching", label: "Searching", help: "Search index and queries." },
      { key: "logStreaming", label: "Streaming", help: "FFmpeg sessions and IPTV requests." },
      { key: "logHttp", label: "HTTP", help: "ASP.NET request logging." },
    ];
    return (
      <Pane title="Logging" subtitle="Minimum level written per category. Verbose and Debug are noisy — use for troubleshooting only.">
        <Card padded={false}>
          {CATS.map((c, i) => (
            <Row key={c.key} first={i === 0} label={c.label} help={c.help} control={180}>
              <Select size="sm" fullWidth value={v[c.key]} onChange={(e) => set(c.key, e.target.value)} options={LOG_LEVELS} />
            </Row>
          ))}
        </Card>
      </Pane>
    );
  }

  function SystemPane({ v, set }) {
    const [copied, setCopied] = React.useState(false);
    const UUID = "6f1b0a2e-93c4-4d1e-b7aa-0e5f2c9d8a41";
    return (
      <Pane title="System" subtitle="HDHomeRun emulation, connected media sources and server info.">
        <Card title="HDHomeRun" padded={false}>
          <Row first label="Tuner count" help="Concurrent streams advertised to clients that speak HDHR.">
            <NumInput value={v.tunerCount} onChange={(x) => set("tunerCount", x)} unit="tuners" />
          </Row>
          <Row label="Device UUID" help="Identity presented to HDHR clients." control={340}>
            <div style={{ display: "flex", alignItems: "center", gap: 6, width: "100%", justifyContent: "flex-end" }}>
              <span style={{ ...mono, font: "var(--text-xs)/1 var(--font-mono)", color: "var(--text-secondary)", overflow: "hidden", textOverflow: "ellipsis" }}>{UUID}</span>
              <IconButton size="sm" variant="ghost" title="Copy UUID" onClick={() => { setCopied(true); setTimeout(() => setCopied(false), 1500); }}>
                <Ico n={copied ? "Check" : "Copy"} s={13} />
              </IconButton>
            </div>
          </Row>
        </Card>

        <Card title="Media sources" subtitle="Read-only — add or edit connections in the legacy UI" padded={false}>
          {SOURCES.map((s, i) => (
            <FlushRow key={s.id} first={i === 0}>
              <Ico n={s.kind === "Local" ? "HardDrive" : "Cast"} s={15} color="var(--text-secondary)" />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ font: "var(--weight-medium) var(--text-sm)/1 var(--font-sans)", color: "var(--text-primary)" }}>{s.name}</div>
                <div style={{ marginTop: 2, font: "var(--text-2xs)/1 var(--font-sans)", color: "var(--text-disabled)" }}>{s.address}</div>
              </div>
              {s.lastScan && <span style={{ font: "var(--text-2xs)/1 var(--font-sans)", color: "var(--text-disabled)", whiteSpace: "nowrap" }}>{s.lastScan}</span>}
            </FlushRow>
          ))}
          <div style={{ padding: "10px 16px", borderTop: "1px solid var(--border-hairline)" }}>
            <LegacyCallout>Connections, credentials and library sync are managed in the legacy UI.</LegacyCallout>
          </div>
        </Card>

        <Card title="About" padded={false}>
          <Row first control={220} label="Version">
            <span style={{ ...mono, font: "var(--text-sm)/1 var(--font-mono)", color: "var(--text-primary)" }}>v26.8.0</span>
          </Row>
          <Row control={220} label="Health checks" help="14 checks — 2 warnings">
            <Button size="sm" variant="secondary" endIcon={<Ico n="ArrowRight" s={13} />}>Open Dashboard</Button>
          </Row>
          <Row control={220} label="Classic UI" help="Blocks/decos/templates and playout editors still live here.">
            <Button size="sm" variant="secondary" endIcon={<Ico n="ExternalLink" s={13} />}>Open Classic UI</Button>
          </Row>
          <Row control={220} label="Logs" help="Recent server log entries with level and free-text filtering.">
            <Button size="sm" variant="secondary" endIcon={<Ico n="ArrowRight" s={13} />}>Open Logs</Button>
          </Row>
          <Row control={220} label="Troubleshooting" help="Version, environment, hardware and FFmpeg capability diagnostics.">
            <Button size="sm" variant="secondary" endIcon={<Ico n="ArrowRight" s={13} />}>Open Troubleshooting</Button>
          </Row>
        </Card>
      </Pane>
    );
  }

  /* ---------- screen ---------- */

  function Settings() {
    const [section, setSection] = React.useState("general");
    const [saved, setSaved] = React.useState(DEFAULTS);
    const [draft, setDraft] = React.useState(DEFAULTS);
    const [justSaved, setJustSaved] = React.useState(false);
    const set = (k, val) => setDraft((d) => ({ ...d, [k]: val }));
    const dirty = Object.keys(DEFAULTS).filter((k) => draft[k] !== saved[k]);

    const PANES = {
      general: GeneralPane, streaming: StreamingPane, playout: PlayoutPane,
      xmltv: XmltvPane, iptv: IptvPane, scanner: ScannerPane, logging: LoggingPane, system: SystemPane,
    };
    const PaneCmp = PANES[section];

    return (
      <div style={{ display: "flex", height: "100%", minHeight: 0 }}>
        {/* sub-nav rail */}
        <div style={{ width: 224, flex: "0 0 auto", borderRight: "1px solid var(--border-hairline)", overflow: "auto", padding: "12px 8px" }}>
          {SECTIONS.map((s) => {
            const active = s.id === section;
            return (
              <button key={s.id} type="button" onClick={() => setSection(s.id)} className="ctv-press"
                style={{ display: "flex", alignItems: "center", gap: 10, width: "100%", padding: "9px 10px", marginBottom: 2, border: "none", borderRadius: "var(--radius-sm)", cursor: "pointer", textAlign: "left",
                  background: active ? "var(--ctv-accent-soft)" : "transparent" }}
                onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = "var(--ctv-surface-2)"; }}
                onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = "transparent"; }}>
                <Ico n={s.icon} s={16} color={active ? "var(--ctv-accent)" : "var(--text-secondary)"} />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ font: `var(--weight-medium) var(--text-sm)/1 var(--font-sans)`, color: active ? "var(--text-primary)" : "var(--text-secondary)" }}>{s.label}</div>
                  <div style={{ marginTop: 3, font: "var(--text-2xs)/1 var(--font-sans)", color: "var(--text-disabled)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{s.hint}</div>
                </div>
              </button>
            );
          })}
        </div>

        {/* pane */}
        <div style={{ flex: 1, minWidth: 0, minHeight: 0, position: "relative", display: "flex", flexDirection: "column" }}>
          <div style={{ flex: 1, minHeight: 0, overflow: "auto", padding: "20px 24px 96px" }}>
            <PaneCmp key={section} v={draft} set={set} />
          </div>

          {/* floating save bar */}
          {(dirty.length > 0 || justSaved) && (
            <div style={{ position: "absolute", left: 0, right: 0, bottom: 0, display: "flex", justifyContent: "center", padding: "0 24px 18px", pointerEvents: "none" }}>
              <div className="ctv-lift" style={{ pointerEvents: "auto", display: "flex", alignItems: "center", gap: 14, padding: "10px 12px 10px 16px", borderRadius: "var(--radius-pill)", background: "var(--surface-raised)", border: "1px solid var(--border-control)", boxShadow: "var(--shadow-pop)", animation: "ctv-fade-in 200ms cubic-bezier(0.16,1,0.3,1)" }}>
                {justSaved ? (
                  <React.Fragment>
                    <Ico n="CircleCheck" s={15} color="var(--status-ok)" />
                    <span style={{ font: "var(--weight-medium) var(--text-sm)/1 var(--font-sans)", color: "var(--text-primary)" }}>Settings saved</span>
                  </React.Fragment>
                ) : (
                  <React.Fragment>
                    <span style={{ font: "var(--weight-medium) var(--text-sm)/1 var(--font-sans)", color: "var(--text-primary)" }}>
                      {dirty.length} unsaved {dirty.length === 1 ? "change" : "changes"}
                    </span>
                    <span style={{ width: 1, height: 18, background: "var(--border-hairline)" }} />
                    <Button size="sm" variant="ghost" onClick={() => setDraft(saved)}>Discard</Button>
                    <Button size="sm" variant="primary" startIcon={<Ico n="Check" s={14} />}
                      onClick={() => { setSaved(draft); setJustSaved(true); setTimeout(() => setJustSaved(false), 1800); }}>
                      Save changes
                    </Button>
                  </React.Fragment>
                )}
              </div>
            </div>
          )}
        </div>
      </div>
    );
  }

  window.CTVSettings = Settings;
})();
