feat: initial commit — Webflow to Next.js conversion
QuantumLab template converted to Next.js 16 + React 19 + TypeScript: - 8 page routes (home, about, blog, contact, careers, team-members, coming-soon, 404) - Dynamic routes for blog posts, career positions, and team members - GSAP animations (marquee, counters, button hovers) - IntersectionObserver-based scroll reveal (blur-to-clear transitions) - Dark mode with next-themes - React Hook Form + Zod contact form - Framer Motion page transitions - Lottie animations via lottie-web Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = path.resolve(__dirname, "..");
|
||||
|
||||
const TSX_FILES = [];
|
||||
function walkDir(dir) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
||||
walkDir(full);
|
||||
} else if (entry.name.endsWith(".tsx")) {
|
||||
TSX_FILES.push(full);
|
||||
}
|
||||
}
|
||||
}
|
||||
walkDir(path.join(ROOT, "components"));
|
||||
|
||||
let totalReplaced = 0;
|
||||
|
||||
for (const file of TSX_FILES) {
|
||||
let content = fs.readFileSync(file, "utf8");
|
||||
let changed = false;
|
||||
|
||||
// Replace <img ... /> with <Image ... />
|
||||
// Match self-closing img tags
|
||||
content = content.replace(/<img\s([^>]*?)\s*\/>/g, (match, attrs) => {
|
||||
const srcMatch = attrs.match(/src="([^"]*)"/);
|
||||
if (!srcMatch) return match;
|
||||
const src = srcMatch[1];
|
||||
|
||||
// Skip SVG inline and external URLs
|
||||
if (src.startsWith("http://") || src.startsWith("https://")) return match;
|
||||
if (src.startsWith("data:")) return match;
|
||||
|
||||
const widthMatch = attrs.match(/width=\{(\d+)\}/);
|
||||
const heightMatch = attrs.match(/height=\{(\d+)\}/);
|
||||
const altMatch = attrs.match(/alt="([^"]*)"/);
|
||||
const classMatch = attrs.match(/className="([^"]*)"/);
|
||||
const loadingMatch = attrs.match(/loading="([^"]*)"/);
|
||||
const sizesMatch = attrs.match(/sizes="([^"]*)"/);
|
||||
|
||||
// Remove srcSet from attrs (next/image handles it)
|
||||
let cleanAttrs = attrs
|
||||
.replace(/srcSet="[^"]*"/g, "")
|
||||
.replace(/loading="[^"]*"/g, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
|
||||
// Ensure width and height exist
|
||||
const width = widthMatch ? widthMatch[1] : "0";
|
||||
const height = heightMatch ? heightMatch[1] : "0";
|
||||
|
||||
// If no width/height, use fill mode
|
||||
if (width === "0" || height === "0") {
|
||||
// Remove width/height attrs
|
||||
cleanAttrs = cleanAttrs
|
||||
.replace(/width=\{\d+\}/g, "")
|
||||
.replace(/height=\{\d+\}/g, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
changed = true;
|
||||
totalReplaced++;
|
||||
return `<Image ${cleanAttrs} fill style={{ objectFit: "cover" }} />`;
|
||||
}
|
||||
|
||||
changed = true;
|
||||
totalReplaced++;
|
||||
return `<Image ${cleanAttrs} />`;
|
||||
});
|
||||
|
||||
if (changed) {
|
||||
// Add Image import if not present
|
||||
if (!content.includes('import Image from "next/image"') && !content.includes("import Image from 'next/image'")) {
|
||||
// Add after existing imports or at top
|
||||
const firstImport = content.indexOf("import ");
|
||||
if (firstImport !== -1) {
|
||||
const lineEnd = content.indexOf("\n", firstImport);
|
||||
content = content.slice(0, lineEnd + 1) + 'import Image from "next/image"\n' + content.slice(lineEnd + 1);
|
||||
} else {
|
||||
content = 'import Image from "next/image"\n\n' + content;
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(file, content, "utf8");
|
||||
console.log(`Updated: ${path.relative(ROOT, file)}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nTotal <img> → <Image>: ${totalReplaced}`);
|
||||
Reference in New Issue
Block a user