105- 'name': The character race/type name (e.g., Elf, Cyborg, Star Pilot). Be creative.
106- 'description': A brief description (1-2 sentences max).
107- 'styleHint': 3-5 descriptive keywords for image generation (e.g., "steampunk robot, gears, brass", "mystical elf, ancient forest, glowing runes").
108- 'effectColor': A hex color code (#RRGGBB format) that fits the character's theme.
109- 'borderAnimationHint': OPTIONAL. A single keyword: 'shimmer', 'pulse', or 'none'. Default to 'none'.
110- 'maskSrc': OPTIONAL. A URL to a subtle, tileable black and white texture/pattern image (like from transparenttextures.com) suitable for a holographic overlay mask (multiply blend). Ensure it's a direct image URL (e.g., ending in .png). If unsure, omit this property (a default will be used).
111112Return STRICTLY as a single JSON object: { "races": [ { race1 }, { race2 }, ... ] }. No introductory text or explanations outside the JSON structure.`;
282}
283284/* <<< Holographic Content Area (The Image Section) >>> */
285.holographic-content {
286position: relative; /* Containing block for overlays */
304}
305306/* Base Image within Holo Wrapper */
307.holographic-content > img {
308display: block;
314border-radius: inherit; /* Inherit top radius */
315background-color: #2a2a2e; /* Placeholder BG */
316pointer-events: none; /* Prevent image dragging */
317}
318324height: 100%;
325pointer-events: none;
326z-index: 2; /* Above base image */
327border-radius: inherit; /* Match top radius */
328background-size: 180% 180%; /* Zoomed gradient for movement */
345mix-blend-mode: color-dodge; /* The key shine effect */
346opacity: var(--holo-gradient-opacity); /* Control intensity */
347z-index: 3; /* Above base image, below mask */
348/* background-image is set dynamically via JS per card */
349}
350365pointer-events: none;
366}
367.mask img { /* Style the actual image inside the mask container */
368display: block;
369width: 100%;
370height: 100%;
371object-fit: cover; /* Cover the container */
372opacity: 0.6; /* Make texture image itself slightly transparent */
373background: transparent !important; /* Ensure no bg color interferes */
374border-radius: inherit;
439/* Border Animations */
440@keyframes border-shimmer { /* Needs careful gradient stops */
4410% { border-image-source: linear-gradient(90deg, transparent 0%, rgba(from var(--effect-color) r g b / 0) 10%, var(--effect-color) 50%, rgba(from var(--effect-color) r g b / 0) 90%, transparent 100%); }
44250% { border-image-source: linear-gradient(90deg, transparent 0%, rgba(from var(--effect-color) r g b / 0) 40%, var(--effect-color) 90%, rgba(from var(--effect-color) r g b / 0) 100%, transparent 100%); }
443100% { border-image-source: linear-gradient(90deg, transparent 0%, rgba(from var(--effect-color) r g b / 0) 10%, var(--effect-color) 50%, rgba(from var(--effect-color) r g b / 0) 90%, transparent 100%); }
444}
445@keyframes border-pulse {
450border-width: 2px;
451border-style: solid;
452border-image-slice: 1;
453/* border-image-source set by keyframes */
454animation: border-shimmer var(--border-anim-duration) linear infinite;
455}
468flex-grow: 1; /* Take remaining space */
469position: relative;
470z-index: 5; /* Above base image/overlays */
471background: inherit; /* Inherit card background */
472border-radius: 0 0 18px 18px; /* Match card bottom radius */
537#carousel-container { height: var(--card-height-desktop); }
538.race-card { width: var(--card-width-desktop); height: var(--card-height-desktop); min-height: 0; }
539.holographic-content { height: 300px; } /* Fixed height for image area on desktop */
540.race-card .card-content { padding: 25px; gap: 15px; }
541.race-card h3 { font-size: 26px; }
592const HOLOGRAPHIC_CONTENT_CLASS = 'holographic-content';
593const SPECULAR_LAYER_CLASS = 'specular';
594const MASK_LAYER_CLASS = 'mask'; // The container div for the mask image
595const SHADER_LAYER_CLASS = 'shader-layer'; // Common class for specular/mask layers
596674card.style.setProperty('--effect-color', safeEffectColor);
675676// --- Image Generation URL ---
677// Using maxm-imggenurl service example - adapt if needed
678const prompt = encodeURIComponent(\`professional character portrait, centered composition, detailed illustration, \${safeStyleHint}, \${safeName}, cinematic lighting, high fantasy art style\`);
679const imageUrl = \`https://maxm-imggenurl.web.val.run/\${prompt}.jpg\`;
680const fallbackColor = safeEffectColor.substring(1); // Use effect color for placeholder
681const fallbackUrl = \`https://placehold.co/350x300/\${fallbackColor}/1A1A1E?text=\${encodeURIComponent(safeName)}\`; // Placeholder uses image area dimensions
682683// --- Holographic Gradient ---
686687// --- Card Inner HTML Structure ---
688// <<< Structure includes holographic content area with base image, specular layer, and mask layer >>>
689card.innerHTML = \`
690<div class="\${HOLOGRAPHIC_CONTENT_CLASS}">
691<img src="\${imageUrl}" alt="Image of \${safeName}" loading="lazy" onerror="this.onerror=null; this.src='\${fallbackUrl}'; console.warn('Image failed for \${safeName}. Using fallback.');">
692693<div class="\${SPECULAR_LAYER_CLASS} \${SHADER_LAYER_CLASS}" style="background-image: \${specularGradient};">
694\${useMaskSrc ? \`<div class="\${MASK_LAYER_CLASS}"><img src="\${useMaskSrc}" alt="" loading="lazy" onerror="this.parentElement.style.display='none'; console.warn('Mask image failed for \${safeName}');"></div>\` : ''}
695</div>
696</div>
941if (!holoContent) return; // Should always exist, but check anyway
942943const rect = holoContent.getBoundingClientRect(); // Get bounds of the image area
944945// Calculate mouse position relative to the holographic content area
untitled-7077main.tsx68 matches
45// Import using namespace to see if API methods are attached
6import * as domtoimage from "https://esm.sh/dom-to-image-more";
7// Log the imported object on load to verify
8console.log("domtoimage library namespace:", domtoimage);
910// --- Texture Data ---
27const PERSPECTIVE = 600;
28const BASE_TEXTURE_URL = "https://www.transparenttextures.com";
29const IMAGE_WIDTH = 120;
30const GRID_GAP = "1rem";
31const UPLOADED_IMAGE_WIDTH = 300;
3233// --- Holographic Image Component ---
34// (Keep HolographicImage component as is)
35interface HolographicImageProps {
36baseSrc: string;
37textureUrl: string;
45forwardedRef?: React.Ref<HTMLDivElement>;
46}
47const HolographicImage = React.forwardRef<HTMLDivElement, HolographicImageProps>(({
48baseSrc,
49textureUrl,
123124const perspectiveWrapperClasses = `perspective-wrapper ${framed ? "framed" : ""} ${
125isUploaded ? "uploaded-image-wrapper" : ""
126} ${containerClassName}`.trim().replace(/\s+/g, " ");
127const shaderContainerClasses = `shader`.trim().replace(/\s+/g, " ");
142display: "block",
143width: "100%",
144height: isUploaded ? "auto" : `${IMAGE_WIDTH}px`,
145objectFit: "cover",
146}}
147onError={(e) => {
148(e.target as HTMLImageElement).src = `https://placehold.co/${width}x${
149isUploaded ? width : IMAGE_WIDTH
150}/333/eee?text=Error`;
151(e.target as HTMLImageElement).alt = "Error loading image";
152}}
153/>
159style={{ display: "block" }}
160onError={(e) => {
161(e.target as HTMLImageElement).style.display = "none";
162}}
163/>
164</div>
165</div>
166{caption && <p className="holographic-image-caption">{caption}</p>}
167</div>
168);
172// (Keep gradientStyles as is)
173const gradientStyles = `
174.gradient-sparrow { background-image: linear-gradient( hsl(359, 70%, 50%), hsl(16, 70%, 55%), hsl(33, 70%, 60%), hsl(45, 70%, 65%), hsl(58, 70%, 70%), hsl(58, 70%, 75%), hsl(58, 70%, 80%), hsl(96, 70%, 75%), hsl(146, 70%, 70%), hsl(183, 70%, 65%), hsl(225, 70%, 60%), hsl(265, 70%, 55%), hsl(303, 70%, 50%) ); }
175.gradient-deer { background-image: linear-gradient( hsl(0, 0%, 10%) 30%, hsl(104, 30%, 80%) 45%, hsl(273, 25%, 30%) 65%, hsl(0, 0%, 5%) 70% ); }
176.gradient-tech { background-image: linear-gradient( 45deg, hsl(180, 100%, 50%), hsl(210, 100%, 60%), hsl(240, 80%, 60%), hsl(280, 80%, 55%), hsl(180, 100%, 50%) ); }
177.gradient-sunset { background-image: linear-gradient(to right, #ff7e5f, #feb47b); }
178.gradient-ocean { background-image: linear-gradient(to right, #00c6ff, #0072ff); }
179.gradient-purple { background-image: linear-gradient(to right, #da22ff, #9733ee); }
180`;
181182// --- Main App Component ---
183function App() {
184const [uploadedImage, setUploadedImage] = useState<string | null>(null);
185const [selectedTextureUrl, setSelectedTextureUrl] = useState<string>(
186`${BASE_TEXTURE_URL}${patternsData[0].downloadUrl}`,
189const [isRecording, setIsRecording] = useState(false);
190const [recordProgress, setRecordProgress] = useState(0);
191const uploadedImageRef = useRef<HTMLDivElement>(null);
192193// (Keep baseImages, gradientClasses, patternsToDisplay, handleFileChange as is)
194const baseImages = [
195"https://maxm-imggenurl.web.val.run/a-futuristic-cyberpunk-landscape-with-neon-lights",
196"https://maxm-imggenurl.web.val.run/a-serene-mountain-landscape-with-misty-peaks",
214const reader = new FileReader();
215reader.onloadend = () => {
216setUploadedImage(reader.result as string);
217};
218reader.readAsDataURL(file);
220};
221222// --- Video Export Implementation (Using dom-to-image-more namespace) ---
223const handleExportVideo = async () => {
224// **MODIFIED:** Add specific logging before the check
225console.log("handleExportVideo called. State:", {
226isRecording,
227hasRef: !!uploadedImageRef.current, // Log if ref exists (true/false)
228hasDomToImage: !!domtoimage, // Log if library object exists (true/false)
229});
230231// **MODIFIED:** Updated check with specific reasons and logging
232// Check if the library object exists and has the 'toPng' method needed
233const isLibraryReady = domtoimage && typeof domtoimage.toPng === "function";
234235if (isRecording || !uploadedImageRef.current || !isLibraryReady) {
236let reason = "";
237if (isRecording) reason = "Already recording.";
238else if (!uploadedImageRef.current) reason = "Target element ref not found.";
239else if (!isLibraryReady) reason = "dom-to-image library not loaded or invalid."; // More specific
240else reason = "Unknown check failure.";
241244// Optional: Add specific alert if library check fails
245if (!isLibraryReady) {
246alert("Error: dom-to-image library failed to load or is not valid. Cannot export video.");
247}
248return; // Stop execution
253setRecordProgress(0);
254255const targetElement = uploadedImageRef.current; // The perspective-wrapper div
256const shaderElement = targetElement.querySelector(".shader") as HTMLElement; // The inner shader div
257357console.log("Recording started...");
358359// --- Animation Loop (Using dom-to-image namespace) ---
360let currentFrame = 0;
361const recordFrame = () => {
368setRecordProgress(Math.round((currentFrame / totalFrames) * 100));
369370// --- Capture Frame using dom-to-image-more ---
371new Promise<void>(async (resolve, reject) => {
372try {
376377// **MODIFIED:** Call using namespace if needed (already correct)
378const dataUrl = await domtoimage.toPng(targetElement, options);
379380const img = new Image();
381img.onload = () => {
382ctx.clearRect(0, 0, hiddenCanvas!.width, hiddenCanvas!.height);
383ctx.drawImage(img, 0, 0, hiddenCanvas!.width, hiddenCanvas!.height);
384resolve();
385};
386img.onerror = (err) => {
387console.error("Failed to load generated image data URL", err);
388reject(new Error("Image loading failed"));
389};
390img.src = dataUrl;
391} catch (error) {
392console.error(`Error capturing frame ${currentFrame} with dom-to-image:`, error);
393reject(error);
394}
426<div className="App">
427<header style={{ textAlign: "center", margin: "2vh 0 4vh 0", padding: "0 1rem" }}>
428<h1>React Holographic Image Tool</h1>
429<p>Upload an image or move mouse/touch over grid images</p>
430</header>
431442}}
443>
444<h2>Upload Your Image</h2>
445<input
446type="file"
447accept="image/*"
448onChange={handleFileChange}
449style={{ display: "block", margin: "1rem auto" }}
450disabled={isRecording}
451/>
452{uploadedImage && (
453<div
454style={{
501</select>
502</div>
503{/* --- Display Uploaded Image --- */}
504<HolographicImage
505forwardedRef={uploadedImageRef}
506key="uploaded-image"
507baseSrc={uploadedImage}
508textureUrl={selectedTextureUrl}
509alt="Uploaded holographic image"
510width={UPLOADED_IMAGE_WIDTH}
511gradientClass={selectedGradient}
512isUploaded={true}
518<button
519onClick={handleExportVideo}
520disabled={isRecording || !(domtoimage && typeof domtoimage.toPng === "function")}
521style={{
522padding: "0.8rem 1.5rem",
523fontSize: "1rem",
524cursor: (isRecording || !(domtoimage && typeof domtoimage.toPng === "function")) ? "wait" : "pointer",
525backgroundColor: (isRecording || !(domtoimage && typeof domtoimage.toPng === "function"))
526? "#ccc"
527: "#007bff",
528color: (isRecording || !(domtoimage && typeof domtoimage.toPng === "function")) ? "#555" : "white",
529border: "none",
530borderRadius: "5px",
531opacity: (isRecording || !(domtoimage && typeof domtoimage.toPng === "function")) ? 0.6 : 1,
532minWidth: "180px",
533}}
546)}
547{/* **MODIFIED:** Error message check uses isLibraryReady */}
548{!(domtoimage && typeof domtoimage.toPng === "function") && !isRecording && (
549<p style={{ fontSize: "0.8em", color: "#ff8888", marginTop: "0.5em" }}>
550Error: Capture library failed to load correctly.
562style={{
563display: "grid",
564gridTemplateColumns: `repeat(auto-fit, minmax(${IMAGE_WIDTH}px, 1fr))`,
565gap: GRID_GAP,
566padding: GRID_GAP,
573if (!pattern) return null;
574const fullTextureUrl = `${BASE_TEXTURE_URL}${pattern.downloadUrl}`;
575const baseImageSrc = baseImages[index % baseImages.length];
576const gradientClass = gradientClasses[index % gradientClasses.length];
577return (
578<HolographicImage
579key={pattern.title + index}
580baseSrc={baseImageSrc}
581textureUrl={fullTextureUrl}
582alt={`Holographic ${pattern.title}`}
583width={IMAGE_WIDTH}
584gradientClass={gradientClass}
585caption={`${index + 1}. ${pattern.title}`}
594{/* (Keep Footer as is) */}
595<footer style={{ textAlign: "center", marginTop: "4vh", padding: "1rem", color: "#aaa", fontSize: "0.8em" }}>
596<p>Textures from transparenttextures.com | Base Images via val.run endpoint.</p>
597<p>
598<strong>Note:</strong> Requires correct{" "}
617export async function server(request: Request): Promise<Response> {
618return new Response(
619`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Interactive Holographic Tool</title><style>:root{--mouse-x:0.5;--mouse-y:0.5;--rotate-x:0deg;--rotate-y:0deg;--bg-pos-x:50%;--bg-pos-y:50%}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.6;margin:0;padding:0;min-height:100vh;background-color:#1e1e1e;color:#eee;overflow-x:hidden}.App{max-width:100%}.holographic-image-caption{margin:.4rem 0 0;padding:0 .2rem;font-size:.7em;color:#aaa;line-height:1.3;min-height:1.3em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;text-align:center}.perspective-wrapper{display:block;position:relative;transition:transform .2s ease-out,box-shadow .3s ease;border-radius:6px;overflow:hidden;margin:0 auto}.perspective-wrapper:hover{transform:scale(1.03);z-index:10}.uploaded-image-wrapper:hover{transform:scale(1.05);box-shadow:0 8px 25px rgba(0,0,0,.6)}.perspective-wrapper.framed{padding:8px;background-color:#282828;box-shadow:0 4px 10px rgba(0,0,0,.4);border:1px solid #444}.shader{position:relative;overflow:hidden;backface-visibility:hidden;display:block;line-height:0;transform-style:preserve-3d;transform:rotateX(var(--rotate-x)) rotateY(var(--rotate-y));transition:transform .08s linear;will-change:transform;border-radius:4px;height:${IMAGE_WIDTH}px;width:100%}.uploaded-image-wrapper .shader{height:auto;max-height:60vh}.perspective-wrapper:not(:hover) .shader{transition:transform .6s cubic-bezier(.23,1,.32,1)}.shader>img{display:block;width:100%;height:100%;object-fit:cover;position:relative;z-index:1;border-radius:inherit}.uploaded-image-wrapper .shader>img{height:auto;max-height:calc(60vh - 16px);object-fit:contain}.shader-layer{position:absolute;inset:0;width:100%;height:100%;background-size:cover;background-position:center;pointer-events:none;z-index:2;border-radius:inherit}.specular{mix-blend-mode:color-dodge;opacity:.95;background-position:var(--bg-pos-x) var(--bg-pos-y);background-size:180% 180%;transition:background-position .6s cubic-bezier(.23,1,.32,1);will-change:background-position}.perspective-wrapper:hover .specular{transition:background-position .08s linear}.mask{mix-blend-mode:multiply;opacity:.8;position:absolute;inset:0;z-index:3;width:100%;height:100%;display:block;object-fit:cover;background-repeat:repeat;background-size:auto;filter:contrast(1.1) brightness(1.0)}.mask img{display:block;width:100%;height:100%;object-fit:cover;background:transparent!important}${gradientStyles}a{color:#9cf;text-decoration:none}a:hover{text-decoration:underline}button{padding:.6rem 1.2rem;font-size:.9rem;cursor:pointer;background-color:#4a4a4a;color:#fff;border:1px solid #666;border-radius:5px;transition:background-color .2s ease,border-color .2s ease}button:hover:not(:disabled){background-color:#5a5a5a;border-color:#888}button:active:not(:disabled){background-color:#3a3a3a}button:disabled{cursor:not-allowed;opacity:.6}input[type=file]{color:#ccc;padding:5px}input[type=file]::file-selector-button{padding:.5rem 1rem;margin-right:1rem;border-radius:4px;border:1px solid #555;background-color:#3a3a3a;color:#eee;cursor:pointer;transition:background-color .2s ease}input[type=file]::file-selector-button:hover{background-color:#4a4a4a}input[type=file]:disabled{opacity:.6;cursor:not-allowed}input[type=file]:disabled::file-selector-button{cursor:not-allowed}select{padding:.5rem;border-radius:4px;background-color:#333;color:#eee;border:1px solid #555;min-width:150px}select:disabled{opacity:.6;cursor:not-allowed}</style></head><body><div id="root"><noscript>Please enable JavaScript to view this interactive page.</noscript></div><script type="module" src="${import.meta.url}"></script></body></html>`,
620{ headers: { "content-type": "text/html; charset=utf-8" } },
621);
26"title": "Markdown Editor",
27"code":
28"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Markdown Editor</title>\n <link href=\"https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css\" rel=\"stylesheet\">\n</head>\n<body class=\"bg-white\">\n <div class=\"max-w-full mx-auto p-4 pt-6 md:p-6 lg:p-8\">\n <h1 class=\"text-3xl text-center mb-4\">Markdown Editor</h1>\n <div class=\"flex flex-row\">\n <div class=\"editor p-4 rounded-lg border border-gray-200 w-full md:w-1/2\">\n <textarea id=\"editor\" class=\"w-full h-screen p-2 border border-gray-200 rounded-lg\" placeholder=\"Type your Markdown here...\"></textarea>\n </div>\n <div class=\"preview p-4 rounded-lg border border-gray-200 w-full md:w-1/2 ml-2 md:ml-4 lg:ml-8\">\n <div id=\"preview\"></div>\n </div>\n </div>\n <p class=\"text-center mt-4\">Built on <a href=\"https://cerebrascoder.com\">Cerebras Coder</a></p>\n </div>\n\n <script>\n const editor = document.getElementById('editor');\n const preview = document.getElementById('preview');\n\n // Initialize textarea with default markdown\n const defaultMarkdown = `\n# Introduction to Markdown\nMarkdown is a lightweight markup language that is easy to read and write. It is often used for formatting text in plain text editors, chat applications, and even web pages.\n\n## Headers\nHeaders are denoted by the # symbol followed by a space. The number of # symbols determines the level of the header:\n# Heading 1\n## Heading 2\n### Heading 3\n\n## Emphasis\nYou can use emphasis to make your text **bold** or *italic*:\n*Italics*\n**Bold**\n\n## Lists\nYou can use lists to organize your text:\n* Item 1\n* Item 2\n* Item 3\nOr\n1. Item 1\n2. Item 2\n3. Item 3\n\n## Links\nYou can use links to reference external resources:\n[Google](https://www.google.com)\n\n## Images\nYou can use images to add visual content:\n\n`;\n editor.value = defaultMarkdown;\n\n // Update preview on input\n editor.addEventListener('input', () => {\n const markdown = editor.value;\n const html = markdownToHtml(markdown);\n preview.innerHTML = html;\n });\n\n // Initialize preview with default markdown\n const defaultHtml = markdownToHtml(defaultMarkdown);\n preview.innerHTML = defaultHtml;\n\n // Function to convert Markdown to HTML\n function markdownToHtml(markdown) {\n // Bold\n markdown = markdown.replace(/\\*\\*(.*?)\\*\\*/g, '<b>$1</b>');\n\n // Italic\n markdown = markdown.replace(/\\*(.*?)\\*/g, '<i>$1</i>');\n\n // Links\n markdown = markdown.replace(/\\[(.*?)\\]\\((.*?)\\)/g, '<a href=\"$2\">$1</a>');\n\n // Images\n markdown = markdown.replace(/!\\[(.*?)\\]\\((.*?)\\)/g, '<img src=\"$2\" alt=\"$1\">');\n\n // Headings\n markdown = markdown.replace(/(^#{1,6} )(.*)/gm, (match, level, text) => {\n return `<h${level.length}>${text}</h${level.length}>`;\n });\n\n // Lists\n markdown = markdown.replace(/^(\\*|\\d+\\.) (.*)/gm, (match, marker, text) => {\n if (marker.startsWith('*')) {\n return `<li>${text}</li>`;\n } else {\n return `<li>${text}</li>`;\n }\n });\n\n // Line breaks\n markdown = markdown.replace(/\\n/g, '<br>');\n\n // Fix for nested lists\n markdown = markdown.replace(/<li><li>/g, '<li>');\n markdown = markdown.replace(/<\\/li><\\/li>/g, '</li>');\n\n // Wrap lists in ul\n markdown = markdown.replace(/(<li>.*<\\/li>)/g, '<ul>$1</ul>');\n\n return markdown;\n }\n </script>\n</body>\n</html>",
29"performance": {
30"tokensPerSecond": 4092.96,
cerebras_coderindex.html1 match
21<meta property="og:description" content="Turn your ideas into fully functional apps in less than a second β powered by Llama3.3-70b on Cerebras's super-fast wafer chips. Code is 100% open-source, hosted on Val Town."">
22<meta property="og:type" content="website">
23<meta property="og:image" content="https://stevekrouse-blob_admin.web.val.run/api/public/CerebrasCoderOG.jpg">
24
25
stevensDemoREADME.md1 match
3It's common to have code and types that are needed on both the frontend and the backend. It's important that you write this code in a particularly defensive way because it's limited by what both environments support:
45
67For example, you *cannot* use the `Deno` keyword. For imports, you can't use `npm:` specifiers, so we reccomend `https://esm.sh` because it works on the server & client. You *can* use TypeScript because that is transpiled in `/backend/index.ts` for the frontend. Most code that works on the frontend tends to work in Deno, because Deno is designed to support "web-standards", but there are definitely edge cases to look out for.
stevensDemoREADME.md1 match
21## `favicon.svg`
2223As of this writing Val Town only supports text files, which is why the favicon is an SVG and not an .ico or any other binary image format. If you need binary file storage, check out [Blob Storage](https://docs.val.town/std/blob/).
2425## `components/`
stevensDemoindex.ts15 matches
73});
7475// --- Blob Image Serving Routes ---
7677// GET /api/images/:filename - Serve images from blob storage
78app.get("/api/images/:filename", async (c) => {
79const filename = c.req.param("filename");
8081try {
82// Get image data from blob storage
83const imageData = await blob.get(filename);
8485if (!imageData) {
86return c.json({ error: "Image not found" }, 404);
87}
8890let contentType = "application/octet-stream"; // Default
91if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) {
92contentType = "image/jpeg";
93} else if (filename.endsWith(".png")) {
94contentType = "image/png";
95} else if (filename.endsWith(".gif")) {
96contentType = "image/gif";
97} else if (filename.endsWith(".svg")) {
98contentType = "image/svg+xml";
99}
100101// Return the image with appropriate headers
102return new Response(imageData, {
103headers: {
104"Content-Type": contentType,
107});
108} catch (error) {
109console.error(`Error serving image ${filename}:`, error);
110return c.json(
111{ error: "Failed to load image", details: error.message },
112500,
113);
stevensDemoindex.html3 matches
10href="/public/favicon.svg"
11sizes="any"
12type="image/svg+xml"
13/>
14<link rel="preconnect" href="https://fonts.googleapis.com" />
36height: 100%;
37font-family: "Pixelify Sans", sans-serif;
38image-rendering: pixelated;
39}
40body::before {
50/* For pixel art aesthetic */
51* {
52image-rendering: pixelated;
53}
54</style>
stevensDemohandleUSPSEmail.ts12 matches
12}
1314type ImageSummary = {
15sender: string;
16recipient: (typeof RECIPIENTS)[number] | "both" | "other";
22anthropic: Anthropic,
23htmlContent: string,
24imageSummaries: ImageSummary[]
25) {
26try {
36text: `Analyze the following content from an email and provide a response as a JSON blob (only JSON, no other text) with two parts.
3738The email is from the USPS showing mail I'm receiving. Metadata about packages is stored directly in the email. Info about mail pieces is in images, so I've included summaries of those as well.
3940Your response should include:
66And here is info about the mail pieces:
6768${JSON.stringify(imageSummaries)}`,
69},
70],
95const anthropic = new Anthropic({ apiKey });
9697// Process each image attachment serially
98const summaries = [];
99for (const [index, attachment] of e.attachments.entries()) {
100try {
101const imageData = await attachment.arrayBuffer();
102const base64Image = btoa(
103String.fromCharCode(...new Uint8Array(imageData))
104);
105112content: [
113{
114type: "image",
115source: {
116type: "base64",
117media_type: attachment.type,
118data: base64Image,
119},
120},
148summaries.push(parsedResponse);
149} catch (error) {
150console.error(`Image analysis error:`, error);
151summaries.push({
152sender: "Error",
153recipient: "Error",
154type: "error",
155notes: `Image ${index + 1} Analysis Failed: ${error.message}`,
156});
157}
stevensDemo.cursorrules2 matches
187## Val Town Platform Specifics
188- **Redirects:** Use `return new Response(null, { status: 302, headers: { Location: "/place/to/redirect" }})` instead of `Response.redirect` which is broken
189- **Images:** Avoid external images or base64 images. Use emojis, unicode symbols, or icon fonts/libraries instead
190- For AI-generated images, use: `https://maxm-imggenurl.web.val.run/the-description-of-your-image`
191- **Storage:** DO NOT use the Deno KV module for storage
192- **Browser APIs:** DO NOT use the `alert()`, `prompt()`, or `confirm()` methods