Add image-embedding preview builder, fix button wrapping on phones

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msa87P1aZQSV9MWhEu3VfJ
This commit is contained in:
Claude committed 2026-10-02 02:31:18 +00:00
1 parent 6f3e3bc858
commit 1500ba1054
5 files changed
+85 -10

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
// Builds ONE self-contained HTML file for the Claude cloud preview.
// Claude's cloud pages block outside images, so every image is downloaded
// here and baked into the page as a data: URI.
// Usage: node scripts/build-preview.mjs <output.html>
import { execFileSync } from 'node:child_process';
import { readFileSync, readdirSync, writeFileSync } from 'node:fs';
const out = process.argv[2] ?? "jack-preview.html";
import { buildSync } from 'esbuild';
import { rmSync, writeFileSync as write } from 'node:fs';
// 1. Read the real list of image URLs from src/data/content.ts
buildSync({ entryPoints: ['src/data/content.ts'], bundle: true, format: 'esm', outfile: 'dist-tmp/content.mjs', logLevel: 'error' });
const content = await import(new URL('../dist-tmp/content.mjs', import.meta.url).href);
rmSync('dist-tmp', { recursive: true, force: true });
const urls = new Set();
const walk = (v) => {
if (typeof v === 'string' && v.startsWith('https://')) urls.add(v);
else if (v && typeof v === 'object') Object.values(v).forEach(walk);
};
walk(content);
// 2. Download each one and turn it into a data: URI
const mime = { png: 'image/png', gif: 'image/gif', webp: 'image/webp', jpg: 'image/jpeg' };
const map = {};
let total = 0;
for (const url of urls) {
try {
const buf = execFileSync('curl', ['-sSfL', '-m', '60', url], { maxBuffer: 64 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
const head = buf.subarray(0, 12).toString('latin1');
const type = head.startsWith('GIF') ? 'gif' : head.includes('WEBP') ? 'webp' : head.startsWith('\x89PNG') ? 'png' : 'jpg';
map[url] = `data:${mime[type]};base64,${buf.toString('base64')}`;
total += buf.length;
} catch {
console.warn('could not download:', url.slice(0, 90));
}
}
console.log(`images embedded: ${Object.keys(map).length}/${urls.size} (${(total / 1048576).toFixed(1)} MB)`);
// 3. Build with the pictures baked in, then put the empty lookup file back
const stub = 'src/data/embedded.ts';
const original = readFileSync(stub, 'utf8');
write(stub, `export const embedded: Record<string, string> = ${JSON.stringify(map)};\n`);
try {
execFileSync('npm', ['run', 'build'], { stdio: 'inherit' });
} finally {
write(stub, original);
}
const dir = 'dist/assets/';
const files = readdirSync(dir);
let js = readFileSync(dir + files.find((f) => f.endsWith('.js')), 'utf8');
const css = readFileSync(dir + files.find((f) => f.endsWith('.css')), 'utf8');
js = js.replace(/<\/script/g, '<\\/script');
const html = `<title>Jack 3D Creator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<style>${css}
html,body{background:#0C0C0C}
img{background:linear-gradient(135deg,#17171c,#24242c)}</style>
<div id="root"></div>
<script>document.addEventListener("error",function(e){var t=e.target;if(t&&t.tagName==="IMG"){t.removeAttribute("src");t.alt="";}},true);</script>
<script type="module">${js}</script>
`;
writeFileSync(out, html);
const mb = Buffer.byteLength(html) / 1048576;
console.log(`wrote ${out} (${mb.toFixed(1)} MB)${mb > 16 ? ' WARNING: over the 16 MB page limit' : ''}`);
@@ -2,7 +2,7 @@ export default function ContactButton() {
return (
<a
href="mailto:hello@example.com"
className="inline-block rounded-full px-8 py-3 text-xs font-medium uppercase tracking-widest text-white sm:px-10 sm:py-3.5 sm:text-sm md:px-12 md:py-4 md:text-base"
className="inline-block whitespace-nowrap rounded-full px-8 py-3 text-xs font-medium uppercase tracking-widest text-white sm:px-10 sm:py-3.5 sm:text-sm md:px-12 md:py-4 md:text-base"
style={{
background: 'linear-gradient(123deg, #18011F 7%, #B600A8 37%, #7621B0 72%, #BE4C00 100%)',
boxShadow: '0px 4px 4px rgba(181, 1, 167, 0.25), 4px 4px 12px #7721B1 inset',
@@ -2,7 +2,7 @@ export default function LiveProjectButton() {
return (
<button
type="button"
className="rounded-full border-2 border-[#D7E2EA] px-8 py-3 text-sm font-medium uppercase tracking-widest text-[#D7E2EA] transition-colors hover:bg-[#D7E2EA]/10 sm:px-10 sm:py-3.5 sm:text-base"
className="whitespace-nowrap rounded-full border-2 border-[#D7E2EA] px-8 py-3 text-sm font-medium uppercase tracking-widest text-[#D7E2EA] transition-colors hover:bg-[#D7E2EA]/10 sm:px-10 sm:py-3.5 sm:text-base"
>
Live Project
</button>
+12 -8
View File
@@ -1,13 +1,17 @@
import { embedded } from './embedded';
const A = (url: string) => embedded[url] ?? url;
const FIGMA = 'https://shrug-person-78902957.figma.site/_components/v2';
const ABOUT = `${FIGMA}/ebb2b8f25d8e24d5f0a5ca8af4c950de81aa2fd7`;
export const portrait = `${FIGMA}/d24c01ad3a56fc65e942a1f501eb73db42d7cf9a/Rectangle_40443.81459862.png`;
export const portrait = A(`${FIGMA}/d24c01ad3a56fc65e942a1f501eb73db42d7cf9a/Rectangle_40443.81459862.png`);
export const aboutIcons = {
moon: `${ABOUT}/moon_icon.11395d36.png`,
object: `${ABOUT}/p59_1.4659672e.png`,
lego: `${ABOUT}/lego_icon-1.703bb594.png`,
group: `${ABOUT}/Group_134-1.2e04f3ce.png`,
moon: A(`${ABOUT}/moon_icon.11395d36.png`),
object: A(`${ABOUT}/p59_1.4659672e.png`),
lego: A(`${ABOUT}/lego_icon-1.703bb594.png`),
group: A(`${ABOUT}/Group_134-1.2e04f3ce.png`),
};
export const aboutText =
@@ -35,7 +39,7 @@ export const marqueeImages = [
'hero-wealth-preview-B70idl_u',
'hero-luminex-preview-CxOP7ce6',
'hero-celestia-preview-0yO3jXO8',
].map((n) => `https://motionsites.ai/assets/${n}.gif`);
].map((n) => A(`https://motionsites.ai/assets/${n}.gif`));
export const services = [
{ name: '3D Modeling', description: 'Creation of detailed objects, characters, or environments tailored to specific client needs, ideal for games, products, and visualizations.' },
@@ -46,9 +50,9 @@ export const services = [
];
const cf = (file: string) =>
`https://images.higgs.ai/?default=1&output=webp&url=${encodeURIComponent(
A(`https://images.higgs.ai/?default=1&output=webp&url=${encodeURIComponent(
`https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/${file}`
)}&w=1280&q=85`;
)}&w=1280&q=85`);
export const projects = [
{
+3
View File
@@ -0,0 +1,3 @@
// Filled in automatically by scripts/build-preview.mjs (image URL -> data: URI).
// Empty in normal use, so the site loads images from their original URLs.
export const embedded: Record<string, string> = {};