// blog.jsx — index + post view (state-toggled) const { useState: useStateB } = React; const POSTS_FALLBACK = [ { slug: "logo-animation-file-formats-explained", title: "Logo animation file formats, explained.", excerpt: "MP4, MOV, GIF, Lottie, WebM. When to use which — and why most clients only need two of them.", category: "Practical", minutes: 6, date: "May 04, 2026", cover: "fmt", body: [ { kind: "p", text: "When you commission a logo animation, you'll usually get back three or four file types. Most clients have no idea what to do with them. Here's a cheat sheet, sorted by where you'll actually use each one." }, { kind: "h", text: "MP4 — the workhorse" }, { kind: "p", text: "This is the file you'll use 80% of the time. It plays everywhere, it's small enough to email, and it looks correct on every screen. If you only have time to grab one file from your delivery folder, grab the MP4." }, { kind: "pull", text: "If you only have time to grab one file, grab the MP4." }, { kind: "h", text: "MOV with alpha — for layering" }, { kind: "p", text: "Comes with a transparent background, which means you can drop your logo over video footage, photographs, anywhere the background isn't a flat colour. Slightly heavier file than MP4. Editors love it." }, { kind: "h", text: "GIF — only when you must" }, { kind: "p", text: "GIFs are everywhere, but they're a terrible format for motion graphics. Limited to 256 colours, no transparency that doesn't look like a 1998 web page. The only reason to ship one is if your platform genuinely doesn't accept anything else." }, { kind: "h", text: "Lottie / JSON — for websites" }, { kind: "p", text: "If your animation is going to live on a website, ask for Lottie. It's a JSON file that renders animation as vectors, which means infinitely sharp at any size and tiny in filesize. Not all animations export cleanly to Lottie — anything with heavy raster effects won't — but for clean vector marks it's gold." }, ], }, { slug: "premium-vs-cheap-logo-animation", title: "Why a $50 logo animation isn't a $50 logo animation.", excerpt: "A pricing breakdown of what you're actually paying for — and why the cheapest option ends up costing you more.", category: "Opinion", minutes: 4, date: "Apr 18, 2026", cover: "pricing", body: [ { kind: "p", text: "I've animated a lot of logos. I've also seen a lot of clients come to me after spending fifty dollars on a marketplace template and getting something they couldn't use. So let me show you what that fifty dollars actually buys, and what mine does." }, { kind: "h", text: "What you get for $50" }, { kind: "p", text: "A template. Someone built a generic logo reveal years ago — maybe a swoop with sparkles, maybe a glitch effect — and uploaded it to a marketplace. The seller swaps in your logo over a base mask and renders it. The same animation has been sold to thousands of other companies. It probably looks fine. It also doesn't say anything about your brand." }, { kind: "h", text: "What you get for $200" }, { kind: "p", text: "Custom motion designed for your specific mark. Timing tuned to the proportions of your logo. Sound design built around the animation rather than slapped on top. Files in formats you'll actually use. A brand asset that lasts five years instead of looking dated within six months." }, { kind: "pull", text: "Cheap motion graphics age in dog years. Custom work doesn't." }, { kind: "h", text: "The hidden cost of cheap" }, { kind: "p", text: "Every cheap logo animation I've seen has been redone within eighteen months. The client gets it, uses it once, realises it looks like everyone else's, and commissions a new one. By the time they're three swap-outs in, they've spent more than they would've on a custom job at the start." }, { kind: "p", text: "Cheap motion graphics age in dog years. Custom work doesn't." }, ], }, { slug: "animated-logo-youtube-channel", title: "The two-second logo: making intros for YouTube.", excerpt: "If your intro is over three seconds, your viewers are leaving. A breakdown of intros that work in the new attention economy.", category: "How-to", minutes: 5, date: "Apr 02, 2026", cover: "youtube", body: [ { kind: "p", text: "Watch any successful YouTube channel from 2019 and you'll see a six-second logo intro. Watch one from 2026 and the intro is two seconds, sometimes less. The data is brutal: every additional second of intro is another percent of viewers who close the tab." }, { kind: "h", text: "Cut your intro in half. Then cut it again." }, { kind: "p", text: "Most creators send me a three-second concept and I send back two-second cuts. Almost every time, the shorter one tests better in retention analytics. Your intro is not where you tell viewers who you are — it's where you confirm they're in the right place." }, { kind: "h", text: "The 'snap' principle" }, { kind: "p", text: "Good two-second intros land on a single sharp beat. Logo enters, hits a final pose, locks. No drift, no afterthought, no settling. The animation tells the viewer 'this is content, get ready' — and then it gets out of the way." }, { kind: "pull", text: "Your intro isn't where you tell viewers who you are. It's where you confirm they're in the right place." }, { kind: "h", text: "Sound matters more than you think" }, { kind: "p", text: "Half the punch of a great two-second logo is in the sound design. A clean impact, one whoosh, maybe a tonal sting — that's it. If your intro relies on visual fireworks because the sound is silent, you're losing half your impact on every play." }, ], }, ]; function getPosts() { return window.VM_POSTS || POSTS_FALLBACK; } function Blog({ setRoute }) { const [post, setPost] = useStateB(null); if (post) return setPost(null)} setPost={setPost} setRoute={setRoute} />; return ; } /* ─── index ─── */ function Index({ setPost, setRoute }) { return (
— Studio journal

Notes from
the studio.

Thoughts on logo animation, pricing, formats, and what makes motion actually land. New posts roughly monthly.

{/* featured */} {/* grid of remaining */}
{getPosts().slice(1).map((p, i) => ( setPost(p)} /> ))}
); } function FeaturedCard({ post }) { return (
FEATURED {post.category.toUpperCase()}
{post.title}

{post.excerpt}

{post.date.toUpperCase()} · {post.minutes} MIN READ
Read essay
); } function PostCard({ post, onClick }) { return ( ); } /* ─── cover art (no real images — make distinct generative covers) ─── */ function CoverArt({ kind, featured }) { return (
{/* atmosphere */}
{/* artwork */}
{kind === "fmt" && } {kind === "pricing" && } {kind === "youtube" && }
{/* grain */}
\")", mixBlendMode: "overlay", opacity: 0.6, }} />
); } function CornerMark({ pos }) { const map = { tl: { top: 16, left: 16, borderTop: "1px solid rgba(255,255,255,0.5)", borderLeft: "1px solid rgba(255,255,255,0.5)" }, tr: { top: 16, right: 16, borderTop: "1px solid rgba(255,255,255,0.5)", borderRight: "1px solid rgba(255,255,255,0.5)" }, bl: { bottom: 16, left: 16, borderBottom: "1px solid rgba(255,255,255,0.5)", borderLeft: "1px solid rgba(255,255,255,0.5)" }, br: { bottom: 16, right: 16, borderBottom: "1px solid rgba(255,255,255,0.5)", borderRight: "1px solid rgba(255,255,255,0.5)" }, }; return
; } function FormatGlyph() { return ( {["MP4","MOV","GIF","JSON"].map((ext, i) => ( {ext} ))} ); } function PricingGlyph() { return ( $50 $200 ); } function YouTubeGlyph() { return ( ); } /* ─── post view ─── */ function Post({ post, onBack, setPost, setRoute }) { return (
{/* breadcrumb */}
· {post.category.toUpperCase()}
{/* header */}

{post.title}

Alex Moretti
{post.date.toUpperCase()} · {post.minutes} MIN READ
{/* cover */}
{/* body */}
{post.body.map((b, i) => { if (b.kind === "h") return (

{b.text}

); if (b.kind === "pull") return (
{b.text}
); return (

{b.text}

); })}
{/* footer / next */}
— Keep reading
{getPosts().filter(p => p.slug !== post.slug).slice(0, 2).map(p => ( { window.scrollTo({ top: 0 }); setPost(p); }} /> ))}
); } /* ─── newsletter strip ─── */ function NewsletterStrip({ setRoute }) { return (
— Studio dispatch

One essay per month.
Nothing else.

New posts, occasional behind-the-scenes of recent commissions, and first-look at slots when I open them. Unsubscribe with one click.

312 SUBSCRIBERS · NO SPAM
); } const inputStyleC = { width: "100%", padding: "14px 16px", borderRadius: "var(--radius-sm)", background: "var(--glass)", border: "1px solid var(--line-2)", color: "var(--ink)", fontFamily: "var(--font-sans)", fontSize: 15, outline: "none", }; Object.assign(window, { Blog, VM_POSTS: POSTS_FALLBACK });