1import{u as he,r as s,j as t,e as me,S as ue,_ as ve}from"./index-3d88130a.js";import{g as i}from"./styled-components.browser.esm-ef6924ab.js";import we from"./purify.es-c09583cd.js";import{u as be}from"./usePageLoading-43692476.js";const ye=i.div` 2 min-height: 100vh; 3 background: linear-gradient(135deg, #f5f7ff 0%, #ffffff 100%); 4 padding: clamp(5rem, 6vw, 6rem) 2rem 2.5rem; 5 position: relative; 6 7 &::before { 8 content: ''; 9 position: absolute; 10 inset: 0; 11 background: 12 linear-gradient(90deg, rgba(99, 102, 241, 0.05) 1px, transparent 1px), 13 linear-gradient(rgba(99, 102, 241, 0.05) 1px, transparent 1px); 14 background-size: 20px 20px; 15 pointer-events: none; 16 } 17`,Pe=i.div` 18 display: flex; 19 gap: 2rem; 20 max-width: 1400px; 21 margin: 0 auto; 22 position: relative; 23 z-index: 1; 24 25 @media (max-width: 1024px) { 26 flex-direction: column; 27 } 28`,q=i.div` 29 flex: 1; 30 min-width: 320px; 31 background: rgba(255, 255, 255, 0.88); 32 border-radius: 20px; 33 padding: clamp(1.25rem, 2vw, 1.75rem); 34 border: 1px solid rgba(255, 255, 255, 0.35); 35 box-shadow: 0 20px 50px rgba(79, 70, 229, 0.12); 36 backdrop-filter: blur(12px); 37 display: flex; 38 flex-direction: column; 39 gap: 1.25rem; 40`,ke=i(q)` 41 position: relative; 42 overflow: hidden; 43`,je=i.header` 44 display: flex; 45 flex-direction: column; 46 gap: 0.75rem; 47`,Se=i.h1` 48 font-size: clamp(1.75rem, 2.4vw, 2.3rem); 49 margin: 0; 50 background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%); 51 -webkit-background-clip: text; 52 -webkit-text-fill-color: transparent; 53 font-weight: 800; 54 letter-spacing: -0.02em; 55`,Ce=i.p` 56 margin: 0; 57 color: #475569; 58 line-height: 1.65; 59`,N=i.div` 60 display: flex; 61 flex-direction: column; 62 gap: 0.35rem; 63`,O=i.h2` 64 font-size: 1.15rem; 65 font-weight: 600; 66 color: #111827; 67 margin: 0; 68`,W=i.p` 69 font-size: 0.95rem; 70 color: #64748b; 71 margin: 0; 72 line-height: 1.5; 73`,J=i.div` 74 display: flex; 75 flex-direction: column; 76 gap: 0.5rem; 77`,V=i.label` 78 font-size: 0.92rem; 79 font-weight: 600; 80 color: #475569; 81`,Te=i.div` 82 display: grid; 83 grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); 84 gap: 0.5rem; 85`,ze=i.button` 86 display: inline-flex; 87 align-items: center; 88 justify-content: center; 89 gap: 0.3rem; 90 padding: 0.5rem 0.75rem; 91 border-radius: 10px; 92 border: 2px solid ${({$selected:e})=>e?"rgba(99, 102, 241, 0.8)":"rgba(148, 163, 184, 0.4)"}; 93 background: ${({$selected:e})=>e?"rgba(99, 102, 241, 0.12)":"rgba(248, 250, 252, 0.8)"}; 94 color: ${({$selected:e})=>e?"#4338ca":"#475569"}; 95 font-size: 0.85rem; 96 font-weight: 600; 97 cursor: pointer; 98 transition: all 0.2s ease; 99 100 &:hover { 101 border-color: rgba(99, 102, 241, 0.8); 102 color: #4338ca; 103 transform: translateY(-1px); 104 } 105 106 &:focus-visible { 107 outline: none; 108 box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.18); 109 } 110`,Re=i(J)` 111 flex: 1; 112`,Le=i.textarea` 113 width: 100%; 114 min-height: 360px; 115 font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; 116 font-size: 0.95rem; 117 line-height: 1.6; 118 color: #0f172a; 119 border: 1px solid rgba(148, 163, 184, 0.4); 120 border-radius: 14px; 121 padding: 1rem; 122 background: rgba(255, 255, 255, 0.95); 123 resize: vertical; 124 transition: border 0.2s ease, box-shadow 0.2s ease; 125 box-shadow: inset 0 2px 6px rgba(15, 23, 42, 0.04); 126 127 &:focus { 128 border-color: rgba(99, 102, 241, 0.5); 129 box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15); 130 outline: none; 131 } 132`,Be=i.div` 133 display: flex; 134 flex-wrap: wrap; 135 gap: 0.75rem; 136`,F=i.button` 137 border: none; 138 border-radius: 999px; 139 padding: 0.65rem 1.4rem; 140 font-size: 0.9rem; 141 font-weight: 600; 142 cursor: pointer; 143 transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; 144 display: inline-flex; 145 align-items: center; 146 gap: 0.4rem; 147 background: ${({variant:e})=>e==="primary"?"linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%)":"rgba(99, 102, 241, 0.08)"}; 148 color: ${({variant:e})=>e==="primary"?"#fff":"#4338ca"}; 149 box-shadow: ${({variant:e})=>e==="primary"?"0 18px 30px rgba(79, 70, 229, 0.25)":"none"}; 150 151 &:disabled { 152 opacity: 0.6; 153 cursor: not-allowed; 154 } 155 156 &:not(:disabled):hover { 157 transform: translateY(-1px); 158 box-shadow: ${({variant:e})=>e==="primary"?"0 20px 36px rgba(79, 70, 229, 0.3)":"0 8px 18px rgba(79, 70, 229, 0.15)"}; 159 } 160`,Fe=i.div` 161 display: flex; 162 gap: 0.75rem; 163 flex-wrap: wrap; 164`,K=i.span` 165 padding: 0.35rem 0.85rem; 166 border-radius: 999px; 167 background: rgba(15, 23, 42, 0.05); 168 color: #475569; 169 font-size: 0.8rem; 170 font-weight: 500; 171`,Y=i.div` 172 margin-top: 0.75rem; 173 padding: 0.85rem 1rem; 174 border-radius: 14px; 175 font-size: 0.9rem; 176 background: ${({type:e})=>e==="success"?"rgba(16, 185, 129, 0.12)":"rgba(248, 113, 113, 0.15)"}; 177 color: ${({type:e})=>e==="success"?"#047857":"#b91c1c"}; 178 border: 1px solid 179 ${({type:e})=>e==="success"?"rgba(16, 185, 129, 0.3)":"rgba(248, 113, 113, 0.3)"};
180`,Me=i.div` 181 flex: 1; 182 border-radius: 18px; 183 border: 1px solid rgba(148, 163, 184, 0.35); 184 padding: 1.5rem; 185 background: #ffffff; 186 min-height: 460px; 187 position: relative; 188 overflow: auto; 189 box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35); 190`,Ee=i.div` 191 width: 100%; 192 min-height: 100%; 193 display: flex; 194 align-items: ${({$centered:e})=>e?"center":"flex-start"}; 195 justify-content: ${({$centered:e})=>e?"center":"flex-start"}; 196`,$e=i.div` 197 display: inline-flex; 198 align-items: center; 199 justify-content: center; 200 transform-origin: top left; 201 transition: transform 0.15s ease; 202 flex-shrink: 0; 203`,Ae=i.div` 204 display: inline-flex; 205 align-items: center; 206 justify-content: center; 207 flex-direction: column; 208 209 svg { 210 width: auto; 211 height: auto; 212 max-width: none; 213 } 214`,Ge=i.div` 215 text-align: center; 216 color: #94a3b8; 217 font-size: 0.95rem; 218 line-height: 1.6; 219`,De=i.div` 220 display: flex; 221 justify-content: flex-end; 222 margin-bottom: 0.75rem; 223`,Ie=i(F)` 224 position: relative; 225`,Ue=i.div` 226 max-width: 1220px; 227 margin: 3rem auto 0; 228 padding: clamp(1.5rem, 3vw, 2.5rem); 229 background: linear-gradient(135deg, rgba(99, 102, 241, 0.12) 0%, rgba(255, 255, 255, 0.85) 55%, rgba(244, 246, 255, 0.9) 100%); 230 border-radius: 28px; 231 border: 1px solid rgba(99, 102, 241, 0.18); 232 box-shadow: 0 35px 60px rgba(79, 70, 229, 0.18); 233 position: relative; 234 z-index: 1; 235 display: flex; 236 flex-direction: column; 237 gap: 1.75rem; 238`,X=i.section` 239 background: rgba(255, 255, 255, 0.95); 240 border-radius: 20px; 241 padding: clamp(1.5rem, 3vw, 2.25rem); 242 border: 1px solid rgba(148, 163, 184, 0.2); 243 box-shadow: 0 18px 32px rgba(79, 70, 229, 0.12); 244`,_e=i.h2` 245 font-size: 1.6rem; 246 font-weight: 700; 247 color: #1f2937; 248 margin-bottom: 1rem; 249`,He=i.div` 250 display: grid; 251 grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); 252 gap: 1.25rem; 253`,Ne=i.article` 254 background: rgba(249, 250, 255, 0.92); 255 border: 1px solid rgba(99, 102, 241, 0.15); 256 border-radius: 18px; 257 padding: 1.35rem; 258 box-shadow: 0 16px 28px rgba(99, 102, 241, 0.12); 259 display: flex; 260 flex-direction: column; 261 gap: 0.35rem; 262`,Oe=i.h3` 263 font-size: 1rem; 264 margin: 0; 265 font-weight: 600; 266 color: #1f2937; 267`,We=i.p` 268 font-size: 0.92rem; 269 color: #4b5563; 270 margin: 0; 271 line-height: 1.55; 272`,P=[{id:"gradientBadge",labelKey:"tools.svgPreviewer.examples.gradientBadge",code:`<svg width="320" height="200" viewBox="0 0 320 200" xmlns="http://www.w3.org/2000/svg"> 273 <defs> 274 <linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%"> 275 <stop offset="0%" stop-color="#6366F1"/> 276 <stop offset="100%" stop-color="#A855F7"/> 277 </linearGradient> 278 <filter id="shadow" x="-20%" y="-20%" width="140%" height="140%"> 279 <feDropShadow dx="0" dy="12" stdDeviation="20" flood-color="#000000" flood-opacity="0.2"/> 280 </filter> 281 </defs> 282 <rect width="320" height="200" rx="32" fill="url(#gradient)" filter="url(#shadow)"/> 283 <g transform="translate(32, 42)" fill="white"> 284 <text x="0" y="0" font-size="18" fill="rgba(255,255,255,0.8)">AI Toolbox</text> 285 <text x="0" y="36" font-size="32" font-weight="700">SVG Preview</text> 286 <text x="0" y="70" font-size="16" fill="rgba(255,255,255,0.8)">Live rendering · Export ready</text> 287 </g> 288 <circle cx="250" cy="60" r="26" fill="rgba(255,255,255,0.2)" stroke="rgba(255,255,255,0.6)" stroke-width="2"/> 289 <path d="M244 60 L253 69 L266 49" stroke="white" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/> 290</svg>`},{id:"htmlLayout",labelKey:"tools.svgPreviewer.examples.htmlBlock",code:`<div style="width: 320px; padding: 24px; border-radius: 24px; background: #0f172a; color: white; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI';"> 291 <h2 style="margin: 0 0 12px; font-size: 1.25rem;">Inline HTML + SVG</h2> 292 <p style="opacity: 0.8; margin: 0 0 16px; line-height: 1.4;"> 293 Mix HTML and SVG snippets to prototype UI blocks and badges with gradient fills. 294 </p> 295 <svg width="272" height="80" viewBox="0 0 272 80" xmlns="http://www.w3.org/2000/svg"> 296 <defs> 297 <linearGradient id="pill" x1="0%" y1="0%" x2="100%" y2="0%"> 298 <stop offset="0%" stop-color="#22d3ee"/> 299 <stop offset="100%" stop-color="#818cf8"/> 300 </linearGradient> 301 </defs> 302 <rect x="0" y="0" width="272" height="80" rx="40" fill="url(#pill)" opacity="0.15"/> 303 <text x="26" y="48" fill="#bae6fd" font-size="24" font-weight="600">export.png</text> 304 <circle cx="216" cy="40" r="20" fill="rgba(255,255,255,0.3)"/> 305 <path d="M208 40 L214 46 L226 34" stroke="white" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/> 306 </svg> 307</div>`},{id:"chart",labelKey:"tools.svgPreviewer.examples.chart",code:`<svg width="360" height="220" viewBox="0 0 360 220" xmlns="http://www.w3.org/2000/svg"> 308 <defs>
309 <linearGradient id="line" x1="0%" y1="0%" x2="0%" y2="100%"> 310 <stop offset="0%" stop-color="#38bdf8"/> 311 <stop offset="100%" stop-color="#2563eb"/> 312 </linearGradient> 313 <filter id="glow"> 314 <feGaussianBlur stdDeviation="4" result="coloredBlur"/> 315 <feMerge> 316 <feMergeNode in="coloredBlur"/> 317 <feMergeNode in="SourceGraphic"/> 318 </feMerge> 319 </filter> 320 </defs> 321 <rect width="360" height="220" rx="28" fill="#0f172a"/> 322 <g transform="translate(32, 32)"> 323 <text x="0" y="-8" fill="#94a3b8" font-size="12">SVG Sparkline · last 7 days</text> 324 <polyline points="0,130 40,110 80,120 120,80 160,90 200,40 240,70 280,30" 325 fill="none" stroke="url(#line)" stroke-width="4" stroke-linecap="round" filter="url(#glow)"/> 326 <circle cx="200" cy="40" r="6" fill="#38bdf8"/> 327 <text x="200" y="20" text-anchor="middle" fill="#e0f2fe" font-size="12">+48%</text> 328 <line x1="0" y1="130" x2="280" y2="130" stroke="rgba(148,163,184,0.35)" stroke-dasharray="6 6"/> 329 </g> 330</svg>`}],k="custom",qe=()=>{const{t:e}=he(),Q=be(),[c,j]=s.useState(P[0].code),[M,S]=s.useState(P[0].id),[E,u]=s.useState(""),[$,A]=s.useState(""),[G,D]=s.useState(!1),p=s.useRef(null),v=s.useRef(null),I="svg-previewer-editor",[Z,w]=s.useState(1),[ee,b]=s.useState(!0),U=s.useMemo(()=>{if(!c.trim())return"";try{return we.sanitize(c,{USE_PROFILES:{svg:!0,svgFilters:!0,html:!0},ADD_ATTR:["style","class","viewBox","fill","stroke","stroke-width","stroke-linecap","stroke-linejoin","filterUnits","markerHeight","markerWidth","orient","gradientUnits","clipPathUnits","preserveAspectRatio","xmlns","xmlns:xlink"],ADD_TAGS:["foreignObject","animate","animateMotion","animateTransform","linearGradient","radialGradient","stop","pattern","mask","clipPath","feDropShadow","feGaussianBlur"]})}catch(r){return console.error("Failed to sanitize SVG/HTML snippet",r),""}},[c]),f=!!c.trim(),te=c.length,re=c.split(/\n/).length;s.useEffect(()=>{if(!f){w(1),b(!0);return}const r=()=>{if(!v.current||!p.current)return;const n=v.current,d=p.current,a=window.getComputedStyle(n),C=(parseFloat(a.paddingLeft)||0)+(parseFloat(a.paddingRight)||0),T=(parseFloat(a.paddingTop)||0)+(parseFloat(a.paddingBottom)||0),z=Math.max(0,n.clientWidth-C),R=Math.max(0,n.clientHeight-T),h=d.scrollWidth,g=d.scrollHeight;if(!h||!g){w(1),b(!0);return}const m=z/h,x=R/g,l=Math.min(1,m,x);if(!Number.isFinite(l)||l<=0){w(1),b(!0);return}w(l),b(l===1)},o=typeof ResizeObserver<"u"?new ResizeObserver(r):null;return r(),o==null||o.observe(p.current),o==null||o.observe(v.current),window.addEventListener("resize",r),()=>{o==null||o.disconnect(),window.removeEventListener("resize",r)}},[f,U]);const ie=r=>{if(S(r),r===k)return;const o=P.find(n=>n.id===r);o&&j(o.code)},oe=r=>{M!==k&&S(k),j(r.target.value)},ne=async()=>{if(!(navigator!=null&&navigator.clipboard)){u(e("tools.svgPreviewer.notifications.copyUnavailable"));return}try{await navigator.clipboard.writeText(c),u(e("tools.svgPreviewer.notifications.copied")),setTimeout(()=>u(""),2e3)}catch(r){console.error("Clipboard copy failed",r),u(e("tools.svgPreviewer.notifications.copyFailed"))}},se=()=>{j(""),S(k)},_=r=>{const o=r.toDataURL("image/png"),n=document.createElement("a");n.href=o,n.download=`svg-preview-${Date.now()}.png`,n.click()},ae=async r=>{const o=(await ve(()=>import("./html2canvas.esm-e0a7d97b.js"),[])).default,n=document.createElement("div"),d=r.cloneNode(!0);n.style.position="fixed",n.style.left="-10000px",n.style.top="0",n.style.pointerEvents="none",n.style.background="#ffffff",n.appendChild(d),document.body.appendChild(n);try{const a=await o(d,{backgroundColor:"#ffffff",scale:2,useCORS:!0});_(a)}finally{document.body.removeChild(n)}},le=async r=>{const o=r.cloneNode(!0);o.getAttribute("xmlns")||o.setAttribute("xmlns","http://www.w3.org/2000/svg");const d=new XMLSerializer().serializeToString(o),a=o.getAttribute("viewBox"),[,,C,T]=a?a.split(/\s+/):[],z=o.getAttribute("width"),R=o.getAttribute("height"),h=r.getBoundingClientRect(),g=parseFloat(z)||parseFloat(C)||h.width||800,m=parseFloat(R)||parseFloat(T)||h.height||600,x=window.devicePixelRatio||1,l=document.createElement("canvas");l.width=Math.max(1,Math.round(g*x)),l.height=Math.max(1,Math.round(m*x));const L=l.getContext("2d");L.scale(x,x),await new Promise((ge,xe)=>{const y=new Image,B=URL.createObjectURL(new Blob([d],{type:"image/svg+xml;charset=utf-8"}));y.onload=()=>{L.clearRect(0,0,g,m),L.drawImage(y,0,0,g,m),URL.revokeObjectURL(B),ge()},y.onerror=fe=>{URL.revokeObjectURL(B),xe(fe)},y.src=B}),_(l)},ce=async()=>
330{if(!(!p.current||!f)){D(!0),A("");try{const r=p.current,o=r.querySelectorAll("svg");o.length===1&&r.childElementCount===1&&r.firstElementChild===o[0]?await le(o[0]):await ae(r)}catch(r){console.error("Failed to export preview",r),A(e("tools.svgPreviewer.errors.export"))}finally{D(!1)}}},H=[{title:e("tools.svgPreviewer.tips.sanitize.title"),desc:e("tools.svgPreviewer.tips.sanitize.description")},{title:e("tools.svgPreviewer.tips.gradients.title"),desc:e("tools.svgPreviewer.tips.gradients.description")},{title:e("tools.svgPreviewer.tips.export.title"),desc:e("tools.svgPreviewer.tips.export.description")}],de=e("tools.svgPreviewer.tipsTitle"),pe=H.length>0;return t.jsxs(t.Fragment,{children:[Q&&t.jsx(me,{}),t.jsx(ue,{title:e("tools.svgPreviewer.metaTitle"),description:e("tools.svgPreviewer.metaDescription")}),t.jsxs(ye,{children:[t.jsxs(Pe,{children:[t.jsxs(q,{children:[t.jsxs(N,{children:[t.jsx(O,{children:e("tools.svgPreviewer.editorTitle")}),t.jsx(W,{children:e("tools.svgPreviewer.editorDescription")})]}),t.jsxs(J,{children:[t.jsx(V,{as:"span",children:e("tools.svgPreviewer.selectPreset")}),t.jsx(Te,{role:"group","aria-label":e("tools.svgPreviewer.selectPreset"),children:P.map(r=>{const o=e(r.labelKey),n=M===r.id;return t.jsx(ze,{type:"button","aria-pressed":n,$selected:n,onClick:()=>ie(r.id),children:o},r.id)})})]}),t.jsxs(Re,{children:[t.jsx(V,{htmlFor:I,children:e("tools.svgPreviewer.inputLabel")}),t.jsx(Le,{id:I,value:c,onChange:oe,spellCheck:!1,placeholder:e("tools.svgPreviewer.placeholder")})]}),t.jsxs(Fe,{children:[t.jsx(K,{children:`${re} ${e("tools.svgPreviewer.stats.lines")}`}),t.jsx(K,{children:`${te} ${e("tools.svgPreviewer.stats.chars")}`})]}),t.jsxs(Be,{children:[t.jsx(F,{variant:"secondary",type:"button",onClick:ne,children:e("tools.svgPreviewer.actions.copy")}),t.jsx(F,{type:"button",onClick:se,children:e("tools.svgPreviewer.actions.clear")})]}),E&&t.jsx(Y,{type:"success",children:E})]}),t.jsxs(ke,{"aria-live":"polite",children:[t.jsxs(N,{children:[t.jsx(O,{children:e("tools.svgPreviewer.previewTitle")}),t.jsx(W,{children:e("tools.svgPreviewer.previewDescription")})]}),t.jsx(De,{children:t.jsx(Ie,{variant:"primary",type:"button",disabled:!f||G,onClick:ce,children:e(G?"tools.svgPreviewer.actions.exporting":"tools.svgPreviewer.actions.export")})}),t.jsx(Me,{ref:v,children:f?t.jsx(Ee,{$centered:ee,children:t.jsx($e,{style:{transform:`scale(${Z})`},children:t.jsx(Ae,{ref:p,dangerouslySetInnerHTML:{__html:U}})})}):t.jsx(Ge,{children:e("tools.svgPreviewer.previewPlaceholder")})}),$&&t.jsx(Y,{children:$})]})]}),t.jsxs(Ue,{children:[t.jsx(X,{children:t.jsxs(je,{as:"section",children:[t.jsx(Se,{children:e("tools.svgPreviewer.title")}),t.jsx(Ce,{children:e("tools.svgPreviewer.description")})]})}),pe&&t.jsxs(X,{children:[t.jsx(_e,{children:de}),t.jsx(He,{children:H.map(r=>t.jsxs(Ne,{children:[t.jsx(Oe,{children:r.title}),t.jsx(We,{children:r.desc})]},r.title))})]})]})]})]})};export{qe as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.