From 77fef0783382c3c6ebd68ad5a1afb0feee5454ea Mon Sep 17 00:00:00 2001 From: Elliot Hopkins <160373706+elliothco@users.noreply.github.com> Date: Sun, 12 Apr 2026 12:27:01 -0700 Subject: [PATCH] Add hero iframe extension demo with streamed summary mock --- public/extension-demo/index.html | 151 +++ public/extension-demo/lightning.svg | 9 + public/extension-demo/marked.min.js | 69 + public/extension-demo/popup-demo.js | 495 ++++++++ public/extension-demo/popup.css | 1252 +++++++++++++++++++ public/images/hero.jpeg | Bin 0 -> 172633 bytes public/images/lede-usage-1.jpeg | Bin 0 -> 59354 bytes public/images/lede-usage-2.jpeg | Bin 0 -> 23181 bytes public/images/lede-usage-3.jpeg | Bin 0 -> 15444 bytes src/components/sections/HeroWithProof.astro | 62 +- src/components/sections/UsageMicro.astro | 5 + src/pages/index.astro | 6 +- src/styles/global.css | 70 +- 13 files changed, 2072 insertions(+), 47 deletions(-) create mode 100644 public/extension-demo/index.html create mode 100644 public/extension-demo/lightning.svg create mode 100644 public/extension-demo/marked.min.js create mode 100644 public/extension-demo/popup-demo.js create mode 100644 public/extension-demo/popup.css create mode 100644 public/images/hero.jpeg create mode 100644 public/images/lede-usage-1.jpeg create mode 100644 public/images/lede-usage-2.jpeg create mode 100644 public/images/lede-usage-3.jpeg diff --git a/public/extension-demo/index.html b/public/extension-demo/index.html new file mode 100644 index 0000000..1677a97 --- /dev/null +++ b/public/extension-demo/index.html @@ -0,0 +1,151 @@ + + +
+ + + +Don't bury the lede
++ Put the main point first—then ask anything about the page—with AI. +
+'+(n?i:R(i,!0))+`
+`:""+(n?i:R(i,!0))+`
+`}blockquote({tokens:e}){return`+${this.parser.parse(e)}+`}html({text:e}){return e}heading({tokens:e,depth:t}){return`
${this.parser.parseInline(e)}
+`}table(e){let t="",n="";for(let i=0;i${R(e,!0)}`}br(e){return"An error occurred:
"+R(n.message+"",!0)+"";return t?Promise.resolve(s):s}if(t)return Promise.reject(n);throw n}}};var M=new E;function k(l,e){return M.parse(l,e)}k.options=k.setOptions=function(l){return M.setOptions(l),k.defaults=M.defaults,N(k.defaults),k};k.getDefaults=z;k.defaults=w;k.use=function(...l){return M.use(...l),k.defaults=M.defaults,N(k.defaults),k};k.walkTokens=function(l,e){return M.walkTokens(l,e)};k.parseInline=M.parseInline;k.Parser=b;k.parser=b.parse;k.Renderer=$;k.TextRenderer=_;k.Lexer=x;k.lexer=x.lex;k.Tokenizer=S;k.Hooks=L;k.parse=k;var it=k.options,ot=k.setOptions,lt=k.use,at=k.walkTokens,ct=k.parseInline,pt=k,ut=b.parse,ht=x.lex; + +if(__exports != exports)module.exports = exports;return module.exports})); diff --git a/public/extension-demo/popup-demo.js b/public/extension-demo/popup-demo.js new file mode 100644 index 0000000..2bcc306 --- /dev/null +++ b/public/extension-demo/popup-demo.js @@ -0,0 +1,495 @@ +/** + * Standalone marketing demo: same markup/CSS as the extension popup, mocked data only. + * Sync with webai-summarizer/popup when the real UI changes (popup.html, popup.css). + */ +(function () { + const THEMES = ["light", "dark", "system"]; + const DEFAULT_SUGGESTION = "Why would this be worth reading?"; + const STATIC_SUGGESTIONS = [ + "What should I skip on this page?", + "Who is this written for?", + ]; + + const SAMPLE_SUMMARY = `## The lede + +This is the **same chrome** as the Lede extension: summarize the active tab, then chat with answers grounded in that text. + +- **Quick Summary** is real in the build you install—here it’s sample copy. +- **Chat** uses placeholder replies so you can try the layout without an API key. +- Use **Regenerate** after a summary to replay the flow.`; + + const ASSISTANT_REPLIES = [ + "In the installed extension I read the tab’s extracted text and answer from that. Here you’re seeing canned replies so the marketing site stays fast and private.", + "After you install, open **Settings** (this demo’s gear only shows a hint) to point at **Ollama** or your own API and model.", + "Try **Regenerate** on the summary card to replay the streamed summary—useful when a page updates underneath you.", + ]; + + let currentTheme = "system"; + let isLoading = false; + let isChatLoading = false; + let chatReplyGeneration = 0; + let assistantTurn = 0; + let demoStreamGen = 0; + let quickSummary = ""; + const chatHistory = []; + /** Pretend page extraction succeeded (real popup gates chat on this). */ + const currentPageContent = "demo"; + + const resultContainer = document.getElementById("result"); + const initialState = document.getElementById("initial-state"); + const summarizeBtn = document.getElementById("summarize-btn"); + const summarizeLabel = document.getElementById("summarize-label"); + const settingsBtn = document.getElementById("settings-btn"); + const themeBtn = document.getElementById("theme-btn"); + const chatContainer = document.getElementById("chat-container"); + const chatInput = document.getElementById("chat-input"); + const chatSendBtn = document.getElementById("chat-send"); + const footer = document.getElementById("footer"); + const contentContainerEl = document.querySelector(".content-container"); + + function escapeHtml(text) { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; + } + + function sanitizeHtml(html) { + const template = document.createElement("template"); + template.innerHTML = html; + + const allowedTags = new Set([ + "P", + "BR", + "HR", + "H1", + "H2", + "H3", + "H4", + "H5", + "H6", + "UL", + "OL", + "LI", + "STRONG", + "EM", + "CODE", + "PRE", + "BLOCKQUOTE", + "A", + "TABLE", + "THEAD", + "TBODY", + "TR", + "TH", + "TD", + ]); + + const walker = document.createTreeWalker( + template.content, + NodeFilter.SHOW_ELEMENT, + ); + const toReplace = []; + const toClean = []; + + while (walker.nextNode()) { + const el = walker.currentNode; + if (!allowedTags.has(el.tagName)) { + toReplace.push(el); + continue; + } + toClean.push(el); + } + + for (const el of toReplace) { + const safeText = document.createTextNode(el.textContent || ""); + el.replaceWith(safeText); + } + + for (const el of toClean) { + const attrs = [...el.attributes]; + for (const attr of attrs) { + const name = attr.name.toLowerCase(); + const value = attr.value || ""; + if (name.startsWith("on")) { + el.removeAttribute(attr.name); + continue; + } + if (el.tagName === "A" && name === "href") { + if (!value.startsWith("http://") && !value.startsWith("https://")) { + el.removeAttribute("href"); + } else { + el.setAttribute("target", "_blank"); + el.setAttribute("rel", "noopener noreferrer"); + } + continue; + } + if (el.tagName === "A" && (name === "target" || name === "rel")) { + continue; + } + el.removeAttribute(attr.name); + } + } + + return template.innerHTML; + } + + function renderMarkdown(text) { + if (typeof marked === "undefined" || !marked.parse) { + return escapeHtml(text).replace(/\n/g, "
UCMMnq?yIR4yu&VwfmtowW98UKm=slNE8M84Zf{@dHW
z*_J!E_rEna+pVe9YR}DKD(5y)HyoUiRN`UfEmm
zvwEe9WtsXki*xJ}qL1&3)+guR-2K?U)4!)q@rRlA+ss32wg<%Axw+?6GxNFMCL23B
z7 wj*?!(1n}3_vi2b-<
zm|g!-{cz!{@@t+?kCse2n`_Z;c_XFq;#^0S)|v)S1t#C%58L1D{vdtOYI}R>eXbu~
zvep3~*3T>XxoF-4BhyoFran2d#OKVOycw3v41$eue_dtHgt_axcHWAMv!_+>-nOGqnB7(Q=e;b2
z1WuRKFa