index.html, animations.css, motion.js, and clone.sh.bash clone.sh — this does the actual wget mirror of your site (browser CORS blocks direct fetch).public_html/.Paste a Framer URL. Get clean HTML + self-contained CSS animations + a wget script — ready to host anywhere.
index.html, animations.css, motion.js, and clone.sh.bash clone.sh — this does the actual wget mirror of your site (browser CORS blocks direct fetch).public_html/.'); 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|||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 `