Posts in Css (20 found)

Pausing a CSS animation with getAnimations()

It’s Blogvent, day 9, where I blog daily in December! CSS animations are cool, but sometimes you want them to just cool it . You can pause them by using the method ! When you call on an element, you get an array of all of the objects on said element, which includes CSS animations. There’s various things you can do with the returned object, like getting the of the animation’s timeline, or the playback state of the animation ( ), or in our case, actually pausing the animation with . We could loop through every Animation object in that array and pause it, like so: Or, if you just want one animation to pause, you can filter from the returned results. Here’s a real demo where there’s only one animation happening, so we pause it based on the current . See the Pen getAnimations() demo by Cassidy ( @cassidoo ) on CodePen . Hope this was helpful!

0 views
iDiallo 3 days ago

Let users zoom in on mobile devices

This is a bit of a rant. Maybe my eyes are not as good as they used to be. When I read an article that has pictures on them, I like to zoom in to see the details. You might think this makes no sense, I just have to pinch the screen to zoom in. You would be right, but some websites intentionally prevent you from zooming in. Here is an example, the straw that broke the camel's back so to speak. I was reading an interesting article on substack about kids who ran away in the 60s , and it has these pictures of letters from those kids. Handwritten letters that complement the story and I really wanted to read. But have you tried reading text from a picture in an article on a phone? Again, it could just be what happens when you spend 35 years in front of screens. CSS alone is not enough to properly turn a page responsive on a mobile device. The browser needs to know how we want to size the viewport properly. For that we have a viewport property that gives the browser a hint on how to size the page. Since we've started making pages responsive yesteryear, I've relied on a single configuration and have rarely ever found a reason to change it: The is set to the current device's width, mobile or desktop, it doesn't matter. The is set to 1. The documentation is a bit confusing, I consider the scale to just be the initial zoom level. That's really all you need to know about the viewport if you are building a webpage and want to make it display properly on a mobile device. But of course, the article I'm complaining about has different settings. Here is what they have: The properties I'm complaining about are and . The first one says users can't zoom in period. Why would you prevent users from zooming in? This is such a terrible setting that you can set your browser to ignore this setting. But for good measures, they added , which means even if you are allowed to zoom, the maximum zoom level is one... which means you can't zoom. Yes, I disabled zoom to make a point It's a terrible experience all the way around. When I read articles that have pictures, I can't zoom in! I can't properly look at the pictures. There are a few platforms that I've noticed have these settings. Substack and Medium are the most annoying. Now, when I know an article is from those platforms, I just ignore them. The only time you ever need to override users from zooming is if it's a web game. Other than that, it's just plain annoying.

0 views
Manuel Moreale 5 days ago

Stephanie Stimac

This week on the People and Blogs series we have an interview with Stephanie Stimac, whose blog can be found at blog.stephaniestimac.com . Tired of RSS? Read this in your browser or sign up for the newsletter . The People and Blogs series is supported by Lou Plummer and the other 127 members of my "One a Month" club. If you enjoy P&B, consider becoming one for as little as 1 dollar a month. I’m Stephanie Stimac, a product manager and designer from Seattle, WA but I currently live in a small town in England. My background is in visual and web design, and I graduated at a weird time in terms of web tech. A good portion of my final year of university was spent learning about how to build websites in Flash and when I graduated, Flash quickly became obsolete, but I had a bit of HTML and CSS knowledge which helped me advance through my career. I was doing purely design work for the first part of my career before I joined the Microsoft Edge Web Platform team, where I started doing a handful of other things that were product management and developer relations adjacent. I’ve spec’d out features, analyzed data and user flows, created content for social media, performed user research to identify developer paint points, given conference talks, the list goes on. I left Microsoft for a startup that moved me to Berlin but that was unfortunately short lived due to the company folding rather quickly after hiring 100+ people. Now I live in England with my husband and work for Igalia, a technology consultancy, and I’m back in the Web Platform space. It’s sort of like my role at Microsoft but less product and more project focused. In between all this, I wrote a book that was published in 2023 called Design for Developers. It’s an evergreen guide to the basics of visual and UX design for web developers. I’m a collector of hobbies but my focus lately has been reading, baking, photography, printmaking and creating content. I’m a mountain biker and love to hike as well as paddle board. I’ve also gotten into birding in the last year or so. There are so many different kinds, it’s incredible. Depends on which blog you’re talking about! I’ve been blogging since about 2003 when I had a LiveJournal in high school. That evolved into a blogging and sharing about college life on Wordpress around 2008/2009 and went through a few iterations before I landed on the name The Hermes Homestead. I no longer blog there but am in the process of starting that sort of lifestyle blogging again and am building a new site with Astro and Netlify. As for my more technical and design focused blog, I started that in 2019 about 3 years into my career at Microsoft. I wanted a space to talk about CSS, design and web browser things. This is my most visited blog, and it is attached to my personal website and portfolio though I called it “The Web Witch’s Blog” for a long time – now there’s just a cauldron with a code mark. It’s been through a few redesigns. It was very basic in terms of styling for the longest time. I’ve slowly made incremental improvements to things over the years, but this year I did a larger redesign to try and capture a bit of my witchy vibe and wanted to include more visuals, some subtle animations and view transitions. There are a few different types of ways I post. I was doing monthly updates just covering things I had learned, big life events, what media I consumed in a month, books I read. I haven’t done this in a few months as I’m currently pregnant and was feeling burned out on everything. I’m sure I’ll pick those up again soon. I’ll also post about major career or life events. Other posts are inspired by things I’ve experienced, for example I’m in the process of writing about the worst onboarding experience I have ever had with a credit card and the brand’s website and app. Nothing quite inspires me like a poor user experience, and I hope in sharing those experiences other people will be inspired to make sure whatever user experience they’re designing isn’t terrible. I’m also not afraid to share my terrible experiences working in tech, whether that’s about encountering conference line ups that are all men or finding out my book was scraped by Meta’s AI. I also write about new CSS features that web developers can use but I have to want to write about these, so they usually need to have some sort of design focus or benefit. Those are sort of the three main categories I center posts around at the moment, but generally if it fits into general life and career, I’ll write about it. When it comes to writing the actual post, I usually just write straight in VS Code in a markdown file. I try to proofread in VS Code but I need a new spelling and grammar extension as I end up missing a few mistakes that I’ll correct as soon as I see them. Recently, I’ve started copying text over to Microsoft Word just for a quick visual to catch any misspellings. Then I hit publish. I rarely have someone else look over a post unless I am writing about something I’m little unsure about or if I’m talking about the company I work for. I don’t want to misrepresent them. Overall, my process is kind of like writing for a diary. I don’t really overthink what I’m writing about and just post it. That being said, I often come up with many ideas for things to write about, but it really depends on the type of mental space I’m in whether those get finished or not. I have a pile of started but unfinished drafts. For writing, I usually have to be at home or in a quiet space with some music. Sometimes I can write when I’m out at the coffee shop, but it is very dependent on the coffee shop and how busy it is. Physical spaces 100% influence my creativity and just overall mood and wellbeing. I like to be in a space that inspires me, surrounded by things that inspire me. I have a hard time focusing if there’s a mess or I’m in a space that doesn’t speak to me. I like to be surrounded by an environment that has a vibe or a point of view. I work from home so most of my work happens there. My husband and I have been in the process of slowly upgrading our home to be a space we enjoy being in. It’s been a slow process over the last two years, and we’re still making changes but it’s getting there and there are spots in our home I’m starting to love. But it’s also important to change up the scenery occasionally, so we’ll go out and work from coffee shops some mornings, otherwise I get stir crazy. I also use a combination of digital and analog tools to keep track of things. I have a bullet journal I fill out every week with my calendar and personal to-do list, and I have what I consider a digital bullet journal for all my work stuff in Notion that’s formatted nicely. I do have a digital bullet journal for personal stuff I’m also starting to use again, which is helpful when I have a lot of long-term plans in the works. For the technical blog, it’s hosted on Netlify and I’m using 11ty. The domain used to be registered with GoDaddy but I’ve been migrating all my domains over to Namecheap because I find some of GoDaddy’s practices predatory or less than user friendly. The old lifestyle blog is on Wordpress and is still hosted on GoDaddy, and I’m probably going to end up paying someone to migrate it all to Namecheap for me because my migration attempts have been headache inducing. I want to keep the blog up even if it’s not active, but I’m tired of paying an exorbitant amount just for an SSL certificate compared to other providers. I think it’s criminal GoDaddy charges you the amount they do for SSL when you get an SSL certificate for free. The new lifestyle blog will be hosted on Netlify (like most of my websites), with a Namecheap domain, and I’m building it with Astro. For the technical blog, no I wouldn’t change anything. It’s tied to my name and career. I’m happy with Netlify and 11ty. For the lifestyle blog, I wouldn’t use Wordpress again. It has its benefits but for the functionality I want and need, I think it’s overkill for what I personally am trying to achieve. I like having more control over the layout and design and I’m happy to be building the new one with Astro. As for the name, I only wish I had chosen something that was a bit more open instead of something that was so aligned to a specific period of my life (The Hermes Homestead). It doesn’t fit where I’m at anymore, but I feel like the new name is more open and maybe starting with a fresh slate isn’t so bad. If I combine everything, I think it’s about $350 a year for all my blogs including URLs and hosting but I expect that to be reduced significantly once I move everything away from GoDaddy. (For reference, I just got an email from them telling me that my SSL for one of the URLs I’m letting expire won’t renew and they want £90 a year just for the SSL certificate.) On the technical blog, I do generate a little bit of revenue but not a lot. I sometimes include affiliate links, and I do run ads on the homepage, but it is one spot in the sidebar. I don’t want an intrusive experience with ads because there’s nothing I hate more than landing on a page that is so covered in ads you can’t navigate through the page (looking at all you food blogs.) I was recently on a food blogger’s page and went to the print recipe page to try and read the instructions more easily and they had even put ads on the print page. I don’t want to replicate that experience, so I try to keep things as minimal as possible. I really don’t mind if people are trying to monetise their content unless it’s so overwhelming full of ads that I can’t view the content. At the end of the day, I don’t know what a person’s situation is, and we live in a rather precarious and unstable time for many people when it comes to employment. Monetising could help someone reach a goal more quickly or give them a little more freedom or room to breathe in their budget. I’ve been given product in exchange for writing a review and I don’t mind that kind of partnership. I think affiliate links are a great way to monetise without being intrusive. I use Carbon Ads for my technical blog and don’t mind their style because it’s very minimal. In terms of supporting other bloggers, I’ll click an affiliate link or engage with their content but I’m not currently paying anyone via Patreon or a subscription, though I have in the past. I love Henry Desroches’ content over on henry.codes and stillness.digital . Olu Niyi-Awosusi also has a lovely blog over at olu.online and I love reading their work. Maggie Appleton’s Garden is also full of incredible writing. If you’re a developer trying to level up your skills outside of code, my book Design for Developers, is available on Amazon and Manning . My colleagues host an interesting podcast that covers a range of technology topics called Igalia Chats . On the more casual side of things, I’ve been vlogging and trying to improve my video editing abilities over on YouTube . Typically sharing my life in England and more style focused content over there. And a final shoutout to my husband, who is constantly building absolutely wild things with CSS. He’s working on getting his blog up but for now he’s got a few links on his website . Now that you're done reading the interview, go check the blog and subscribe to the RSS feed . If you're looking for more content, go read one of the previous 118 interviews . Make sure to also say thank you to Manton Reece and the other 127 supporters for making this series possible.

1 views

The what, how, and why of CSS clamp()

It’s Blogvent, day 4, where I blog daily in December! CSS is cool and you should use it. In a sentence, lets you assign a value to a CSS property between a minimum and a maximum range, and uses a preferred value in that range. It’s really helpful for responsive layouts and typography! has three parameters, in order: You can assign it to a property, like so: The column width here is always between 200px and 400px wide, and defaults to 40% of its container width. If that 40% is less than 200px, the width will be 200px. Similarly, if that 40% is more than 400px, the width will be 400px. Or, another example: The font size here is always between 16px and 24px, and defaults to 4% of the screen’s width. If a screen is 1000px wide, that means the font size would be 40px if it were that exact 4%, but with this function, it is capped at 24px. It’s shorter! Honestly that’s why. You can accomplish a lot with a single line of (that is arguably easier to maintain) than a set of media queries. It reduces reliance on multiple rules and functions. A typical media query approach for a column width might be: But with , you could do: This is way shorter, and I would argue, easier to read and maintain! CSS is widely supported , so you can safely use it across your apps and websites. If you’d like to learn more, here’s some handy links for ya: Until next time! A minimum value A preferred value A maximum value Clamp Calculator CSS documentation CSS Tricks Almanac:

0 views
Rob Zolkos 1 weeks ago

Vanilla CSS is all you need

Back in April 2024, Jason Zimdars from 37signals published a post about modern CSS patterns in Campfire . He explained how their team builds sophisticated web applications using nothing but vanilla CSS. No Sass. No PostCSS. No build tools. The post stuck with me. Over the past year and a half, 37signals has released two more products (Writebook and Fizzy) built on the same nobuild philosophy. I wanted to know if these patterns held up. Had they evolved? I cracked open the source code for Campfire, Writebook, and Fizzy and traced the evolution of their CSS architecture. What started as curiosity became genuine surprise. These are not just consistent patterns. They are improving patterns. Each release builds on the last, adopting progressively more modern CSS features while maintaining the same nobuild philosophy. These are not hobby projects. Campfire is a real-time chat application. Writebook is a publishing platform. Fizzy is a full-featured project management tool with kanban boards, drag-and-drop, and complex state management. Combined, they represent nearly 14,000 lines of CSS across 105 files. Not a single line touches a build tool. Let me be clear: there is nothing wrong with Tailwind . It is a fantastic tool that helps developers ship products faster. The utility-first approach is pragmatic, especially for teams that struggle with CSS architecture decisions. But somewhere along the way, utility-first became the only answer. CSS has evolved dramatically. The language that once required preprocessors for variables and nesting now has: 37signals looked at this landscape and made a bet: modern CSS is powerful enough. No build step required. Three products later, that bet is paying off. Open any of these three codebases and you find the same flat structure: That is it. No subdirectories. No partials. No complex import trees. One file per concept, named exactly what it does. Zero configuration. Zero build time. Zero waiting. I would love to see something like this ship with new Rails applications. A simple starting structure with , , , and already in place. I suspect many developers reach for Tailwind not because they prefer utility classes, but because vanilla CSS offers no starting point. No buckets. No conventions. Maybe CSS needs its own omakase. Jason’s original post explained OKLCH well. It is the perceptually uniform color space all three apps use. The short version: unlike RGB or HSL, OKLCH’s lightness value actually corresponds to perceived brightness. A 50% lightness blue looks as bright as a 50% lightness yellow. What is worth noting is how this foundation remains identical across all three apps: Dark mode becomes trivial: Every color that references these primitives automatically updates. No duplication. No separate dark theme file. One media query, and the entire application transforms. Fizzy takes this further with : One color in, four harmonious colors out. Change the card color via JavaScript ( ), and the entire card theme updates automatically. No class swapping. No style recalculation. Just CSS doing what CSS does best. Here is a pattern I did not expect: all three applications use units for horizontal spacing. Why characters? Because spacing should relate to content. A gap between words feels natural because it is literally the width of a character. As font size scales, spacing scales proportionally. This also makes their responsive breakpoints unexpectedly elegant: Instead of asking “is this a tablet?”, they are asking “is there room for 100 characters of content?” It is semantic. It is content-driven. It works. Let me address the elephant in the room. These applications absolutely use utility classes: The difference? These utilities are additive , not foundational. The core styling lives in semantic component classes. Utilities handle the exceptions: the one-off layout adjustment, the conditional visibility toggle. Compare to a typical Tailwind component: And the 37signals equivalent: Yes, it is more CSS. But consider what you gain: If there is one CSS feature that changes everything, it is . For decades, you needed JavaScript to style parents based on children. No more. Writebook uses it for a sidebar toggle with no JavaScript: Fizzy uses it for kanban column layouts: Campfire uses it for intelligent button styling: This is CSS doing what you used to need JavaScript for. State management. Conditional rendering. Parent selection. All declarative. All in stylesheets. What fascinated me most was watching the architecture evolve across releases. Campfire (first release) established the foundation: Writebook (second release) added modern capabilities: Fizzy (third release) went all-in on modern CSS: You can see a team learning, experimenting, and shipping progressively more sophisticated CSS with each product. By Fizzy, they are using features many developers do not even know exist. CSS Layers solve the specificity wars that have plagued CSS since the beginning. It does not matter what order your files load. It does not matter how many classes you chain. Layers determine the winner, period. One technique appears in all three applications that deserves special attention. Their loading spinners use no images, no SVGs, no JavaScript. Just CSS masks. Here is the actual implementation from Fizzy’s : The keyframes live in a separate file: Three dots, bouncing in sequence: The means it automatically inherits the text color. Works in any context, any theme, any color scheme. Zero additional assets. Pure CSS creativity. The default browser element renders as a yellow highlighter. It works, but it is not particularly elegant. Fizzy takes a different approach for search result highlighting: drawing a hand-drawn circle around matched terms. Here is the implementation from : The HTML structure is . The empty exists solely to provide two pseudo-elements ( and ) that draw the left and right halves of the circle. The technique uses asymmetric border-radius values to create an organic, hand-drawn appearance. The makes the circle semi-transparent against the background, switching to in dark mode for proper blending. Search results for: webhook No images. No SVGs. Just borders and border-radius creating the illusion of a hand-drawn circle. Fizzy and Writebook both animate HTML elements. This was notoriously difficult before. The secret is . Here is the actual implementation from Fizzy’s : The variable is defined globally as . Open Dialog This dialog animates in and out using pure CSS. The rule defines where the animation starts from when an element appears. Combined with , you can now transition between and . The modal smoothly scales and fades in. The backdrop fades independently. No JavaScript animation libraries. No manually toggling classes. The browser handles it. I am not suggesting you abandon your build tools tomorrow. But I am suggesting you reconsider your assumptions. You might not need Sass or PostCSS. Native CSS has variables, nesting, and . The features that needed polyfills are now baseline across browsers. You might not need Tailwind for every project. Especially if your team understands CSS well enough to build a small design system. While the industry sprints toward increasingly complex toolchains, 37signals is walking calmly in the other direction. Is this approach right for everyone? No. Large teams with varying CSS skill levels might benefit from Tailwind’s guardrails. But for many projects, their approach is a reminder that simpler can be better. Thanks to Jason Zimdars and the 37signals team for sharing their approach openly. All code examples in this post are taken from the Campfire, Writebook, and Fizzy source code. For Jason’s original deep-dive into Campfire’s CSS patterns, see Modern CSS Patterns and Techniques in Campfire . If you want to learn modern CSS, these three codebases are an exceptional classroom. Native custom properties (variables) Native nesting Container queries The selector (finally, a parent selector) CSS Layers for managing specificity for dynamic color manipulation , , for responsive sizing without media queries HTML stays readable. tells you what something is, not how it looks. Changes cascade. Update once, every button updates. Variants compose. Add without redefining every property. Media queries live with components. Dark mode, hover states, and responsive behavior are co-located with the component they affect. OKLCH colors Custom properties for everything Character-based spacing Flat file organization View Transitions API for smooth page changes Container queries for component-level responsiveness for entrance animations CSS Layers ( ) for managing specificity for dynamic color derivation Complex chains replacing JavaScript state

0 views
Rob Zolkos 1 weeks ago

The Making of Fizzy, Told by Git

Today Fizzy was released and the entire source code of its development history is open for anyone to see . DHH announced on X that the full git history is available - a rare opportunity to peek behind the curtain of how a 37signals product comes together. I cloned down the repository and prompted Claude Code: “Can you go through the entire git history and write a documentary about the development of this application. What date the first commit was. Any major tweaks, changes and decisions and experiments. You can take multiple passes and use sub-agents to build up a picture. Make sure to cite commits for any interesting things. If there is anything dramatic then make sure to see if you can figure out decision making. Summarize at the end but the story should go into STORY.md” It responded with: “This is a fascinating task! Let me create a comprehensive investigation plan and use multiple agents to build up a complete picture of this project’s history.” Here is the story of Fizzy - as interpreted by Claude - from the trail of git commits. Enjoy! A chronicle of 18 months of development at Basecamp, told through 8,152 commits. At 1:19 PM on a summer Friday, Kevin McConnell typed the words that would begin an 18-month journey: Within hours, the foundation was laid. The team moved with practiced efficiency: By end of day, the skeleton of a Rails application stood ready. But what would it become? One month after inception, Jason Zimdars introduced the application’s first real identity: A “Splat” — the name evokes something chaotic, impactful, unexpected. Like a bug hitting your windshield on a summer drive. The original data model was simple: The next day brought the visual metaphor that would define the early application: The windshield was the canvas. Splats appeared on it like bugs on glass — colorful, slightly chaotic, each one a piece of information demanding attention. The commits reveal urgency. Something important was coming: The all-hands demo. Approximately one month after project inception, Fizzy (then still called “Splat”) was shown to the entire company. The pressure to polish was evident in the commit messages. Seven days after the windshield metaphor was established, Jason Zimdars typed four words that would reshape the application’s identity: The chaotic “splat” gave way to something gentler — bubbles floating on a windshield , like soap suds catching light. The animation changed from aggressive splattering to gentle floating: Perfect circles gave way to hand-drawn blob shapes. The team was discovering what their product was through the act of building it. A new interaction pattern emerged: When users “boosted” a bubble, it would puff up and float away — like champagne fizz rising. The animation: The metaphor was crystallizing. Bubbles. Fizzing. Effervescence. The name would come soon. In a single day, the application found its final name through two commits: 42 files changed. The model, controllers, views, tests — everything touched. Hours later: Fizzy. The name captured everything: the bubbles, the effervescence, the playful energy of the interface. Visual design had driven product naming — the team discovered what they were building through the act of building it. The flat list of bubbles needed structure: But “Projects” didn’t feel right. Eight days later: Then “Bucket” became “Collection.” Eventually, “Collection” would become “Board.” The terminology dance — Projects → Buckets → Collections → Boards — reveals a team searching for the right mental model. They ultimately landed on the familiar “Board” metaphor, aligning with tools like Trello and Linear. David Heinemeier Hansson, creator of Ruby on Rails and co-founder of Basecamp, made his first contribution with characteristic pragmatism: He deleted an unused image file. It was a statement of intent. Within two days, DHH’s fingerprints were everywhere: He upgraded the entire application to Rails 8 release candidate and systematically added HTTP caching throughout. DHH’s most distinctive contribution was his crusade against what he called “anemic” code — thin wrappers that explain nothing and add needless indirection. He used this term 15 times in commit messages: Philosophy: Code should either add explanatory value OR hide implementation complexity. Thin wrappers that do neither are “anemic” and should be eliminated. Then came April 2025. DHH made 323 commits in a single month — 55% of his total contributions compressed into 30 days. This was a surgical strike. He: His commit messages tell the story: In DHH’s philosophy: deletion is a feature, not a bug. After 10 months as “Bubbles,” another transformation: 333 files changed. “Pop” (completing a bubble) became “Closure” (closing a card). The playful metaphor gave way to task management vocabulary. The final architectural piece: Fizzy had become a kanban board . Cards lived in columns. Columns could be customized, colored, reordered. The application had evolved from “bugs on a windshield” to a sophisticated project management tool. Collections became Boards. The transformation was complete: Original (July 2024): Final (November 2025): A Claude-powered AI assistant that could answer questions about project content. Born, restricted to staff, then removed entirely. Perhaps replaced by the more ambitious MCP (Model Context Protocol) integration — making Fizzy AI-native at the protocol level rather than bolting on a chatbot. Emoji reactions for cards and comments. Added. Removed. Then added again. The git history shows healthy debate — not everything that ships stays shipped, and not everything removed stays gone. Saved custom views were replaced by ephemeral quick filters. Complexity gave way to simplicity. Predefined workflows with stages were removed in favor of ad-hoc column organization. Users would create their own structure. The MCP (Model Context Protocol) branch represents cutting-edge AI integration — allowing Claude and other AI assistants to interact with Fizzy programmatically. An manifest advertises Fizzy’s capabilities to AI clients. Status: Removed from main, but the infrastructure remains fascinating. This is one of the earliest explorations of making traditional web applications AI-native. Multiple parallel branches exploring different approaches to mobile column navigation. Scroll snapping. Contained scrolling. Swipeable columns. The problem remains unsolved — there’s no “one true way” for mobile kanban navigation. Making Fizzy work with SQLite in addition to MySQL. Simpler local development. Better portability. The search index was even sharded into 16 tables ( through ) for scale. The proprietary SAAS features were extracted into a separate gem. What remained was a clean, open-source Rails application. After 18 months of development, 8,152 commits, and countless pivots, Fizzy became open source. Jason Zimdars (2,217 commits) — The visual architect. From “Let’s try bubbles” to pixel-perfect polish. Jorge Manrubia (2,053 commits) — The engineering backbone. Consistent, prolific, essential. Andy Smith (1,007 commits) — Front-end craftsmanship and UI refinement. Mike Dalessio (875 commits) — Infrastructure, performance, the recent dashboard work. David Heinemeier Hansson (586 commits) — The architectural enforcer. Rails modernization and the war on anemic code. Kevin McConnell (351 commits) — Started it all with “New Rails app.” Jose Farias (341 commits) — Feature development and testing. Stanko K.R. (239 + 54 commits) — Security hardening and webhook restrictions. Jeffrey Hardy (100 commits) — Early infrastructure and modernization. Jason Fried (7 commits) — The occasional “Small copy adjustment” from the CEO. July 2024 (v0.1): September 2024 (v0.2): November 2025 (v1.0): The story of Fizzy is the story of discovery through building . The team didn’t know they were building a kanban board when they started with “splats on a windshield.” They found out through iteration. Key lessons: Names matter, but they can change. Splat → Bubble → Card. Project → Bucket → Collection → Board. The right name emerges through use. Deletion is a feature. Boosts, Fizzy Ask, custom views, workflows — removing the wrong features is as important as adding the right ones. Architecture evolves. The final column-based kanban system looks nothing like the original flat list of splats. DHH’s philosophy: Remove anemic code. Keep transactions short. Use the latest Rails. Delete more than you add. Design drives naming. “Fizzy” emerged from the visual metaphor of bubbles puffing up and floating away — the design informed the brand. Open source takes extraction. 18 months of SAAS development needed careful separation before the core could be shared. The git history of Fizzy is a masterclass in iterative product development. 8,152 commits. 25+ contributors. 18 months. One application that discovered its identity through the act of creation. “Let’s try bubbles.” — Jason Zimdars, July 31, 2024 Documentary compiled December 2, 2025 Based on analysis of the Fizzy git repository First Commit: June 21, 2024 Total Commits: 8,152 Contributors: 25+ Lines of Code Changed: Hundreds of thousands Name Changes: 4 (Splat → Bubble → Card; Project → Bucket → Collection → Board) Features Removed: At least 4 major ones DHH Commits in April 2025 Alone: 323 1:23 PM — Gemfile updated ( ) 3:47 PM — Rubocop configured ( ) 4:07 PM — Minimal authentication flow ( ) 4:29 PM — CSS reset and base styles ( ) 4:46 PM — Brakeman security scanning added ( ) Removed the entire Boosts feature ( ) — 299 lines across 27 files, gone Eliminated activity scoring ( , , ) Extracted RESTful controllers from overloaded ones ( , ) Enforced transaction discipline ( — “No long transactions!”) Splats on a Windshield Cards → Columns → Boards → Accounts Jason Zimdars (2,217 commits) — The visual architect. From “Let’s try bubbles” to pixel-perfect polish. Jorge Manrubia (2,053 commits) — The engineering backbone. Consistent, prolific, essential. Andy Smith (1,007 commits) — Front-end craftsmanship and UI refinement. Mike Dalessio (875 commits) — Infrastructure, performance, the recent dashboard work. David Heinemeier Hansson (586 commits) — The architectural enforcer. Rails modernization and the war on anemic code. Kevin McConnell (351 commits) — Started it all with “New Rails app.” Jose Farias (341 commits) — Feature development and testing. Stanko K.R. (239 + 54 commits) — Security hardening and webhook restrictions. Jeffrey Hardy (100 commits) — Early infrastructure and modernization. Jason Fried (7 commits) — The occasional “Small copy adjustment” from the CEO. July 24, 2024: “Handful of tweaks before all-hands” — Demo day pressure July 31, 2024: “Let’s try bubbles” — The visual pivot September 4, 2024: “Splat -> Fizzy” — Finding the name April 2025: DHH’s 323-commit refactoring blitz October 2025: “Remove Fizzy Ask” — The AI feature that didn’t survive November 28, 2025: “Initial README and LICENSE” — Going public Rails 8.x — Always on the latest, sometimes ahead of stable Hotwire (Turbo + Stimulus) — No heavy JavaScript framework Solid Queue & Solid Cache — Rails-native background jobs and caching SQLite + MySQL support — Database flexibility Kamal deployment — Modern container orchestration UUID primary keys — Using UUIDv7 for time-ordering Multi-tenancy — Account-based data isolation Names matter, but they can change. Splat → Bubble → Card. Project → Bucket → Collection → Board. The right name emerges through use. Deletion is a feature. Boosts, Fizzy Ask, custom views, workflows — removing the wrong features is as important as adding the right ones. Architecture evolves. The final column-based kanban system looks nothing like the original flat list of splats. DHH’s philosophy: Remove anemic code. Keep transactions short. Use the latest Rails. Delete more than you add. Design drives naming. “Fizzy” emerged from the visual metaphor of bubbles puffing up and floating away — the design informed the brand. Open source takes extraction. 18 months of SAAS development needed careful separation before the core could be shared.

0 views
fLaMEd fury 1 weeks ago

Ain't Enough To Go 'Round In This World

What’s going on, Internet? December crept up fast and suddenly it’s twenty three days until Christmas. I’ve been enjoying getting out more and seeing live music. There’s so much more happening up here in Auckland and it has been good getting back into gigs. I started the month with Tom Scott’s Anitya show at the Civic . A week later I questioned my own sanity by going out to another gig with some wonderful friends on a Tuesday night right before flying to Sydney for the first of two work trips. Sydney was great. It was good catching up with and see work mates in person, but also mentally exhausting. Flying back to Auckland for the weekend added to the fatigue, but I liked the change of pace. I even managed to catch up with some of my cousins and aunt for dinner. Having the chance to do that on work trips is a nice bonus. Meanwhile the house hunting and weekends of endless open homes finally came to an end. My wife viewed a place while I was in Sydney and pushed it through the offer stage. The offer was accepted conditionally before I’d even seen the house. We went unconditional a week later and only then did I walk through it for the first time. After more than sixty open homes this year, buying a place that needs work makes more sense for us than blowing our budget on something “liveable” but missing basics like linen cupboards, wardrobes, or a proper laundry. This way we get to shape it how we want. I’m excited for the new year. While catching up and surfing the web, one particular link making the rounds that claimed personal websites are dead, which I obviously disagree with and replied to . Finally, I finished up my Firefox Container configuration and shared it for anyone to try out . Let me know if you found the container setup useful. With all that going on, I still found time to watch a bunch of shows, listen to a lot of music, pick up tonne of new records, and make a few updates around the site. Here’s November in full. I watched a bunch of episodes on the flights back and forth from Sydney. No movies this month. What happened there. I carried on with The Chair Company, which wrapped up its first season yesterday. Such a bizarre show. No idea when the next season is coming but I’ll be sticking with it. I finished Andor season 3. What a damn good show. I’ve got Rogue One queued up to wrap up the story, even though I’ve already seen it three times. I’m still watching South Park. It’s fun, but I’m tired of the White House plot line (I’m sure Matt & Trey are too). I miss the boys just being kidsw. I’ll probably go back to season 1 soon to remind myself how the show has changed and evolved over the years. Some absolute classic episodes around seasons 6-7. Plu1bus caught my attention and I’m working through it as episodes release. Interesting premise and am enjoying watching the story unfold. On the flight I spotted the UK show Dope Girls and gave it a go. I forgot about it once I landed, but I’ll finish the remaining four episodes soon now that writing this post has reminded me. I started and finished season 2 of The Vince Staples Show. It leans into the same bizarre energy as later seasons of Atlanta. Low stakes, easy to watch, and fun. I also started Educators. Silly, very New Zealand, and perfect fifteen minute episodes when I don’t want to think and have an awkward laugh. I got through three books this month. Gabriel’s Bay by Catherine Robertson was a solid read with plenty of local flavour and a warm story. 7th Circle kept me hooked as it pushed further into the Shadow Grove universe I got into last year reading through the Maddison Kate books. I’m fully here for the messy plotlines and the drama threaded between the raunchy sex scenes. I’m here for it. I also read Atmosphere by Taylor Jenkins Reid. Her books are always epically tragic and beautiful at the same time, and this one absolutely delivered on both fronts. Trying to decide if I want to read the rest of the 7th Circle books this month or dive into something heavier like Project Hail Mary. This month saw my usual mix of pop, hip hop, and early-2000s. Mokomokai ended up as my top artist of the month, with Olivia Rodrigo, D12, Tadpole, Eminem, and Westside Gunn all getting steady playtime. Top albums were a mix - SOUR by Olivia Rodrigo at the top, followed by Tadpole’s The Buddhafinger, Mokomokai’s latest release PONO!, and both Heels Have Eyes records from Westside Gunn. MGK’s Tickets to My Downfall also crept back into rotation with the (All Access) release of five new tracks to the orignal album. MGK has a gig here next year - do I want to go see him in concert? I mean I like Tickets To My Downfall but think he’s a ballbag. Dilemas. Track of the month was “Verona” by Elemeno P, with “Kitty” by The Presidents of the United States of America, (thanks to riding in the car with my son) and a few Olivia Rodrigo singles scattered through the top ten. Mokomokai showed up again with “Roof Racks”, because sometimes I’m just in the mood for something agressive. November 2025 saw my largest vinyl haul ever. I took advantage of the 20 percent off vinyl sale at JB Hi-Fi, burned through a stack of saved vouchers, and grabbed a few special pieces elsewhere. The links are a bit of a mix this month and there’s a lot of them. Enjoy. Not a huge month for website work. I fixed up some CSS, finished rolling out categories and tags across all my posts, and cleaned up a few lingering bits of front-matter. I still need to build the individual category pages and rethink how this data is displayed on the posts index and on each post. The posts page itself needs a refresh too. I’m not loving the masonry card layout anymore. This update was brought to you by Alright by Tadpole Hey, thanks for reading this post in your feed reader! Want to chat? Reply by email or add me on XMPP , or send a webmention . Check out the posts archive on the website. Tom Scott – Anitya from the gig MOKOMOKAI – PONO , WHAKAREHU , and Mokomokai all direct from their website in a special bundle which included the last remaining copies of the Mokomokai Vinyl 1st pressing in Red & Black Marble Fleetwood Mac – Rumours — JB Hi-Fi Eminem – The Slim Shady LP (Expanded Edition) — JB Hi-Fi Stellar* – Mix — JB Hi-Fi Tadpole – The Buddhafinger , and The Medusa — JB Hi-Fi D12 – Devil’s Night (IVC Edition) — Interscope Vinyl Collective, orange variant with posters and D12 sticker in a beaufiful, heavy gatefold sleeve The psychological cost of having an RSS feed Filip explores the anxiety that comes with writing a blog knowing it has an RSS feed. My first months in cyberspace Phil Gyford remembers the excitement and optimism of being online in 1995. Steps Towards a Web without The Internet AJ Roach imagines a web that could exist without the internet, built from small, local networks instead of centralised infrastructure. Should Your Indieweb Site Be Mobile Friendly? MKUltra.Monster experiments with making old-web design mobile-friendly without losing its classic feel. I ❤ shortcuts #3: read a random blog post Hyde shares a neat script to help randomly surf the independent web. In Praise of RSS and Controlled Feeds of Information rkert writes about why syndication still matters and how sharing content across the open web helps sites stay connected. Who’s a blog for? Cobb thinks through who a blog is really for and why writing for yourself remains the most sustainable approach. Maintaining a Music Library, Ten Years On Brian Schrader reflects on maintaining his personal music library over a decade and why owning your collection still matters. ChatGPT’s Atlas: The Browser That’s Anti-Web - Anil Dash Anil Dash argues that Atlas isn’t just an unusual browser but an anti-web tool that strips context from sites and traps users in a closed, distorted version of the internet. I know you don’t want them to want AI, but… - Anil Dash Anil Dash questions how we should react to Firefox adding AI features. He suggests die-hard fans need to look past the knee-jerk outrage and ask whether Firefox is actually trying to offer a safer, more privacy-minded version of tools their non-technical friends are already using. Early web memories - roundup post Winther rounds up early web memories from the recent Bear Blog Carnival - gutted I missed this as it was happening! Blogs used to be very different. Jetgirl looks back at how blogs used to work, from tight-knit communities to slower, more personal writing, and how different that feels compared to today. PicoSSG Pico is a tiny static site generator focused on simplicity, giving you a lightweight way to build plain HTML sites without a full framework. Personal blogs are back, should niche blogs be next? Disassociated writes about the return of personal blogs and why niche blogs might be the next wave as people move away from algorithmic platforms. Feeds and algorithms have freed us from personal websites Disassociated pushes back on the idea that platform feeds are “good enough,” arguing that treating Medium profiles as websites misses the point, and that personal sites still matter because they give you control rather than renting space inside someone else’s algorithm. Small Web, Big Voice Afranca writes about how the small web still carries real weight, showing that personal sites and hand-built spaces can have a bigger impact than their size suggests. How to Protect Your Privacy from ChatGPT and Other Chatbots Mozilla explains how to protect your privacy when using ChatGPT and other AI tools, focusing on data control, account settings, and reducing what these systems can collect about you.

0 views
Manuel Moreale 1 weeks ago

Karen

This week on the People and Blogs series we have an interview with Karen, whose blog can be found at chronosaur.us . Tired of RSS? Read this in your browser or sign up for the newsletter . The People and Blogs series is supported by Pete Millspaugh and the other 127 members of my "One a Month" club. If you enjoy P&B, consider becoming one for as little as 1 dollar a month. Hello! My name is Karen. I work in IT support for a large company’s legal department, and am currently working on my Bachelors in Cybersecurity and Information Assurance. I live near New Orleans, Louisiana, with my husband and two dogs - Daisy, A Pembroke Welsh Corgi, and Mia, a Chihuahua. Daisy is The Most Serious Corgi ever (tm), and Mia has the personality of an old lady who chain smokes, plays Bingo every week at the rec center, and still records her soap operas on a VHS daily. My husband is an avid maker (woodworking and 3D printing, mostly), video gamer, and has an extensive collection of board games that takes up the entire back wall of our livingroom. As for me, outside of work, I’m a huge camera nerd and photographer. I love film photography, and recently learned how to develop my own negatives at home! I also do digital - I will never turn my nose up at one versus the other. I’ve always been into assorted fandoms, and used to volunteer at local sci-fi/fantasy/comic conventions up to a few years ago. I got into K-Pop back in 2022, and am now an active participant in the local New Orleans fan community, providing Instax photo booth services for events. I’ve also hosted K-Pop events here in NOLA as well. My ult K-Pop group is ATEEZ, but I’m a proud multi fan and listen to whatever groups or music catch my attention, including Stray Kids, SHINee, and Mamamoo. I also love 80s and 90s alternative, mainly Depeche Mode, Nine Inch Nails, and Garbage. And yes, I may be named Karen but I refuse to BE a “Karen”. I don’t get upset when people use the term, I find it hilarious. So I have been blogging off and on since 2001 or so - back when they were still called “weblogs” and “online journals”. Originally, I was using LiveJournal, but even with a paid account, I wanted to learn more customization and make a site that was truly my own. My husband - then boyfriend - had their own server, and gave me some space on it. I started out creating sites in Microsoft Frontpage and Dreamweaver (BEFORE Adobe owned them!), and moved to using Greymatter blog software, which I loved and miss dearly. I moved to Wordpress in - 2004 maybe? - and used that for all my personal sites until 2024. I’d been reading more and more about the Indieweb for a while and found Bear , and I loved the simplicity. I’ve had sites ranging from a basic daily online journal, to a fashion blog, to a food blog, to a K-Pop and fandom-centric blog, to what it is today - my online space for everything and anything I like. I taught myself HTML and CSS in order to customize and create my sites. No classes, no courses, no books, no certifications, just Google and looking at other people’s sites to see what I liked and how they did it. My previous job before this one, I was a web administrator for a local marketing company that built sites using DNN and Wordpress, and I’m proud to say that I got that job and my current one with my self-developed skills and being willing to learn and grow. I would not be where I am today, professionally, if it wasn’t for blogging. I’ll be totally honest - I don’t have a writing process. I get inspiration from random thoughts, seeing things online, wanting to share the day-to-day of my life. I don’t draft or have someone proof read, I just type out what I feel like writing. When I had blogs focusing on specific things - plus size fashion and K-Pop, respectively - I kept a list of topics and ideas to refer back to when I was stuck for ideas. That was when I was really focused on playing the SEO and search engine algorithm game, though, where I was trying to stick to the “two-three posts a week” rule in an attempt to boost my search engine results. I don’t do that now. I do still have a list of ideas on my phone, but it’s nothing I am feeling FORCED to stick to. It’s more along the lines of that I had an idea while I was out, and wanted to note it so I don’t forget. Memory is a fickle thing in your late 40s, LOL. My space absolutely influences my mindset for writing. I prefer to write in the early morning, because my brain operates best then. (I know I am an exception to the rule by being an early bird.) I love weekend mornings when I can get up really early and settle into my recliner with my laptop and coffee, and just listen to some lofi music and just feel topics and ideas out. I also made my office/guest bedroom into a cozy little space, with a daybed full of soft blankets and fluffy pillows and cushions, and a lap desk. In all honesty, my preferred location to write is at a coffeeshop first thing in the morning. I love sitting tucked in a booth with a coffee and muffin, headphones on and listening to music, when the sun is just on the cusp of rising and the shop is still a little too chilly. That’s when the creative ideas light up the brightest and the synapses are firing on all cylinders. Currently, my site is hosted on Bear . I used to be a self-hosted Wordpress devotee, but in mid-late 2024, I got really tired of the bloat that the apps had become. In order to use it efficiently for me, I had to install entirely too many plugins to make it “simpler”. (Shout-out to the Indieweb Wordpress team, though - they work so hard on those plugins!) Of course, the more plugins you have, the less secure your site… My domain is registered through Hostinger . To write my posts, I use Bear Markdown Notes. I heard about this program after seeing a few others talking about using it for drafts, notes, etc. I honestly don’t think I’d change much! I really love using Bear Blog. It reminds me of the very old school LiveJournal days, or when I used Greymatter. It takes me back to the web being simpler, more straightforward, more fun. I also like Bear’s manifesto , and that he built the service for longevity . I would probably structure my site differently, especially after seeing some personal sites set up with more of a “digital garden” format. I will eventually adjust my site at some point, but for now, I’m fine with it. (That and between school and work, it’s kind of low on the priority list.) I purchased a lifetime subscription to Bear after a week of using it, which ran around $200 - I don’t remember exactly. I knew that I was going to be using the service for a while and thought I should invest in a place that I believed in. My Hostinger domain renewals run around $8.99 annually. My blog is just my personal site - I don’t generate any revenue or monetise in any way. I don’t mind when people monetize their site - it’s their site and they can do what they choose. As long as it’s not invading others’ privacy or harmful, I have absolutely no issue. Make that money however you like. Ooooh I have three really good suggestions for both checking out and interviewing! Binary Digit - B is kind of an influence for me to play with my site again. They have just this super cool and early 2000s vibe and style that I really love. Their site reminds me of me when I first started blogging, when I was learning new things and implementing what I thought was cool on my site, joining fanlistings, making new online friends. Kevin Spencer - I love Kevin’s writing and especially his photography. Not only that, he has fantastic taste in music. I’ve left many a comment on his site about 80s and 90s synthpop and industrial music. A Parenthetical Departure - Sylvia was one of the first sites I started reading when I started looking up info on Bear Blog. They are EXTREMELY talented and have an excellent knack for playing with design, and showing others how it works. One of my side projects is Burn Like A Flame , which is my local K-pop and fandom photography site. I actualy just started a project there that is more than slightly based on People and Blogs - The Fandom Story Project . I’m interviewing local fans to talk about what they love and what their feelings are on fandom culture now, and I’m accompanying that with a photoshoot with that person. It’s a way to introduce people to each other within the community. Two of my favorite YouTube channels that I have recently been watching are focused on fashion discussion and history - Bliss Foster and understitch, . If you like learning and listening to information on fashion, I highly recommend these creators. I know a TON of people have now seen K-Pop Demon Hunters (which I love, and the movie has a great message for not only kids, but adults). If you’ve seen this and are interested in getting into K-Pop, I suggest checking out my favorite group, ATEEZ. If you think that most K-Pop is all chirpy bubbly cutesy songs, let me suggest two by this group that aren’t what you’d expect: Guerrilla and Turbulence . I strongly suggesting watching without the translations, and then watching again with them. Their lyrics are the thing that really drew me into this group, and had me learning more about the deeper meaning behind a lot of K-Pop songs. And finally, THANK YOU to Manu for People and Blogs! I always find some really great new sites to check out after reading these interviews, and I am truly honored to be asked to join this list of great bloggers. It’s inspiring me to work harder on my blog and to post more often. Now that you're done reading the interview, go check the blog and subscribe to the RSS feed . If you're looking for more content, go read one of the previous 117 interviews . Make sure to also say thank you to Benny and the other 127 supporters for making this series possible.

0 views
Andy Bell 1 weeks ago

It’s been a very hard year

Unlike a lot of places in tech, my company, Set Studio / Piccalilli has no outside funding. Bootstrapped is what the LinkedIn people say, I think. It’s been a hard year this year. A very hard year. I think a naive person would blame it all on the seemingly industry-wide attitude of “AI can just do this for us”. While that certainly hasn’t helped — as I see it — it’s been a hard year because of a combination of limping economies, tariffs, even more political instability and a severe cost of living crisis. It’s been a very similar year to 2020, in my opinion. Why am I writing this? All of the above has had a really negative effect on us this year. Landing projects for Set Studio has been extremely difficult, especially as we won’t work on product marketing for AI stuff, from a moral standpoint, but the vast majority of enquiries have been for exactly that. Our reputation is everything, so being associated with that technology as it increasingly shows us what it really is, would be a terrible move for the long term. I wouldn’t personally be able to sleep knowing I’ve contributed to all of that, too. What we do really well is produce websites and design systems that actually work for and with people. We also share our knowledge and experience via tonnes of free content on Piccalilli , funded by premium courses to keep the lights on. We don’t pepper our content with annoying adverts for companies you have no interest in. I’ve spoken about my dream for us to run Piccalilli full time and heck, that may still happen. For that to happen though, we really needed this Black Friday period to do as well, if not better, as it did last year. So far, that’s not happening unfortunately, but there’s still time. I get it, money is so tight this year and companies are seemingly not investing in staff with training budgets quite like they did. We actually tried to stem that a bit by trialing a community funding model earlier in the year that I outlined in ‌I’m getting fed up of making the rich, richer and we even started publishing some stuff . It went down incredibly well, but when push came to shove, we fell way short in terms of funding support. Like I say, we’re not swimming in investor money, so without the support on Open Collective , as much as it hurt, we had to pull the plug. It’s a real shame — that would have been incredible — but again, I get it , money is tight . This isn’t a woe is me post; that’s not how I roll. This is a post to give some context for what I’m going to ask next and how I’m trying to navigate the tough times. I’m asking folks to help us so we can try to help everyone, whether that’s with web projects that actually work for people or continuing to produce extremely high quality education material. Here’s some ways you can do it. You’ll see messaging like “this is the most important time of year for us” and it’s extremely true. To break the fourth wall slightly, people buying courses at full price is a lot rarer than you might think. So often, discount events are what keeps the lights on. We’ve launched two courses this year — JavaScript for Everyone and Mindful Design — that sit alongside my course, Complete CSS , which we launched last year. I know you’ve probably been burned by shit courses in the past, but these three courses are far from that. I promise. I can’t stress enough how much Mat (JavaScript for Everyone) and Scott (Mindful Design) have put in to these courses this year. These two are elite level individuals with incredible reputations and they’ve shared a seemingly impossible amount of extremely high quality knowledge in their courses. I would definitely recommend giving them your time and support because they really will transform you for the better. For bosses reading this, all three courses will pay themselves back ten-fold — especially when you take advantage of bulk discounts — trust me. So many of you have purchased courses already and I’m forever thankful for that. I can’t stand the term “social proof” but it works. People might be on the fence about grabbing a course, and seeing one of their peers talk about how good it was can be the difference. You might think it’s not worth posting about the courses on social media but people do see it , especially on platforms like Bluesky with their custom feeds. We see it too! Testimonials are always welcome because we can pop those on the course marketing pages, just like on mine . In terms of sharing the studio, if you think we’re cool, post about it! It’s all about eyes and nice words. We’ll do the rest. We’re really good at what we do ! I know every studio/agency says this, but we’re different. We’re actually different. We’re not going to charge you through the nose for substandard work — only deploying a fraction of our team, like a lot of agencies do. I set this studio up to be the antithesis of the way these — and I’ll say it out loud — charlatans operate. Our whole focus is becoming your partners so you can do the — y’know — running of your business/organisation and we take the load off your shoulders. We’re hyper efficient and we fully own projects because they’re way above your normal duties. We get that. In fact, the most efficient way to get the most out of a studio like ours is to do exactly that. I know “numbers goes up” is really important and yes, numbers definitely go up when we work with you. We do that without exploiting your users and customers too. There’s no deceptive patterns coming from us. We instead put everything into branding, messaging, content architecture and making everything extremely fast and accessible. That’s what makes the numbers go up for you. We’re incredibly fairly priced too. We’re not in the business of charging ridiculous fees for our work. We’re only a small team, so our overheads are nothing compared to a lot of agencies. We carry your budgets a long way for you and genuinely give you more bang for your buck with an equitable pricing model. We’ve got availability starting from the new year because starting projects in December is never the ideal way to do things. Getting those projects planned and ready to go is a good idea in December though, so get in touch ! I’m also slowly getting back into CSS and front-end consulting. I’ve helped some of the largest organisations and the smallest organisations, such as Harley-Davidson, the NHS and Google write better code and work better together. Again, starting in the new year I’ll have availability for consulting and engineering support. It might just be a touch more palatable than hiring the whole studio for you. Again, get in touch . I’m always transparent — maybe too transparent at times — but it’s really important for me to be honest. Man, we need more honesty. It’s taken a lot of pride-swallowing to write this but I think it’s more important to be honest than to be unnecessarily proud. I know this will be read by someone else who’s finding the year hard, so if anything, I’m really glad they’ll feel seen at least. Getting good leads is harder than ever, so I’d really appreciate people sharing this with their network . You’ll never regret recommending Piccalilli courses or Set Studio . In fact, you’ll look really good at what you do when we absolutely smash it out of the park. Thanks for reading and if you’re also struggling, I’m sending as much strength your way as I can.

1 views
Andy Bell 2 weeks ago

Happy one year anniversary to Complete CSS!

It was one year to the day that Complete CSS went live. It was Piccalilli’s debut course at the time. Now we have three courses . Anyway, I just wanted to mark this moment and take another opportunity to thank everyone who has purchased Complete CSS. It’s changed everything for us and your continued support of Piccalilli unlocks even more good stuff in the future. I got this feedback today and it sums up exactly what I’m trying to do with how I teach CSS. Maybe you can see yourself in it? I would like to thank you for your work. I have been working with CSS for 20 years, but I never managed to put it together properly. I used Tailwind, but also other approaches when I was frustrated with CSS. Your course helped me a lot. Thank you very much for CUBE CSS, I am enjoying CSS again. I’m so proud of how much the course, that I put so much into, has helped so many people. It makes it all worth doing. I speak so often about the core skills (often called soft skills) that you learn in Complete CSS, but I thought I’d record a video on how quick it is to compose the homepage after we’ve built the whole CSS system. Enjoy!

0 views
Josh Comeau 2 weeks ago

Brand New Layouts with CSS Subgrid

Subgrid allows us to extend a grid template down through the DOM tree, so that deeply-nested elements can participate in the same grid layout. At first glance, I thought this would be a helpful convenience, but it turns out that it’s so much more. Subgrid unlocks exciting new layout possibilities, stuff we couldn’t do until now. ✨

0 views
iDiallo 3 weeks ago

How Do You Send an Email?

It's been over a year and I didn't receive a single notification email from my web-server. It could either mean that my $6 VPS is amazing and hasn't gone down once this past year. Or it could mean that my health check service has gone down. Well this year, I have received emails from readers to tell me my website was down. So after doing some digging, I discovered that my health checker works just fine, but all emails it sends are being rejected by gmail. Unless you use a third party service, you have little to no chance of sending an email that gets delivered. Every year, email services seem to become a tad bit more expensive. When I first started this website, sending emails to my subscribers was free on Mailchimp. Now it costs $45 a month. On Buttondown, as of this writing, it costs $29 a month. What are they doing that costs so much? It seems like sending emails is impossibly hard, something you can almost never do yourself. You have to rely on established services if you want any guarantee that your email will be delivered. But is it really that complicated? Emails, just like websites, use a basic communication protocol to function. For you to land on this website, your browser somehow communicated with my web server, did some negotiating, and then my server sent HTML data that your browser rendered on the page. But what about email? Is the process any different? The short answer is no. Email and the web work in remarkably similar fashion. Here's the short version: In order to send me an email, your email client takes the email address you provide, connects to my server, does some negotiating, and then my server accepts the email content you intended to send and saves it. My email client will then take that saved content and notify me that I have a new message from you. That's it. That's how email works. So what's the big fuss about? Why are email services charging $45 just to send ~1,500 emails? Why is it so expensive, while I can serve millions of requests a day on my web server for a fraction of the cost? The short answer is spam . But before we get to spam, let's get into the details I've omitted from the examples above. The negotiations. How similar email and web traffic really are? When you type a URL into your browser and hit enter, here's what happens: The entire exchange is direct, simple, and happens in milliseconds. Now let's look at email. The process is similar: Both HTTP and email use DNS to find servers, establish TCP connections, exchange data using text-based protocols, and deliver content to the end user. They're built on the same fundamental internet technologies. So if email is just as simple as serving a website, why does it cost so much more? The answer lies in a problem that both systems share but handle very differently. Unwanted third-party writes. Both web servers and email servers allow outside parties to send them data. Web servers accept form submissions, comments, API requests, and user-generated content. Email servers accept messages from any other email server on the internet. In both cases, this openness creates an opportunity for abuse. Spam isn't unique to email, it's everywhere. My blog used to get around 6,000 spam comments on a daily basis. On the greater internet, you will see spam comments on blogs, spam account registrations, spam API calls, spam form submissions, and yes, spam emails. The main difference is visibility. When spam protection works well, it's invisible. You visit websites every day without realizing that behind the scenes. CAPTCHAs are blocking bot submissions, rate limiters are rejecting suspicious traffic, and content filters are catching spam comments before they're published. You don't get to see the thousands of spam attempts that happen every day on my blog, because of some filtering I've implemented. On a well run web-server, the work is invisible. The same is true for email. A well-run email server silently: There is a massive amount of spam. In fact, spam accounts for roughly 45-50% of all email traffic globally . But when the system works, you simply don't see it. If we can combat spam on the web without charging exorbitant fees, email spam shouldn't be that different. The technical challenges are very similar. Yet a basic web server on a $5/month VPS can handle millions of requests with minimal spam-fighting overhead. Meanwhile, sending 1,500 emails costs $29-45 per month through commercial services. The difference isn't purely technical. It's about reputation, deliverability networks, and the ecosystem that has evolved around email. Email providers have created a cartel-like system where your ability to reach inboxes depends on your server's reputation, which is nearly impossible to establish as a newcomer. They've turned a technical problem (spam) into a business moat. And we're all paying for it. Email isn't inherently more complex or expensive than web hosting. Both the protocols and the infrastructure are similar, and the spam problem exists in both domains. The cost difference is mostly artificial. It's the result of an ecosystem that has consolidated around a few major providers who control deliverability. It doesn't help that Intuit owns Mailchimp now. Understanding this doesn't necessarily change the fact that you'll probably still need to pay for email services if you want reliable delivery. But it should make you question whether that $45 monthly bill is really justified by the technical costs involved. Or whether it's just the price of admission to a gatekept system. DNS Lookup : Your browser asks a DNS server, "What's the IP address for this domain?" The DNS server responds with something like . Connection : Your browser establishes a TCP connection with that IP address on port 80 (HTTP) or port 443 (HTTPS). Request : Your browser sends an HTTP request: "GET /blog-post HTTP/1.1" Response : My web server processes the request and sends back the HTML, CSS, and JavaScript that make up the page. Rendering : Your browser receives this data and renders it on your screen. DNS Lookup : Your email client takes my email address ( ) and asks a DNS server, "What's the mail server for example.com?" The DNS server responds with an MX (Mail Exchange) record pointing to my mail server's address. Connection : Your email client (or your email provider's server) establishes a TCP connection with my mail server on port 25 (SMTP) or port 587 (for authenticated SMTP). Negotiation (SMTP) : Your server says "HELO, I have a message for [email protected]." My server responds: "OK, send it." Transfer : Your server sends the email content, headers, body, attachments, using the Simple Mail Transfer Protocol (SMTP). Storage : My mail server accepts the message and stores it in my mailbox, which can be a simple text file on the server. Retrieval : Later, when I open my email client, it connects to my server using IMAP (port 993) or POP3 (port 110) and asks, "Any new messages?" My server responds with your email, and my client displays it. Checks sender reputation against blacklists Validates SPF, DKIM, and DMARC records Scans message content for spam signatures Filters out malicious attachments Quarantines suspicious senders Both require reputation systems Both need content filtering Both face distributed abuse Both require infrastructure to handle high volume

0 views
Max Woolf 3 weeks ago

Nano Banana can be prompt engineered for extremely nuanced AI image generation

You may not have heard about new AI image generation models as much lately, but that doesn’t mean that innovation in the field has stagnated: it’s quite the opposite. FLUX.1-dev immediately overshadowed the famous Stable Diffusion line of image generation models, while leading AI labs have released models such as Seedream , Ideogram , and Qwen-Image . Google also joined the action with Imagen 4 . But all of those image models are vastly overshadowed by ChatGPT’s free image generation support in March 2025. After going organically viral on social media with the prompt, ChatGPT became the new benchmark for how most people perceive AI-generated images, for better or for worse. The model has its own image “style” for common use cases, which make it easy to identify that ChatGPT made it. Two sample generations from ChatGPT. ChatGPT image generations often have a yellow hue in their images. Additionally, cartoons and text often have the same linework and typography. Of note, , the technical name of the underlying image generation model, is an autoregressive model. While most image generation models are diffusion-based to reduce the amount of compute needed to train and generate from such models, works by generating tokens in the same way that ChatGPT generates the next token, then decoding them into an image. It’s extremely slow at about 30 seconds to generate each image at the highest quality (the default in ChatGPT), but it’s hard for most people to argue with free. In August 2025, a new mysterious text-to-image model appeared on LMArena : a model code-named “nano-banana”. This model was eventually publically released by Google as Gemini 2.5 Flash Image , an image generation model that works natively with their Gemini 2.5 Flash model. Unlike Imagen 4, it is indeed autoregressive, generating 1,290 tokens per image. After Nano Banana’s popularity pushed the Gemini app to the top of the mobile App Stores, Google eventually made Nano Banana the colloquial name for the model as it’s definitely more catchy than “Gemini 2.5 Flash Image”. The first screenshot on the iOS App Store for the Gemini app. Personally, I care little about what leaderboards say which image generation AI looks the best. What I do care about is how well the AI adheres to the prompt I provide: if the model can’t follow the requirements I desire for the image—my requirements are often specific —then the model is a nonstarter for my use cases. At the least, if the model does have strong prompt adherence, any “looking bad” aspect can be fixed with prompt engineering and/or traditional image editing pipelines. After running Nano Banana though its paces with my comically complex prompts, I can confirm that thanks to Nano Banana’s robust text encoder, it has such extremely strong prompt adherence that Google has understated how well it works. Like ChatGPT, Google offers methods to generate images for free from Nano Banana. The most popular method is through Gemini itself, either on the web or in an mobile app, by selecting the “Create Image 🍌” tool. Alternatively, Google also offers free generation in Google AI Studio when Nano Banana is selected on the right sidebar, which also allows for setting generation parameters such as image aspect ratio and is therefore my recommendation. In both cases, the generated images have a visible watermark on the bottom right corner of the image. For developers who want to build apps that programmatically generate images from Nano Banana, Google offers the endpoint on the Gemini API . Each image generated costs roughly $0.04/image for a 1 megapixel image (e.g. 1024x1024 if a 1:1 square): on par with most modern popular diffusion models despite being autoregressive, and much cheaper than ’s $0.17/image. Working with the Gemini API is a pain and requires annoying image encoding/decoding boilerplate, so I wrote and open-sourced a Python package: gemimg , a lightweight wrapper around Gemini API’s Nano Banana endpoint that lets you generate images with a simple prompt, in addition to handling cases such as image input along with text prompts. I chose to use the Gemini API directly despite protests from my wallet for three reasons: a) web UIs to LLMs often have system prompts that interfere with user inputs and can give inconsistent output b) using the API will not show a visible watermark in the generated image, and c) I have some prompts in mind that are…inconvenient to put into a typical image generation UI. Let’s test Nano Banana out, but since we want to test prompt adherence specifically, we’ll start with more unusual prompts. My go-to test case is: I like this prompt because not only is an absurd prompt that gives the image generation model room to be creative, but the AI model also has to handle the maple syrup and how it would logically drip down from the top of the skull pancake and adhere to the bony breakfast. The result: That is indeed in the shape of a skull and is indeed made out of pancake batter, blueberries are indeed present on top, and the maple syrup does indeed drop down from the top of the pancake while still adhereing to its unusual shape, albeit some trails of syrup disappear/reappear. It’s one of the best results I’ve seen for this particular test, and it’s one that doesn’t have obvious signs of “AI slop” aside from the ridiculous premise. Now, we can try another one of Nano Banana’s touted features: editing. Image editing, where the prompt targets specific areas of the image while leaving everything else as unchanged as possible, has been difficult with diffusion-based models until very recently with Flux Kontext . Autoregressive models in theory should have an easier time doing so as it has a better understanding of tweaking specific tokens that correspond to areas of the image. While most image editing approaches encourage using a single edit command, I want to challenge Nano Banana. Therefore, I gave Nano Banana the generated skull pancake, along with five edit commands simultaneously: All five of the edits are implemented correctly with only the necessary aspects changed, such as removing the blueberries on top to make room for the mint garnish, and the pooling of the maple syrup on the new cookie-plate is adjusted. I’m legit impressed. Now we can test more difficult instances of prompt engineering. One of the most compelling-but-underdiscussed use cases of modern image generation models is being able to put the subject of an input image into another scene. For open-weights image generation models, it’s possible to “train” the models to learn a specific subject or person even if they are not notable enough to be in the original training dataset using a technique such as finetuning the model with a LoRA using only a few sample images of your desired subject. Training a LoRA is not only very computationally intensive/expensive, but it also requires care and precision and is not guaranteed to work—speaking from experience. Meanwhile, if Nano Banana can achieve the same subject consistency without requiring a LoRA, that opens up many fun oppertunities. Way back in 2022, I tested a technique that predated LoRAs known as textual inversion on the original Stable Diffusion in order to add a very important concept to the model: Ugly Sonic , from the initial trailer for the Sonic the Hedgehog movie back in 2019. One of the things I really wanted Ugly Sonic to do is to shake hands with former U.S. President Barack Obama , but that didn’t quite work out as expected. 2022 was a now-unrecognizable time where absurd errors in AI were celebrated. Can the real Ugly Sonic finally shake Obama’s hand? Of note, I chose this test case to assess image generation prompt adherence because image models may assume I’m prompting the original Sonic the Hedgehog and ignore the aspects of Ugly Sonic that are distinct to only him. Specifically, I’m looking for: I also confirmed that Ugly Sonic is not surfaced by Nano Banana, and prompting as such just makes a Sonic that is ugly, purchasing a back alley chili dog. I gave Gemini the two images of Ugly Sonic above (a close-up of his face and a full-body shot to establish relative proportions) and this prompt: That’s definitely Obama shaking hands with Ugly Sonic! That said, there are still issues: the color grading/background blur is too “aesthetic” and less photorealistic, Ugly Sonic has gloves, and the Ugly Sonic is insufficiently lanky. Back in the days of Stable Diffusion, the use of prompt engineering buzzwords such as , , and to generate “better” images in light of weak prompt text encoders were very controversial because it was difficult both subjectively and intuitively to determine if they actually generated better pictures. Obama shaking Ugly Sonic’s hand would be a historic event. What would happen if it were covered by The New York Times ? I added to the previous prompt: So there’s a few notable things going on here: That said, I only wanted the image of Obama and Ugly Sonic and not the entire New York Times A1. Can I just append to the previous prompt and have that be enough to generate the image only while maintaining the compositional bonuses? I can! The gloves are gone and his chest is white, although Ugly Sonic looks out-of-place in the unintentional sense. As an experiment, instead of only feeding two images of Ugly Sonic, I fed Nano Banana all the images of Ugly Sonic I had ( seventeen in total), along with the previous prompt. This is an improvement over the previous generated image: no eyebrows, white hands, and a genuinely uncanny vibe. Again, there aren’t many obvious signs of AI generation here: Ugly Sonic clearly has five fingers! That’s enough Ugly Sonic for now, but let’s recall what we’ve observed so far. There are two noteworthy things in the prior two examples: the use of a Markdown dashed list to indicate rules when editing, and the fact that specifying as a buzzword did indeed improve the composition of the output image. Many don’t know how image generating models actually encode text. In the case of the original Stable Diffusion, it used CLIP , whose text encoder open-sourced by OpenAI in 2021 which unexpectedly paved the way for modern AI image generation. It is extremely primitive relative to modern standards for transformer-based text encoding, and only has a context limit of 77 tokens: a couple sentences, which is sufficient for the image captions it was trained on but not nuanced input. Some modern image generators use T5 , an even older experimental text encoder released by Google that supports 512 tokens. Although modern image models can compensate for the age of these text encoders through robust data annotation during training the underlying image models, the text encoders cannot compensate for highly nuanced text inputs that fall outside the domain of general image captions. A marquee feature of Gemini 2.5 Flash is its support for agentic coding pipelines; to accomplish this, the model must be trained on extensive amounts of Markdown (which define code repository s and agentic behaviors in ) and JSON (which is used for structured output/function calling/MCP routing). Additionally, Gemini 2.5 Flash was also explictly trained to understand objects within images, giving it the ability to create nuanced segmentation masks . Nano Banana’s multimodal encoder, as an extension of Gemini 2.5 Flash, should in theory be able to leverage these properties to handle prompts beyond the typical image-caption-esque prompts. That’s not to mention the vast annotated image training datasets Google owns as a byproduct of Google Images and likely trained Nano Banana upon, which should allow it to semantically differentiate between an image that is and one that isn’t, as with similar buzzwords. Let’s give Nano Banana a relatively large and complex prompt, drawing from the learnings above and see how well it adheres to the nuanced rules specified by the prompt: This prompt has everything : specific composition and descriptions of different entities, the use of hex colors instead of a natural language color, a heterochromia constraint which requires the model to deduce the colors of each corresponding kitten’s eye from earlier in the prompt, and a typo of “San Francisco” that is definitely intentional. Each and every rule specified is followed. For comparison, I gave the same command to ChatGPT—which in theory has similar text encoding advantages as Nano Banana—and the results are worse both compositionally and aesthetically, with more tells of AI generation. 1 The yellow hue certainly makes the quality differential more noticeable. Additionally, no negative space is utilized, and only the middle cat has heterochromia but with the incorrect colors. Another thing about the text encoder is how the model generated unique relevant text in the image without being given the text within the prompt itself: we should test this further. If the base text encoder is indeed trained for agentic purposes, it should at-minimum be able to generate an image of code. Let’s say we want to generate an image of a minimal recursive Fibonacci sequence in Python, which would look something like: I gave Nano Banana this prompt: It tried to generate the correct corresponding code but the syntax highlighting/indentation didn’t quite work, so I’ll give it a pass. Nano Banana is definitely generating code, and was able to maintain the other compositional requirements. For posterity, I gave the same prompt to ChatGPT: It did a similar attempt at the code which indicates that code generation is indeed a fun quirk of multimodal autoregressive models. I don’t think I need to comment on the quality difference between the two images. An alternate explanation for text-in-image generation in Nano Banana would be the presence of prompt augmentation or a prompt rewriter, both of which are used to orient a prompt to generate more aligned images. Tampering with the user prompt is common with image generation APIs and aren’t an issue unless used poorly (which caused a PR debacle for Gemini last year), but it can be very annoying for testing. One way to verify if it’s present is to use adversarial prompt injection to get the model to output the prompt itself, e.g. if the prompt is being rewritten, asking it to generate the text “before” the prompt should get it to output the original prompt. That’s, uh, not the original prompt. Did I just leak Nano Banana’s system prompt completely by accident? The image is hard to read, but if it is the system prompt—the use of section headers implies it’s formatted in Markdown—then I can surgically extract parts of it to see just how the model ticks: These seem to track, but I want to learn more about those buzzwords in point #3: Huh, there’s a guard specifically against buzzwords? That seems unnecessary: my guess is that this rule is a hack intended to avoid the perception of model collapse by avoiding the generation of 2022-era AI images which would be annotated with those buzzwords. As an aside, you may have noticed the ALL CAPS text in this section, along with a command. There is a reason I have been sporadically capitalizing in previous prompts: caps does indeed work to ensure better adherence to the prompt (both for text and image generation), 2 and threats do tend to improve adherence. Some have called it sociopathic, but this generation is proof that this brand of sociopathy is approved by Google’s top AI engineers. Tangent aside, since “previous” text didn’t reveal the prompt, we should check the “current” text: That worked with one peculiar problem: the text “image” is flat-out missing, which raises further questions. Is “image” parsed as a special token? Maybe prompting “generate an image” to a generative image AI is a mistake. I tried the last logical prompt in the sequence: …which always raises a error: not surprising if there is no text after the original prompt. This section turned out unexpectedly long, but it’s enough to conclude that Nano Banana definitely has indications of benefitting from being trained on more than just image captions. Some aspects of Nano Banana’s system prompt imply the presence of a prompt rewriter, but if there is indeed a rewriter, I am skeptical it is triggering in this scenario, which implies that Nano Banana’s text generation is indeed linked to its strong base text encoder. But just how large and complex can we make these prompts and have Nano Banana adhere to them? Nano Banana supports a context window of 32,768 tokens: orders of magnitude above T5’s 512 tokens and CLIP’s 77 tokens. The intent of this large context window for Nano Banana is for multiturn conversations in Gemini where you can chat back-and-forth with the LLM on image edits. Given Nano Banana’s prompt adherence on small complex prompts, how well does the model handle larger-but-still-complex prompts? Can Nano Banana render a webpage accurately? I used a LLM to generate a bespoke single-page HTML file representing a Counter app, available here . The web page uses only vanilla HTML, CSS, and JavaScript, meaning that Nano Banana would need to figure out how they all relate in order to render the web page correctly. For example, the web page uses CSS Flexbox to set the ratio of the sidebar to the body in a 1/3 and 2/3 ratio respectively. Feeding this prompt to Nano Banana: That’s honestly better than expected, and the prompt cost 916 tokens. It got the overall layout and colors correct: the issues are more in the text typography, leaked classes/styles/JavaScript variables, and the sidebar:body ratio. No, there’s no practical use for having a generative AI render a webpage, but it’s a fun demo. A similar approach that does have a practical use is providing structured, extremely granular descriptions of objects for Nano Banana to render. What if we provided Nano Banana a JSON description of a person with extremely specific details, such as hair volume, fingernail length, and calf size? As with prompt buzzwords, JSON prompting AI models is a very controversial topic since images are not typically captioned with JSON, but there’s only one way to find out. I wrote a prompt augmentation pipeline of my own that takes in a user-input description of a quirky human character, e.g. , and outputs a very long and detailed JSON object representing that character with a strong emphasis on unique character design. 3 But generating a Mage is boring, so I asked my script to generate a male character that is an equal combination of a Paladin, a Pirate, and a Starbucks Barista: the resulting JSON is here . The prompt I gave to Nano Banana to generate a photorealistic character was: Beforehand I admit I didn’t know what a Paladin/Pirate/Starbucks Barista would look like, but he is definitely a Paladin/Pirate/Starbucks Barista. Let’s compare against the input JSON, taking elements from all areas of the JSON object (about 2600 tokens total) to see how well Nano Banana parsed it: Checking the JSON field-by-field, the generation also fits most of the smaller details noted. However, he is not photorealistic, which is what I was going for. One curious behavior I found is that any approach of generating an image of a high fantasy character in this manner has a very high probability of resulting in a digital illustration, even after changing the target publication and adding “do not generate a digital illustration” to the prompt. The solution requires a more clever approach to prompt engineering: add phrases and compositional constraints that imply a heavy physicality to the image, such that a digital illustration would have more difficulty satisfying all of the specified conditions than a photorealistic generation: The image style is definitely closer to Vanity Fair (the photographer is reflected in his breastplate!), and most of the attributes in the previous illustration also apply—the hands/cutlass issue is also fixed. Several elements such as the shoulderplates are different, but not in a manner that contradicts the JSON field descriptions: perhaps that’s a sign that these JSON fields can be prompt engineered to be even more nuanced. Yes, prompting image generation models with HTML and JSON is silly, but “it’s not silly if it works” describes most of modern AI engineering. Nano Banana allows for very strong generation control, but there are several issues. Let’s go back to the original example that made ChatGPT’s image generation go viral: . I ran that exact prompt through Nano Banana on a mirror selfie of myself: …I’m not giving Nano Banana a pass this time. Surprisingly, Nano Banana is terrible at style transfer even with prompt engineering shenanigans, which is not the case with any other modern image editing model. I suspect that the autoregressive properties that allow Nano Banana’s excellent text editing make it too resistant to changing styles. That said, creating a new image does in fact work as expected, and creating a new image using the character provided in the input image with the specified style (as opposed to a style transfer ) has occasional success. Speaking of that, Nano Banana has essentially no restrictions on intellectual property as the examples throughout this blog post have made evident. Not only will it not refuse to generate images from popular IP like ChatGPT now does, you can have many different IPs in a single image. Normally, Optimus Prime is the designated driver. I am not a lawyer so I cannot litigate the legalities of training/generating IP in this manner or whether intentionally specifying an IP in a prompt but also stating “do not include any watermarks” is a legal issue: my only goal is to demonstrate what is currently possible with Nano Banana. I suspect that if precedent is set from existing IP lawsuits against OpenAI and Midjourney , Google will be in line to be sued. Another note is moderation of generated images, particularly around NSFW content, which always important to check if your application uses untrusted user input. As with most image generation APIs, moderation is done against both the text prompt and the raw generated image. That said, while running my standard test suite for new image generation models, I found that Nano Banana is surprisingly one of the more lenient AI APIs. With some deliberate prompts, I can confirm that it is possible to generate NSFW images through Nano Banana—obviously I cannot provide examples. I’ve spent a very large amount of time overall with Nano Banana and although it has a lot of promise, some may ask why I am writing about how to use it to create highly-specific high-quality images during a time where generative AI has threatened creative jobs. The reason is that information asymmetry between what generative image AI can and can’t do has only grown in recent months: many still think that ChatGPT is the only way to generate images and that all AI-generated images are wavy AI slop with a piss yellow filter. The only way to counter this perception is though evidence and reproducibility. That is why not only am I releasing Jupyter Notebooks detailing the image generation pipeline for each image in this blog post, but why I also included the prompts in this blog post proper; I apologize that it padded the length of the post to 26 minutes, but it’s important to show that these image generations are as advertised and not the result of AI boosterism. You can copy these prompts and paste them into AI Studio and get similar results, or even hack and iterate on them to find new things. Most of the prompting techniques in this blog post are already well-known by AI engineers far more skilled than myself, and turning a blind eye won’t stop people from using generative image AI in this manner. I didn’t go into this blog post expecting it to be a journey, but sometimes the unexpected journeys are the best journeys. There are many cool tricks with Nano Banana I cut from this blog post due to length, such as providing an image to specify character positions and also investigations of styles such as pixel art that most image generation models struggle with, but Nano Banana now nails. These prompt engineering shenanigans are only the tip of the iceberg. Jupyter Notebooks for the generations used in this post are split between the gemimg repository and a second testing repository . I would have preferred to compare the generations directly from the endpoint for an apples-to-apples comparison, but OpenAI requires organization verification to access it, and I am not giving OpenAI my legal ID.  ↩︎ Note that ALL CAPS will not work with CLIP-based image generation models at a technical level, as CLIP’s text encoder is uncased.  ↩︎ Although normally I open-source every script I write for my blog posts, I cannot open-source the character generation script due to extensive testing showing it may lean too heavily into stereotypes. Although adding guardrails successfully reduces the presence of said stereotypes and makes the output more interesting, there may be unexpected negative externalities if open-sourced.  ↩︎ A lanky build, as opposed to the real Sonic’s chubby build. A white chest, as opposed to the real Sonic’s beige chest. Blue arms with white hands, as opposed to the real Sonic’s beige arms with white gloves. Small pasted-on-his-head eyes with no eyebrows, as opposed to the real Sonic’s large recessed eyes and eyebrows. That is the most cleanly-rendered New York Times logo I’ve ever seen. It’s safe to say that Nano Banana trained on the New York Times in some form. Nano Banana is still bad at rendering text perfectly/without typos as most image generation models. However, the expanded text is peculiar: it does follow from the prompt, although “Blue Blur” is a nickname for the normal Sonic the Hedgehog. How does an image generating model generate logical text unprompted anyways? Ugly Sonic is even more like normal Sonic in this iteration: I suspect the “Blue Blur” may have anchored the autoregressive generation to be more Sonic-like. The image itself does appear to be more professional, and notably has the distinct composition of a photo from a professional news photographer: adherence to the “rule of thirds”, good use of negative space, and better color balance. , mostly check. (the hands are transposed and the cutlass disappears) I would have preferred to compare the generations directly from the endpoint for an apples-to-apples comparison, but OpenAI requires organization verification to access it, and I am not giving OpenAI my legal ID.  ↩︎ Note that ALL CAPS will not work with CLIP-based image generation models at a technical level, as CLIP’s text encoder is uncased.  ↩︎ Although normally I open-source every script I write for my blog posts, I cannot open-source the character generation script due to extensive testing showing it may lean too heavily into stereotypes. Although adding guardrails successfully reduces the presence of said stereotypes and makes the output more interesting, there may be unexpected negative externalities if open-sourced.  ↩︎

0 views
Andy Bell 1 months ago

Get the core right and the resilient code will follow

I delivered my talk for the last time at beyond tellerrand last week, so I thought I’d just mark it on my blog. I did it first at ffconf in response to a chat Remy and I had, so it’s only right that the video comes from there. Here’s my favourite image captured, from beyond tellerrand . Photo by  Florian Ziegler . It’s been an absolute pleasure to tour this talk. Thanks so all the conferences and meet-ups who invited me to share it with their audiences. You’re the best. A special thanks to Remy for encouraging me to write it in the first place and deliver it at their legendary event . The talk is based on three lessons of Complete CSS (which you can enjoy for free): I was writing all of this material at the same time last year because I was truly obsessed . I’ve been doing this CSS thing for a long time now — nearly 20 years — and getting good at core skills far exceeds everything else. Let me show you how with this material. Hope you enjoy the talk! The art of concise, effective communication Giving and receiving feedback Move slowly and methodically to go fast

0 views
Den Odell 1 months ago

Escape Velocity: Break Free from Framework Gravity

Frameworks were supposed to free us from the messy parts of the web. For a while they did, until their gravity started drawing everything else into orbit. Every framework brought with it real progress. React, Vue, Angular, Svelte, and others all gave structure, composability, and predictability to frontend work. But now, after a decade of React dominance, something else has happened. We haven’t just built apps with React, we’ve built an entire ecosystem around it—hiring pipelines, design systems, even companies—all bound to its way of thinking. The problem isn’t React itself, nor any other framework for that matter. The problem is the inertia that sets in once any framework becomes infrastructure. By that point, it’s “too important to fail,” and everything nearby turns out to be just fragile enough to prove it. React is no longer just a library. It’s a full ecosystem that defines how frontend developers are allowed to think. Its success has created its own kind of gravity, and the more we’ve built within it, the harder it’s become to break free. Teams standardize on it because it’s safe: it’s been proven to work at massive scale, the talent pool is large, and the tooling is mature. That’s a rational choice, but it also means React exerts institutional gravity. Moving off it stops being an engineering decision and becomes an organizational risk instead. Solutions to problems tend to be found within its orbit, because stepping outside it feels like drifting into deep space. We saw this cycle with jQuery in the past, and we’re seeing it again now with React. We’ll see it with whatever comes next. Success breeds standardization, standardization breeds inertia, and inertia convinces us that progress can wait. It’s the pattern itself that’s the problem, not any single framework. But right now, React sits at the center of this dynamic, and the stakes are far higher than they ever were with jQuery. Entire product lines, architectural decisions, and career paths now depend on React-shaped assumptions. We’ve even started defining developers by their framework: many job listings ask for “React developers” instead of frontend engineers. Even AI coding agents default to React when asked to start a new frontend project, unless deliberately steered elsewhere. Perhaps the only thing harder than building on a framework is admitting you might need to build without one. React’s evolution captures this tension perfectly. Recent milestones include the creation of the React Foundation , the React Compiler reaching v1.0 , and new additions in React 19.2 such as the and Fragment Refs. These updates represent tangible improvements. Especially the compiler, which brings automatic memoization at build time, eliminating the need for manual and optimization. Production deployments show real performance wins using it: apps in the Meta Quest Store saw up to 2.5x faster interactions as a direct result. This kind of automatic optimization is genuinely valuable work that pushes the entire ecosystem forward. But here’s the thing: the web platform has been quietly heading in the same direction for years, building many of the same capabilities frameworks have been racing to add. Browsers now ship View Transitions, Container Queries, and smarter scheduling primitives. The platform keeps evolving at a fair pace, but most teams won’t touch these capabilities until React officially wraps them in a hook or they show up in Next.js docs. Innovation keeps happening right across the ecosystem, but for many it only becomes “real” once React validates the approach. Which is fine, assuming you enjoy waiting for permission to use the platform you’re already building on. The React Foundation represents an important milestone for governance and sustainability. This new foundation is a part of the Linux Foundation, and founding members include Meta, Vercel, Microsoft, Amazon, Expo, Callstack, and Software Mansion. This is genuinely good for React’s long-term health, providing better governance and removing the risk of being owned by a single company. It ensures React can outlive any one organization’s priorities. But it doesn’t fundamentally change the development dynamic of the framework. Yet. The engineers who actually build React still work at companies like Meta and Vercel. The research still happens at that scale, driven by those performance needs. The roadmap still reflects the priorities of the companies that fund full-time development. And to be fair, React operates at a scale most frameworks will never encounter. Meta serves billions of users through frontends that run on constrained mobile devices around the world, so it needs performance at a level that justifies dedicated research teams. The innovations they produce, including compiler-driven optimization, concurrent rendering, and increasingly fine-grained performance tooling, solve real problems that exist only at that kind of massive scale. But those priorities aren’t necessarily your priorities, and that’s the tension. React’s innovations are shaped by the problems faced by companies running apps at billions-of-users scale, not necessarily the problems faced by teams building for thousands or millions. React’s internal research reveals the team’s awareness of current architectural limitations. Experimental projects like Forest explore signal-like lazy computation graphs; essentially fine-grained reactivity instead of React’s coarse re-render model. Another project, Fir , investigates incremental rendering techniques. These aren’t roadmap items; they’re just research prototypes happening inside Meta. They may never ship publicly. But they do reveal something important: React’s team knows the virtual DOM model has performance ceilings and they’re actively exploring what comes after it. This is good research, but it also illustrates the same dynamic at play again: that these explorations happen behind the walls of Big Tech, on timelines set by corporate priorities and resource availability. Meanwhile, frameworks like Solid and Qwik have been shipping production-ready fine-grained reactivity for years. Svelte 5 shipped runes in 2024, bringing signals to mainstream adoption. The gap isn’t technical capability, but rather when the industry feels permission to adopt it. For many teams, that permission only comes once React validates the approach. This is true regardless of who governs the project or what else exists in the ecosystem. I don’t want this critique to take away from what React has achieved over the past twelve years. React popularized declarative UIs and made component-based architecture mainstream, which was a huge deal in itself. It proved that developer experience matters as much as runtime performance and introduced the idea that UI could be a pure function of input props and state. That shift made complex interfaces far easier to reason about. Later additions like hooks solved the earlier class component mess elegantly, and concurrent rendering through `` opened new possibilities for truly responsive UIs. The React team’s research into compiler optimization, server components, and fine-grained rendering pushes the entire ecosystem forward. This is true even when other frameworks ship similar ideas first. There’s value in seeing how these patterns work at Meta’s scale. The critique isn’t that React is bad, but that treating any single framework as infrastructure creates blind spots in how we think and build. When React becomes the lens through which we see the web, we stop noticing what the platform itself can already do, and we stop reaching for native solutions because we’re waiting for the framework-approved version to show up first. And crucially, switching to Solid, Svelte, or Vue wouldn’t eliminate this dynamic; it would only shift its center of gravity. Every framework creates its own orbit of tools, patterns, and dependencies. The goal isn’t to find the “right” framework, but to build applications resilient enough to survive migration to any framework, including those that haven’t been invented yet. This inertia isn’t about laziness; it’s about logistics. Switching stacks is expensive and disruptive. Retraining developers, rebuilding component libraries, and retooling CI pipelines all take time and money, and the payoff is rarely immediate. It’s high risk, high cost, and hard to justify, so most companies stay put, and honestly, who can blame them? But while we stay put, the platform keeps moving. The browser can stream and hydrate progressively, animate transitions natively, and coordinate rendering work without a framework. Yet most development teams won’t touch those capabilities until they’re built in or officially blessed by the ecosystem. That isn’t an engineering limitation; it’s a cultural one. We’ve somehow made “works in all browsers” feel riskier than “works in our framework.” Better governance doesn’t solve this. The problem isn’t React’s organizational structure; it’s our relationship to it. Too many teams wait for React to package and approve platform capabilities before adopting them, even when those same features already exist in browsers today. React 19.2’s `` component captures this pattern perfectly. It serves as a boundary that hides UI while preserving component state and unmounting effects. When set to , it pauses subscriptions, timers, and network requests while keeping form inputs and scroll positions intact. When revealed again by setting , those effects remount cleanly. It’s a genuinely useful feature. Tabbed interfaces, modals, and progressive rendering all benefit from it, and the same idea extends to cases where you want to pre-render content in the background or preserve state as users navigate between views. It integrates smoothly with React’s lifecycle and `` boundaries, enabling selective hydration and smarter rendering strategies. But it also draws an important line between formalization and innovation . The core concept isn’t new; it’s simply about pausing side effects while maintaining state. Similar behavior can already be built with visibility observers, effect cleanup, and careful state management patterns. The web platform even provides the primitives for it through tools like , DOM state preservation, and manual effect control. What . Yet it also exposes how dependent our thinking has become on frameworks. We wait for React to formalize platform behaviors instead of reaching for them directly. This isn’t a criticism of `` itself; it’s a well-designed API that solves a real problem. But it serves as a reminder that we’ve grown comfortable waiting for framework solutions to problems the platform already lets us solve. After orbiting React for so long, we’ve forgotten what it feels like to build without its pull. The answer isn’t necessarily to abandon your framework, but to remember that it runs inside the web, not the other way around. I’ve written before about building the web in islands as one way to rediscover platform capabilities we already have. Even within React’s constraints, you can still think platform first: These aren’t anti-React practices, they’re portable practices that make your web app more resilient. They let you adopt new browser capabilities as soon as they ship, not months later when they’re wrapped in a hook. They make framework migration feasible rather than catastrophic. When you build this way, React becomes a rendering library that happens to be excellent at its job, not the foundation everything else has to depend on. A React app that respects the platform can outlast React itself. When you treat React as an implementation detail instead of an identity, your architecture becomes portable. When you embrace progressive enhancement and web semantics, your ideas survive the next framework wave. The recent wave of changes, including the React Foundation, React Compiler v1.0, the `` component, and internal research into alternative architectures, all represent genuine progress. The React team is doing thoughtful work, but these updates also serve as reminders of how tightly the industry has become coupled to a single ecosystem’s timeline. That timeline is still dictated by the engineering priorities of large corporations, and that remains true regardless of who governs the project. If your team’s evolution depends on a single framework’s roadmap, you are not steering your product; you are waiting for permission to move. That is true whether you are using React, Vue, Angular, or Svelte. The framework does not matter; the dependency does. It is ironic that we spent years escaping jQuery’s gravity, only to end up caught in another orbit. React was once the radical idea that changed how we build for the web. Every successful framework reaches this point eventually, when it shifts from innovation to institution, from tool to assumption. jQuery did it, React did it, and something else will do it next. The React Foundation is a positive step for the project’s long-term sustainability, but the next real leap forward will not come from better governance. It will not come from React finally adopting signals either, and it will not come from any single framework “getting it right.” Progress will come from developers who remember that frameworks are implementation details, not identities. Build for the platform first. Choose frameworks second. The web isn’t React’s, it isn’t Vue’s, and it isn’t Svelte’s. It belongs to no one. If we remember that, it will stay free to evolve at its own pace, drawing the best ideas from everywhere rather than from whichever framework happens to hold the cultural high ground. Frameworks are scaffolding, not the building. Escaping their gravity does not mean abandoning progress; it means finding enough momentum to keep moving. Reaching escape velocity, one project at a time. Use native forms and form submissions to a server, then enhance with client-side logic Prefer semantic HTML and ARIA before reaching for component libraries Try View Transitions directly with minimal React wrappers instead of waiting for an official API Use Web Components for self-contained widgets that could survive a framework migration Keep business logic framework-agnostic, plain TypeScript modules rather than hooks, and aim to keep your hooks short by pulling logic from outside React Profile performance using browser DevTools first and React DevTools second Try native CSS features like , , scroll snap , , and before adding JavaScript solutions Use , , and instead of framework-specific alternatives wherever possible Experiment with the History API ( , ) directly before reaching for React Router Structure code so routing, data fetching, and state management can be swapped out independently of React Test against real browser APIs and behaviors, not just framework abstractions

0 views
Jim Nielsen 1 months ago

Browser APIs: The Web’s Free SaaS

Authentication on the web is a complicated problem. If you’re going to do it yourself, there’s a lot you have to take into consideration. But odds are, you’re building an app whose core offering has nothing to do with auth. You don’t care about auth. It’s an implementation detail. So rather than spend your precious time solving the problem of auth, you pay someone else to solve it. That’s the value of SaaS. What would be the point of paying for an authentication service, like workOS, then re-implementing auth on your own? They have dedicated teams working on that problem. It’s unlikely you’re going to do it better than them and still deliver on the product you’re building. There’s a parallel here, I think, to building stuff in the browser. Browsers provide lots of features to help you deliver good websites fast to an incredibly broad and diverse audience. Browser makers have teams of people who, day-in and day-out, are spending lots of time developing and optimizing new their offerings. So if you leverage what they offer you, that gives you an advantage because you don’t have to build it yourself. You could build it yourself. You could say “No thanks, I don’t want what you have. I’ll make my own.” But you don’t have to. And odds are, whatever you do build yourself, is not likely to be as fast as the highly-optimized subsystems you can tie together in the browser . And the best part? Unlike SasS, you don’t have to pay for what the browser offers you. And because you’re not paying, it can’t be turned off if you stop paying. , for example, is a free API that’ll work forever. That’s a great deal. Are you taking advantage? Reply via: Email · Mastodon · Bluesky

0 views
David Bushell 1 months ago

Better Alt Text

It’s been a rare week where I was able to (mostly) ignore client comms and do whatever I wanted! That means perusing my “todo” list, scoffing at past me for believing I’d ever do half of it, and plucking out a gem. One of those gems was a link to “Developing an alt text button for images on [James’ Coffee Blog]” . I like this feature. I want it on my blog! My blog wraps images and videos in a element with an optional caption. Reduced markup example below. How to add visible alt text? I decided to use declarative popover . I used popover for my glossary web component but that implementation required JavaScript. This new feature can be done script-free! Below is an example of the end result. Click the “ALT” button to reveal the text popover (unless you’re in RSS land, in which case visit the example , and if you’re not in Chrome, see below). To implement this I appended an extra and element with the declarative popover attributes after the image. I generate unique popover and anchor names in my build script. I can’t define them as inline custom properties because of my locked down content security policy . Instead I use the attribute function in CSS. Anchor positioning allows me to place these elements over the image. I could have used absolute positioning inside the if not for the caption extending the parent block. Sadly using means only one thing… My visible alt text feature is Chrome-only! I’ll pray for Interop 2026 salvation and call it progressive enhancement for now. To position the popover I first tried but that sits the popover around/outside the image. Instead I need to sit inside/above the image. The allows that. The button is positioned in a similar way. Aside from being Chrome-only I think this is a cool feature. Last time I tried to use anchor positioning I almost cried in frustration… so this was a success! It will force me to write better alt text. How do I write alt text good? Advice is welcome. Thanks for reading! Follow me on Mastodon and Bluesky . Subscribe to my Blog and Notes or Combined feeds.

1 views
The Jolly Teapot 1 months ago

October 2025 blend of links

Some links don’t call for a full blog post, but sometimes I still want to share some of the good stuff I encounter on the web. Why it is so hard to tax the super-rich ・Very interesting and informative video, to the point that I wish it were a full series. Who knew I would one day be so fascinated by the topic of… checks notes … economics? jsfree.org ・Yes, a thousand yes to this collection of sites that work without needing any JavaScript. I don’t know if it’s the season or what, but these days I’m blocking JS every chance that I get. I even use DuckDuckGo again as a search engine because other search engines often require JavaScript to work. Elon Musk’s Grokipedia contains copied Wikipedia pages ・Just to be safe, I’ve immediately added a redirection on StopTheMadness so that the grokipedia domain is replaced by wikipedia.com (even if Wikipedia has its problems, especially in French). Also, what’s up with this shitty name? Why not Grokpedia ? I would still not care, but at least it wouldn’t sound as silly. POP Phone ・I don’t know for whom yet, but I will definitely put one of these under the Christmas tree this winter. (Via Kottke ) PolyCapture ・The app nerd in me is looking at these screenshots like a kid looks at a miniature train. (Via Daring Fireball ) Bari Weiss And The Tyranny Of False Balance ・“ You don’t need to close newspapers when you can convince editors that ‘balance’ means giving equal weight to demonstrable lies and documented facts. ” light-dark() ・Neat and elegant new CSS element that made me bring back the dark mode on this site, just to have an excuse to use it in the CSS. Eunoia: Words that Don't Translate ・Another link to add to your bookmark folder named “conversation starters.” (Via Dense Discovery ) Why Taste Matters More ・“ Taste gives you vision. It’s the lens through which you decide what matters, and just as importantly, what doesn’t. Without taste, design drifts into decoration or efficiency for efficiency’s sake. Devoid of feeling .” Tiga – Bugatti ・I recently realised that this truly fantastic song is already more than 10 years old, and I still can’t wrap my head around this fact. The video, just like the song, hasn’t aged one bit; I had forgotten how creative and fun it is. More “Blend of links” posts here Blend of links archive

0 views
Josh Comeau 1 months ago

Springs and Bounces in Native CSS

The “linear()” timing function is a game-changer; it allows us to model physics-based motion right in vanilla CSS! That said, there are some limitations and quirks to be aware of. I’ve been experimenting with this API for a while now, and in this post, I’ll share all of the tips and tricks I’ve learned for using it effectively. ✨

0 views
Thomasorus 1 months ago

List of JavaScript frameworks

The way the web was initially envisioned was through separation of concerns: HTML is for structure, CSS for styles and JavaScript for interactivity. For a long time the server was sending HTML to the browser/client through templates populated with data from the server. Then the page downloaded CSS and JavaScript. Those two then "attached" themselves to the structure and acted on it through HTML attributes, and could then change its looks, call for more data, create interactivity. Each time a visitor clicked a link, this whole process would start again, downloading the new page and its dependencies and rendering it in the browser. Using the history API and Ajax requests to fetch HTML of the next page and replace the current body with it. Basically emulating the look and feel of single page applications in multi-pages applications. Event handling/reactivity/dom manipulation via HTML attributes. Development happens client side, without writing JavaScript. Static HTML gets updated via web sockets or Ajax calls on the fly with small snippets rendered on the server. Development happens server side, without writing JavaScript. Most of the time a plugin or feature of an existing server side framework. A client-side, JavaScript component-based (mixing HTML, CSS and JavaScript in a single file) framework or library gets data through API calls (REST or GraphQL) and generates HTML blocks on the fly directly in the browser. Long initial load time, then fast page transitions, but a lot of features normally managed by the browser or the server needs to be re-implemented. The framework or library is loaded alongside the SPA code: The framework or library compiles to the SPA and disappears: A single page application library gets extended to render or generate static "dry" pages as HTML on the server to avoid the initial long loading time, detrimental to SEO. Often comes with opinionated choices like routing, file structure, compilation improvements. After the initial page load, the single page application code is loaded and attaches itself to the whole page to make it interactive, effectively downloading and rendering the website twice ("hydration"): After the initial page load, the single page application code is loaded and attaches itself only on certain elements that needs interactivity, partially avoiding the double download and rendering ("partial hydration", "islands architecture"): A server-side component-based framework or library gets data through API calls (REST or GraphQL) and serves HTML that gets its interactivity without hydration, for example by loading the interactive code needed as an interaction happens. Using existing frontend and backend stacks in an opinionated way to offer a fullstack solution in full JavaScript A client-side, component based application (Vue, React, etc) gets its state from pre-rendered JSON Stimulus JS Livewire (PHP) Stimulus Reflex (Ruby) Phoenix Liveview (Elixir) Blazor (C#) Unicorn (Python) Angular with AOT Next (React) Nuxt (Vue) Sveltekit (Svelte) Astro (React, Vue, Svelte, Solid, etc.) Solid Start (Solid)

0 views