#AI paired with #surveillance tech, especially #Flock, is enabling all kinds of distopian Fourth Amendment violations.
404media.co/police-said-they…
It’s getting darker here on earth and climate change could be the reason.
404media.co/earth-is-getting…
Alexander Bell 📱 retweeted
A 1.7 magnitude earthquake occurred 5.59mi SSW of Gorman, CA. Details: eqbot.com/4ze Map: eqbot.com/4zN
Elon Musk:
- Welcomed neo-Nazis to X
- Replies to neo-Nazis
- Reposts neo-Nazis
- Funds neo-Nazis through X
- Engages with racist memes
- Posts racist, anti-trans and other bigoted rhetoric
- Performed 3 Nazi salutes on stage while the world was watching
This isn't hard.
Alexander Bell 📱 retweeted
It's here! 🎉
We're very excited to share with you the future of open voice, the Home Assistant Voice Preview Edition! This has been 2 years in the making - we look forward to taking you on this journey with us. 😌
Links in the reply 👇🏼
Alexander Bell 📱 retweeted
Here's how I usually structure my Next/React apps.
Let's talk about styling, data fetching, UI libraries, settings, and more.
Styling
The short story is Tailwind. Why? It was easy for me to pick up, stays consistent whenever I revisit old projects, and works great with AI / @v0. I've wrote a lot more about this here: leerob.io/blog/css
Data Fetching
For those who have been around Next.js for a while, you know the quirks of the Pages Router. No end-to-end type safety? No global data fetching??
The App Router is so much nicer in this regard. So, I've been defaulting to this for almost two years now (time flies...): nextjs.org/docs/app/building…
Most of the time, I fetch data in a Server Component (like a page or layout). Occasionally, I'll still pull for an external lib for client-fetches (e.g. swr) when working with existing codebases.
A newer pattern that I've started to sometimes use is forwarding Promises to a Context provider, and then "unwrapping" the Promise with `use`. The data can then be used from a hook in any client component. Can work well if you have global data you need to use deep in the component tree (esp. if there's lots of client components): github.com/vercel/commerce/b…
UI Libraries
Can I say something without everyone getting mad?
I wasn't really a fan of shadcn/ui at first. It felt like a lot of dependencies and "weight" in my repo. There was something more minimalist about just putting the deps in `package.json`.
It was similar to my initial reaction to Tailwind. "Eww". Well, that was wrong too. Maybe that was just my familiarity with existing component libraries.
I've really changed my opinion. Unstyled, accessible UI components that integrate with Tailwind are... really nice. And that can be easily edited and customized.
You either use a component library, or you build your own. And there's so many dang quirks to get accessible components right. I think that's why unstyled primitives are growing so quickly. It's really a new generation of component libraries.
Other Libraries
I almost never use ESLint on personal projects anymore. I still use it when working on larger projects with the team. But type checking + Prettier has been good enough. Long live TypeScript.
We're hoping to add support for Biome to Next.js, and then maybe having the single unified toolchain will be my new approach. Not sure yet. Prettier has worked so well, for so long, I'm apprehensive to any change. cc: JS fatigue.
In general, I'm trying to have fewer dependencies, because you can often get an AI-assisted version of just the one function you needed versus bringing in the whole package. Things like date formatting, or markdown parsing. I wrote more about this here: leerob.io/blog/2023
• An extremely underrated Postgres client is `postgres.js`: github.com/porsager/postgres. However, most of the time I'm using `drizzle` as my ORM.
• `zod` somehow finds its way into almost all of my projects these types.
Forms
You can get surprisingly far with the new React hooks like `useActionState` and Server Actions. Throw some Zod in there, and it's pretty easy to get client/server-side validation with ease: github.com/vercel/next.js/tr…
There's a few community packages automatically setting this up for you, which is nice: next-safe-action.dev/
Charts
shadcn/ui: xitter.cf/shadcn/status/18093521…
Icons
shadcn/ui, so `lucide-react` currently: lucide.dev/icons/
Fonts
Geist: vercel.com/font
Settings
I daily drive VS Code. I do want to learn neovim at some point, btw. I don't have any fancy extensions, mostly just the essentials: Tailwind, Prettier, MDX, vscode-icons.
Maybe will try Zed again when the git support improves: xitter.cf/leeerob/status/1769052…. Or maybe some of the new AI enhanced editors (I do use Copilot currently).
A few VS Code settings you might have that I like:
"editor.cursorSmoothCaretAnimation": "on",
"editor.smoothScrolling": true,
"editor.cursorBlinking": "solid",
I'd also recommend checking out custom editor labels if you hate the page/layout/route duplication: xitter.cf/nextjs/status/17835083…
Payments
Stripe It was easy for me to pick up, stays consistent whenever I revisit old projects, and works great with AI / @v0.
Iterating
I spend a decent amount of time now talking to @v0 to help make edits or debug things. Definitely not perfect, and it gets things wrong sometimes, but saves me a lot of time versus writing everything from scratch.
Especially for more tedious refactors that are basically just pushing code into different places, or reformatting/restructuring it.
You can kind of 'fire and forget' @v0, then do something else, return in 15 seconds or so, copy the output, and keep moving. It's like my concurrency level went from 1 to 2. AI agent workflows might make this even easier in the future.
Summary
"Well, you know, that's just like uh, your opinion, man."
Alexander Bell 📱 retweeted
Animated exclusive accordions with CSS 😎
::details-content {
transition: content-visibility, height;
transition-behavior: allow-discrete;
}
[open]::details-content { height: calc-size(auto); }
<details name=accordion>
<summary>Exclusive</summary>
</details>
On the way! 🫶
Blink: Intent to Ship: CSS interpolate-size property and calc-size() function groups.google.com/a/chromium…
Alexander Bell 📱 retweeted
How to build a product around SEO
ImagenMIA has gotten over 200k views on Google the last 40 days. On average it gets 4.000 Google views/day & 250+ unique visitors/day 🚀
I'm using the same approach to build a new product - here is exactly what I'm doing:
1. SEO Research
The first step is to find good keywords we can target and build our product around. I used Ahrefs and Google Keyword Planner for this.
One tip I can give is that tends to be very difficult to find high volume low competition keywords in English. So if you speak other languages - use that. There is an entire market out there that is up for grabs.
For Ahrefs, here is what you're looking for:
- High volume (1.000+ at a minimum)
- Low Keyword Difficulty (KD) - <10
- Long-tail keywords/variations/combinations
For Google Keyword Planner, we want:
- High average monthly searches for the countries you selected (10K+)
- Low competition
This step is the most important one. Spend a lot of time on this to find something good as all other steps depend on this one.
2. MVP ideation & build
Based on the good keywords you found in step one, now you can start thinking about what you can build to target the keywords. Important here is to consider user intent - what is the user looking for when searching for those keywords?
You can get an idea by doing a Google Search yourself and seeing what results you get.
In my case this was pretty easy - there are lots of similar products that exist but they all target the English market.
I was born and lived in Spain until I was 10 years old before moving to Belgium, so those Spanish speaking skills make it easy for me to build the same product, but for the Spanish market.
I built the core feature in less than a day (without finetuning & experimenting with AI model - this took longer and a lot of experimentation/trial and error over a few weeks).
I always use the same self-made boilerplate I have for all my projects. It uses NuxtJS, TailwindCSS + DaisyUI, Supabase, PostHog analytics and LemonSqueezy for payments.
This makes it super easy for me to quickly build new things.
3. Dynamic pages to target tail keywords
You'll want to build as many landing pages as possible.
Then build a database with information for your different long-tail keywords you gathered in step one.
For my case, I figured a lot of searches were people looking for design ideas for their living room, bathrooms, stairs, ...
So I built pages with these variables:
- Rooms (currently 26+)
- Style of the room (14 on average)
Which gives me a total of around 370 dynamic pages (26 room types x 14 styles)
Once you have this, build a landing page structure that can be populated based on the URL. I also make sure that each of the pages crosslinks to other pages to give them more internal authority (good for search engines).
And since my project is about interior design, I make sure to have different images per room & style.
Voila - you now have 370 different landing pages bringing in potential traffic! For ImagenMIA, it's one of those pages that brings in the most traffic btw!
4. Blog articles
We're not done yet. We also want to generate good blog articles to target even more variations of the keywords/related topics.
This was easy to do as I have been doing this for all my other projects.
I built my own script that:
1. Generates a list of potential blog article topics
2. Generates an SEO optimized article for each
3. Gets an image for the article using Unsplash
4. Pushes the article to my Supabase blog table
All I have to do is run node programmaticSEO.js and blog articles are automatically generated and added to my project.
I have built my article pages in a way that uses the article title as <title> and article description as description for the page.
So far, I have generated over 400 blog articles. But I could easily generate 5.000 or more if I wanted to. I usually like to do this gradually, every couple of days/weeks. Don't want to push too much content at once + Google likes pages that are regularly updated.
I don't review my articles or read them. I only do that after identifying which ones are getting traffic from Google.
I also make sure that each article contains some marketing components about my product.
5. Some other optimizations
- Images: I have a script that converts all my images to WebP format - smaller size and easier to load.
- Pagespeed: I use websites like PageSpeed or SEOptimer to do checks on performance, then I try to improve what needs to be improved
- Basic SEO checks: free Ahrefs audits
- Crosslinking: I put a link in the footer of all my projects to my new project. This transfers some authority to my newer projects
- Auto-indexing: I don't want to wait 3 months before Google (maybe) starts indexing my stuff. I submit my new pages for indexing using an auto-indexer
And that's the core idea of it!
I will usually repeat and finetune all steps a few time as time goes by. Usually I want to see initial traffic and see for which specific keywords your page is getting the most traffic.
Only then will I build more features to avoid building stuff for nothing.
Hope this was useful! 😄
Alexander Bell 📱 retweeted
3D Office - ThreeJS + Blender 😁
Live + code @CodePen : codepen.io/ricardoolivaalons…
#html #css #javascript #threejs #blender #3Droom
Alexander Bell 📱 retweeted
Q: Are icon wrappers still worth using?
A: I don’t think so. A much easier way to support multiple icon sizes is with number variables!
Here’s how..
Alexander Bell 📱 retweeted
a new, year-long study found Google Search really has gotten worse, and top-ranking sites are "on average more optimized, more monetized with affiliate marketing, and they show signs of lower text quality" Also evidence of AI-generated top-ranking results
404media.co/google-search-re…
Alexander Bell 📱 retweeted
those little details™
Scale up and down on scroll with CSS 📜✨
a {
animation: scale, scale;
animation-direction: normal, reverse;
animation-timeline: view(inline);
animation-range: entry, exit;
}
@keyframes scale { 0% { scale: 0; } }
Alexander Bell 📱 retweeted
This kinda sums up better what CSS offset-path gives you 🏎️
A responsive way to animate an element along the perimeter of another 🎬 Use the rect() value to control the inset of that path ✨
And the great thing is, it's responsive 🤙
CSS Tip! ✨
It's 2024 and you have a new way to make animated borders 🚀
.glow::after {
offset-path: rect(0 100% 100% 0 round var(--radius));
animation: loop;
}
@keyframes loop { to { offset-distance: 100%; }}
Using the offset-* properties you can animate elements along the perimeter of others 😍
The rect() value gained support in Safari 17.2 🙌
To start, you create an element and put it inside your main element. For example, you put a span inside the button 🤙
<button>
<span data-glow />
Click me!
</button>
Make the element fill its parent with absolute positioning and inset
[data-glow] {
position: absolute;
inset: 0;
}
Now the good part, you use a pseudoelement on that element and define an offset-path
[data-glow]::after {
content: "";
offset-path: rect(0 auto auto 0 round var(--radius));
animation: loop 2.6s infinite linear;
}
With the rect value, you are saying the path fills the parent container:
top: 0
right: auto || 100%
bottom: auto || 100%
left: 0
Then you can use round to make sure the path uses the same radius as whatever the parent has
The @keyframes animation merely animates the offset-distance of that pseudoelement to 100%
@keyframes loop { to { offset-distance: 100%; }}
You can see this more clearly in the video 🫶
The offset-* properties also include an offset-anchor property. This allows you to dictate which point of the element follows the path. For example:
anchor-offset: 100% 50%;
This means that the "right, center" of the element will follow the perimeter of the parent element 🤙
Lastly, the visuals 🎨 For color, you can use a gradient such as a linear gradient to fill the pseudo-element.
[data-glow]::after {
background:
radial-gradient(
circle at right,
hsl(320 90% 100%),
transparent 50%
);
}
Then clip away everything so you only have the border and can still have translucent backgrounds, etc. Use a mask with mask-composite ✨ A little transparent border trick:
[data-glow] {
border: 2px solid transparent;
mask:
linear-gradient(transparent, transparent),
linear-gradient(white, white);
mask-clip: padding-box, border-box;
mask-composite: intersect;
}
Bit of a long one. Hope you find it useful 🙏
@CodePen link below 👇
Alexander Bell 📱 retweeted
iOS users, it's here: CarPlay!
Update your app to the latest version to get CarPlay support. From your car you'll have easy access to your devices, areas, and custom actions you can create in the app. Check out our garage door demo and read the blog here:
home-assistant.io/blog/2024/…
Alexander Bell 📱 retweeted
CSS Trick 🧲
You can create magnetic links with the power of custom properties and some JavaScript 💪
a {
translate: calc(clamp(-1, var(--x), 1) * var(--pad-x)) ...;
transition:
translate var(--s, 1s) var(--ease, var(--elastic));
}
a:hover { --s: 0s; }
The trick here is to pad out the list items wrapping your links and use that as a translation limit 🛑
Start by using some JavaScript to calculate a value between -1 and 1 for both the x/y axis on pointermove for each list item, not the link! 🔗
If your pointer was at the center of the item, you'd get [0,0]. If it was in the top right, you'd get [1,-1] ☝️
It's worth checking out the JavaScript snippet to see how the mapping function works. Essentially, you create a function that when given a value between two bounds, will give you a mapped value back 🤙
const mapX = mapRange(
item.offsetWidth * -0.5,
item.offsetWidth * 0.5,
1,
-1
)
Then, on pointermove, you plug the pointer position in to get the value back out and pass that into your CSS
const x = mapX(item.centerX - event.x)
document.documentElement.style.setProperty(--x, x)
When the pointer leaves the list item, you make sure to reset these values back to 0 ✨
Once CSS has your values, it's the trick of updating the translation of each part
You know that in each axis, you only want to translate the link by the padding amount
li a {
translate:
calc(clamp(-1, var(--x), 1) * var(--pad-x))
calc(clamp(-1, var(--y), 1) * var(--pad-y));
transition:
translate var(--speed, 1s) var(--ease, var(--elastic));
}
This will translate the link within the list item by the desired amount. The cool part here is that you can set an offset for the text inside the link and have that move at a different rate ⭐️ By only updating the --pad-x/y custom properties for the <span> inside the link, you can control how much it moves
nav a span {
--pad-x: 0.25rem;
--pad-y: 0.25rem;
}
And the last piece, how do you update the behavior for transition speeds? And so it springs back like that?
Again, use custom properties ✨
a:hover { --s: 0s; }
a {
transition:
translate var(--s, 1s) var(--ease, var(--elastic));
}
By default, a link will use --elastic easing via linear() and have a transition-duration of 1s. When a link is hovered that speed becomes 0s because you want the link to magnetise to your pointer. How about that little gap between when your pointer enters the item but hasn't hovered the link? Set a different transition so it transitions to being hovered 🫶
nav li:hover a {
--ease: ease-out;
--speed: 0.1s;
}
That's kinda it! 🙌 Use JavaScript (~40 loc) to get the information and then let CSS do all the lifting for you 💪
Any questions or suggestions, let me know 🙏 If you want a walkthrough video, also let me know please 🙏
@CodePen link below 👇
Alexander Bell 📱 retweeted
CSS Tip! 🎠
You can create a responsive infinite marquee animation with container queries and no duplicate items 🤙
li{ animation: slide; }
@keyframes slide {
to { translate: 0% calc(var(--i) * -100%);}}
The trick is animating the items, not the list 😎
More tricks 👇
To get this one working, you need to animate the items and not the list (Watch the video first?). Each item needs to know its row index (--i) in the list and the parent needs to know how many rows are in the list:
ul { --count: 12; }
li:nth-of-type(1),
li:nth-of-type(2) { --i: 0; }
li:nth-of-type(3),
li:nth-of-type(4) { --i: 1; }
Once you have that, translate each item based on its row index in the list
li {
translate: 0% calc((var(--count) - var(--i)) * 100%);
}
Now for the animation. The key here is that each row has an animation-delay calculated from its index (--i). That number is offset to make it negative so the animation start is offset ✨
ul { --duration: 10s; }
li {
--delay:
calc((var(--duration) / var(--count)) * (var(--i) - 8));
animation:
slide var(--duration) var(--delay) infinite linear;
}
Make sure to wrap that animation in:
@media (prefers-reduced-motion: no-preference) { ... }
Lastly, the fun parts! 🤓
To create the "vignette" mask. Use a layered mask on the container 😷
.scene {
--buff: 3rem;
height: 100%;
width: 100%;
mask:
linear-gradient(transparent, white var(--buff) calc(100% - var(--buff)), transparent),
linear-gradient(90deg, transparent, white var(--buff) calc(100% - var(--buff)), transparent);
mask-composite: intersect;
}
To create the 3D skewed effect, use a chained transform (Try toggling it in the demo ⚡️):
.grid {
transform:
rotateX(20deg)
rotateZ(-20deg)
skewX(20deg);
}
As for the responsive part, use container queries! 🔥
article { container-type: inline-size; }
When the article (card) is narrower than 400px update the grid and animation settings 🤙 Double the rows means double the duration!
@container (width < 400px) {
.grid {
--count: 12;
grid-template-columns: 1fr;
}
li:nth-of-type(1) { --i: 0; }
li:nth-of-type(2) { --i: 1; }
li:nth-of-type(3) { --i: 2; }
li:nth-of-type(4) { --i: 3; }
li {
--duration: 20s;
}
}
CSS has the magic to be able to update those animations at runtime based on your custom property values 😎
An added bonus in this demo is that it doesn't require any JavaScript at all, for any of it 🤯 We can use CSS :has() for those toggles that update the styles, even the theme toggle! 🫶
Any questions, let me know! Make sure to check out the video. Will do a walkthrough one to follow-up 🤙
@CodePen link below! 👇
Skewed infinite carousel w/ vignette effect ✨
Built entirely with @tailwindcss – zero JavaScript required.
Live demo & code 👇
Alexander Bell 📱 retweeted
Nice thing here is you can use CSS tricks shared from the last few months 🤙
For example, background-attachment: fixed to spread a gradient between cards 😎
xitter.cf/jh3yy/status/167…
CSS Tip! 🤙
You can use mask-composite and some JavaScript to create this pointer proximity following glow border ✨
.glow {
mask-composite: intersect;
mask-clip: padding-box, border-box;
mask:
linear-gradient(#0000, #0000),
conic-gradient(#0000 0deg, #fff, #0000 45deg);
}
The trick is to mask a background-image with a combination of mask layers. mask-composite: intersect; means the mask used will be the intersection of the layers 🔥 use source-in, xor; in browsers that don't support intersect;
In this demo, you can use pseudoelements and rely on scoped custom properties to do a lot of the heavy lifting for you 🙌
Once you've masked the background, you need to update the starting angle of the conic-gradient on pointermove 👆
You can work that out by getting the center point of each card and then calculating the angle between that and the pointer with Math.atan2 🤓
let ANGLE = Math.atan2(
event?.y - CARD_CENTER[1],
event?.x - CARD_CENTER[0]
) * 180 / Math.PI
ANGLE = ANGLE < 0 ? ANGLE + 360 : ANGLE;
CARD.style.setProperty('--start', ANGLE + 90)
You plug that into your conic-gradient mask as a custom property accounting for --spread ⚡️
conic-gradient(from calc((var(--angle) - (var(--spread) * 0.5)) * 1deg), #000 0deg, #fff, #0000 calc(var(--spread) * 1deg));
To get the blur, you apply a blur to the glow container on each card 🤙
.glows {
filter: blur(calc(var(--blur) * 1px);
}
That's it! Layers of masks that are clipped and composited before being blurred 😎 The added trick is to fade each one in when the pointer is in the defined proximity of the card. For example, don't show unless within 100px of a card. You can see that in the video. Check out the JavaScript code for that 🫶
Couldn't resist making this one 😁
@CodePen link below! 👇




















