v2.0 — full pipeline

Clone any Framer site.
No CDN. No runtime.

Paste a Framer URL. Get clean HTML + self-contained CSS animations + a wget script — ready to host anywhere.

Framer site URL
Options
1fetch
2assets
3animate
4clean
5generate
6package
pipeline log
· waiting for URL...
how to use on hostinger
01
Enter your Framer URL above and click "Clone + export". This generates all 5 files instantly.
02
Download the ZIP — it contains index.html, animations.css, motion.js, and clone.sh.
03
Run the clone script locally first: bash clone.sh — this does the actual wget mirror of your site (browser CORS blocks direct fetch).
04
Upload to Hostinger via File Manager or FTP. Upload everything inside the cloned folder to public_html/.
05
Your site is live. No Framer CDN, no runtime JS dependency. Pure static HTML + CSS.
\n'); html = html.replace(/data-framer-animation='[^']*'/g, ''); html = html.replace(/data-framer-animation="[^"]*"/g, ''); return html; } function buildCSS(animData) { let c = `/* animations.css */\n/* Generated by framer-cloner — replaces @framer/motion */\n/* Zero dependencies */\n\n`; if(animData.length === 0) { c += `/* No Framer animations detected */\n/* Fallback: fade-in for all elements */\n\n`; c += `@keyframes fm-fadein {\n from { opacity: 0; transform: translateY(16px); }\n to { opacity: 1; transform: none; }\n}\n\n`; c += `[data-framer-appear-id] {\n animation: fm-fadein 0.6s cubic-bezier(0.25,0.46,0.45,0.94) forwards;\n}\n`; return c; } c += `/* Initial hidden states */\n`; animData.forEach(a => { c += `.fm-${a.safe}-init { `; if(a.ini.opacity!=null) c += `opacity: ${a.ini.opacity}; `; const t = buildTransform(a.ini); if(t!=='none') c += `transform: ${t}; `; c += `}\n`; }); c += `\n/* Keyframe definitions */\n`; animData.forEach(a => { c += a.kf + '\n'; }); c += `\n/* Animation trigger classes */\n`; animData.forEach(a => { c += a.rule + '\n'; }); const hasHover = animData.some(a => a.hoverCSS); if(hasHover) { c += `\n/* Hover states */\n`; animData.forEach(a => { if(a.hoverCSS) c += a.hoverCSS + '\n'; }); } c += `\n/* Stagger delay helpers */\n`; for(let i=0;i<16;i++) c += `.fm-delay-${i} { animation-delay: ${(i*0.08).toFixed(2)}s !important; }\n`; return c; } function buildJS(animData) { const hasScroll = animData.some(a => a.isScroll); let j = `/* motion.js */\n/* Replaces @framer/motion — zero dependencies, ~50 lines */\n\n(function() {\n\n`; if(hasScroll) { j += ` /* Scroll-triggered animations via IntersectionObserver */\n`; j += ` var scrollIO = new IntersectionObserver(function(entries) {\n`; j += ` entries.forEach(function(entry) {\n`; j += ` if(entry.isIntersecting) {\n`; j += ` var id = entry.target.getAttribute('data-framer-appear-id');\n`; j += ` if(id) entry.target.classList.add('fm-' + id.replace(/[^a-z0-9]/gi, '-').toLowerCase());\n`; j += ` scrollIO.unobserve(entry.target);\n`; j += ` }\n });\n`; j += ` }, { rootMargin: '-80px 0px', threshold: 0.1 });\n\n`; j += ` document.querySelectorAll('[data-framer-scroll-animate]').forEach(function(el) {\n`; j += ` scrollIO.observe(el);\n });\n\n`; } j += ` /* On-load animations */\n`; j += ` function applyAnim(el) {\n`; j += ` var id = el.getAttribute('data-framer-appear-id') || el.getAttribute('data-framer-name');\n`; j += ` if(!id) return;\n`; j += ` var safe = id.replace(/[^a-z0-9]/gi, '-').toLowerCase();\n`; j += ` el.classList.add('fm-' + safe + '-init');\n`; j += ` requestAnimationFrame(function() {\n`; j += ` setTimeout(function() { el.classList.add('fm-' + safe); }, 16);\n`; j += ` });\n }\n\n`; j += ` document.querySelectorAll('[data-framer-appear-id]:not([data-framer-scroll-animate])').forEach(applyAnim);\n`; j += ` document.querySelectorAll('[data-framer-name]:not([data-framer-appear-id]):not([data-framer-scroll-animate])').forEach(applyAnim);\n\n`; j += ` /* Strip leftover data-framer-* attrs (clean DOM) */\n`; j += ` document.querySelectorAll('[data-framer-appear-id],[data-framer-name],[data-framer-scroll-animate],[data-framer-hover-animate],[data-framer-component-type]').forEach(function(el) {\n`; j += ` Array.from(el.attributes).filter(function(a) { return a.name.startsWith('data-framer'); }).forEach(function(a) { el.removeAttribute(a.name); });\n`; j += ` });\n\n`; j += `})();\n`; return j; } function buildSH(url) { return `#!/bin/bash # clone.sh — Full Framer site clone for Hostinger # Requirements: wget (brew install wget / apt install wget) # Usage: bash clone.sh URL="${url}" HOST=$(echo "$URL" | sed 's|https\\?://||' | cut -d'/' -f1 | tr '.' '-') OUT="./$HOST" echo "" echo " Framer → Hostinger Cloner" echo " Cloning: $URL" echo "" # 1. Mirror full site wget \\ --mirror \\ --convert-links \\ --adjust-extension \\ --page-requisites \\ --no-parent \\ --no-check-certificate \\ --user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36" \\ --wait=0.4 \\ --random-wait \\ -P "$OUT" \\ "$URL" echo "Mirror complete. Post-processing..." # 2. Strip Framer runtime from all HTML files find "$OUT" -name "*.html" | while read f; do # Remove Framer CDN scripts perl -i -0pe 's|]*(?:framer\\.com|__framer)[^>]*>.*?||gsi' "$f" # Remove Google Fonts CDN links (will self-host) perl -i -pe 's|]*fonts\\.googleapis\\.com[^>]*>||g' "$f" # Strip data-framer-animation inline JSON perl -i -pe "s|data-framer-animation='[^']*'||g" "$f" perl -i -pe 's|data-framer-animation="[^"]*"||g' "$f" echo " cleaned: $f" done # 3. Copy generated animation files echo "Copying animations.css and motion.js..." cp animations.css "$OUT/" cp motion.js "$OUT/" # 4. Inject file references into all HTML find "$OUT" -name "*.html" | while read f; do perl -i -pe 's||\\n|' "$f" perl -i -pe 's||\\n|' "$f" done # 5. Download Google Fonts locally (optional) echo "" echo "Tip: Self-host fonts at https://google-webfonts-helper.herokuapp.com" echo " Download .woff2 files → upload to public_html/assets/fonts/" echo "" echo "Done! Files are in: $OUT/" echo "" echo "To test locally:" echo " cd $OUT && python3 -m http.server 8080" echo " open http://localhost:8080" echo "" echo "To deploy on Hostinger:" echo " Upload everything inside $OUT/ to public_html/ via File Manager or FTP" echo "" `; } function buildMap(animData) { if(!animData.length) return JSON.stringify({ note: 'No Framer animations detected', animations: [] }, null, 2); return JSON.stringify({ generated: new Date().toISOString(), total: animData.length, scrollTriggers: animData.filter(a=>a.isScroll).length, hoverStates: animData.filter(a=>a.hover).length, animations: animData.map(a => ({ element: a.origId, cssClass: `fm-${a.safe}`, trigger: a.isScroll ? 'scroll (IntersectionObserver)' : 'onload', duration: a.dur+'s', delay: a.delay+'s', easing: a.ease, from: a.ini, to: a.tgt, hasHover: !!(a.hover) })) }, null, 2); } function fallbackHTML(url) { return ` Cloned Site `; } async function dlZip() { if(typeof JSZip === 'undefined') { lg('JSZip not loaded yet, try again','warn'); return; } const z = new JSZip(); z.file('index.html', G.cleanHtml); z.file('animations.css', G.css); z.file('motion.js', G.js); z.file('clone.sh', G.sh); z.file('animation-map.json', G.map); z.folder('assets/fonts'); z.folder('assets/images'); z.file('README.md', `# Framer Clone — ${G.url} ## Upload to Hostinger (quickest) 1. Unzip this file 2. Run: bash clone.sh 3. Upload everything from the generated folder to public_html/ in Hostinger File Manager 4. Done ## Files - index.html — cleaned HTML, Framer runtime removed - animations.css — all @keyframes (replaces Framer Motion) - motion.js — IntersectionObserver + anim trigger (zero deps) - clone.sh — wget mirror script (run locally first) - animation-map.json — debug: all detected animations ## Test locally cd cloned-folder python3 -m http.server 8080 open http://localhost:8080 ## Requirements for clone.sh Mac: brew install wget perl Linux: apt install wget perl Windows: Use WSL `); const host = G.url.replace(/https?:\/\//,'').split('/')[0].replace(/\./g,'-') || 'framer-site'; const blob = await z.generateAsync({ type:'blob', compression:'DEFLATE', compressionOptions:{ level:6 } }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `${host}-clone.zip`; document.body.appendChild(a); a.click(); document.body.removeChild(a); lg('ZIP downloaded — upload to Hostinger public_html/', 'ok'); } function copyTab() { const active = document.querySelector('.out-body.active'); if(active) navigator.clipboard.writeText(active.textContent).then(() => lg('Copied to clipboard', 'ok')); } function previewHtml() { const h = document.getElementById('out-html').textContent; if(!h) return; const blob = new Blob([h], { type:'text/html' }); window.open(URL.createObjectURL(blob), '_blank'); }