Website for the Lede browser extension.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add hero iframe extension demo with streamed summary mock

+2072 -47
+151
public/extension-demo/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 + <meta name="robots" content="noindex" /> 7 + <title>Lede — interactive demo</title> 8 + <link rel="preconnect" href="https://fonts.googleapis.com" /> 9 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 10 + <link 11 + href="https://fonts.googleapis.com/css2?family=Sora:wght@500;600;700&display=swap" 12 + rel="stylesheet" 13 + /> 14 + <link rel="stylesheet" href="popup.css" /> 15 + </head> 16 + <body> 17 + <div class="app"> 18 + <div class="header"> 19 + <div class="header-left"> 20 + <div class="logo-mark"> 21 + <span class="brand-icon" aria-hidden="true"></span> 22 + </div> 23 + <div class="brand-stack"> 24 + <span class="logo-text" id="brand-name">Lede</span> 25 + </div> 26 + </div> 27 + <div class="header-right"> 28 + <button id="theme-btn" class="icon-btn" type="button" title="Toggle theme"> 29 + <svg 30 + id="theme-icon-light" 31 + width="15" 32 + height="15" 33 + viewBox="0 0 24 24" 34 + fill="none" 35 + stroke="currentColor" 36 + stroke-width="2" 37 + stroke-linecap="round" 38 + stroke-linejoin="round" 39 + > 40 + <circle cx="12" cy="12" r="5" /> 41 + <line x1="12" y1="1" x2="12" y2="3" /> 42 + <line x1="12" y1="21" x2="12" y2="23" /> 43 + <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" /> 44 + <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" /> 45 + <line x1="1" y1="12" x2="3" y2="12" /> 46 + <line x1="21" y1="12" x2="23" y2="12" /> 47 + <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" /> 48 + <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" /> 49 + </svg> 50 + <svg 51 + id="theme-icon-dark" 52 + width="15" 53 + height="15" 54 + viewBox="0 0 24 24" 55 + fill="none" 56 + stroke="currentColor" 57 + stroke-width="2" 58 + stroke-linecap="round" 59 + stroke-linejoin="round" 60 + class="hidden" 61 + > 62 + <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" /> 63 + </svg> 64 + <svg 65 + id="theme-icon-system" 66 + width="15" 67 + height="15" 68 + viewBox="0 0 24 24" 69 + fill="none" 70 + stroke="currentColor" 71 + stroke-width="2" 72 + stroke-linecap="round" 73 + stroke-linejoin="round" 74 + class="hidden" 75 + > 76 + <rect x="2" y="3" width="20" height="14" rx="2" ry="2" /> 77 + <line x1="8" y1="21" x2="16" y2="21" /> 78 + <line x1="12" y1="17" x2="12" y2="21" /> 79 + </svg> 80 + </button> 81 + <button id="settings-btn" class="icon-btn" type="button" title="Settings"> 82 + <svg 83 + width="15" 84 + height="15" 85 + viewBox="0 0 24 24" 86 + fill="none" 87 + stroke="currentColor" 88 + stroke-width="2" 89 + stroke-linecap="round" 90 + stroke-linejoin="round" 91 + > 92 + <circle cx="12" cy="12" r="3" /> 93 + <path 94 + d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" 95 + /> 96 + </svg> 97 + </button> 98 + </div> 99 + </div> 100 + 101 + <div class="content-container"> 102 + <div id="initial-state" class="initial-state"> 103 + <div class="initial-icon"> 104 + <span class="brand-icon" aria-hidden="true"></span> 105 + </div> 106 + <p class="initial-title" id="initial-title">Don't bury the lede</p> 107 + <p class="initial-sub" id="initial-sub"> 108 + Put the main point first—then ask anything about the page—with AI. 109 + </p> 110 + </div> 111 + <div id="result" class="result hidden"></div> 112 + </div> 113 + 114 + <div id="chat-container" class="chat-container hidden"> 115 + <div class="chat-input-wrap"> 116 + <input 117 + type="text" 118 + id="chat-input" 119 + class="chat-input" 120 + placeholder="Ask Lede about this page…" 121 + autocomplete="off" 122 + /> 123 + <button id="chat-send" class="chat-send-btn" type="button" title="Send"> 124 + <svg 125 + width="14" 126 + height="14" 127 + viewBox="0 0 24 24" 128 + fill="none" 129 + stroke="currentColor" 130 + stroke-width="2" 131 + stroke-linecap="round" 132 + stroke-linejoin="round" 133 + > 134 + <line x1="22" y1="2" x2="11" y2="13"></line> 135 + <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon> 136 + </svg> 137 + </button> 138 + </div> 139 + </div> 140 + 141 + <div class="footer" id="footer"> 142 + <button id="summarize-btn" class="footer-btn primary" type="button"> 143 + <span id="summarize-label">Quick Summary</span> 144 + </button> 145 + </div> 146 + </div> 147 + 148 + <script src="marked.min.js"></script> 149 + <script src="popup-demo.js"></script> 150 + </body> 151 + </html>
+9
public/extension-demo/lightning.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> 3 + <svg width="100%" height="100%" viewBox="0 0 571 991" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"> 4 + <g transform="matrix(1,0,0,1,-242.737675,-4.302677)"> 5 + <g transform="matrix(2.061595,0.357455,-0.357455,2.061595,-342.211752,-730.395512)"> 6 + <path d="M520.828,281.713L496.332,468.258L618.867,419.852C623.264,418.115 628.272,419.151 631.619,422.489C634.966,425.827 636.015,430.832 634.29,435.233C591.046,545.573 527.57,652.425 447.165,756.331C443.667,760.853 437.444,762.24 432.356,759.633C427.268,757.027 424.759,751.165 426.385,745.684L487.827,538.566L392.453,556.504C388.107,557.321 383.666,555.656 380.929,552.182C378.192,548.709 377.611,544.002 379.422,539.967L498.215,275.303C500.629,269.926 506.62,267.132 512.291,268.739C517.961,270.347 521.595,275.869 520.828,281.713Z" style="fill:rgb(241,91,47);"/> 7 + </g> 8 + </g> 9 + </svg>
+69
public/extension-demo/marked.min.js
··· 1 + /** 2 + * marked v15.0.12 - a markdown parser 3 + * Copyright (c) 2011-2025, Christopher Jeffrey. (MIT Licensed) 4 + * https://github.com/markedjs/marked 5 + */ 6 + 7 + /** 8 + * DO NOT EDIT THIS FILE 9 + * The code in this file is generated from files in ./src/ 10 + */ 11 + (function(g,f){if(typeof exports=="object"&&typeof module<"u"){module.exports=f()}else if("function"==typeof define && define.amd){define("marked",f)}else {g["marked"]=f()}}(typeof globalThis < "u" ? globalThis : typeof self < "u" ? self : this,function(){var exports={};var __exports=exports;var module={exports}; 12 + "use strict";var H=Object.defineProperty;var be=Object.getOwnPropertyDescriptor;var Te=Object.getOwnPropertyNames;var we=Object.prototype.hasOwnProperty;var ye=(l,e)=>{for(var t in e)H(l,t,{get:e[t],enumerable:!0})},Re=(l,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Te(e))!we.call(l,s)&&s!==t&&H(l,s,{get:()=>e[s],enumerable:!(n=be(e,s))||n.enumerable});return l};var Se=l=>Re(H({},"__esModule",{value:!0}),l);var kt={};ye(kt,{Hooks:()=>L,Lexer:()=>x,Marked:()=>E,Parser:()=>b,Renderer:()=>$,TextRenderer:()=>_,Tokenizer:()=>S,defaults:()=>w,getDefaults:()=>z,lexer:()=>ht,marked:()=>k,options:()=>it,parse:()=>pt,parseInline:()=>ct,parser:()=>ut,setOptions:()=>ot,use:()=>lt,walkTokens:()=>at});module.exports=Se(kt);function z(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}var w=z();function N(l){w=l}var I={exec:()=>null};function h(l,e=""){let t=typeof l=="string"?l:l.source,n={replace:(s,i)=>{let r=typeof i=="string"?i:i.source;return r=r.replace(m.caret,"$1"),t=t.replace(s,r),n},getRegex:()=>new RegExp(t,e)};return n}var m={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^<a /i,endATag:/^<\/a>/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^</,endAngleBracket:/>$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:l=>new RegExp(`^( {0,3}${l})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),hrRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}#`),htmlBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}<(?:[a-z].*>|!--)`,"i")},$e=/^(?:[ \t]*(?:\n|$))+/,_e=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,Le=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,O=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,ze=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,F=/(?:[*+-]|\d{1,9}[.)])/,ie=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,oe=h(ie).replace(/bull/g,F).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),Me=h(ie).replace(/bull/g,F).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),Q=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,Pe=/^[^\n]+/,U=/(?!\s*\])(?:\\.|[^\[\]\\])+/,Ae=h(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",U).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),Ee=h(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,F).getRegex(),v="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",K=/<!--(?:-?>|[\s\S]*?(?:-->|$))/,Ce=h("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))","i").replace("comment",K).replace("tag",v).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),le=h(Q).replace("hr",O).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex(),Ie=h(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",le).getRegex(),X={blockquote:Ie,code:_e,def:Ae,fences:Le,heading:ze,hr:O,html:Ce,lheading:oe,list:Ee,newline:$e,paragraph:le,table:I,text:Pe},re=h("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",O).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3} )[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex(),Oe={...X,lheading:Me,table:re,paragraph:h(Q).replace("hr",O).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",re).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex()},Be={...X,html:h(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment",K).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:I,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:h(Q).replace("hr",O).replace("heading",` *#{1,6} *[^ 13 + ]`).replace("lheading",oe).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},qe=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,ve=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,ae=/^( {2,}|\\)\n(?!\s*$)/,De=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,D=/[\p{P}\p{S}]/u,W=/[\s\p{P}\p{S}]/u,ce=/[^\s\p{P}\p{S}]/u,Ze=h(/^((?![*_])punctSpace)/,"u").replace(/punctSpace/g,W).getRegex(),pe=/(?!~)[\p{P}\p{S}]/u,Ge=/(?!~)[\s\p{P}\p{S}]/u,He=/(?:[^\s\p{P}\p{S}]|~)/u,Ne=/\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g,ue=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,je=h(ue,"u").replace(/punct/g,D).getRegex(),Fe=h(ue,"u").replace(/punct/g,pe).getRegex(),he="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",Qe=h(he,"gu").replace(/notPunctSpace/g,ce).replace(/punctSpace/g,W).replace(/punct/g,D).getRegex(),Ue=h(he,"gu").replace(/notPunctSpace/g,He).replace(/punctSpace/g,Ge).replace(/punct/g,pe).getRegex(),Ke=h("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,ce).replace(/punctSpace/g,W).replace(/punct/g,D).getRegex(),Xe=h(/\\(punct)/,"gu").replace(/punct/g,D).getRegex(),We=h(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),Je=h(K).replace("(?:-->|$)","-->").getRegex(),Ve=h("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment",Je).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),q=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,Ye=h(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label",q).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),ke=h(/^!?\[(label)\]\[(ref)\]/).replace("label",q).replace("ref",U).getRegex(),ge=h(/^!?\[(ref)\](?:\[\])?/).replace("ref",U).getRegex(),et=h("reflink|nolink(?!\\()","g").replace("reflink",ke).replace("nolink",ge).getRegex(),J={_backpedal:I,anyPunctuation:Xe,autolink:We,blockSkip:Ne,br:ae,code:ve,del:I,emStrongLDelim:je,emStrongRDelimAst:Qe,emStrongRDelimUnd:Ke,escape:qe,link:Ye,nolink:ge,punctuation:Ze,reflink:ke,reflinkSearch:et,tag:Ve,text:De,url:I},tt={...J,link:h(/^!?\[(label)\]\((.*?)\)/).replace("label",q).getRegex(),reflink:h(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",q).getRegex()},j={...J,emStrongRDelimAst:Ue,emStrongLDelim:Fe,url:h(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/},nt={...j,br:h(ae).replace("{2,}","*").getRegex(),text:h(j.text).replace("\\b_","\\b_| {2,}\\n").replace(/\{2,\}/g,"*").getRegex()},B={normal:X,gfm:Oe,pedantic:Be},P={normal:J,gfm:j,breaks:nt,pedantic:tt};var st={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},fe=l=>st[l];function R(l,e){if(e){if(m.escapeTest.test(l))return l.replace(m.escapeReplace,fe)}else if(m.escapeTestNoEncode.test(l))return l.replace(m.escapeReplaceNoEncode,fe);return l}function V(l){try{l=encodeURI(l).replace(m.percentDecode,"%")}catch{return null}return l}function Y(l,e){let t=l.replace(m.findPipe,(i,r,o)=>{let a=!1,c=r;for(;--c>=0&&o[c]==="\\";)a=!a;return a?"|":" |"}),n=t.split(m.splitPipe),s=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length<e;)n.push("");for(;s<n.length;s++)n[s]=n[s].trim().replace(m.slashPipe,"|");return n}function A(l,e,t){let n=l.length;if(n===0)return"";let s=0;for(;s<n;){let i=l.charAt(n-s-1);if(i===e&&!t)s++;else if(i!==e&&t)s++;else break}return l.slice(0,n-s)}function de(l,e){if(l.indexOf(e[1])===-1)return-1;let t=0;for(let n=0;n<l.length;n++)if(l[n]==="\\")n++;else if(l[n]===e[0])t++;else if(l[n]===e[1]&&(t--,t<0))return n;return t>0?-2:-1}function me(l,e,t,n,s){let i=e.href,r=e.title||null,o=l[1].replace(s.other.outputLinkReplace,"$1");n.state.inLink=!0;let a={type:l[0].charAt(0)==="!"?"image":"link",raw:t,href:i,title:r,text:o,tokens:n.inlineTokens(o)};return n.state.inLink=!1,a}function rt(l,e,t){let n=l.match(t.other.indentCodeCompensation);if(n===null)return e;let s=n[1];return e.split(` 14 + `).map(i=>{let r=i.match(t.other.beginningSpace);if(r===null)return i;let[o]=r;return o.length>=s.length?i.slice(s.length):i}).join(` 15 + `)}var S=class{options;rules;lexer;constructor(e){this.options=e||w}space(e){let t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){let t=this.rules.block.code.exec(e);if(t){let n=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?n:A(n,` 16 + `)}}}fences(e){let t=this.rules.block.fences.exec(e);if(t){let n=t[0],s=rt(n,t[3]||"",this.rules);return{type:"code",raw:n,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:s}}}heading(e){let t=this.rules.block.heading.exec(e);if(t){let n=t[2].trim();if(this.rules.other.endingHash.test(n)){let s=A(n,"#");(this.options.pedantic||!s||this.rules.other.endingSpaceChar.test(s))&&(n=s.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){let t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:A(t[0],` 17 + `)}}blockquote(e){let t=this.rules.block.blockquote.exec(e);if(t){let n=A(t[0],` 18 + `).split(` 19 + `),s="",i="",r=[];for(;n.length>0;){let o=!1,a=[],c;for(c=0;c<n.length;c++)if(this.rules.other.blockquoteStart.test(n[c]))a.push(n[c]),o=!0;else if(!o)a.push(n[c]);else break;n=n.slice(c);let p=a.join(` 20 + `),u=p.replace(this.rules.other.blockquoteSetextReplace,` 21 + $1`).replace(this.rules.other.blockquoteSetextReplace2,"");s=s?`${s} 22 + ${p}`:p,i=i?`${i} 23 + ${u}`:u;let d=this.lexer.state.top;if(this.lexer.state.top=!0,this.lexer.blockTokens(u,r,!0),this.lexer.state.top=d,n.length===0)break;let g=r.at(-1);if(g?.type==="code")break;if(g?.type==="blockquote"){let T=g,f=T.raw+` 24 + `+n.join(` 25 + `),y=this.blockquote(f);r[r.length-1]=y,s=s.substring(0,s.length-T.raw.length)+y.raw,i=i.substring(0,i.length-T.text.length)+y.text;break}else if(g?.type==="list"){let T=g,f=T.raw+` 26 + `+n.join(` 27 + `),y=this.list(f);r[r.length-1]=y,s=s.substring(0,s.length-g.raw.length)+y.raw,i=i.substring(0,i.length-T.raw.length)+y.raw,n=f.substring(r.at(-1).raw.length).split(` 28 + `);continue}}return{type:"blockquote",raw:s,tokens:r,text:i}}}list(e){let t=this.rules.block.list.exec(e);if(t){let n=t[1].trim(),s=n.length>1,i={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");let r=this.rules.other.listItemRegex(n),o=!1;for(;e;){let c=!1,p="",u="";if(!(t=r.exec(e))||this.rules.block.hr.test(e))break;p=t[0],e=e.substring(p.length);let d=t[2].split(` 29 + `,1)[0].replace(this.rules.other.listReplaceTabs,Z=>" ".repeat(3*Z.length)),g=e.split(` 30 + `,1)[0],T=!d.trim(),f=0;if(this.options.pedantic?(f=2,u=d.trimStart()):T?f=t[1].length+1:(f=t[2].search(this.rules.other.nonSpaceChar),f=f>4?1:f,u=d.slice(f),f+=t[1].length),T&&this.rules.other.blankLine.test(g)&&(p+=g+` 31 + `,e=e.substring(g.length+1),c=!0),!c){let Z=this.rules.other.nextBulletRegex(f),te=this.rules.other.hrRegex(f),ne=this.rules.other.fencesBeginRegex(f),se=this.rules.other.headingBeginRegex(f),xe=this.rules.other.htmlBeginRegex(f);for(;e;){let G=e.split(` 32 + `,1)[0],C;if(g=G,this.options.pedantic?(g=g.replace(this.rules.other.listReplaceNesting," "),C=g):C=g.replace(this.rules.other.tabCharGlobal," "),ne.test(g)||se.test(g)||xe.test(g)||Z.test(g)||te.test(g))break;if(C.search(this.rules.other.nonSpaceChar)>=f||!g.trim())u+=` 33 + `+C.slice(f);else{if(T||d.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4||ne.test(d)||se.test(d)||te.test(d))break;u+=` 34 + `+g}!T&&!g.trim()&&(T=!0),p+=G+` 35 + `,e=e.substring(G.length+1),d=C.slice(f)}}i.loose||(o?i.loose=!0:this.rules.other.doubleBlankLine.test(p)&&(o=!0));let y=null,ee;this.options.gfm&&(y=this.rules.other.listIsTask.exec(u),y&&(ee=y[0]!=="[ ] ",u=u.replace(this.rules.other.listReplaceTask,""))),i.items.push({type:"list_item",raw:p,task:!!y,checked:ee,loose:!1,text:u,tokens:[]}),i.raw+=p}let a=i.items.at(-1);if(a)a.raw=a.raw.trimEnd(),a.text=a.text.trimEnd();else return;i.raw=i.raw.trimEnd();for(let c=0;c<i.items.length;c++)if(this.lexer.state.top=!1,i.items[c].tokens=this.lexer.blockTokens(i.items[c].text,[]),!i.loose){let p=i.items[c].tokens.filter(d=>d.type==="space"),u=p.length>0&&p.some(d=>this.rules.other.anyLine.test(d.raw));i.loose=u}if(i.loose)for(let c=0;c<i.items.length;c++)i.items[c].loose=!0;return i}}html(e){let t=this.rules.block.html.exec(e);if(t)return{type:"html",block:!0,raw:t[0],pre:t[1]==="pre"||t[1]==="script"||t[1]==="style",text:t[0]}}def(e){let t=this.rules.block.def.exec(e);if(t){let n=t[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal," "),s=t[2]?t[2].replace(this.rules.other.hrefBrackets,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",i=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return{type:"def",tag:n,raw:t[0],href:s,title:i}}}table(e){let t=this.rules.block.table.exec(e);if(!t||!this.rules.other.tableDelimiter.test(t[2]))return;let n=Y(t[1]),s=t[2].replace(this.rules.other.tableAlignChars,"").split("|"),i=t[3]?.trim()?t[3].replace(this.rules.other.tableRowBlankLine,"").split(` 36 + `):[],r={type:"table",raw:t[0],header:[],align:[],rows:[]};if(n.length===s.length){for(let o of s)this.rules.other.tableAlignRight.test(o)?r.align.push("right"):this.rules.other.tableAlignCenter.test(o)?r.align.push("center"):this.rules.other.tableAlignLeft.test(o)?r.align.push("left"):r.align.push(null);for(let o=0;o<n.length;o++)r.header.push({text:n[o],tokens:this.lexer.inline(n[o]),header:!0,align:r.align[o]});for(let o of i)r.rows.push(Y(o,r.header.length).map((a,c)=>({text:a,tokens:this.lexer.inline(a),header:!1,align:r.align[c]})));return r}}lheading(e){let t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:t[2].charAt(0)==="="?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){let t=this.rules.block.paragraph.exec(e);if(t){let n=t[1].charAt(t[1].length-1)===` 37 + `?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:n,tokens:this.lexer.inline(n)}}}text(e){let t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){let t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){let t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){let t=this.rules.inline.link.exec(e);if(t){let n=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(n)){if(!this.rules.other.endAngleBracket.test(n))return;let r=A(n.slice(0,-1),"\\");if((n.length-r.length)%2===0)return}else{let r=de(t[2],"()");if(r===-2)return;if(r>-1){let a=(t[0].indexOf("!")===0?5:4)+t[1].length+r;t[2]=t[2].substring(0,r),t[0]=t[0].substring(0,a).trim(),t[3]=""}}let s=t[2],i="";if(this.options.pedantic){let r=this.rules.other.pedanticHrefTitle.exec(s);r&&(s=r[1],i=r[3])}else i=t[3]?t[3].slice(1,-1):"";return s=s.trim(),this.rules.other.startAngleBracket.test(s)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(n)?s=s.slice(1):s=s.slice(1,-1)),me(t,{href:s&&s.replace(this.rules.inline.anyPunctuation,"$1"),title:i&&i.replace(this.rules.inline.anyPunctuation,"$1")},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){let s=(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," "),i=t[s.toLowerCase()];if(!i){let r=n[0].charAt(0);return{type:"text",raw:r,text:r}}return me(n,i,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s||s[3]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){let r=[...s[0]].length-1,o,a,c=r,p=0,u=s[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(u.lastIndex=0,t=t.slice(-1*e.length+r);(s=u.exec(t))!=null;){if(o=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!o)continue;if(a=[...o].length,s[3]||s[4]){c+=a;continue}else if((s[5]||s[6])&&r%3&&!((r+a)%3)){p+=a;continue}if(c-=a,c>0)continue;a=Math.min(a,a+c+p);let d=[...s[0]][0].length,g=e.slice(0,r+s.index+d+a);if(Math.min(r,a)%2){let f=g.slice(1,-1);return{type:"em",raw:g,text:f,tokens:this.lexer.inlineTokens(f)}}let T=g.slice(2,-2);return{type:"strong",raw:g,text:T,tokens:this.lexer.inlineTokens(T)}}}}codespan(e){let t=this.rules.inline.code.exec(e);if(t){let n=t[2].replace(this.rules.other.newLineCharGlobal," "),s=this.rules.other.nonSpaceChar.test(n),i=this.rules.other.startingSpaceChar.test(n)&&this.rules.other.endingSpaceChar.test(n);return s&&i&&(n=n.substring(1,n.length-1)),{type:"codespan",raw:t[0],text:n}}}br(e){let t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){let t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){let t=this.rules.inline.autolink.exec(e);if(t){let n,s;return t[2]==="@"?(n=t[1],s="mailto:"+n):(n=t[1],s=n),{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let n,s;if(t[2]==="@")n=t[0],s="mailto:"+n;else{let i;do i=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??"";while(i!==t[0]);n=t[0],t[1]==="www."?s="http://"+t[0]:s=t[0]}return{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(e){let t=this.rules.inline.text.exec(e);if(t){let n=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:n}}}};var x=class l{tokens;options;state;tokenizer;inlineQueue;constructor(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||w,this.options.tokenizer=this.options.tokenizer||new S,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let t={other:m,block:B.normal,inline:P.normal};this.options.pedantic?(t.block=B.pedantic,t.inline=P.pedantic):this.options.gfm&&(t.block=B.gfm,this.options.breaks?t.inline=P.breaks:t.inline=P.gfm),this.tokenizer.rules=t}static get rules(){return{block:B,inline:P}}static lex(e,t){return new l(t).lex(e)}static lexInline(e,t){return new l(t).inlineTokens(e)}lex(e){e=e.replace(m.carriageReturn,` 38 + `),this.blockTokens(e,this.tokens);for(let t=0;t<this.inlineQueue.length;t++){let n=this.inlineQueue[t];this.inlineTokens(n.src,n.tokens)}return this.inlineQueue=[],this.tokens}blockTokens(e,t=[],n=!1){for(this.options.pedantic&&(e=e.replace(m.tabCharGlobal," ").replace(m.spaceLine,""));e;){let s;if(this.options.extensions?.block?.some(r=>(s=r.call({lexer:this},e,t))?(e=e.substring(s.raw.length),t.push(s),!0):!1))continue;if(s=this.tokenizer.space(e)){e=e.substring(s.raw.length);let r=t.at(-1);s.raw.length===1&&r!==void 0?r.raw+=` 39 + `:t.push(s);continue}if(s=this.tokenizer.code(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="paragraph"||r?.type==="text"?(r.raw+=` 40 + `+s.raw,r.text+=` 41 + `+s.text,this.inlineQueue.at(-1).src=r.text):t.push(s);continue}if(s=this.tokenizer.fences(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.heading(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.hr(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.blockquote(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.list(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.html(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.def(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="paragraph"||r?.type==="text"?(r.raw+=` 42 + `+s.raw,r.text+=` 43 + `+s.raw,this.inlineQueue.at(-1).src=r.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title});continue}if(s=this.tokenizer.table(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.lheading(e)){e=e.substring(s.raw.length),t.push(s);continue}let i=e;if(this.options.extensions?.startBlock){let r=1/0,o=e.slice(1),a;this.options.extensions.startBlock.forEach(c=>{a=c.call({lexer:this},o),typeof a=="number"&&a>=0&&(r=Math.min(r,a))}),r<1/0&&r>=0&&(i=e.substring(0,r+1))}if(this.state.top&&(s=this.tokenizer.paragraph(i))){let r=t.at(-1);n&&r?.type==="paragraph"?(r.raw+=` 44 + `+s.raw,r.text+=` 45 + `+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=r.text):t.push(s),n=i.length!==e.length,e=e.substring(s.raw.length);continue}if(s=this.tokenizer.text(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="text"?(r.raw+=` 46 + `+s.raw,r.text+=` 47 + `+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=r.text):t.push(s);continue}if(e){let r="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(r);break}else throw new Error(r)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n=e,s=null;if(this.tokens.links){let o=Object.keys(this.tokens.links);if(o.length>0)for(;(s=this.tokenizer.rules.inline.reflinkSearch.exec(n))!=null;)o.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(s=this.tokenizer.rules.inline.anyPunctuation.exec(n))!=null;)n=n.slice(0,s.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;(s=this.tokenizer.rules.inline.blockSkip.exec(n))!=null;)n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);let i=!1,r="";for(;e;){i||(r=""),i=!1;let o;if(this.options.extensions?.inline?.some(c=>(o=c.call({lexer:this},e,t))?(e=e.substring(o.raw.length),t.push(o),!0):!1))continue;if(o=this.tokenizer.escape(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.tag(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.link(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(o.raw.length);let c=t.at(-1);o.type==="text"&&c?.type==="text"?(c.raw+=o.raw,c.text+=o.text):t.push(o);continue}if(o=this.tokenizer.emStrong(e,n,r)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.codespan(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.br(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.del(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.autolink(e)){e=e.substring(o.raw.length),t.push(o);continue}if(!this.state.inLink&&(o=this.tokenizer.url(e))){e=e.substring(o.raw.length),t.push(o);continue}let a=e;if(this.options.extensions?.startInline){let c=1/0,p=e.slice(1),u;this.options.extensions.startInline.forEach(d=>{u=d.call({lexer:this},p),typeof u=="number"&&u>=0&&(c=Math.min(c,u))}),c<1/0&&c>=0&&(a=e.substring(0,c+1))}if(o=this.tokenizer.inlineText(a)){e=e.substring(o.raw.length),o.raw.slice(-1)!=="_"&&(r=o.raw.slice(-1)),i=!0;let c=t.at(-1);c?.type==="text"?(c.raw+=o.raw,c.text+=o.text):t.push(o);continue}if(e){let c="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(c);break}else throw new Error(c)}}return t}};var $=class{options;parser;constructor(e){this.options=e||w}space(e){return""}code({text:e,lang:t,escaped:n}){let s=(t||"").match(m.notSpaceStart)?.[0],i=e.replace(m.endingNewline,"")+` 48 + `;return s?'<pre><code class="language-'+R(s)+'">'+(n?i:R(i,!0))+`</code></pre> 49 + `:"<pre><code>"+(n?i:R(i,!0))+`</code></pre> 50 + `}blockquote({tokens:e}){return`<blockquote> 51 + ${this.parser.parse(e)}</blockquote> 52 + `}html({text:e}){return e}heading({tokens:e,depth:t}){return`<h${t}>${this.parser.parseInline(e)}</h${t}> 53 + `}hr(e){return`<hr> 54 + `}list(e){let t=e.ordered,n=e.start,s="";for(let o=0;o<e.items.length;o++){let a=e.items[o];s+=this.listitem(a)}let i=t?"ol":"ul",r=t&&n!==1?' start="'+n+'"':"";return"<"+i+r+`> 55 + `+s+"</"+i+`> 56 + `}listitem(e){let t="";if(e.task){let n=this.checkbox({checked:!!e.checked});e.loose?e.tokens[0]?.type==="paragraph"?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&e.tokens[0].tokens[0].type==="text"&&(e.tokens[0].tokens[0].text=n+" "+R(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=!0)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:!0}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`<li>${t}</li> 57 + `}checkbox({checked:e}){return"<input "+(e?'checked="" ':"")+'disabled="" type="checkbox">'}paragraph({tokens:e}){return`<p>${this.parser.parseInline(e)}</p> 58 + `}table(e){let t="",n="";for(let i=0;i<e.header.length;i++)n+=this.tablecell(e.header[i]);t+=this.tablerow({text:n});let s="";for(let i=0;i<e.rows.length;i++){let r=e.rows[i];n="";for(let o=0;o<r.length;o++)n+=this.tablecell(r[o]);s+=this.tablerow({text:n})}return s&&(s=`<tbody>${s}</tbody>`),`<table> 59 + <thead> 60 + `+t+`</thead> 61 + `+s+`</table> 62 + `}tablerow({text:e}){return`<tr> 63 + ${e}</tr> 64 + `}tablecell(e){let t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`</${n}> 65 + `}strong({tokens:e}){return`<strong>${this.parser.parseInline(e)}</strong>`}em({tokens:e}){return`<em>${this.parser.parseInline(e)}</em>`}codespan({text:e}){return`<code>${R(e,!0)}</code>`}br(e){return"<br>"}del({tokens:e}){return`<del>${this.parser.parseInline(e)}</del>`}link({href:e,title:t,tokens:n}){let s=this.parser.parseInline(n),i=V(e);if(i===null)return s;e=i;let r='<a href="'+e+'"';return t&&(r+=' title="'+R(t)+'"'),r+=">"+s+"</a>",r}image({href:e,title:t,text:n,tokens:s}){s&&(n=this.parser.parseInline(s,this.parser.textRenderer));let i=V(e);if(i===null)return R(n);e=i;let r=`<img src="${e}" alt="${n}"`;return t&&(r+=` title="${R(t)}"`),r+=">",r}text(e){return"tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):"escaped"in e&&e.escaped?e.text:R(e.text)}};var _=class{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return""+e}image({text:e}){return""+e}br(){return""}};var b=class l{options;renderer;textRenderer;constructor(e){this.options=e||w,this.options.renderer=this.options.renderer||new $,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new _}static parse(e,t){return new l(t).parse(e)}static parseInline(e,t){return new l(t).parseInline(e)}parse(e,t=!0){let n="";for(let s=0;s<e.length;s++){let i=e[s];if(this.options.extensions?.renderers?.[i.type]){let o=i,a=this.options.extensions.renderers[o.type].call({parser:this},o);if(a!==!1||!["space","hr","heading","code","table","blockquote","list","html","paragraph","text"].includes(o.type)){n+=a||"";continue}}let r=i;switch(r.type){case"space":{n+=this.renderer.space(r);continue}case"hr":{n+=this.renderer.hr(r);continue}case"heading":{n+=this.renderer.heading(r);continue}case"code":{n+=this.renderer.code(r);continue}case"table":{n+=this.renderer.table(r);continue}case"blockquote":{n+=this.renderer.blockquote(r);continue}case"list":{n+=this.renderer.list(r);continue}case"html":{n+=this.renderer.html(r);continue}case"paragraph":{n+=this.renderer.paragraph(r);continue}case"text":{let o=r,a=this.renderer.text(o);for(;s+1<e.length&&e[s+1].type==="text";)o=e[++s],a+=` 66 + `+this.renderer.text(o);t?n+=this.renderer.paragraph({type:"paragraph",raw:a,text:a,tokens:[{type:"text",raw:a,text:a,escaped:!0}]}):n+=a;continue}default:{let o='Token with "'+r.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}}return n}parseInline(e,t=this.renderer){let n="";for(let s=0;s<e.length;s++){let i=e[s];if(this.options.extensions?.renderers?.[i.type]){let o=this.options.extensions.renderers[i.type].call({parser:this},i);if(o!==!1||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(i.type)){n+=o||"";continue}}let r=i;switch(r.type){case"escape":{n+=t.text(r);break}case"html":{n+=t.html(r);break}case"link":{n+=t.link(r);break}case"image":{n+=t.image(r);break}case"strong":{n+=t.strong(r);break}case"em":{n+=t.em(r);break}case"codespan":{n+=t.codespan(r);break}case"br":{n+=t.br(r);break}case"del":{n+=t.del(r);break}case"text":{n+=t.text(r);break}default:{let o='Token with "'+r.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}}return n}};var L=class{options;block;constructor(e){this.options=e||w}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}provideLexer(){return this.block?x.lex:x.lexInline}provideParser(){return this.block?b.parse:b.parseInline}};var E=class{defaults=z();options=this.setOptions;parse=this.parseMarkdown(!0);parseInline=this.parseMarkdown(!1);Parser=b;Renderer=$;TextRenderer=_;Lexer=x;Tokenizer=S;Hooks=L;constructor(...e){this.use(...e)}walkTokens(e,t){let n=[];for(let s of e)switch(n=n.concat(t.call(this,s)),s.type){case"table":{let i=s;for(let r of i.header)n=n.concat(this.walkTokens(r.tokens,t));for(let r of i.rows)for(let o of r)n=n.concat(this.walkTokens(o.tokens,t));break}case"list":{let i=s;n=n.concat(this.walkTokens(i.items,t));break}default:{let i=s;this.defaults.extensions?.childTokens?.[i.type]?this.defaults.extensions.childTokens[i.type].forEach(r=>{let o=i[r].flat(1/0);n=n.concat(this.walkTokens(o,t))}):i.tokens&&(n=n.concat(this.walkTokens(i.tokens,t)))}}return n}use(...e){let t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(n=>{let s={...n};if(s.async=this.defaults.async||s.async||!1,n.extensions&&(n.extensions.forEach(i=>{if(!i.name)throw new Error("extension name required");if("renderer"in i){let r=t.renderers[i.name];r?t.renderers[i.name]=function(...o){let a=i.renderer.apply(this,o);return a===!1&&(a=r.apply(this,o)),a}:t.renderers[i.name]=i.renderer}if("tokenizer"in i){if(!i.level||i.level!=="block"&&i.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let r=t[i.level];r?r.unshift(i.tokenizer):t[i.level]=[i.tokenizer],i.start&&(i.level==="block"?t.startBlock?t.startBlock.push(i.start):t.startBlock=[i.start]:i.level==="inline"&&(t.startInline?t.startInline.push(i.start):t.startInline=[i.start]))}"childTokens"in i&&i.childTokens&&(t.childTokens[i.name]=i.childTokens)}),s.extensions=t),n.renderer){let i=this.defaults.renderer||new $(this.defaults);for(let r in n.renderer){if(!(r in i))throw new Error(`renderer '${r}' does not exist`);if(["options","parser"].includes(r))continue;let o=r,a=n.renderer[o],c=i[o];i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u||""}}s.renderer=i}if(n.tokenizer){let i=this.defaults.tokenizer||new S(this.defaults);for(let r in n.tokenizer){if(!(r in i))throw new Error(`tokenizer '${r}' does not exist`);if(["options","rules","lexer"].includes(r))continue;let o=r,a=n.tokenizer[o],c=i[o];i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u}}s.tokenizer=i}if(n.hooks){let i=this.defaults.hooks||new L;for(let r in n.hooks){if(!(r in i))throw new Error(`hook '${r}' does not exist`);if(["options","block"].includes(r))continue;let o=r,a=n.hooks[o],c=i[o];L.passThroughHooks.has(r)?i[o]=p=>{if(this.defaults.async)return Promise.resolve(a.call(i,p)).then(d=>c.call(i,d));let u=a.call(i,p);return c.call(i,u)}:i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u}}s.hooks=i}if(n.walkTokens){let i=this.defaults.walkTokens,r=n.walkTokens;s.walkTokens=function(o){let a=[];return a.push(r.call(this,o)),i&&(a=a.concat(i.call(this,o))),a}}this.defaults={...this.defaults,...s}}),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return x.lex(e,t??this.defaults)}parser(e,t){return b.parse(e,t??this.defaults)}parseMarkdown(e){return(n,s)=>{let i={...s},r={...this.defaults,...i},o=this.onError(!!r.silent,!!r.async);if(this.defaults.async===!0&&i.async===!1)return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof n>"u"||n===null)return o(new Error("marked(): input parameter is undefined or null"));if(typeof n!="string")return o(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(n)+", string expected"));r.hooks&&(r.hooks.options=r,r.hooks.block=e);let a=r.hooks?r.hooks.provideLexer():e?x.lex:x.lexInline,c=r.hooks?r.hooks.provideParser():e?b.parse:b.parseInline;if(r.async)return Promise.resolve(r.hooks?r.hooks.preprocess(n):n).then(p=>a(p,r)).then(p=>r.hooks?r.hooks.processAllTokens(p):p).then(p=>r.walkTokens?Promise.all(this.walkTokens(p,r.walkTokens)).then(()=>p):p).then(p=>c(p,r)).then(p=>r.hooks?r.hooks.postprocess(p):p).catch(o);try{r.hooks&&(n=r.hooks.preprocess(n));let p=a(n,r);r.hooks&&(p=r.hooks.processAllTokens(p)),r.walkTokens&&this.walkTokens(p,r.walkTokens);let u=c(p,r);return r.hooks&&(u=r.hooks.postprocess(u)),u}catch(p){return o(p)}}}onError(e,t){return n=>{if(n.message+=` 67 + Please report this to https://github.com/markedjs/marked.`,e){let s="<p>An error occurred:</p><pre>"+R(n.message+"",!0)+"</pre>";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; 68 + 69 + if(__exports != exports)module.exports = exports;return module.exports}));
+495
public/extension-demo/popup-demo.js
··· 1 + /** 2 + * Standalone marketing demo: same markup/CSS as the extension popup, mocked data only. 3 + * Sync with webai-summarizer/popup when the real UI changes (popup.html, popup.css). 4 + */ 5 + (function () { 6 + const THEMES = ["light", "dark", "system"]; 7 + const DEFAULT_SUGGESTION = "Why would this be worth reading?"; 8 + const STATIC_SUGGESTIONS = [ 9 + "What should I skip on this page?", 10 + "Who is this written for?", 11 + ]; 12 + 13 + const SAMPLE_SUMMARY = `## The lede 14 + 15 + This is the **same chrome** as the Lede extension: summarize the active tab, then chat with answers grounded in that text. 16 + 17 + - **Quick Summary** is real in the build you install—here it’s sample copy. 18 + - **Chat** uses placeholder replies so you can try the layout without an API key. 19 + - Use **Regenerate** after a summary to replay the flow.`; 20 + 21 + const ASSISTANT_REPLIES = [ 22 + "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.", 23 + "After you install, open **Settings** (this demo’s gear only shows a hint) to point at **Ollama** or your own API and model.", 24 + "Try **Regenerate** on the summary card to replay the streamed summary—useful when a page updates underneath you.", 25 + ]; 26 + 27 + let currentTheme = "system"; 28 + let isLoading = false; 29 + let isChatLoading = false; 30 + let chatReplyGeneration = 0; 31 + let assistantTurn = 0; 32 + let demoStreamGen = 0; 33 + let quickSummary = ""; 34 + const chatHistory = []; 35 + /** Pretend page extraction succeeded (real popup gates chat on this). */ 36 + const currentPageContent = "demo"; 37 + 38 + const resultContainer = document.getElementById("result"); 39 + const initialState = document.getElementById("initial-state"); 40 + const summarizeBtn = document.getElementById("summarize-btn"); 41 + const summarizeLabel = document.getElementById("summarize-label"); 42 + const settingsBtn = document.getElementById("settings-btn"); 43 + const themeBtn = document.getElementById("theme-btn"); 44 + const chatContainer = document.getElementById("chat-container"); 45 + const chatInput = document.getElementById("chat-input"); 46 + const chatSendBtn = document.getElementById("chat-send"); 47 + const footer = document.getElementById("footer"); 48 + const contentContainerEl = document.querySelector(".content-container"); 49 + 50 + function escapeHtml(text) { 51 + const div = document.createElement("div"); 52 + div.textContent = text; 53 + return div.innerHTML; 54 + } 55 + 56 + function sanitizeHtml(html) { 57 + const template = document.createElement("template"); 58 + template.innerHTML = html; 59 + 60 + const allowedTags = new Set([ 61 + "P", 62 + "BR", 63 + "HR", 64 + "H1", 65 + "H2", 66 + "H3", 67 + "H4", 68 + "H5", 69 + "H6", 70 + "UL", 71 + "OL", 72 + "LI", 73 + "STRONG", 74 + "EM", 75 + "CODE", 76 + "PRE", 77 + "BLOCKQUOTE", 78 + "A", 79 + "TABLE", 80 + "THEAD", 81 + "TBODY", 82 + "TR", 83 + "TH", 84 + "TD", 85 + ]); 86 + 87 + const walker = document.createTreeWalker( 88 + template.content, 89 + NodeFilter.SHOW_ELEMENT, 90 + ); 91 + const toReplace = []; 92 + const toClean = []; 93 + 94 + while (walker.nextNode()) { 95 + const el = walker.currentNode; 96 + if (!allowedTags.has(el.tagName)) { 97 + toReplace.push(el); 98 + continue; 99 + } 100 + toClean.push(el); 101 + } 102 + 103 + for (const el of toReplace) { 104 + const safeText = document.createTextNode(el.textContent || ""); 105 + el.replaceWith(safeText); 106 + } 107 + 108 + for (const el of toClean) { 109 + const attrs = [...el.attributes]; 110 + for (const attr of attrs) { 111 + const name = attr.name.toLowerCase(); 112 + const value = attr.value || ""; 113 + if (name.startsWith("on")) { 114 + el.removeAttribute(attr.name); 115 + continue; 116 + } 117 + if (el.tagName === "A" && name === "href") { 118 + if (!value.startsWith("http://") && !value.startsWith("https://")) { 119 + el.removeAttribute("href"); 120 + } else { 121 + el.setAttribute("target", "_blank"); 122 + el.setAttribute("rel", "noopener noreferrer"); 123 + } 124 + continue; 125 + } 126 + if (el.tagName === "A" && (name === "target" || name === "rel")) { 127 + continue; 128 + } 129 + el.removeAttribute(attr.name); 130 + } 131 + } 132 + 133 + return template.innerHTML; 134 + } 135 + 136 + function renderMarkdown(text) { 137 + if (typeof marked === "undefined" || !marked.parse) { 138 + return escapeHtml(text).replace(/\n/g, "<br>"); 139 + } 140 + try { 141 + const html = marked.parse(text, { 142 + breaks: true, 143 + gfm: true, 144 + headerIds: false, 145 + mangle: false, 146 + }); 147 + return sanitizeHtml(html); 148 + } catch { 149 + return escapeHtml(text).replace(/\n/g, "<br>"); 150 + } 151 + } 152 + 153 + function scrollContentToBottom() { 154 + if (!contentContainerEl) return; 155 + contentContainerEl.scrollTop = contentContainerEl.scrollHeight; 156 + } 157 + 158 + function showToast(message) { 159 + const toast = document.createElement("div"); 160 + toast.className = "toast"; 161 + toast.textContent = message; 162 + document.body.appendChild(toast); 163 + setTimeout(() => toast.remove(), 3200); 164 + } 165 + 166 + function applyTheme(theme) { 167 + const root = document.documentElement; 168 + const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; 169 + 170 + if (theme === "system") { 171 + root.setAttribute("data-theme", prefersDark ? "dark" : "light"); 172 + } else { 173 + root.setAttribute("data-theme", theme); 174 + } 175 + 176 + document 177 + .getElementById("theme-icon-light") 178 + .classList.toggle("hidden", theme !== "light"); 179 + document 180 + .getElementById("theme-icon-dark") 181 + .classList.toggle("hidden", theme !== "dark"); 182 + document 183 + .getElementById("theme-icon-system") 184 + .classList.toggle("hidden", theme !== "system"); 185 + 186 + const labels = { 187 + light: "Light mode", 188 + dark: "Dark mode", 189 + system: "System theme", 190 + }; 191 + themeBtn.title = labels[theme]; 192 + } 193 + 194 + function normalizeHexColor(value) { 195 + const raw = (value || "").trim().toUpperCase(); 196 + const withHash = raw.startsWith("#") ? raw : `#${raw}`; 197 + return /^#[0-9A-F]{6}$/.test(withHash) ? withHash : null; 198 + } 199 + 200 + function darkenHexColor(hexColor, amount) { 201 + const normalized = normalizeHexColor(hexColor); 202 + if (!normalized) return hexColor; 203 + const r = parseInt(normalized.slice(1, 3), 16); 204 + const g = parseInt(normalized.slice(3, 5), 16); 205 + const b = parseInt(normalized.slice(5, 7), 16); 206 + const factor = 1 - amount; 207 + const toHex = (n) => 208 + Math.round(Math.max(0, Math.min(255, n * factor))) 209 + .toString(16) 210 + .padStart(2, "0") 211 + .toUpperCase(); 212 + return `#${toHex(r)}${toHex(g)}${toHex(b)}`; 213 + } 214 + 215 + function applyAccentColor(color) { 216 + const normalized = normalizeHexColor(color) || "#F15B2F"; 217 + document.documentElement.style.setProperty("--brand", normalized); 218 + document.documentElement.style.setProperty( 219 + "--brand-hover", 220 + darkenHexColor(normalized, 0.1), 221 + ); 222 + document.documentElement.style.setProperty( 223 + "--brand-active", 224 + darkenHexColor(normalized, 0.2), 225 + ); 226 + } 227 + 228 + function setSummarizeLabel(text) { 229 + summarizeLabel.textContent = text; 230 + } 231 + 232 + function setLoading(loading, busyFooterLabel) { 233 + isLoading = loading; 234 + summarizeBtn.disabled = loading; 235 + if (loading) { 236 + setSummarizeLabel(busyFooterLabel || "Summarizing…"); 237 + } else { 238 + setSummarizeLabel(quickSummary ? "Regenerate" : "Quick Summary"); 239 + } 240 + } 241 + 242 + function removeChatSuggestionsUI() { 243 + resultContainer.querySelector(".chat-suggestions")?.remove(); 244 + } 245 + 246 + function ensureChatSection() { 247 + let chatSection = resultContainer.querySelector(".chat-section"); 248 + if (!chatSection) { 249 + chatSection = document.createElement("div"); 250 + chatSection.className = "chat-section"; 251 + const divider = document.createElement("hr"); 252 + divider.className = "chat-divider"; 253 + chatSection.appendChild(divider); 254 + resultContainer.appendChild(chatSection); 255 + } 256 + return chatSection; 257 + } 258 + 259 + function appendUserChatBubble(text) { 260 + const chatSection = ensureChatSection(); 261 + const msgEl = document.createElement("div"); 262 + msgEl.className = "chat-message user"; 263 + msgEl.innerHTML = renderMarkdown(text); 264 + chatSection.appendChild(msgEl); 265 + scrollContentToBottom(); 266 + } 267 + 268 + function renderSuggestions() { 269 + removeChatSuggestionsUI(); 270 + const allSuggestions = [DEFAULT_SUGGESTION, ...STATIC_SUGGESTIONS]; 271 + const suggestionsEl = document.createElement("div"); 272 + suggestionsEl.className = "chat-suggestions"; 273 + suggestionsEl.innerHTML = allSuggestions 274 + .map((s) => `<button type="button" class="suggestion-btn">${escapeHtml(s)}</button>`) 275 + .join(""); 276 + suggestionsEl.querySelectorAll(".suggestion-btn").forEach((btn) => { 277 + btn.addEventListener("click", () => { 278 + removeChatSuggestionsUI(); 279 + chatInput.value = btn.textContent; 280 + sendChatMessage(); 281 + }); 282 + }); 283 + resultContainer.appendChild(suggestionsEl); 284 + scrollContentToBottom(); 285 + } 286 + 287 + function showSummary(content) { 288 + initialState.classList.add("hidden"); 289 + resultContainer.classList.remove("hidden"); 290 + 291 + let htmlContent = ""; 292 + htmlContent += renderMarkdown(content); 293 + resultContainer.innerHTML = htmlContent; 294 + 295 + const buttonContainer = document.createElement("div"); 296 + buttonContainer.className = "summary-actions"; 297 + 298 + const copyBtnEl = document.createElement("button"); 299 + copyBtnEl.type = "button"; 300 + copyBtnEl.className = "copy-summary-btn"; 301 + copyBtnEl.innerHTML = ` 302 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 303 + <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/> 304 + <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> 305 + </svg> 306 + <span>Copy</span> 307 + `; 308 + copyBtnEl.addEventListener("click", async () => { 309 + if (!quickSummary) return; 310 + try { 311 + await navigator.clipboard.writeText(quickSummary); 312 + copyBtnEl.classList.add("copied"); 313 + copyBtnEl.innerHTML = ` 314 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 315 + <polyline points="20 6 9 17 4 12"/> 316 + </svg> 317 + <span>Copied</span> 318 + `; 319 + setTimeout(() => { 320 + copyBtnEl.classList.remove("copied"); 321 + copyBtnEl.innerHTML = ` 322 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 323 + <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/> 324 + <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> 325 + </svg> 326 + <span>Copy</span> 327 + `; 328 + }, 2000); 329 + } catch { 330 + showToast("Could not copy to clipboard."); 331 + } 332 + }); 333 + buttonContainer.appendChild(copyBtnEl); 334 + 335 + const regenerateBtnEl = document.createElement("button"); 336 + regenerateBtnEl.type = "button"; 337 + regenerateBtnEl.className = "copy-summary-btn"; 338 + regenerateBtnEl.innerHTML = ` 339 + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 340 + <polyline points="23 4 23 10 17 10"></polyline> 341 + <path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path> 342 + </svg> 343 + <span>Regenerate</span> 344 + `; 345 + regenerateBtnEl.addEventListener("click", () => { 346 + if (isLoading) return; 347 + chatHistory.length = 0; 348 + assistantTurn = 0; 349 + quickSummary = ""; 350 + chatContainer.classList.add("hidden"); 351 + chatInput.value = ""; 352 + runDemoSummary(); 353 + }); 354 + buttonContainer.appendChild(regenerateBtnEl); 355 + 356 + resultContainer.appendChild(buttonContainer); 357 + 358 + footer.classList.add("hidden"); 359 + chatContainer.classList.remove("hidden"); 360 + renderSuggestions(); 361 + scrollContentToBottom(); 362 + } 363 + 364 + function runDemoSummary() { 365 + if (isLoading) return; 366 + const gen = ++demoStreamGen; 367 + setLoading(true, "Streaming…"); 368 + initialState.classList.add("hidden"); 369 + resultContainer.classList.remove("hidden"); 370 + 371 + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { 372 + quickSummary = SAMPLE_SUMMARY; 373 + setLoading(false); 374 + showSummary(quickSummary); 375 + return; 376 + } 377 + 378 + const streamingEl = document.createElement("div"); 379 + streamingEl.className = "streaming-content"; 380 + streamingEl.innerHTML = ` 381 + <div class="streaming-placeholder"> 382 + <div class="pulse-dot"></div> 383 + <div class="pulse-dot"></div> 384 + <div class="pulse-dot"></div> 385 + </div> 386 + `; 387 + resultContainer.replaceChildren(streamingEl); 388 + requestAnimationFrame(() => scrollContentToBottom()); 389 + 390 + const full = SAMPLE_SUMMARY; 391 + let i = 0; 392 + 393 + function tick() { 394 + if (gen !== demoStreamGen) return; 395 + 396 + if (i >= full.length) { 397 + quickSummary = full; 398 + setLoading(false); 399 + showSummary(quickSummary); 400 + return; 401 + } 402 + 403 + const step = 4 + Math.floor(Math.random() * 8); 404 + i = Math.min(full.length, i + step); 405 + const slice = full.slice(0, i); 406 + const placeholder = streamingEl.querySelector(".streaming-placeholder"); 407 + if (placeholder && slice.trim()) placeholder.remove(); 408 + streamingEl.innerHTML = renderMarkdown(slice); 409 + scrollContentToBottom(); 410 + 411 + const delay = 4 + Math.floor(Math.random() * 10); 412 + window.setTimeout(tick, delay); 413 + } 414 + 415 + window.setTimeout(tick, 50); 416 + } 417 + 418 + async function sendChatMessage() { 419 + const message = chatInput.value.trim(); 420 + if (!message || isChatLoading || !currentPageContent) return; 421 + 422 + removeChatSuggestionsUI(); 423 + const gen = ++chatReplyGeneration; 424 + 425 + chatHistory.push({ role: "user", content: message }); 426 + chatInput.value = ""; 427 + appendUserChatBubble(message); 428 + 429 + isChatLoading = true; 430 + chatSendBtn.disabled = true; 431 + 432 + const chatSection = ensureChatSection(); 433 + const loadingEl = document.createElement("div"); 434 + loadingEl.className = "chat-message assistant loading"; 435 + loadingEl.dataset.chatPending = "1"; 436 + loadingEl.innerHTML = '<div class="chat-spinner"></div>'; 437 + chatSection.appendChild(loadingEl); 438 + scrollContentToBottom(); 439 + 440 + const delay = 520 + Math.floor(Math.random() * 480); 441 + window.setTimeout(() => { 442 + if (gen !== chatReplyGeneration) return; 443 + 444 + const reply = 445 + ASSISTANT_REPLIES[assistantTurn % ASSISTANT_REPLIES.length]; 446 + assistantTurn += 1; 447 + 448 + chatHistory.push({ role: "assistant", content: reply }); 449 + loadingEl.classList.remove("loading"); 450 + delete loadingEl.dataset.chatPending; 451 + loadingEl.innerHTML = renderMarkdown(reply); 452 + scrollContentToBottom(); 453 + 454 + isChatLoading = false; 455 + chatSendBtn.disabled = false; 456 + chatInput.focus(); 457 + }, delay); 458 + } 459 + 460 + summarizeBtn.addEventListener("click", () => { 461 + if (isLoading) return; 462 + runDemoSummary(); 463 + }); 464 + 465 + settingsBtn.addEventListener("click", () => { 466 + showToast("Demo only — open the installed extension for real settings."); 467 + }); 468 + 469 + themeBtn.addEventListener("click", () => { 470 + const idx = THEMES.indexOf(currentTheme); 471 + currentTheme = THEMES[(idx + 1) % THEMES.length]; 472 + applyTheme(currentTheme); 473 + }); 474 + 475 + window 476 + .matchMedia("(prefers-color-scheme: dark)") 477 + .addEventListener("change", () => { 478 + if (currentTheme === "system") applyTheme("system"); 479 + }); 480 + 481 + chatSendBtn.addEventListener("click", () => { 482 + if (isChatLoading) return; 483 + sendChatMessage(); 484 + }); 485 + 486 + chatInput.addEventListener("keydown", (e) => { 487 + if (e.key === "Enter" && !e.shiftKey) { 488 + e.preventDefault(); 489 + if (!isChatLoading) sendChatMessage(); 490 + } 491 + }); 492 + 493 + applyTheme(currentTheme); 494 + applyAccentColor("#F15B2F"); 495 + })();
+1252
public/extension-demo/popup.css
··· 1 + * { 2 + margin: 0; 3 + padding: 0; 4 + box-sizing: border-box; 5 + } 6 + 7 + /* ── CSS variables – Light (default) ── */ 8 + :root { 9 + --bg: #f5f0e8; 10 + --bg-subtle: #ede8de; 11 + --border: #e0d8cc; 12 + --border-hover: #c8c0b4; 13 + --text: #1a1a1a; 14 + --text-secondary: #2a2a2a; 15 + --text-muted: #aaa; 16 + --text-faint: #ccc; 17 + --text-em: #666; 18 + --icon-btn: #aaa; 19 + --icon-btn-hover: #555; 20 + --primary-bg: #1a1a1a; 21 + --primary-bg-hover: #333; 22 + --primary-bg-active: #000; 23 + --primary-text: #f5f0e8; 24 + --secondary-color: #aaa; 25 + --scrollbar: #d8d0c4; 26 + --spinner-track: #e0d8cc; 27 + --spinner-head: #888; 28 + --error-bg: #fdf0f0; 29 + --error-border: #f0d8d8; 30 + --error-text: #c05050; 31 + --toast-bg: #1a1a1a; 32 + --toast-text: #f5f0e8; 33 + --code-bg: #ede8de; 34 + --code-text: #555; 35 + --pre-text: #444; 36 + --table-th: #ede8de; 37 + --link: #555; 38 + --link-hover: #111; 39 + --heading: #111; 40 + --strong: #111; 41 + --blockquote: #888; 42 + --brand: #f15b2f; 43 + --brand-hover: #d94e27; 44 + --brand-active: #bf4522; 45 + 46 + /* Typography — modular rem scale (~1.25); Sora for chrome/headings, system UI for body */ 47 + --font-display: "Sora", system-ui, sans-serif; 48 + /* System sans for long reading — neutral and legible at small sizes */ 49 + --font-body: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; 50 + --font-mono: ui-monospace, "SF Mono", "Cascadia Code", monospace; 51 + --text-xs: 0.6875rem; 52 + --text-sm: 0.8125rem; 53 + --text-md: 0.875rem; 54 + --text-base: 1rem; 55 + --text-lg: 1.25rem; 56 + --text-xl: 1.5625rem; 57 + --lh-heading: 1.25; 58 + --lh-ui: 1.35; 59 + --lh-prose: 1.62; 60 + --lh-prose-dark: 1.7; 61 + 62 + /* Motion — main flow (summarize, stream, chat) */ 63 + --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1); 64 + --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1); 65 + --motion-enter: 0.32s; 66 + --motion-tap: 0.14s; 67 + --motion-toast: 0.22s; 68 + } 69 + 70 + /* ── Dark theme ── */ 71 + [data-theme="dark"] { 72 + --bg: #1a1a1a; 73 + --bg-subtle: #252525; 74 + --border: #2e2e2e; 75 + --border-hover: #444; 76 + --text: #e8e3db; 77 + --text-secondary: #d0cbc3; 78 + --text-muted: #666; 79 + --text-faint: #444; 80 + --text-em: #999; 81 + --icon-btn: #555; 82 + --icon-btn-hover: #bbb; 83 + --primary-bg: #e8e3db; 84 + --primary-bg-hover: #ccc8c0; 85 + --primary-bg-active: #fff; 86 + --primary-text: #1a1a1a; 87 + --secondary-color: #555; 88 + --scrollbar: #333; 89 + --spinner-track: #2e2e2e; 90 + --spinner-head: #888; 91 + --error-bg: #2a1a1a; 92 + --error-border: #4a2a2a; 93 + --error-text: #e08080; 94 + --toast-bg: #e8e3db; 95 + --toast-text: #1a1a1a; 96 + --code-bg: #252525; 97 + --code-text: #aaa; 98 + --pre-text: #bbb; 99 + --table-th: #252525; 100 + --link: #aaa; 101 + --link-hover: #e8e3db; 102 + --heading: #e8e3db; 103 + --strong: #e8e3db; 104 + --blockquote: #666; 105 + --brand: #f15b2f; 106 + --brand-hover: #d94e27; 107 + --brand-active: #bf4522; 108 + } 109 + 110 + html { 111 + font-size: 100%; 112 + } 113 + 114 + body { 115 + font-family: var(--font-body); 116 + font-size: var(--text-base); 117 + font-kerning: normal; 118 + width: 400px; 119 + height: 560px; 120 + background: var(--bg); 121 + color: var(--text); 122 + overflow: hidden; 123 + transition: 124 + background 0.15s, 125 + color 0.15s; 126 + } 127 + 128 + [data-theme="dark"] .result, 129 + [data-theme="dark"] .streaming-content, 130 + [data-theme="dark"] .chat-message.assistant, 131 + [data-theme="dark"] .chat-message.user { 132 + line-height: 1.75; 133 + } 134 + 135 + [data-theme="dark"] .result p, 136 + [data-theme="dark"] .streaming-content p, 137 + [data-theme="dark"] .chat-message.assistant p, 138 + [data-theme="dark"] .chat-message.user p { 139 + line-height: 1.75; 140 + } 141 + 142 + .app { 143 + display: flex; 144 + flex-direction: column; 145 + height: 100%; 146 + } 147 + 148 + /* ── Header ── */ 149 + .header { 150 + display: flex; 151 + justify-content: space-between; 152 + align-items: center; 153 + padding: 13px 18px; 154 + border-bottom: 1px solid var(--border); 155 + flex-shrink: 0; 156 + } 157 + 158 + .header-left { 159 + display: flex; 160 + align-items: center; 161 + gap: 7px; 162 + } 163 + 164 + .header-right { 165 + display: flex; 166 + align-items: center; 167 + gap: 2px; 168 + } 169 + 170 + .logo-mark { 171 + width: 13px; 172 + height: 13px; 173 + display: inline-flex; 174 + align-items: center; 175 + justify-content: center; 176 + } 177 + 178 + .logo-mark .brand-icon { 179 + width: 100%; 180 + height: 100%; 181 + display: block; 182 + background: var(--brand); 183 + -webkit-mask: url("lightning.svg") center / contain no-repeat; 184 + mask: url("lightning.svg") center / contain no-repeat; 185 + } 186 + 187 + .brand-stack { 188 + display: flex; 189 + flex-direction: column; 190 + gap: 2px; 191 + line-height: var(--lh-heading); 192 + } 193 + 194 + .logo-text { 195 + font-family: var(--font-display); 196 + font-size: var(--text-sm); 197 + font-weight: 600; 198 + color: var(--text); 199 + letter-spacing: -0.02em; 200 + } 201 + 202 + .icon-btn { 203 + background: transparent; 204 + border: none; 205 + cursor: pointer; 206 + color: var(--icon-btn); 207 + width: 28px; 208 + height: 28px; 209 + display: flex; 210 + align-items: center; 211 + justify-content: center; 212 + border-radius: 6px; 213 + transition: 214 + color var(--motion-tap) var(--ease-out-quart), 215 + transform var(--motion-tap) var(--ease-out-quart); 216 + flex-shrink: 0; 217 + } 218 + 219 + .icon-btn:hover { 220 + color: var(--icon-btn-hover); 221 + transform: scale(1.06); 222 + } 223 + 224 + .icon-btn:active { 225 + transform: scale(0.96); 226 + } 227 + 228 + /* ── Content area ── */ 229 + .content-container { 230 + flex: 1; 231 + min-height: 0; 232 + overflow-y: auto; 233 + padding: 18px; 234 + scrollbar-width: thin; 235 + scrollbar-color: var(--scrollbar) transparent; 236 + /* Isolate layout inside the transcript pane from the rest of the flex column. */ 237 + contain: layout; 238 + } 239 + 240 + .content-container::-webkit-scrollbar { 241 + width: 3px; 242 + } 243 + .content-container::-webkit-scrollbar-track { 244 + background: transparent; 245 + } 246 + .content-container::-webkit-scrollbar-thumb { 247 + background: var(--scrollbar); 248 + border-radius: 3px; 249 + } 250 + 251 + /* ── Initial state ── */ 252 + .initial-state { 253 + display: flex; 254 + flex-direction: column; 255 + align-items: flex-start; 256 + justify-content: flex-end; 257 + height: 100%; 258 + padding-bottom: 4px; 259 + gap: 8px; 260 + } 261 + 262 + .initial-state.hidden { 263 + display: none; 264 + } 265 + 266 + .initial-state:not(.hidden) .initial-icon { 267 + animation: fadeUp var(--motion-enter) var(--ease-out-quart) 0.04s both; 268 + } 269 + 270 + .initial-state:not(.hidden) .initial-title { 271 + animation: fadeUp var(--motion-enter) var(--ease-out-quart) 0.1s both; 272 + } 273 + 274 + .initial-state:not(.hidden) .initial-sub { 275 + animation: fadeUp var(--motion-enter) var(--ease-out-quart) 0.16s both; 276 + } 277 + 278 + .initial-icon { 279 + width: 18px; 280 + height: 18px; 281 + margin-bottom: 4px; 282 + } 283 + 284 + .initial-icon .brand-icon { 285 + width: 100%; 286 + height: 100%; 287 + display: block; 288 + background: var(--brand); 289 + -webkit-mask: url("lightning.svg") center / contain no-repeat; 290 + mask: url("lightning.svg") center / contain no-repeat; 291 + } 292 + 293 + .initial-title { 294 + font-family: var(--font-display); 295 + font-size: var(--text-lg); 296 + font-weight: 600; 297 + line-height: var(--lh-heading); 298 + letter-spacing: -0.02em; 299 + color: var(--text); 300 + } 301 + 302 + .initial-sub { 303 + font-family: var(--font-body); 304 + font-size: var(--text-sm); 305 + font-weight: 400; 306 + color: var(--text-secondary); 307 + line-height: var(--lh-ui); 308 + letter-spacing: 0; 309 + max-width: 38ch; 310 + } 311 + 312 + /* ── Loading ── */ 313 + .loading-wrap { 314 + display: flex; 315 + flex-direction: column; 316 + align-items: center; 317 + justify-content: center; 318 + height: 100%; 319 + gap: 12px; 320 + animation: fadeUp var(--motion-enter) var(--ease-out-quart) both; 321 + } 322 + 323 + .spinner { 324 + width: 18px; 325 + height: 18px; 326 + border: 1.5px solid var(--spinner-track); 327 + border-top-color: var(--spinner-head); 328 + border-radius: 50%; 329 + animation: spin 0.75s linear infinite; 330 + } 331 + 332 + @keyframes spin { 333 + to { 334 + transform: rotate(360deg); 335 + } 336 + } 337 + 338 + .loading-label { 339 + font-family: var(--font-display); 340 + font-size: var(--text-xs); 341 + font-weight: 600; 342 + color: var(--text-muted); 343 + letter-spacing: 0.08em; 344 + text-transform: uppercase; 345 + } 346 + 347 + /* ── Result ── */ 348 + .result { 349 + /* Summary body — same stack/size as chat Q&A */ 350 + font-family: var(--font-body); 351 + font-size: var(--text-md); 352 + line-height: 1.7; 353 + color: var(--text-secondary); 354 + max-width: 72ch; 355 + } 356 + 357 + .result.hidden { 358 + display: none; 359 + } 360 + 361 + /* ── Truncation Warning ── */ 362 + .truncation-warning { 363 + display: flex; 364 + align-items: center; 365 + gap: 8px; 366 + padding: 10px 12px; 367 + margin-bottom: 16px; 368 + background: var(--bg-subtle); 369 + border: 1px solid var(--border); 370 + border-radius: 6px; 371 + font-family: var(--font-body); 372 + font-size: var(--text-sm); 373 + font-weight: 400; 374 + color: var(--text-secondary); 375 + letter-spacing: 0; 376 + line-height: var(--lh-ui); 377 + animation: fadeUp var(--motion-enter) var(--ease-out-quart) both; 378 + } 379 + 380 + .truncation-warning svg { 381 + flex-shrink: 0; 382 + color: var(--text-muted); 383 + } 384 + 385 + @keyframes fadeUp { 386 + from { 387 + opacity: 0; 388 + transform: translateY(6px); 389 + } 390 + to { 391 + opacity: 1; 392 + transform: translateY(0); 393 + } 394 + } 395 + 396 + /* ── Markdown ── */ 397 + .result > * + * { 398 + margin-top: 10px; 399 + } 400 + 401 + .result > *:first-child { 402 + margin-top: 0; 403 + } 404 + 405 + .result h1, 406 + .result h2, 407 + .result h3, 408 + .result h4, 409 + .result h5, 410 + .result h6 { 411 + font-family: var(--font-display); 412 + font-weight: 600; 413 + color: var(--heading); 414 + line-height: var(--lh-heading); 415 + } 416 + 417 + .result h1 { 418 + font-size: var(--text-base); 419 + margin-top: 1rem; 420 + } 421 + .result h2 { 422 + font-size: var(--text-md); 423 + margin-top: 0.875rem; 424 + } 425 + .result h3 { 426 + font-size: var(--text-sm); 427 + margin-top: 0.75rem; 428 + } 429 + .result h4, 430 + .result h5, 431 + .result h6 { 432 + font-size: var(--text-sm); 433 + color: var(--text-muted); 434 + margin-top: 0.625rem; 435 + } 436 + 437 + .result p { 438 + line-height: inherit; 439 + } 440 + .result p + p { 441 + margin-top: 6px; 442 + } 443 + 444 + .result ul, 445 + .result ol { 446 + padding-left: 18px; 447 + } 448 + 449 + .result li { 450 + line-height: 1.6; 451 + } 452 + .result li + li { 453 + margin-top: 2px; 454 + } 455 + .result li::marker { 456 + color: var(--text-faint); 457 + } 458 + 459 + .result strong { 460 + font-weight: 600; 461 + color: var(--strong); 462 + } 463 + .result em { 464 + font-style: italic; 465 + color: var(--text-em); 466 + } 467 + 468 + .result code { 469 + font-family: var(--font-mono); 470 + font-size: var(--text-xs); 471 + background: var(--code-bg); 472 + padding: 1px 5px; 473 + border-radius: 3px; 474 + color: var(--code-text); 475 + } 476 + 477 + .result pre { 478 + background: var(--code-bg); 479 + padding: 10px 12px; 480 + border-radius: 5px; 481 + overflow-x: auto; 482 + } 483 + 484 + .result pre code { 485 + background: transparent; 486 + padding: 0; 487 + color: var(--pre-text); 488 + } 489 + 490 + .result blockquote { 491 + margin: 0; 492 + padding: 0.5rem 0.75rem; 493 + border: 1px solid var(--border); 494 + border-radius: 6px; 495 + background: var(--bg-subtle); 496 + color: var(--blockquote); 497 + font-style: italic; 498 + } 499 + 500 + .result blockquote + blockquote { 501 + margin-top: 6px; 502 + } 503 + 504 + .result a { 505 + color: var(--link); 506 + text-decoration: underline; 507 + } 508 + .result a:hover { 509 + color: var(--brand); 510 + } 511 + 512 + .result hr { 513 + border: none; 514 + border-top: 1px solid var(--border); 515 + } 516 + 517 + .result table { 518 + border-collapse: collapse; 519 + width: 100%; 520 + font-size: var(--text-sm); 521 + font-variant-numeric: tabular-nums; 522 + } 523 + 524 + .result th, 525 + .result td { 526 + border: 1px solid var(--border); 527 + padding: 5px 9px; 528 + text-align: left; 529 + } 530 + 531 + .result th { 532 + background: var(--table-th); 533 + font-weight: 600; 534 + } 535 + 536 + /* Streaming content - match .result styling for consistency */ 537 + .streaming-content { 538 + font-family: var(--font-body); 539 + font-size: var(--text-md); 540 + line-height: 1.7; 541 + color: var(--text-secondary); 542 + max-width: 72ch; 543 + animation: fadeUp var(--motion-enter) var(--ease-out-quart) both; 544 + } 545 + 546 + .streaming-content > * + * { 547 + margin-top: 10px; 548 + } 549 + 550 + .streaming-content > *:first-child { 551 + margin-top: 0; 552 + } 553 + 554 + /* Loading placeholder before first token */ 555 + .streaming-placeholder { 556 + display: flex; 557 + align-items: center; 558 + gap: 4px; 559 + padding: 4px 0; 560 + } 561 + 562 + .streaming-placeholder .pulse-dot { 563 + width: 6px; 564 + height: 6px; 565 + background: var(--text-muted); 566 + border-radius: 50%; 567 + animation: pulse-dot 1.4s ease-in-out infinite; 568 + } 569 + 570 + .streaming-placeholder .pulse-dot:nth-child(2) { 571 + animation-delay: 0.2s; 572 + } 573 + 574 + .streaming-placeholder .pulse-dot:nth-child(3) { 575 + animation-delay: 0.4s; 576 + } 577 + 578 + @keyframes pulse-dot { 579 + 0%, 580 + 80%, 581 + 100% { 582 + transform: scale(0.6); 583 + opacity: 0.4; 584 + } 585 + 40% { 586 + transform: scale(1); 587 + opacity: 1; 588 + } 589 + } 590 + 591 + /* ── Footer ── */ 592 + .footer { 593 + display: flex; 594 + padding: 11px 18px; 595 + border-top: 1px solid var(--border); 596 + flex-shrink: 0; 597 + align-items: center; 598 + } 599 + 600 + .footer-btn { 601 + display: flex; 602 + align-items: center; 603 + justify-content: center; 604 + padding: 9px 16px; 605 + border-radius: 8px; 606 + font-family: var(--font-display); 607 + font-size: var(--text-sm); 608 + font-weight: 500; 609 + cursor: pointer; 610 + transition: 611 + background var(--motion-tap) var(--ease-out-quart), 612 + color var(--motion-tap) var(--ease-out-quart), 613 + border-color var(--motion-tap) var(--ease-out-quart), 614 + transform var(--motion-tap) var(--ease-out-quart); 615 + border: 1px solid var(--border); 616 + flex: 1; 617 + background: var(--bg-subtle); 618 + color: var(--text-secondary); 619 + } 620 + 621 + .footer-btn:hover:not(:disabled) { 622 + background: var(--bg); 623 + color: var(--text); 624 + border-color: var(--border-hover); 625 + transform: translateY(-1px); 626 + } 627 + 628 + .footer-btn:active:not(:disabled) { 629 + background: var(--bg-subtle); 630 + transform: translateY(0) scale(0.985); 631 + } 632 + .footer-btn:disabled { 633 + opacity: 0.35; 634 + cursor: not-allowed; 635 + } 636 + 637 + .footer-btn.primary { 638 + background: transparent; 639 + color: var(--brand); 640 + border: 1px solid var(--brand); 641 + font-weight: 500; 642 + } 643 + 644 + .footer-btn.primary:hover:not(:disabled) { 645 + background: var(--brand); 646 + color: #fff; 647 + border-color: var(--brand); 648 + } 649 + 650 + .footer-btn.primary:active:not(:disabled) { 651 + background: var(--brand-active); 652 + border-color: var(--brand-active); 653 + color: #fff; 654 + transform: translateY(0) scale(0.985); 655 + } 656 + 657 + .footer-btn:focus-visible, 658 + .chat-send-btn:focus-visible, 659 + .icon-btn:focus-visible, 660 + .suggestion-btn:focus-visible, 661 + .copy-summary-btn:focus-visible { 662 + outline: 2px solid var(--brand); 663 + outline-offset: 2px; 664 + } 665 + 666 + .hidden { 667 + display: none !important; 668 + } 669 + 670 + /* ── Error ── */ 671 + .error-message { 672 + font-family: var(--font-body); 673 + background: var(--error-bg); 674 + border: 1px solid var(--error-border); 675 + color: var(--error-text); 676 + padding: 11px 14px; 677 + border-radius: 6px; 678 + font-size: var(--text-sm); 679 + font-weight: 400; 680 + line-height: var(--lh-ui); 681 + white-space: pre-wrap; 682 + animation: fadeUp var(--motion-enter) var(--ease-out-quart) both; 683 + } 684 + 685 + /* ── PDF Error ── */ 686 + .pdf-error { 687 + display: flex; 688 + flex-direction: column; 689 + align-items: center; 690 + justify-content: center; 691 + height: 100%; 692 + text-align: center; 693 + padding: 40px 20px; 694 + gap: 12px; 695 + } 696 + 697 + .pdf-error-icon { 698 + font-size: 3rem; 699 + font-weight: 300; 700 + color: var(--text-muted); 701 + margin-bottom: 8px; 702 + font-family: var(--font-display); 703 + } 704 + 705 + .pdf-error-title { 706 + font-family: var(--font-display); 707 + font-size: var(--text-base); 708 + font-weight: 600; 709 + color: var(--text); 710 + } 711 + 712 + .pdf-error-message { 713 + font-family: var(--font-body); 714 + font-size: var(--text-sm); 715 + font-weight: 400; 716 + color: var(--text-secondary); 717 + line-height: var(--lh-ui); 718 + letter-spacing: 0; 719 + max-width: 280px; 720 + } 721 + 722 + /* ── Unsupported extraction (YouTube, streaming, Bluesky, email UIs, etc.) ── */ 723 + .youtube-error { 724 + display: flex; 725 + flex-direction: column; 726 + align-items: center; 727 + justify-content: center; 728 + height: 100%; 729 + text-align: center; 730 + padding: 40px 20px; 731 + gap: 12px; 732 + } 733 + 734 + .youtube-error-icon { 735 + font-size: 3rem; 736 + font-weight: 300; 737 + color: var(--text-muted); 738 + margin-bottom: 8px; 739 + font-family: var(--font-display); 740 + } 741 + 742 + .youtube-error-title { 743 + font-family: var(--font-display); 744 + font-size: var(--text-base); 745 + font-weight: 600; 746 + color: var(--text); 747 + } 748 + 749 + .youtube-error-message { 750 + font-family: var(--font-body); 751 + font-size: var(--text-sm); 752 + font-weight: 400; 753 + color: var(--text-secondary); 754 + line-height: var(--lh-ui); 755 + letter-spacing: 0; 756 + max-width: 280px; 757 + } 758 + 759 + /* ── Toast ── */ 760 + .toast { 761 + position: fixed; 762 + bottom: 68px; 763 + left: 50%; 764 + transform: translateX(-50%); 765 + background: var(--toast-bg); 766 + color: var(--toast-text); 767 + padding: 6px 14px; 768 + border-radius: 20px; 769 + font-family: var(--font-body); 770 + font-size: var(--text-xs); 771 + font-weight: 500; 772 + z-index: 100; 773 + white-space: nowrap; 774 + animation: toast-in var(--motion-toast) var(--ease-out-quart) both; 775 + } 776 + 777 + @keyframes toast-in { 778 + from { 779 + opacity: 0; 780 + transform: translateX(-50%) translateY(6px); 781 + } 782 + to { 783 + opacity: 1; 784 + transform: translateX(-50%) translateY(0); 785 + } 786 + } 787 + 788 + /* ── Chat ── */ 789 + .chat-container { 790 + padding: 12px 18px; 791 + border-top: 1px solid var(--border); 792 + flex-shrink: 0; 793 + } 794 + 795 + .chat-container:not(.hidden) .chat-input-wrap { 796 + animation: barRise var(--motion-enter) var(--ease-out-quart) both; 797 + } 798 + 799 + .chat-container.hidden { 800 + display: none; 801 + } 802 + 803 + /* Chat suggestions */ 804 + .chat-suggestions { 805 + display: flex; 806 + flex-wrap: wrap; 807 + gap: 6px; 808 + margin-bottom: 10px; 809 + } 810 + 811 + .suggestion-btn { 812 + padding: 5px 10px; 813 + border-radius: 14px; 814 + border: 1px solid var(--border); 815 + background: var(--bg-subtle); 816 + color: var(--text-muted); 817 + font-family: var(--font-body); 818 + font-size: var(--text-xs); 819 + font-weight: 500; 820 + cursor: pointer; 821 + transition: 822 + border-color var(--motion-tap) var(--ease-out-quart), 823 + color var(--motion-tap) var(--ease-out-quart), 824 + background var(--motion-tap) var(--ease-out-quart), 825 + transform var(--motion-tap) var(--ease-out-quart); 826 + white-space: nowrap; 827 + animation: chipIn var(--motion-enter) var(--ease-out-quart) both; 828 + } 829 + 830 + .suggestion-btn:nth-child(1) { 831 + animation-delay: 0.04s; 832 + } 833 + .suggestion-btn:nth-child(2) { 834 + animation-delay: 0.1s; 835 + } 836 + .suggestion-btn:nth-child(3) { 837 + animation-delay: 0.16s; 838 + } 839 + .suggestion-btn:nth-child(4) { 840 + animation-delay: 0.22s; 841 + } 842 + 843 + .suggestion-btn:hover { 844 + border-color: var(--brand); 845 + color: var(--brand); 846 + background: var(--bg); 847 + transform: translateY(-1px); 848 + } 849 + 850 + .suggestion-btn:active { 851 + transform: translateY(0) scale(0.97); 852 + } 853 + 854 + @keyframes chipIn { 855 + from { 856 + opacity: 0; 857 + transform: translateY(5px); 858 + } 859 + to { 860 + opacity: 1; 861 + transform: translateY(0); 862 + } 863 + } 864 + 865 + @keyframes barRise { 866 + from { 867 + opacity: 0; 868 + transform: translateY(8px); 869 + } 870 + to { 871 + opacity: 1; 872 + transform: translateY(0); 873 + } 874 + } 875 + 876 + /* Suggestion skeleton placeholder */ 877 + .suggestion-skeleton { 878 + padding: 5px 10px; 879 + border-radius: 14px; 880 + border: 1px solid var(--border); 881 + background: var(--bg-subtle); 882 + width: 120px; 883 + height: 24px; 884 + animation: skeleton-pulse 1.5s ease-in-out infinite; 885 + } 886 + 887 + @keyframes skeleton-pulse { 888 + 0%, 889 + 100% { 890 + opacity: 0.4; 891 + } 892 + 50% { 893 + opacity: 0.7; 894 + } 895 + } 896 + 897 + /* Chat messages are rendered inside .result, sharing the same scroll */ 898 + .chat-divider { 899 + margin: 20px 0 16px 0; 900 + border: none; 901 + border-top: 1px solid var(--border); 902 + animation: dividerIn var(--motion-enter) var(--ease-out-quart) both; 903 + } 904 + 905 + @keyframes dividerIn { 906 + from { 907 + opacity: 0; 908 + } 909 + to { 910 + opacity: 1; 911 + } 912 + } 913 + 914 + /* Entrance only on new user bubbles — assistant streams in place (no post-stream rebuild). */ 915 + .chat-message + .chat-message { 916 + margin-top: 14px; 917 + } 918 + 919 + /* User messages — same body type as summary / assistant (Sora only in headings via markdown) */ 920 + .chat-message.user { 921 + font-family: var(--font-body); 922 + font-size: var(--text-md); 923 + line-height: 1.7; 924 + background: var(--primary-bg); 925 + color: var(--primary-text); 926 + padding: 10px 14px; 927 + border-radius: 12px; 928 + border-bottom-right-radius: 4px; 929 + /* Shrink-wrap short questions; cap width when text is long or multi-line. */ 930 + width: fit-content; 931 + max-width: 85%; 932 + margin-left: auto; 933 + transform-origin: center right; 934 + animation: bubbleIn var(--motion-enter) var(--ease-out-quint) both; 935 + } 936 + 937 + @keyframes bubbleIn { 938 + from { 939 + opacity: 0; 940 + transform: translateX(6px) scale(0.98); 941 + } 942 + to { 943 + opacity: 1; 944 + transform: translateX(0) scale(1); 945 + } 946 + } 947 + 948 + /* Assistant messages — match summary `.result` prose */ 949 + .chat-message.assistant { 950 + font-family: var(--font-body); 951 + font-size: var(--text-md); 952 + line-height: 1.7; 953 + color: var(--text-secondary); 954 + max-width: 72ch; 955 + } 956 + 957 + .chat-message.assistant.loading { 958 + display: flex; 959 + align-items: center; 960 + justify-content: center; 961 + padding: 20px 0; 962 + } 963 + 964 + .chat-message.assistant.error { 965 + background: var(--error-bg); 966 + border: 1px solid var(--error-border); 967 + color: var(--error-text); 968 + padding: 10px 14px; 969 + border-radius: 6px; 970 + white-space: pre-wrap; 971 + } 972 + 973 + .chat-spinner { 974 + width: 18px; 975 + height: 18px; 976 + border: 1.5px solid var(--spinner-track); 977 + border-top-color: var(--spinner-head); 978 + border-radius: 50%; 979 + animation: spin 0.75s linear infinite; 980 + } 981 + 982 + /* Chat message markdown (assistant uses same styles as .result) */ 983 + .chat-message.assistant > * + * { 984 + margin-top: 10px; 985 + } 986 + 987 + .chat-message.assistant h1, 988 + .chat-message.assistant h2, 989 + .chat-message.assistant h3, 990 + .chat-message.assistant h4, 991 + .chat-message.assistant h5, 992 + .chat-message.assistant h6 { 993 + font-family: var(--font-display); 994 + font-weight: 600; 995 + color: var(--heading); 996 + line-height: var(--lh-heading); 997 + } 998 + 999 + .chat-message.assistant h1 { 1000 + font-size: var(--text-base); 1001 + margin-top: 1rem; 1002 + } 1003 + .chat-message.assistant h2 { 1004 + font-size: var(--text-md); 1005 + margin-top: 0.875rem; 1006 + } 1007 + .chat-message.assistant h3 { 1008 + font-size: var(--text-sm); 1009 + margin-top: 0.75rem; 1010 + } 1011 + 1012 + .chat-message.assistant p { 1013 + line-height: inherit; 1014 + } 1015 + 1016 + .chat-message.assistant p + p { 1017 + margin-top: 6px; 1018 + } 1019 + 1020 + .chat-message.assistant ul, 1021 + .chat-message.assistant ol { 1022 + padding-left: 18px; 1023 + } 1024 + 1025 + .chat-message.assistant li { 1026 + line-height: 1.6; 1027 + } 1028 + 1029 + .chat-message.assistant li + li { 1030 + margin-top: 2px; 1031 + } 1032 + 1033 + .chat-message.assistant strong { 1034 + font-weight: 600; 1035 + color: var(--strong); 1036 + } 1037 + 1038 + .chat-message.assistant code { 1039 + font-family: var(--font-mono); 1040 + font-size: var(--text-xs); 1041 + background: var(--code-bg); 1042 + padding: 1px 5px; 1043 + border-radius: 3px; 1044 + color: var(--code-text); 1045 + } 1046 + 1047 + .chat-message.assistant pre { 1048 + background: var(--code-bg); 1049 + padding: 10px 12px; 1050 + border-radius: 5px; 1051 + overflow-x: auto; 1052 + } 1053 + 1054 + .chat-message.assistant pre code { 1055 + background: transparent; 1056 + padding: 0; 1057 + color: var(--pre-text); 1058 + } 1059 + 1060 + .chat-message.assistant blockquote { 1061 + margin: 0; 1062 + padding: 0.5rem 0.75rem; 1063 + border: 1px solid var(--border); 1064 + border-radius: 6px; 1065 + background: var(--bg-subtle); 1066 + color: var(--blockquote); 1067 + font-style: italic; 1068 + } 1069 + 1070 + .chat-message.assistant a { 1071 + color: var(--link); 1072 + text-decoration: underline; 1073 + } 1074 + 1075 + .chat-message.assistant a:hover { 1076 + color: var(--brand); 1077 + } 1078 + 1079 + /* User message markdown */ 1080 + .chat-message.user p { 1081 + margin: 0; 1082 + } 1083 + 1084 + .chat-message.user p + p { 1085 + margin-top: 4px; 1086 + } 1087 + 1088 + .chat-message.user strong { 1089 + font-weight: 600; 1090 + } 1091 + 1092 + .chat-input-wrap { 1093 + display: flex; 1094 + gap: 8px; 1095 + align-items: center; 1096 + } 1097 + 1098 + .chat-input { 1099 + flex: 1; 1100 + padding: 8px 12px; 1101 + border: 1px solid var(--border); 1102 + border-radius: 6px; 1103 + background: var(--bg); 1104 + color: var(--text); 1105 + /* Match `.result` summary body — same face as the page text above */ 1106 + font-family: var(--font-body); 1107 + font-size: var(--text-md); 1108 + outline: none; 1109 + transition: 1110 + border-color var(--motion-tap) var(--ease-out-quart), 1111 + box-shadow var(--motion-tap) var(--ease-out-quart); 1112 + } 1113 + 1114 + .chat-input:focus, 1115 + .chat-input:focus-visible { 1116 + outline: none; 1117 + border-color: var(--brand); 1118 + box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand) 22%, transparent); 1119 + } 1120 + 1121 + .chat-input::placeholder { 1122 + color: var(--text-muted); 1123 + } 1124 + 1125 + .chat-send-btn { 1126 + width: 32px; 1127 + height: 32px; 1128 + border-radius: 6px; 1129 + border: none; 1130 + background: var(--brand); 1131 + color: #fff; 1132 + cursor: pointer; 1133 + display: flex; 1134 + align-items: center; 1135 + justify-content: center; 1136 + transition: 1137 + background var(--motion-tap) var(--ease-out-quart), 1138 + transform var(--motion-tap) var(--ease-out-quart), 1139 + opacity var(--motion-tap) var(--ease-out-quart); 1140 + flex-shrink: 0; 1141 + } 1142 + 1143 + .chat-send-btn:hover:not(:disabled) { 1144 + background: var(--brand-hover); 1145 + transform: scale(1.05); 1146 + } 1147 + 1148 + .chat-send-btn:active:not(:disabled) { 1149 + transform: scale(0.94); 1150 + } 1151 + 1152 + .chat-send-btn:disabled { 1153 + opacity: 0.35; 1154 + cursor: not-allowed; 1155 + } 1156 + 1157 + .chat-send-btn.stop-streaming { 1158 + background: var(--icon-btn-hover); 1159 + } 1160 + 1161 + .chat-send-btn.stop-streaming:hover:not(:disabled) { 1162 + background: var(--text-em); 1163 + transform: scale(1.05); 1164 + } 1165 + 1166 + /* Summary action buttons container */ 1167 + .summary-actions { 1168 + display: flex; 1169 + flex-wrap: wrap; 1170 + gap: 8px; 1171 + margin-top: 16px; 1172 + } 1173 + 1174 + /* Copy/regenerate button under summary */ 1175 + .copy-summary-btn { 1176 + display: inline-flex; 1177 + align-items: center; 1178 + gap: 6px; 1179 + padding: 6px 12px; 1180 + border-radius: 6px; 1181 + border: 1px solid var(--border); 1182 + background: transparent; 1183 + color: var(--text-muted); 1184 + font-family: var(--font-body); 1185 + font-size: var(--text-sm); 1186 + font-weight: 500; 1187 + cursor: pointer; 1188 + transition: 1189 + border-color var(--motion-tap) var(--ease-out-quart), 1190 + color var(--motion-tap) var(--ease-out-quart), 1191 + background var(--motion-tap) var(--ease-out-quart), 1192 + transform var(--motion-tap) var(--ease-out-quart); 1193 + animation: fadeUp var(--motion-enter) var(--ease-out-quart) both; 1194 + } 1195 + 1196 + .summary-actions .copy-summary-btn:nth-child(2) { 1197 + animation-delay: 0.06s; 1198 + } 1199 + 1200 + .copy-summary-btn:hover { 1201 + border-color: var(--border-hover); 1202 + color: var(--text); 1203 + transform: translateY(-1px); 1204 + } 1205 + 1206 + .copy-summary-btn:active { 1207 + transform: translateY(0) scale(0.98); 1208 + } 1209 + 1210 + .copy-summary-btn.copied { 1211 + color: var(--text); 1212 + border-color: var(--border-hover); 1213 + } 1214 + 1215 + /* ── Reduced motion: keep feedback, drop travel / spin ── */ 1216 + @media (prefers-reduced-motion: reduce) { 1217 + :root { 1218 + --motion-enter: 0.01ms; 1219 + --motion-tap: 0.01ms; 1220 + --motion-toast: 0.01ms; 1221 + } 1222 + 1223 + .spinner, 1224 + .chat-spinner { 1225 + animation: none; 1226 + border-top-color: var(--spinner-head); 1227 + opacity: 0.88; 1228 + } 1229 + 1230 + .streaming-placeholder .pulse-dot { 1231 + animation: none; 1232 + opacity: 0.55; 1233 + } 1234 + 1235 + .suggestion-skeleton { 1236 + animation: none; 1237 + opacity: 0.55; 1238 + } 1239 + 1240 + .footer-btn:hover:not(:disabled), 1241 + .footer-btn:active:not(:disabled), 1242 + .suggestion-btn:hover, 1243 + .suggestion-btn:active, 1244 + .copy-summary-btn:hover, 1245 + .copy-summary-btn:active, 1246 + .icon-btn:hover, 1247 + .icon-btn:active, 1248 + .chat-send-btn:hover:not(:disabled), 1249 + .chat-send-btn:active:not(:disabled) { 1250 + transform: none; 1251 + } 1252 + }
public/images/hero.jpeg

This is a binary file and will not be displayed.

public/images/lede-usage-1.jpeg

This is a binary file and will not be displayed.

public/images/lede-usage-2.jpeg

This is a binary file and will not be displayed.

public/images/lede-usage-3.jpeg

This is a binary file and will not be displayed.

+18 -44
src/components/sections/HeroWithProof.astro
··· 1 1 --- 2 - /** 3 - * Hero imagery: add optional optimized assets (recommended ~1600–2400px wide, 3:2 or 16:10). 4 - * Drop files as `public/images/hero.avif` and `public/images/hero.webp` to replace the illustration 5 - * for LCP; `<picture>` sources are wired below when those files exist. 6 - */ 7 - import { existsSync } from "node:fs"; 8 - import path from "node:path"; 9 - 10 2 interface Props { 11 3 tangled: string; 12 4 } 13 5 14 6 const { tangled } = Astro.props; 15 - 16 - const imagesDir = path.join(process.cwd(), "public", "images"); 17 - const hasAvif = existsSync(path.join(imagesDir, "hero.avif")); 18 - const hasWebp = existsSync(path.join(imagesDir, "hero.webp")); 19 - 20 - const fallbackSrc = "/images/hero-illustration.svg"; 21 - const alt = 22 - "Lede extension popup over a browser page: summarize and ask questions grounded in the active tab."; 23 7 --- 24 8 25 9 <section class="hero-mast" aria-labelledby="hero-heading"> ··· 51 35 </p> 52 36 </div> 53 37 <figure class="hero-proof reveal-item" style="--i: 3"> 54 - <div class="hero-proof__frame"> 55 - { 56 - hasAvif || hasWebp ? ( 57 - <picture> 58 - {hasAvif ? <source type="image/avif" srcset="/images/hero.avif" /> : null} 59 - {hasWebp ? <source type="image/webp" srcset="/images/hero.webp" /> : null} 60 - <img 61 - src={hasWebp ? "/images/hero.webp" : "/images/hero.avif"} 62 - width="1200" 63 - height="750" 64 - alt={alt} 65 - decoding="async" 66 - fetchpriority="high" 67 - /> 68 - </picture> 69 - ) : ( 70 - <img 71 - src={fallbackSrc} 72 - width="1200" 73 - height="750" 74 - alt={alt} 75 - decoding="async" 76 - fetchpriority="high" 77 - /> 78 - ) 79 - } 38 + <div class="hero-proof__frame hero-proof__frame--demo"> 39 + <iframe 40 + id="extension-demo" 41 + class="extension-demo" 42 + src="/extension-demo/index.html" 43 + title="Interactive Lede popup demo: summarize and chat (mocked responses)" 44 + width="400" 45 + height="560" 46 + loading="eager"></iframe> 80 47 </div> 81 48 <figcaption class="hero-proof__caption"> 82 49 <span class="beta-tag">Beta</span> 83 50 <span class="hero-proof__capline"> 84 - Not in the Chrome Web Store or Firefox Add-ons yet—install from source, then wire Ollama or your API in 85 - settings. 51 + Interactive demo uses the same popup layout as the extension—mocked summary and chat only. 86 52 </span> 53 + <button type="button" class="demo-reset" id="extension-demo-reset">Reset demo</button> 87 54 </figcaption> 88 55 </figure> 89 56 </div> 90 57 </section> 58 + 59 + <script is:inline> 60 + document.getElementById("extension-demo-reset")?.addEventListener("click", function () { 61 + var frame = document.getElementById("extension-demo"); 62 + if (frame != null) frame.src = frame.src; 63 + }); 64 + </script>
+5
src/components/sections/UsageMicro.astro
··· 26 26 </div> 27 27 </li> 28 28 </ol> 29 + 30 + <p class="usage-afterword"> 31 + The hero above is a live shell of the extension popup—try theme, <strong>Quick Summary</strong>, and chat there; 32 + install from source for the real pipeline (page extraction, your model, settings). 33 + </p> 29 34 </section>
+5 -1
src/pages/index.astro
··· 19 19 ? `${base}/images/hero.webp` 20 20 : existsSync(path.join(imagesDir, "hero.avif")) 21 21 ? `${base}/images/hero.avif` 22 - : `${base}/images/hero-illustration.svg`; 22 + : existsSync(path.join(imagesDir, "hero.jpeg")) 23 + ? `${base}/images/hero.jpeg` 24 + : existsSync(path.join(imagesDir, "hero.jpg")) 25 + ? `${base}/images/hero.jpg` 26 + : `${base}/images/hero-illustration.svg`; 23 27 --- 24 28 25 29 <BaseLayout ogUrl={ogUrl} ogImage={ogImage}>
+68 -2
src/styles/global.css
··· 695 695 display: block; 696 696 } 697 697 698 + /* Demo: one surface only — no outer “device frame”; shadow reads like a browser popup. */ 699 + .hero-proof__frame--demo { 700 + padding: 0; 701 + display: flex; 702 + justify-content: center; 703 + align-items: flex-start; 704 + background: transparent; 705 + border: none; 706 + border-radius: 0; 707 + box-shadow: none; 708 + overflow: visible; 709 + animation: none; 710 + } 711 + 712 + .extension-demo { 713 + display: block; 714 + width: 400px; 715 + max-width: 100%; 716 + height: 560px; 717 + margin: 0 auto; 718 + border: 0; 719 + border-radius: 12px; 720 + color-scheme: light dark; 721 + box-shadow: 722 + 0 0 0 1px color-mix(in oklch, var(--color-ink) 7%, transparent), 723 + 0 10px 24px color-mix(in oklch, var(--color-ink) 8%, transparent), 724 + 0 28px 70px color-mix(in oklch, var(--color-ink) 12%, transparent); 725 + } 726 + 727 + @media (max-width: 440px) { 728 + .hero-proof__frame--demo { 729 + justify-content: flex-start; 730 + overflow-x: auto; 731 + overflow-y: visible; 732 + } 733 + 734 + .extension-demo { 735 + max-width: none; 736 + } 737 + } 738 + 739 + .demo-reset { 740 + margin: 0; 741 + padding: 0; 742 + border: none; 743 + background: none; 744 + font: inherit; 745 + font-size: var(--text-sm); 746 + color: var(--color-ink-secondary); 747 + text-decoration: underline; 748 + text-underline-offset: 0.18em; 749 + cursor: pointer; 750 + } 751 + 752 + .demo-reset:hover { 753 + color: var(--color-ink); 754 + } 755 + 698 756 @media (prefers-reduced-motion: no-preference) { 699 - .hero-proof__frame { 757 + .hero-proof__frame:not(.hero-proof__frame--demo) { 700 758 animation: hero-frame-in 0.85s var(--ease-out) both; 701 759 animation-delay: 0.12s; 702 760 } ··· 714 772 } 715 773 716 774 @media (prefers-reduced-motion: reduce) { 717 - .hero-proof__frame { 775 + .hero-proof__frame:not(.hero-proof__frame--demo) { 718 776 animation: none; 719 777 } 720 778 } ··· 880 938 font-size: var(--text-sm); 881 939 color: var(--color-ink-secondary); 882 940 line-height: var(--leading-prose); 941 + } 942 + 943 + .usage-afterword { 944 + margin: var(--space-xl) 0 0; 945 + max-width: 62ch; 946 + font-size: var(--text-sm); 947 + line-height: var(--leading-prose); 948 + color: var(--color-ink-secondary); 883 949 } 884 950 885 951 .split--privacy {