168 return (
169 <div className="no-internet-container">
170 <img src="https://i.postimg.cc/t4q16WRj/images.png" alt="No Internet" />
171 <div className="no-internet-message">
172 No internet connection
181}
182
183function Slideshow({ images }) {
184 useEffect(() => {
185 let currentSlideIndex = 0;
231 slides[0].style.left = "0%";
232 }
233 }, []); // images dependency removed to run once, assuming images don't change post-mount
234
235 return (
236 <div className="slideshow-container">
237 {images.map((src, index) => (
238 <img key={index} className={`mySlides ${index === 0 ? "visible" : ""}`} src={src} alt={`Slide ${index + 1}`} />
239 ))}
1116 }, []); // Empty dependency array, so it runs once on mount
1117
1118 const slideshowImages = [
1119 "https://i.postimg.cc/KvSbQsgQ/download-4.png",
1120 "https://i.postimg.cc/NMzVyLRp/download-5.png",
1189 </button>
1190 </div>
1191 <Slideshow images={slideshowImages} />
1192 <Card {...loginCardData} />
1193 <Card {...scanCodeCardData} />
168 return (
169 <div className="no-internet-container">
170 <img src="https://i.postimg.cc/t4q16WRj/images.png" alt="No Internet" />
171 <div className="no-internet-message">
172 No internet connection
181}
182
183function Slideshow({ images }) {
184 useEffect(() => {
185 let currentSlideIndex = 0;
231 slides[0].style.left = "0%";
232 }
233 }, []); // images dependency removed to run once, assuming images don't change post-mount
234
235 return (
236 <div className="slideshow-container">
237 {images.map((src, index) => (
238 <img key={index} className={`mySlides ${index === 0 ? "visible" : ""}`} src={src} alt={`Slide ${index + 1}`} />
239 ))}
804 }, []); // Empty dependency array, so it runs once on mount
805
806 const slideshowImages = [
807 "https://i.postimg.cc/KvSbQsgQ/download-4.png",
808 "https://i.postimg.cc/NMzVyLRp/download-5.png",
874 </div>
875 </div>
876 <Slideshow images={slideshowImages} />
877 <Card {...loginCardData} />
878 <Card {...scanCodeCardData} />
92 return (
93 <div className="p-1 max-w-3xl mx-auto">
94 <Header image={user?.pfp_url} name={user?.username} onShare={context ? onShare : undefined} />
95 <Feed casts={casts} />
96 </div>
115 return (
116 <div className="p-1 max-w-3xl mx-auto">
117 <Header image={channel.image_url} name={"/" + channel.id} onShare={context ? onShare : undefined} />
118 <Feed casts={casts} />
119 </div>
39 - **`proxy.ts`**: Proxy to the old blog for legacy content
40 - **`rss.ts`**: RSS feed generation
41 - **`favicon.ts`** & **`og-image.ts`**: Asset routes
42- **`/utils/`**: Utility functions for post processing, caching, etc.
43- **`/posts/`**: Markdown files for blog posts
79- Tables
80- Lists
81- Images
82- And other standard markdown features
83
101- Efficient proxy for legacy content
102- Minimal CSS with no external frameworks
103- Optimized image handling
104
105## Deployment
13}) {
14 const description = post?.description ?? SITE_DESCRIPTION;
15 const ogImage = new URL("/og-image.png", BLOG_URL);
16 ogImage.searchParams.append("title", title);
17
18 return (
20 <meta charSet="UTF-8" />
21 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
22 <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
23
24 <title>{title === "Val Town Blog" ? title : `${title} | Val Town Blog`}</title>
34 <meta property="og:description" content={description} />
35
36 <meta property="og:image" content={ogImage} />
37
38 {/* Twitter */}
39 <meta property="twitter:card" content="summary_large_image" />
40 <meta property="twitter:url" content={BLOG_URL} />
41 <meta property="twitter:title" content={title} />
42 <meta property="twitter:description" content={description} />
43
44 <meta property="twitter:image" content={ogImage} />
45
46 {
4const LINE_HEIGHT = 72;
5
6export default function OGImage({
7 title = "Val Town Blog",
8 ...props
3import { Hono } from "https://esm.sh/hono@3.12.0";
4import { renderToStaticMarkup } from "https://esm.sh/react-dom@18.2.0/server";
5import OGImage from "../components/OGImage.tsx";
6
7const app = new Hono();
10 const title = c.req.query("title");
11 const format = c.req.query("format");
12 const svg = renderToStaticMarkup(<OGImage title={title} />);
13
14 if (format === "svg") {
15 return new Response(svg, {
16 headers: {
17 "Content-Type": "image/svg+xml",
18 },
19 });
24 return new Response(png, {
25 headers: {
26 "Content-Type": "image/png",
27 "Content-Length": png.length.toString(),
28 },
4import faviconRoute from "./routes/favicon.ts";
5import homeRoutes from "./routes/home.ts";
6import ogImageRoute from "./routes/og-image.ts";
7import proxyRoutes from "./routes/proxy.ts";
8import rssRoute from "./routes/rss.ts";
28app.route("/rss.xml", rssRoute);
29app.route("/favicon.svg", faviconRoute);
30app.route("/og-image.png", ogImageRoute);
31app.route("/", blogRoutes);
32
1Eventually we should host all our images properly, but for now, drag and drop them here 👇
2
3* https://imagedelivery.net/iHX6Ovru0O7AjmyT5yZRoA/4d90a6f7-247c-4df4-3de6-928364e10000/public
4* https://imagedelivery.net/iHX6Ovru0O7AjmyT5yZRoA/f175100b-a190-4772-7056-04c09f273a00/public
79 "slug": "fal",
80 "link": "/blog/fal",
81 "description": "Bringing lightning fast AI image generation to Val Town",
82 "pubDate": "Thu, 31 Oct 2024 00:00:00 GMT",
83 "author": "Steve Krouse",