--- name: asset-pipeline description: Rules for how the clone pipeline names, organizes, and references downloaded assets. Covers folder layout under public/assets/cloned, hash-based filename convention, font strategy (self-host via next/font/local, fall back to system stack if licensing unclear), SVG inline-vs-file threshold, video format, and import paths. Load this when downloading assets and when referencing them in JSX. user-invocable: false --- # Asset Pipeline All cloned assets live under the generated project's `public/assets/cloned/`. Never outside this tree. Never in the workspace `.clone-workspace` beyond the capture bundle. ## Layout ``` /public/assets/cloned/ images/ # jpg, png, webp, avif videos/ # mp4 (only) fonts/ # woff2 (preferred), woff, ttf svg/ # .svg files when not inlined lottie/ # .json animation data ``` ## Naming Every file is renamed to `-`. The sha1 is of the source URL (not content), truncated to 8 hex chars. Sanitization: lowercase, spaces → `-`, strip everything not in `[a-z0-9.-]`. Examples: - `https://cdn.site.com/hero-bg.jpg` → `a1b2c3d4-hero-bg.jpg` - `https://fonts.site.com/inter-display-500.woff2` → `e5f67890-inter-display-500.woff2` Collisions are effectively impossible at 8 chars for any one site. If they happen, extend to 10. The manifest records the full `local_path` for each asset — the generate agent reads that field, never reconstructs the path. ## Fonts **Preferred: self-hosted via `next/font/local`.** In `src/app/layout.tsx`: ```ts import localFont from 'next/font/local'; const display = localFont({ src: [ { path: '../../public/assets/cloned/fonts/e5f67890-inter-display-400.woff2', weight: '400', style: 'normal' }, { path: '../../public/assets/cloned/fonts/abcd1234-inter-display-500.woff2', weight: '500', style: 'normal' }, ], variable: '--font-display', display: 'swap', }); export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` Then in `tailwind.config.ts`: ```ts theme: { extend: { fontFamily: { display: ['var(--font-display)', 'sans-serif']; } } } ``` **If licensing is unclear** (manifest marks `license_hint: "licensed"` or `"unclear"`), do NOT ship the font file. Substitute with the closest free alternative from a system stack and flag in the final report: | Source font | Fallback | | -------------------------------- | ------------------------------------------------------ | | Any licensed sans | Inter from Google Fonts (via `next/font/google`) | | Any licensed serif | `ui-serif, Georgia, Cambria, "Times New Roman", serif` | | Any licensed mono | `ui-monospace, SFMono-Regular, Menlo, monospace` | | Variable display font (licensed) | Inter or Manrope via `next/font/google` | Record the substitution in the manifest's `skipped[]` with `reason: "licensed_font_unclear"` and log to `logs/skipped.md`. ## SVGs **Inline as React component** when all hold: - Source size < 5KB - SVG is used in-flow, not as a CSS `background-image` - SVG has no `