(() => {
  const builders = document.querySelectorAll("[data-homepage-builder]");
  if (!builders.length) return;

  const permissions = ["public", "user", "mod", "admin"];
  const heroTypes = [
    ["static_image", "Image banner"],
    ["custom_embed", "Embedded content"],
    ["custom_link", "Featured link"],
    ["youtube_video", "YouTube video"],
    ["youtube_channel", "YouTube channel live embed"],
    ["twitch_stream", "Twitch stream"],
    ["discord_server_overview", "Discord server widget"],
    ["none", "Fallback message"]
  ];
  const availabilityModes = [
    ["always", "Always available"],
    ["live_only", "Only while live"],
    ["scheduled", "Scheduled/manual"]
  ];
  const autoplayModes = [
    ["off", "No autoplay"],
    ["muted", "Autoplay muted"],
    ["sound", "Autoplay with sound"]
  ];
  const twitchLayouts = [
    ["stream_only", "Stream only"],
    ["stream_with_chat", "Stream with chat"]
  ];

  const heroTypeLabel = (value) => heroTypes.find(([id]) => id === value)?.[1] || value || "Featured content";

  const parseRows = (source) => {
    try {
      const parsed = JSON.parse(source.value || "[]");
      return Array.isArray(parsed) ? parsed : [];
    } catch {
      return [];
    }
  };

  const field = (label, input, options = {}) => {
    const wrapper = document.createElement("label");
    wrapper.className = "homepage-builder-field";
    if (options.relevance) wrapper.dataset.relevance = options.relevance;
    const span = document.createElement("span");
    span.textContent = label;
    wrapper.append(span, input);
    if (options.help) {
      const help = document.createElement("small");
      help.className = "hint";
      help.textContent = options.help;
      wrapper.append(help);
    }
    return wrapper;
  };

  const textInput = (value = "", placeholder = "") => {
    const input = document.createElement("input");
    input.value = value || "";
    input.placeholder = placeholder;
    return input;
  };

  const numberInput = (value = 0, min = 0) => {
    const input = document.createElement("input");
    input.type = "number";
    input.min = String(min);
    input.value = Number.isFinite(Number(value)) ? String(value) : String(min);
    return input;
  };

  const selectInput = (value, values) => {
    const select = document.createElement("select");
    values.forEach((item) => {
      const [id, label] = Array.isArray(item) ? item : [item, item];
      const option = document.createElement("option");
      option.value = id;
      option.textContent = label;
      option.selected = id === value;
      select.append(option);
    });
    return select;
  };

  const checkbox = (checked = true) => {
    const input = document.createElement("input");
    input.type = "checkbox";
    input.checked = checked !== false;
    return input;
  };

  const linkDefaults = () => ({
    enabled: true,
    label: "",
    description: "",
    url: "",
    icon_mode: "favicon",
    icon_url: "",
    fetched_favicon_url: "",
    permission: "public",
    sort_order: 0
  });

  const heroDefaults = () => ({
    enabled: true,
    type: "static_image",
    title: "",
    description: "",
    priority: 0,
    permission: "public",
    source_url: "",
    image_url: "",
    embed_url: "",
    video_id: "",
    fallback_image_url: "",
    fallback_text: "",
    fallback_url: "",
    start_seconds: 0,
    show_controls: true,
    loop: false,
    embed_height: 0,
    aspect_ratio: "",
    allowed_domains: "",
    iframe_allow: "",
    allow_unsafe_permissions: false,
    availability_mode: "always",
    live_now: false,
    autoplay_mode: "off",
    twitch_layout: "stream_only",
    additional_parent_domains: "",
    youtube_show_chat: false,
    duration_seconds: 0,
    fallback_behavior: "message"
  });

  const firstLetter = (value) => {
    try {
      return new URL(value || "").hostname.replace(/^www\./, "").slice(0, 1).toUpperCase() || "L";
    } catch {
      return "L";
    }
  };

  const parseUrl = (value) => {
    try { return new URL(value || ""); } catch { return null; }
  };

  const youtubeVideoId = (value) => {
    const raw = String(value || "").trim();
    if (/^[A-Za-z0-9_-]{11}$/.test(raw)) return raw;
    const url = parseUrl(value);
    if (!url) return "";
    if (url.hostname.includes("youtu.be")) return url.pathname.split("/").filter(Boolean)[0] || "";
    const pathMatch = url.pathname.match(/\/(?:embed|shorts|live)\/([^/?#]+)/);
    return pathMatch?.[1] || url.searchParams.get("v") || "";
  };

  const youtubeChannelId = (value) => {
    const url = parseUrl(value);
    if (!url) return "";
    return url.pathname.match(/\/(?:channel|c|user)\/([^/?#]+)/)?.[1] || "";
  };

  const twitchChannelName = (value) => {
    const url = parseUrl(value);
    if (!url || !url.hostname.replace(/^www\./, "").endsWith("twitch.tv")) return "";
    const first = url.pathname.split("/").filter(Boolean)[0] || "";
    return ["videos", "directory", "p"].includes(first) ? "" : first;
  };

  const discordServerId = (value) => {
    const raw = String(value || "").trim();
    if (/^\d{10,30}$/.test(raw)) return raw;
    const url = parseUrl(raw);
    return url?.searchParams.get("id") || "";
  };

  const youtubeStartSeconds = (value) => {
    const raw = String(value || "").trim();
    if (/^\d+$/.test(raw)) return Number(raw);
    const url = parseUrl(raw);
    const token = url?.searchParams.get("start") || url?.searchParams.get("t") || "";
    if (/^\d+$/.test(token)) return Number(token);
    const match = token.match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/i);
    if (!match) return 0;
    return ((Number(match[1]) || 0) * 3600) + ((Number(match[2]) || 0) * 60) + (Number(match[3]) || 0);
  };

  const youtubeEmbedUrl = (id, options = {}) => {
    if (!id) return "";
    const url = new URL(`https://www.youtube-nocookie.com/embed/${encodeURIComponent(id)}`);
    if (options.autoplay && options.autoplay !== "off") {
      url.searchParams.set("autoplay", "1");
      if (options.autoplay !== "sound") url.searchParams.set("mute", "1");
    }
    if (options.showControls === false) url.searchParams.set("controls", "0");
    if (options.loop) {
      url.searchParams.set("loop", "1");
      url.searchParams.set("playlist", id);
    }
    if (options.startSeconds > 0) url.searchParams.set("start", String(options.startSeconds));
    return url.toString();
  };

  const safeHttpUrl = (value) => {
    const url = parseUrl(value);
    return url && ["http:", "https:"].includes(url.protocol) ? url.toString() : "";
  };

  const isLocalHost = () => ["localhost", "127.0.0.1", "::1"].includes(window.location.hostname);

  const isDiscordUrl = (value) => {
    const url = parseUrl(value);
    const host = url?.hostname.replace(/^www\./, "").toLowerCase();
    return host === "discord.gg" || host === "discord.com" || host?.endsWith(".discord.com");
  };

  const deriveEmbedUrl = (type, sourceUrl, platformId = "", options = {}) => {
    const host = window.location.hostname || "localhost";
    if (type === "custom_embed") {
      const youtube = youtubeVideoId(sourceUrl);
      if (youtube) return `https://www.youtube-nocookie.com/embed/${encodeURIComponent(youtube)}`;
      const twitch = twitchChannelName(sourceUrl);
      if (twitch) return `https://player.twitch.tv/?channel=${encodeURIComponent(twitch)}&parent=${encodeURIComponent(host)}&muted=true`;
      if (isDiscordUrl(sourceUrl)) return "";
      return safeHttpUrl(sourceUrl);
    }
    if (type === "youtube_video") {
      const id = platformId || youtubeVideoId(sourceUrl);
      return youtubeEmbedUrl(id, {
        autoplay: options.autoplay,
        showControls: options.showControls,
        loop: options.loop,
        startSeconds: options.startSeconds || youtubeStartSeconds(sourceUrl)
      });
    }
    if (type === "youtube_channel") {
      const id = platformId || youtubeChannelId(sourceUrl);
      return id ? `https://www.youtube.com/embed/live_stream?channel=${encodeURIComponent(id)}` : "";
    }
    if (type === "twitch_stream") {
      const channel = platformId || twitchChannelName(sourceUrl);
      if (!channel) return "";
      const params = new URLSearchParams({ channel, muted: "true" });
      const parents = [host, ...String(options.additionalParents || options.additional_parent_domains || "").split(",")]
        .map((domain) => domain.trim().toLowerCase().replace(/^\*\./, ""))
        .filter((domain, index, values) => domain && values.indexOf(domain) === index);
      parents.forEach((domain) => params.append("parent", domain));
      return `https://player.twitch.tv/?${params.toString()}`;
    }
    if (type === "discord_server_overview") {
      const id = platformId || discordServerId(sourceUrl);
      return id ? `discord-widget:${id}` : "";
    }
    return "";
  };

  const heroValidation = (item) => {
    if (item.enabled === false) return { ok: true, message: "Disabled items are not shown." };
    if (item.type === "none") return { ok: true, message: "Fallback message is ready." };
    if (item.type === "static_image") return item.image_url ? { ok: true, message: "Image banner is ready." } : { ok: false, message: "Add an image URL before this banner can be shown." };
    if (item.type === "custom_link") return item.source_url ? { ok: true, message: "Featured link is ready." } : { ok: false, message: "Add a link before this item can be shown." };
    if (item.type === "custom_embed") {
      if (!item.title) return { ok: false, message: "Add a title for this external embed." };
      const embed = item.embed_url || deriveEmbedUrl(item.type, item.source_url, item.video_id);
      if (isDiscordUrl(embed || item.source_url)) return { ok: false, message: "Choose Discord server widget for Discord. Invite links cannot be embedded." };
      const embedUrl = parseUrl(embed);
      if (embedUrl?.protocol === "http:" && !isLocalHost()) return { ok: false, message: "Use HTTPS embed URLs unless Lumi is running locally." };
      return embed ? { ok: true, message: "External embed is ready. The target site must allow iframe embedding." } : { ok: false, message: "Add a valid HTTPS embed URL. Normal website pages often block iframes." };
    }
    if (item.type === "discord_server_overview") {
      const id = item.video_id || discordServerId(item.source_url);
      return id ? { ok: true, message: "Discord widget card is ready. The Discord server widget must be enabled." } : { ok: false, message: "Add the numeric Discord server ID. Invite links cannot be embedded directly." };
    }
    const embed = item.embed_url || deriveEmbedUrl(item.type, item.source_url, item.video_id, item);
    return embed ? { ok: true, message: "Embed preview is ready." } : { ok: false, message: "Add a supported URL or platform ID so Lumi can build an embed." };
  };

  const confirmation = async (options) => {
    if (window.LumiConfirm?.destructive) return window.LumiConfirm.destructive(options);
    return window.confirm(options.text);
  };

  builders.forEach((builder) => {
    const kind = builder.dataset.homepageBuilder;
    const source = builder.querySelector(".homepage-json-source");
    const list = builder.querySelector(`[data-homepage-list="${kind}"]`);
    const addButton = document.querySelector(`[data-homepage-add="${kind}"]`);
    if (!source || !list) return;
    let rows = parseRows(source);

    const sync = () => {
      const next = Array.from(list.querySelectorAll("[data-homepage-row]")).map((row, index) => {
        if (kind === "links") {
          return {
            enabled: row.querySelector("[data-field='enabled']").checked,
            label: row.querySelector("[data-field='label']").value.trim(),
            description: row.querySelector("[data-field='description']").value.trim(),
            url: row.querySelector("[data-field='url']").value.trim(),
            icon_mode: row.querySelector("[data-field='icon_mode']").value,
            icon_url: row.querySelector("[data-field='icon_url']").value.trim(),
            fetched_favicon_url: row.querySelector("[data-field='fetched_favicon_url']").value.trim(),
            permission: row.querySelector("[data-field='permission']").value,
            sort_order: Number(row.querySelector("[data-field='sort_order']").value) || index
          };
        }
        const type = row.querySelector("[data-field='type']").value;
        const sourceUrl = row.querySelector("[data-field='source_url']").value.trim();
        const platformId = row.querySelector("[data-field='video_id']").value.trim();
        const embedInput = row.querySelector("[data-field='embed_url']");
        const startSeconds = Number(row.querySelector("[data-field='start_seconds']")?.value) || 0;
        const showControls = row.querySelector("[data-field='show_controls']")?.checked !== false;
        const loop = row.querySelector("[data-field='loop']")?.checked === true;
        const autoplay = row.querySelector("[data-field='autoplay_mode']").value;
        const additionalParents = row.querySelector("[data-field='additional_parent_domains']")?.value.trim() || "";
        const derivedEmbed = deriveEmbedUrl(type, sourceUrl, platformId, { autoplay, showControls, loop, startSeconds, additionalParents });
        if (!embedInput.value.trim() && derivedEmbed && !derivedEmbed.startsWith("discord-widget:")) embedInput.value = derivedEmbed;
        return {
          enabled: row.querySelector("[data-field='enabled']").checked,
          type,
          title: row.querySelector("[data-field='title']").value.trim(),
          description: row.querySelector("[data-field='description']").value.trim(),
          priority: Number(row.querySelector("[data-field='priority']").value) || index,
          permission: row.querySelector("[data-field='permission']").value,
          source_url: sourceUrl,
          image_url: row.querySelector("[data-field='image_url']").value.trim(),
          embed_url: embedInput.value.trim(),
          video_id: platformId,
          fallback_image_url: row.querySelector("[data-field='fallback_image_url']")?.value.trim() || "",
          fallback_text: row.querySelector("[data-field='fallback_text']")?.value.trim() || "",
          fallback_url: row.querySelector("[data-field='fallback_url']")?.value.trim() || "",
          start_seconds: startSeconds,
          show_controls: showControls,
          loop,
          embed_height: Number(row.querySelector("[data-field='embed_height']")?.value) || 0,
          aspect_ratio: row.querySelector("[data-field='aspect_ratio']")?.value.trim() || "",
          allowed_domains: row.querySelector("[data-field='allowed_domains']")?.value.trim() || "",
          iframe_allow: row.querySelector("[data-field='iframe_allow']")?.value.trim() || "",
          allow_unsafe_permissions: row.querySelector("[data-field='allow_unsafe_permissions']")?.checked === true,
          availability_mode: row.querySelector("[data-field='availability_mode']").value,
          live_now: row.querySelector("[data-field='live_now']")?.checked === true,
          autoplay_mode: row.querySelector("[data-field='autoplay_mode']").value,
          twitch_layout: row.querySelector("[data-field='twitch_layout']")?.value || "stream_only",
          additional_parent_domains: additionalParents,
          youtube_show_chat: row.querySelector("[data-field='youtube_show_chat']")?.checked === true,
          duration_seconds: Number(row.querySelector("[data-field='duration_seconds']").value) || 0,
          fallback_behavior: row.querySelector("[data-field='fallback_behavior']").value
        };
      });
      rows = next;
      source.value = JSON.stringify(next, null, 2);
      source.dispatchEvent(new Event("input", { bubbles: true }));
      renderPreviews();
      renderValidation();
    };

    const addField = (row, labelText, element, name, options = {}) => {
      element.dataset.field = name;
      row.append(field(labelText, element, options));
      return element;
    };

    const updateHeroRelevance = (row) => {
      if (kind !== "heroes") return;
      const type = row.querySelector("[data-field='type']")?.value || "static_image";
      const videoLike = ["youtube_video", "youtube_channel", "twitch_stream"].includes(type);
      const platformId = ["youtube_video", "youtube_channel", "twitch_stream", "discord_server_overview"].includes(type);
      const embedded = ["custom_embed", "youtube_video", "youtube_channel", "twitch_stream"].includes(type);
      const source = ["custom_link", "static_image", "custom_embed", "youtube_video", "youtube_channel", "twitch_stream", "discord_server_overview"].includes(type);
      const image = type === "static_image";
      const fallback = type === "none";
      const fallbackConfig = type !== "none";
      const youtubeVideo = type === "youtube_video";
      const youtube = ["youtube_video", "youtube_channel"].includes(type);
      const twitch = type === "twitch_stream";
      const customEmbed = type === "custom_embed";
      const discord = type === "discord_server_overview";
      row.querySelectorAll("[data-relevance]").forEach((item) => {
        const relevance = item.dataset.relevance;
        const visible =
          relevance === "video" ? videoLike :
          relevance === "platform-id" ? platformId :
          relevance === "embed" ? embedded :
          relevance === "source" ? source :
          relevance === "image" ? image :
          relevance === "fallback" ? fallback :
          relevance === "fallback-config" ? fallbackConfig :
          relevance === "youtube-video" ? youtubeVideo :
          relevance === "youtube" ? youtube :
          relevance === "twitch" ? twitch :
          relevance === "custom-embed" ? customEmbed :
          relevance === "discord" ? discord :
          true;
        item.hidden = !visible;
      });
    };

    const updateLinkRelevance = (row) => {
      if (kind !== "links") return;
      const mode = row.querySelector("[data-field='icon_mode']")?.value || "favicon";
      row.querySelectorAll("[data-relevance]").forEach((item) => {
        const relevance = item.dataset.relevance;
        item.hidden = !(
          (relevance === "manual-icon" && mode === "manual") ||
          (relevance === "fetched-icon" && mode === "favicon")
        );
      });
    };

    const renderPreviews = () => {
      list.querySelectorAll("[data-homepage-row]").forEach((row) => {
        const preview = row.querySelector("[data-homepage-preview]");
        if (!preview) return;
        if (kind === "links") {
          const label = row.querySelector("[data-field='label']").value.trim() || "Homepage link";
          const description = row.querySelector("[data-field='description']").value.trim() || "Open external link";
          const url = row.querySelector("[data-field='url']").value.trim();
          const iconMode = row.querySelector("[data-field='icon_mode']").value;
          const iconUrl =
            iconMode === "manual" ? row.querySelector("[data-field='icon_url']").value.trim() :
            iconMode === "favicon" ? row.querySelector("[data-field='fetched_favicon_url']").value.trim() :
            "";
          preview.innerHTML = `
            <span class="homepage-preview-icon">${iconUrl ? `<img src="${escapeHtml(iconUrl)}" alt="">` : escapeHtml(firstLetter(url))}</span>
            <span><strong>${escapeHtml(label)}</strong><small>${escapeHtml(description)}</small></span>
          `;
          return;
        }
        const type = row.querySelector("[data-field='type']").value;
        const title = row.querySelector("[data-field='title']").value.trim() || heroTypeLabel(type);
        const description = row.querySelector("[data-field='description']").value.trim() || "Featured content preview";
        preview.innerHTML = `
          <span class="eyebrow">${escapeHtml(heroTypeLabel(type))}</span>
          <strong>${escapeHtml(title)}</strong>
          <small>${escapeHtml(description)}</small>
        `;
      });
    };

    const moveRow = (from, direction) => {
      const to = from + direction;
      if (to < 0 || to >= rows.length) return;
      const [item] = rows.splice(from, 1);
      rows.splice(to, 0, item);
      rows.forEach((row, index) => {
        if (kind === "links") row.sort_order = index;
        else row.priority = index;
      });
      render();
    };

    const render = () => {
      list.replaceChildren();
      rows.forEach((item, index) => {
        const row = document.createElement("article");
        row.className = "homepage-builder-row";
        row.dataset.homepageRow = "";
        const header = document.createElement("div");
        header.className = "homepage-builder-row-header";
        const title = document.createElement("strong");
        title.textContent = item.label || item.title || `${kind === "links" ? "Link" : "Featured item"} ${index + 1}`;
        const enabled = checkbox(item.enabled);
        enabled.dataset.field = "enabled";
        const enabledLabel = field("Enabled", enabled);
        header.append(title, enabledLabel);
        row.append(header);

        if (kind === "links") {
          addField(row, "Button text", textInput(item.label, "Commands"), "label", { help: "Short label shown on the homepage card." });
          addField(row, "Short description", textInput(item.description, "Open command list"), "description", { help: "One sentence explaining where the link goes." });
          addField(row, "Link URL", textInput(item.url, "/commands"), "url", { help: "Use a full public URL or a local Lumi path such as /commands." });
          const iconMode = addField(row, "Icon source", selectInput(item.icon_mode || "favicon", [["favicon", "Use site icon when available"], ["manual", "Use my image URL"], ["letter", "Use first letter"]]), "icon_mode", { help: "Choose how the link card icon should be displayed." });
          addField(row, "Custom icon image URL", textInput(item.icon_url, "/assets/icon.svg"), "icon_url", { relevance: "manual-icon", help: "Optional square image or logo URL." });
          addField(row, "Detected site icon URL", textInput(item.fetched_favicon_url, "https://example.com/favicon.ico"), "fetched_favicon_url", { relevance: "fetched-icon", help: "Optional favicon URL if automatic detection found one." });
          addField(row, "Who can see it", selectInput(item.permission || "public", permissions), "permission");
          addField(row, "Display order", numberInput(item.sort_order ?? index, 0), "sort_order");
          iconMode.addEventListener("change", () => updateLinkRelevance(row));
        } else {
          const typeSelect = addField(row, "Content type", selectInput(item.type || "static_image", heroTypes), "type", { help: "Choose what this featured area should display." });
          addField(row, "Headline", textInput(item.title, "Featured stream"), "title");
          addField(row, "Short description", textInput(item.description, "What's happening now"), "description");
          addField(row, "Display priority", numberInput(item.priority ?? index, 0), "priority", { help: "Lower numbers are checked first." });
          addField(row, "Who can see it", selectInput(item.permission || "public", permissions), "permission");
          addField(row, "Normal URL or platform link", textInput(item.source_url, "Paste a YouTube, Twitch, Discord, website, or image URL"), "source_url", { relevance: "source", help: "Paste a normal URL when possible. For Discord, use this for the optional invite/fallback link." });
          addField(row, "Image URL", textInput(item.image_url, "https://example.com/banner.png"), "image_url", { relevance: "image", help: "Direct image URL for image banners." });
          addField(row, "Embed URL", textInput(item.embed_url, "https://example.com/embed"), "embed_url", { relevance: "embed", help: "Use an actual embed/player URL. Discord invite links are not iframes." });
          addField(row, "Platform ID", textInput(item.video_id, "Video, channel, Twitch name, or Discord server ID"), "video_id", { relevance: "platform-id", help: "Discord requires the numeric server ID and the server widget must be enabled." });
          addField(row, "Availability", selectInput(item.availability_mode || "always", availabilityModes), "availability_mode", { relevance: "video", help: "Live-only items stay hidden until the live switch or connected YouTube state says the stream is live." });
          addField(row, "Currently live", checkbox(item.live_now === true), "live_now", { relevance: "video", help: "Manual live switch. For Lumi's connected YouTube channel, cached bot state updates this automatically." });
          addField(row, "Autoplay", selectInput(item.autoplay_mode || "off", autoplayModes), "autoplay_mode", { relevance: "video" });
          addField(row, "YouTube start time", numberInput(item.start_seconds, 0), "start_seconds", { relevance: "youtube-video", help: "Optional start time in seconds. Lumi also reads t= or start= from pasted YouTube URLs." });
          addField(row, "Show YouTube controls", checkbox(item.show_controls !== false), "show_controls", { relevance: "youtube-video" });
          addField(row, "Loop YouTube video", checkbox(item.loop === true), "loop", { relevance: "youtube-video" });
          addField(row, "Show YouTube live chat", checkbox(item.youtube_show_chat === true), "youtube_show_chat", { relevance: "youtube", help: "Chat is shown only when Lumi has a valid live video ID." });
          addField(row, "Twitch layout", selectInput(item.twitch_layout || "stream_only", twitchLayouts), "twitch_layout", { relevance: "twitch", help: "Choose the player alone or player and Twitch chat side by side." });
          addField(row, "Extra Twitch website domains", textInput(item.additional_parent_domains, "stream.example.com, www.example.com"), "additional_parent_domains", { relevance: "twitch", help: "Add every other website hostname where this Twitch player will be shown. Lumi always includes the current hostname." });
          addField(row, "Trusted embed domains", textInput(item.allowed_domains, "example.com, player.example.com"), "allowed_domains", { relevance: "custom-embed", help: "Optional comma-separated allowlist. Leave blank to allow any HTTPS embed URL." });
          addField(row, "Embed aspect ratio", textInput(item.aspect_ratio, "16/9"), "aspect_ratio", { relevance: "custom-embed", help: "Optional ratio such as 16/9 or 4/3." });
          addField(row, "Embed minimum height", numberInput(item.embed_height, 0), "embed_height", { relevance: "custom-embed", help: "Optional minimum height in pixels." });
          addField(row, "Iframe permissions", textInput(item.iframe_allow, "fullscreen; autoplay"), "iframe_allow", { relevance: "custom-embed", help: "Advanced. Camera, microphone, and clipboard are blocked unless explicitly allowed below." });
          addField(row, "Allow sensitive iframe permissions", checkbox(item.allow_unsafe_permissions === true), "allow_unsafe_permissions", { relevance: "custom-embed", help: "Only use for trusted embeds that truly need camera, microphone, or clipboard access." });
          addField(row, "Discord setup note", textInput("Enable Server Settings > Widget in Discord, then paste the numeric server ID.", ""), "discord_note", { relevance: "discord", help: "Discord invite links cannot be embedded directly. Lumi renders the public widget data as a card." }).disabled = true;
          addField(row, "Fallback image URL", textInput(item.fallback_image_url, "https://example.com/fallback.png"), "fallback_image_url", { relevance: "fallback-config", help: "Optional image shown if the embed/widget is unavailable." });
          addField(row, "Fallback text", textInput(item.fallback_text, "This content is unavailable right now."), "fallback_text", { relevance: "fallback-config", help: "Optional message shown when this content cannot load." });
          addField(row, "Fallback link", textInput(item.fallback_url, "https://example.com"), "fallback_url", { relevance: "fallback-config", help: "Optional link shown when this content cannot load." });
          addField(row, "Display timer seconds", numberInput(item.duration_seconds, 0), "duration_seconds", { relevance: "video", help: "Optional display time for rotating featured content." });
          addField(row, "Fallback behavior", selectInput(item.fallback_behavior || "message", [["message", "Show message"], ["link", "Show fallback link/card"], ["hide", "Hide this item"]]), "fallback_behavior", { relevance: "fallback-config" });
          typeSelect.addEventListener("change", () => updateHeroRelevance(row));
        }

        const preview = document.createElement("div");
        preview.className = kind === "links" ? "homepage-link-button homepage-builder-preview" : "homepage-builder-preview hero";
        preview.dataset.homepagePreview = "";
        row.append(preview);
        if (kind === "heroes") {
          const validation = document.createElement("div");
          validation.className = "homepage-builder-validation";
          validation.dataset.homepageValidation = "";
          row.append(validation);
        }

        const actions = document.createElement("div");
        actions.className = "homepage-builder-actions";
        const up = document.createElement("button");
        up.type = "button";
        up.className = "button subtle";
        up.textContent = "Move up";
        up.disabled = index === 0;
        up.addEventListener("click", () => moveRow(index, -1));
        const down = document.createElement("button");
        down.type = "button";
        down.className = "button subtle";
        down.textContent = "Move down";
        down.disabled = index === rows.length - 1;
        down.addEventListener("click", () => moveRow(index, 1));
        const duplicate = document.createElement("button");
        duplicate.type = "button";
        duplicate.className = "button subtle";
        duplicate.textContent = "Duplicate";
        duplicate.addEventListener("click", () => {
          rows.splice(index + 1, 0, { ...rows[index] });
          render();
        });
        const remove = document.createElement("button");
        remove.type = "button";
        remove.className = "button danger";
        remove.textContent = "Remove";
        remove.addEventListener("click", async () => {
          const name = item.label || item.title || `${kind === "links" ? "link" : "featured item"} ${index + 1}`;
          const confirmed = await confirmation({
            title: kind === "links" ? "Remove homepage link" : "Remove featured content",
            text: `Remove ${name}? This only becomes permanent after you save settings.`,
            label: "Remove"
          });
          if (!confirmed) return;
          rows.splice(index, 1);
          render();
        });
        if (kind === "heroes") {
          const test = document.createElement("button");
          test.type = "button";
          test.className = "button subtle";
          test.textContent = "Test preview";
          test.addEventListener("click", () => testHeroPreview(row));
          actions.append(test);
        }
        actions.append(up, down, duplicate, remove);
        row.append(actions);
        row.addEventListener("input", sync);
        row.addEventListener("change", () => {
          updateLinkRelevance(row);
          updateHeroRelevance(row);
          sync();
        });
        list.append(row);
        updateLinkRelevance(row);
        updateHeroRelevance(row);
      });
      sync();
    };

    addButton?.addEventListener("click", () => {
      rows.push(kind === "links" ? linkDefaults() : heroDefaults());
      render();
    });

    render();
  });

  function escapeHtml(value) {
    return String(value || "")
      .replaceAll("&", "&amp;")
      .replaceAll("<", "&lt;")
      .replaceAll(">", "&gt;")
      .replaceAll('"', "&quot;")
      .replaceAll("'", "&#39;");
  }

  function renderValidation() {
    builders.forEach((builder) => {
      if (builder.dataset.homepageBuilder !== "heroes") return;
      builder.querySelectorAll("[data-homepage-row]").forEach((row) => {
        const target = row.querySelector("[data-homepage-validation]");
        if (!target) return;
        const item = {
          enabled: row.querySelector("[data-field='enabled']")?.checked,
          type: row.querySelector("[data-field='type']")?.value,
          title: row.querySelector("[data-field='title']")?.value.trim(),
          source_url: row.querySelector("[data-field='source_url']")?.value.trim(),
          image_url: row.querySelector("[data-field='image_url']")?.value.trim(),
          embed_url: row.querySelector("[data-field='embed_url']")?.value.trim(),
          video_id: row.querySelector("[data-field='video_id']")?.value.trim(),
          autoplay: row.querySelector("[data-field='autoplay_mode']")?.value,
          showControls: row.querySelector("[data-field='show_controls']")?.checked !== false,
          loop: row.querySelector("[data-field='loop']")?.checked === true,
          startSeconds: Number(row.querySelector("[data-field='start_seconds']")?.value) || 0
        };
        const status = heroValidation(item);
        target.classList.toggle("danger", !status.ok);
        target.classList.toggle("success", status.ok);
        target.textContent = status.message;
      });
    });
  }

  function testHeroPreview(row) {
    const type = row.querySelector("[data-field='type']")?.value || "static_image";
    const source = row.querySelector("[data-field='source_url']")?.value.trim() || "";
    const image = row.querySelector("[data-field='image_url']")?.value.trim() || "";
    const embedInput = row.querySelector("[data-field='embed_url']");
    const platformId = row.querySelector("[data-field='video_id']")?.value.trim() || "";
    const options = {
      autoplay: row.querySelector("[data-field='autoplay_mode']")?.value,
      showControls: row.querySelector("[data-field='show_controls']")?.checked !== false,
      loop: row.querySelector("[data-field='loop']")?.checked === true,
      startSeconds: Number(row.querySelector("[data-field='start_seconds']")?.value) || 0,
      additionalParents: row.querySelector("[data-field='additional_parent_domains']")?.value.trim() || ""
    };
    const embed = embedInput?.value.trim() || deriveEmbedUrl(type, source, platformId, options);
    if (embedInput && !embedInput.value.trim() && embed && !embed.startsWith("discord-widget:")) embedInput.value = embed;
    const preview = row.querySelector("[data-homepage-preview]");
    if (!preview) return;
    if (type === "static_image" && image) {
      preview.innerHTML = `<img src="${escapeHtml(image)}" alt="" class="homepage-builder-preview-media"><small>Image preview</small>`;
      return;
    }
    if (type === "discord_server_overview") {
      const id = platformId || discordServerId(source);
      preview.innerHTML = id
        ? `<strong>Discord widget card preview</strong><small>Lumi will fetch public widget data for server ${escapeHtml(id)} after the page is saved. Discord Server Widget must be enabled.</small>`
        : `<strong>Discord widget unavailable</strong><small>Add the numeric Discord server ID. Invite links can be used only as fallback links.</small>`;
      return;
    }
    if (embed) {
      preview.innerHTML = `<iframe src="${escapeHtml(embed)}" title="Featured content preview" class="homepage-builder-preview-media" loading="lazy" allowfullscreen></iframe><small>Embed preview. Some services block previews until saved on the public host.</small>`;
      return;
    }
    if (source) {
      window.open(source, "_blank", "noopener,noreferrer");
      return;
    }
    preview.innerHTML = `<strong>Preview unavailable</strong><small>Add a supported URL, image, or platform ID first.</small>`;
  }
})();
