Mission Trip Media Catalog + Browser Deck Generator

Version 3.5 — collapsible media cards, smart autofill, video support, theme controls, browser deck generation, debug export.

Ready
Generator: v3.5

Report Details

Deck Design Settings

Known Locations + Aliases

One per line. Use alias = Canonical Name for corrections.

Media Catalog

Build Warnings

Preview

Preview uses current catalog entries. Generate the browser deck for the final 16:9 presentation file.

Generated Output

Generated code/output appears below.
Output will appear here.

Import / Export Project JSON

Important: Browser autosave is convenient, but project JSON is the real backup. Export JSON before closing the project.
`;this.output(this.deckHTML); this.setSaveState("Deck generated", "saved"); return this.deckHTML; },fontPairing(pair) { const map = { "Oswald + Inter": ["Oswald:wght@400;500;600;700", "Inter:wght@400;500;700;800", "'Oswald', sans-serif", "'Inter', sans-serif"], "Archivo Narrow + Inter": ["Archivo+Narrow:wght@400;500;600;700", "Inter:wght@400;500;700;800", "'Archivo Narrow', sans-serif", "'Inter', sans-serif"], "Roboto Condensed + Inter": ["Roboto+Condensed:wght@400;500;700", "Inter:wght@400;500;700;800", "'Roboto Condensed', sans-serif", "'Inter', sans-serif"], "League Spartan + Source Sans 3": ["League+Spartan:wght@500;700;800", "Source+Sans+3:wght@400;600;700", "'League Spartan', sans-serif", "'Source Sans 3', sans-serif"], "Montserrat + Open Sans": ["Montserrat:wght@600;700;800", "Open+Sans:wght@400;600;700", "'Montserrat', sans-serif", "'Open Sans', sans-serif"], "Cinzel + Libre Franklin": ["Cinzel:wght@600;700;800", "Libre+Franklin:wght@400;600;700", "'Cinzel', serif", "'Libre Franklin', sans-serif"] }; const f = map[pair] || map["Oswald + Inter"]; return { url: `https://fonts.googleapis.com/css2?family=${f[0]}&family=${f[1]}&display=swap`, title: f[2], body: f[3] }; },renderDeckSlide(s, i, d) { const note = this.escapeHTML(s.media?.note || s.text || s.subtitle || ""); if (s.type === "title") { return `

${this.escapeHTML(s.title)}

${this.escapeHTML(s.subtitle || "")}

`; } if (s.type === "section") { return `

${this.escapeHTML(s.title)}

${this.escapeHTML(s.subtitle || "")}

`; } if (s.type === "scripture") { return `

${this.escapeHTML(s.title)}

${this.escapeHTML(s.text)}

`; } if (s.type === "prayer") { const bullets = String(s.text || "").split(/\n+/).filter(Boolean).map(x => `
  • ${this.escapeHTML(x.replace(/^[-*]\s*/, ""))}
  • `).join(""); return `

    ${this.escapeHTML(s.title)}

    ${bullets ? `
      ${bullets}
    ` : `

    ${this.escapeHTML(s.text || "")}

    `}
    `; } if (s.type === "closing") { return `

    ${this.escapeHTML(s.title)}

    ${this.escapeHTML(s.subtitle || "")}

    `; } return this.renderMediaSlide(s, d); },renderMediaSlide(s, d) { const m = s.media; const layout = m.layout || "Use Global Layout"; const fit = layout === "Force Cover" ? "cover" : layout === "Force Contain" ? "contain" : (d.settings.imageFit || "Contain").toLowerCase(); const isVertical = m.shape.includes("9:16") || m.shape.includes("4:5") || layout.includes("Vertical Phone"); const captionClass = this.captionClass(layout, d.settings.captionPosition); const hideCaption = layout === "Hide Caption" || d.settings.captionPosition === "No Caption"; const note = this.escapeHTML(m.note || m.shows || m.heading || m.caption || "");let mediaHTML = ""; if (m.mediaType === "Video") { if (m.provider === "YouTube") mediaHTML = ``; else if (m.provider === "Vimeo") mediaHTML = ``; else { const play = d.settings.videoPlayback; const attrs = play === "Muted Autoplay" ? "controls autoplay muted playsinline" : play === "Muted Loop" ? "controls autoplay muted loop playsinline" : "controls playsinline"; mediaHTML = ``; } } else { mediaHTML = ``; }const title = m.heading || m.location || m.file || "Mission Report"; const cap = m.caption || m.shows || ""; return `
    ${mediaHTML}

    ${this.escapeHTML(title)}

    ${cap ? `

    ${this.escapeHTML(cap)}

    ` : ""}
    `; },captionClass(layout, globalPos) { if (layout === "Move Caption Lower Right") return "lower-right"; if (layout === "Move Caption Upper Left") return "upper-left"; if (layout === "Move Caption Upper Right") return "upper-right"; if (layout === "Use Side Panel Left") return "side-left"; if (layout === "Use Side Panel Right") return "side-right"; if (globalPos === "Lower Right") return "lower-right"; if (globalPos === "Upper Left") return "upper-left"; if (globalPos === "Upper Right") return "upper-right"; if (globalPos === "Center Bottom") return "center-bottom"; if (globalPos === "Side Panel Left") return "side-left"; if (globalPos === "Side Panel Right") return "side-right"; return "lower-left"; },renderPreview() { const area = document.getElementById("mrg-preview-area"); const slides = this.buildSlideModel(); area.innerHTML = slides.map((s, i) => { const img = s.media?.poster || (s.media?.mediaType === "Image" ? s.media.url : ""); return `
    ${i+1}. ${this.escape(s.label || s.title || "Slide")}
    ${img ? `` : `
    ${this.escape(s.type)}
    `}
    `; }).join(""); },downloadDeck() { if (!this.deckHTML) this.generateDeck(); const d = this.getData(); this.downloadText(`${this.slugify(d.settings.slug || d.settings.title || "mission-report")}-browser-deck.html`, this.deckHTML, "text/html"); },downloadCatalogMarkdown() { const md = this.generateLLM(); const d = this.getData(); this.downloadText(`${this.slugify(d.settings.slug || d.settings.title || "mission-report")}-media-catalog.md`, md, "text/markdown"); },downloadDebugPackage() { if (!this.deckHTML) this.generateDeck(); const data = this.getData(); const pack = { generator: "Mission Trip Media Catalog + Browser Deck Generator", version: this.version, timestamp: new Date().toISOString(), settings: data.settings, warnings: this.runWarnings(), media: data.media, slideModel: this.buildSlideModel(), proclaimList: this.generateProclaim(), deckHTML: this.deckHTML }; this.downloadText(`${this.slugify(data.settings.slug || data.settings.title || "mission-report")}-debug-package.json`, JSON.stringify(pack, null, 2), "application/json"); },downloadProjectJSON() { const d = this.getData(); this.downloadText(`${this.slugify(d.settings.slug || d.settings.title || "mission-report")}-project.json`, JSON.stringify(d, null, 2), "application/json"); },importProjectJSON(file) { if (!file) return; const reader = new FileReader(); reader.onload = () => { try { const data = JSON.parse(reader.result); this.setData(data); this.saveQuiet(); this.output("Imported project JSON."); } catch (e) { this.output("Import failed.\n\n" + e.message); } }; reader.readAsText(file); },copyOutput() { const box = document.getElementById("mrg-output"); const status = document.getElementById("mrg-output-status"); const text = box.textContent || "";if (!text.trim() || text.trim() === "Output will appear here.") { if (status) status.textContent = "Nothing to copy yet. Generate output first."; return; }const copied = () => { if (status) { status.textContent = "Copied to clipboard: " + new Date().toLocaleTimeString([], {hour:"numeric", minute:"2-digit"}); } };if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(copied).catch(() => this.fallbackCopyOutput(text, copied)); } else { this.fallbackCopyOutput(text, copied); } },fallbackCopyOutput(text, onSuccess) { const area = document.createElement("textarea"); area.value = text; area.setAttribute("readonly", ""); area.style.position = "fixed"; area.style.left = "-9999px"; document.body.appendChild(area); area.select();try { document.execCommand("copy"); onSuccess(); } catch (error) { const status = document.getElementById("mrg-output-status"); if (status) status.textContent = "Copy failed. Select the output manually."; }area.remove(); },output(text) { document.getElementById("mrg-output").textContent = text; const status = document.getElementById("mrg-output-status"); if (status) { const chars = String(text || "").length.toLocaleString(); status.textContent = "Output ready — " + chars + " characters. Use Copy Generated Code."; } this.showTab("output"); },downloadText(filename, text, type) { const blob = new Blob([text], { type: type + ";charset=utf-8" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = filename; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(a.href), 1000); },slugify(str) { return String(str || "mission-report").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "mission-report"; },escape(str) { return String(str ?? "").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',"""); },escapeHTML(str) { return this.escape(str).replaceAll("\n", "
    "); } };window.MRG.init();
    Scroll to Top