
Everything posted by Blogger
-
How to Keep Up With New CSS Features
by: Sacha Greif Tue, 17 Jun 2025 13:13:15 +0000 How do you keep up with new CSS features? Let’s say for example that, hypothetically speaking, you run a popular web development survey focused on CSS, and need to figure out what to include in this year’s edition. (In a total coincidence the aforementioned State of CSS survey for this year is actually open right now — go take it to see what’s new in CSS!) You might think you can just type “new CSS features 2025” in Google and be done with it. But while this does give us a few promising leads, it also unearths a lot of cookie-cutter content that proclaims CSS Grid as the “next big thing”, despite the fact it’s been well-supported for over eight years now. We need a better approach. I’ll focus on CSS in this article, but all the resources linked here cover all web platform features, including JavaScript and HTML. Web.dev A good general starting point is Google’s web.dev blog, and more specifically Rachel Andrew‘s monthly web platform recaps. Here’s a small sample of those: New to the web platform in January New to the web platform in February New to the web platform in March New to the web platform in April CSS-Tricks (and others) I’d be remiss to not mention that CSS-Tricks is also a great source for up-to-date CSS knowledge, including an ever-growing almanac of CSS features. But you probably already know that since you’re reading this. And let’s not discount other fine publications that cover CSS. Here are just a few: Smashing Magazine Frontend Masters Blog Piccalilli CSS-Tip Web Platform Features Explorer If you need something a bit more structured to help you figure out what’s new, Web Platform Features Explorer is great way to look up features based on their Baseline status. Web Platform Status A similar tool is the Web Platform Status dashboard. This one features more fine-grained filtering tools, letting you narrow down features by Baseline year or even show features mentioned as Top CSS Interop in the latest State of CSS survey! Another very cool feature is the ability to view a feature’s adoption rate, as measured in terms of percentage of Chrome page views where that feature was used, such as here for the popover HTML attribute: An important caveat: since sites like Facebook and Google account for a very large percentage of all measured page views, this metric can become skewed once one of these platforms adopts a new feature. The Web Platform Status’s stats section also features the “chart of shame” (according to Lea Verou), which highlights how certain browsers might be slightly lagging behind their peers in terms of new feature adoption. Chrome Platform Status That same adoption data can also be found on the Chrome Platform Status dashboard, which gives you even more details, such as usage among top sites, as well as sample URLs of sites that are using a feature. Polypane Experimental Chromium Features Dashboard Polypane is a great developer-focused browser that provides a ton of useful tools like contrast checkers, multi-viewport views, and more. They also provide an experimental Chromium features explorer that breaks new features down by Chromium version, for those of you who want to be at the absolute top of the cutting edge. Kevin Powell’s YouTube Channel As YouTube’s de facto CSS expert, Kevin Powell often puts up great video recaps of new features. You should definitely be following him, but statistically speaking you probably already are! It’s also worth mentioning that Kevin runs a site that publishes weekly HTML and CSS tips. CSS Working Group Of course, you can always also go straight to the source and look at what the CSS Working Group itself has been working on! They have a mailing list you can subscribe to keep tabs on things straight from your inbox, as well as an RSS feed. Browser release notes Most browsers publish a set of release notes any time a new version ships. For the most part, you can get a good pulse on when new CSS features are released by following the three big names in browsers: Chrome release notes Safari release notes Firefox release notes ChatGPT Another way to catch up with CSS is to just ask ChatGPT! This sample prompt worked well enough for me: Other resources If you really want to get in the weeds, Igalia’s BCD Watch displays changes to MDN’s browser-compat-data repo, which itself tracks which features are supported in which browsers. Also, the latest editions of the HTTP Archive Web Almanac do not seem to include a CSS section specifically, but past editions did feature one, which was a great way to catch up with CSS once a year. There’s also caniuse has a news section which does not seem to be frequently updated at the moment, but could potentially become a great resource for up-to-date new feature info in the future. The IntentToShip bot (available on Bluesky, Mastodon, Twitter) posts whenever a browser vendor ships or changes a feature. You can’t get more cutting-edge than that! And lastly, there’s a ton of folks on social media who are frequently discussing new CSS features and sharing their own thoughts and experiments with them. If you’re on Bluesky, there’s a starter pack of CSS-Tricks authors that’s a good spot to find a deep community of people. Wrapping up Of course, another great way to make sure no new features are slipping through the cracks is to take the State of CSS survey once a year. I use all the resources mentioned above to try and make sure each survey includes every new important feature. What’s more, you can bookmark features by adding them to your “reading list” as you take the survey to get a nice recap at the end. So go take this year’s State of CSS survey and then let me know on Bluesky how many new features you learned about! How to Keep Up With New CSS Features originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
-
Role model blog: Anna Salo, Nitor
by: Ani Tue, 17 Jun 2025 09:01:50 +0000 At the end of high school, I was lost which path to follow with my studies or what interest of mine to turn into a career. But I had the dream of having an efficient way to innovate something that could change people’s lives in the future. About meI’m Anna Salo, an accessibility-oriented software developer consultant at Nitor. For the last five years, I have focused on working with frontends for critical mass user services and B2C, both web and mobile. Therefore, I have taken a special interest in creating sustainable and accessible user interfaces. That means following tightly the Web Content Accessibility Guidelines (WCAG), especially on projects that involve public services and systems like student information platforms, which are legally required to meet WCAG accessibility standards. Many online services have been neglecting the accessibility requirements in the past, ending up in a difficult situation where the service doesn’t pass the updated accessibility regulations. With the new legislation coming into effect this summer, even more services like web stores will need to comply, meaning accessibility has become a very essential part of today’s web and mobile consumer service development. My path to IT and accessibility specialist At the end of high school, I was lost which path to follow with my studies or what interest of mine to turn into a career. But I had the dream of having an efficient way to innovate something that could change people’s lives in the future. From early on, I remember taking notice of a bad UI design and UX experience, thinking about how I would have wanted to do this and that somewhat better way. However, I felt insecure of my artistic competence to become a designer myself. This is when the thought of being a developer came along. I had never seriously considered it for myself before, having some preconceived and not-so-flattering misconceptions about the tech field. So, at first, there was no deep enthusiasm behind applying to computer science; it felt more like a suitable path to find my career. I ended up studying Computer Science at the University of Helsinki, earning my Bachelor’s degree. I was happy that most of my fears and misconceptions of the field were false. In fact, the problem-solving nature of it, with a room for creativity and design thinking, suited me well. While studying, I also started my career in startups, initially working on B2B solutions and later moving into B2C. I worked as a full-stack developer but gradually specialized in front end development. I also have the typical tale, I took a cap year from master studies to concentrate on my career, and half a decade later, I’m still on that cap year. Prior to Nitor, I developed a React Native based mobile app for iOS and Android. I had no prior experience with React Native or mobile app development, but I took the initiative to learn it from scratch. This idea ended up being a valuable stepping stone, giving me strong hands-on experience with modern technologies and project models. In those early roles in the startups, I had to implement all steps which gave me a broad and practical understanding of real world development—something that isn’t always taught at university. My journey with accessibility began when I wrote about Human Computer Interaction and Natural User Interfaces on my Bachelor’s thesis. The research I did had a huge impact on my thinking of UI and UX challenges, as I learned about issues I wouldn’t have understood to even consider prior. I began to find my passion, to change the mindset of others towards accessibility-oriented thinking. Working with accessibility has been both challenging and rewarding, and there is always something new to grow my expertise in. My next career target is to test my skills and earn the IAAP Web Accessibility Specialist (WAS) certificate. Now, looking at the kind of work I do today, I’m proud of myself taking that unexpected path. Stepping out of my comfort zone led me to something I truly care about and hopefully will make an improvement to other people’s lives. Anna Salo, Software Developer, Nitor Joining Nitor and working as a consultantJoining Nitor was a lucky coincidence for me. After a few years in the startup world, I have started to feel it would be the right moment to move forward with my career, but I was unsure about the next step. Then Nitor contacted me through LinkedIn for a coffee, so in a way, Nitor found me. I wasn’t familiar with the consulting business back then, but I decided to give it a go. Soon I found myself amazed by their warm and supportive Nitorean-driven work community and their ambition to be a sustainable and quality-driven digital consulting house. I felt this could be the perfect work community for me. I was a bit anxious about whether I would be a good fit in a consulting field, but working in startups turned out to be a significant advantage. The hands-on experience with React Native and a good understanding of user interfaces made me a strong match for what Nitor was looking for. Luckily, I also ended up with a perfect customer case, where I got my chance to work with real and challenging accessibility issues, and later develop my expertise with an accessible Design System as well. When I joined in 2020, Nitor had claimed the ‘Great Place to Work’ title for a few years in a row, and I couldn’t agree more with that evaluation. I get to work with the nicest and most talented people, there is always support available, and I feel valued as part of our work community. Nitor actively supports our self-study and professional development. We’re encouraged to get certifications and given five paid working days a year to focus on our academic training. We also have the mentoring program, from which I got the chance to learn and practise my UI design skills. This kind of support and freedom to grow makes working at Nitor so rewarding for me. One of the things I appreciate is the flexibility we have beyond customer work. We get to use 10% of our work time for internal or self-development—we call this “core work.” That could be working on our internal projects, like the Nitor.com website and internal apps, or contributing to various coding initiatives for the common good, like Virtanen.ai. There’s a lot of room to explore things we’re passionate about and a chance to create something truly great from those things. Keys to better accessibilityMaking a web service fully accessible means considering keyboard navigation, screen reader compatibility, and ensuring all content is properly structured for assistive technologies. You also must think of the frontend usability for people who do not need special assistive technology. That means securing accessible styles by using good colour contrasts and readable font-sizes and ensuring that the frontend has good cognitive accessibility. Avoiding pitfalls requires high code maintainability, writing clean, semantically correct code, and thorough testing across different devices and browsers. Accessibility isn’t always black and white. Sometimes, especially with more complex design or interaction patterns, there isn’t one definitive “correct” way to do something. In cases where the official WCAG (Web Content Accessibility Guidelines) doesn’t give a clear-cut answer, I assess the best approach based on the context. It becomes a bit of a judgment call: what’s the most inclusive and accessible option in this specific situation? That kind of thinking makes accessibility work both challenging and creative. With accessibility, you should avoid overdoing things and ensure that screen readers get only the information they need for the most fluid and informative user experience. You’re not just following rules—you’re solving real problems for real people. When I’m stuck on a problem or feeling overwhelmed, I’ve learned that the best thing I can do is step away and give my brain a break. I often come back with a fresh perspective and usually the solution suddenly feels obvious and straightforward. It’s a reminder that sometimes the best way to handle self-doubt or mental blocks is simply to allow yourself space to reset and return with a clear mind. About the impact of AI I’m genuinely concerned about how the rise of AI is impacting junior developers, especially those at the beginning of their careers. If companies continue hiring mostly senior-level talent and overlook junior developers, it will slow down the progress we’ve made in increasing diversity. You can’t become a senior developer without first being a junior one. We all need that early-career experience to grow, learn, and make mistakes. I know I did—I was a total beginner at my first job, and wouldn’t be where I am today, hadn’t I gotten a chance to learn and improve thanks to the support and feedback from more experienced colleagues. Not hiring coders for junior roles is especially affecting women developers, as we have a growing number of women in tech, many of whom are still at the start of their careers. I am afraid that companies often prioritize very high-level experience, creating a cycle where women don’t get the chance to gain that experience. That’s one of the reasons I’ve started studying design and accessibility more deeply. These are areas where the human perspective is still essential, and I believe they’ll remain relevant even as AI becomes more powerful. Design thinking and accessibility require empathy, context, and a deep understanding of real user needs—things machines still struggle with. I believe AI will transform every profession —none of us is exempt. But we need to be intentional about how we adapt, ensuring we’re not sacrificing long-term skills and diversity for short-term efficiency. What worries me even more is the idea of training new developers to rely solely on AI generated code. If you never have to do the thinking yourself from the start, you miss out on learning how to spot errors, understand best practices, or evaluate the quality of what’s being produced. Without foundational knowledge, how can you critically assess or improve what AI gives you? The post Role model blog: Anna Salo, Nitor first appeared on Women in Tech Finland.
-
Chris’ Corner: Liquid Ass
by: Chris Coyier Mon, 16 Jun 2025 16:23:56 +0000 First a quick heads up about… me. I have a weird itch to do “streaming”, so I’m letting myself just be a hardcore beginner and giving it a shot. The plan is just hang out with whoever shows up and make stuff and talk about front end web development and design. So: Me on Twitch CodePen on YouTube Seems like those two platforms make the most sense for that, so here we go. I made this super sick banner for Twitch, which you can’t even see because it’s covered by UI stuff lol. Welp. I suppose you knew that there’s no way I’m letting “liquid glass” slide by this week. Or should I say: Amazing. Marie actually beat me to it doing a whole Spark issue on it last week. Obviously CodePen users are all over this design trend, as it’s an absolutely magnetic challenge in CSS. Kevin Powell did a video which happened to drop at the perfect time. Kevin is so good at these things I’m like sick with jealousy about it. Maybe my stream will level up my video teaching skills. It’s not like CodePen is only now starting to have these glass-like effects. People have been doing it for ages. It had a particular boon when backdrop-filter: blur(2px); became a thing — that’s more like “frosted” glass — but still, Apple is doing that, too. Maybe -webkit-box-reflect will get new life on the web also? Feels related. Sebastiaan de With fortold it nearly perfectly well. 👏👏👏. Little touches like the reflective progress bar are so cool. I don’t know if Apple is actually doing this particular detail, I don’t have the new OS yet, but Sebastiaan’s idea is awesome. Apple is actually quite serious about this, and released a video of the whole idea. Honestly I think it’s kinda awesome looking. But I did kinda 😬 about the accessibility of it. No chance the text “Nao” above is passing any contrast test. Nao way amiright? Feels like text/background contrast has taken a hit. I haven’t seen a full throated takedown of it yet (there are some mentions though), but I imagine that’s coming. There are already settings in there to tone the effects down, I hear. I thought out loud the other month: literally everything ships inaccessibly. And since having that thought I’ve seen a half dozen things ship that way. Certainly we’re not immune to it, but it’s good motivation to get some more accessibility testing done (we’ve done a good bit already!) on our new editor before it goes out. Random thing before I sign off. The Oatmeal on Erasers is lovely.
-
A Better API for the Resize Observer
by: Zell Liew Mon, 16 Jun 2025 12:47:51 +0000 Resize Observer, Mutation Observer, and Intersection Observers are all good APIs that are more performant than their older counterparts: ResizeObserver is better than the resize event MutationObserver replaces the now deprecated Mutation Events IntersectionObserver lets you do certain scroll interactions with less performance overhead. The API for these three observers are quite similar (but they have their differences which we will go into later). To use an observer, you have to follow the steps below: Create a new observer with the new keyword: This observer takes in an observer function to execute. Do something with the observed changes: This is done via the observer function that is passed into the observer. Observe a specific element: By using the observe method. (Optionally) unobserve the element: By using the unobserve or disconnect method. (depending on which observer you’re using). In practice, the above steps looks like this with the ResizeObserver. // Step 1: Create a new observer const observer = new ResizeObserver(observerFn) // Step 2: Do something with the observed changes function observerFn (entries) { for (let entry of entries) { // Do something with entry } } // Step 3: Observe an element const element = document.querySelector('#some-element') observer.observe(element); // Step 4 (optional): Disconnect the observer observer.disconnect(element) This looks clear (and understandable) after the steps have been made clear. But it can look like a mess without the comments: const observer = new ResizeObserver(observerFn) function observerFn (entries) { for (let entry of entries) { // Do something with entry } } const element = document.querySelector('#some-element') observer.observe(element); The good news is: I think we can improve the observer APIs and make them easier to use. The Resize Observer Let’s start with the ResizeObserver since it’s the simplest of them all. We’ll begin by writing a function that encapsulates the resizeObserver that we create. function resizeObserver () { // ... Do something } The easiest way to begin refactoring the ResizeObserver code is to put everything we’ve created into our resizeObserver first. function resizeObserver () { const observer = new ResizeObserver(observerFn) function observerFn (entries) { for (let entry of entries) { // Do something with entry } } const node = document.querySelector('#some-element') observer.observe(node); } Next, we can pass the element into the function to make it simpler. When we do this, we can eliminate the document.querySelector line. function resizeObserver (element) { const observer = new ResizeObserver(observerFn) function observerFn (entries) { for (let entry of entries) { // Do something with entry } } observer.observe(node); } This makes the function more versatile since we can now pass any element into it. // Usage of the resizeObserver function const node = document.querySelector('#some-element') const obs = resizeObserver(node) This is already much easier than writing all of the ResizeObserver code from scratch whenever you wish to use it. Next, it’s quite obvious that we have to pass in an observer function to the callback. So, we can potentially do this: // Not great function resizeObserver (node, observerFn) { const observer = new ResizeObserver(observerFn) observer.observe(node); } Since observerFn is always the same — it loops through the entries and acts on every entry — we could keep the observerFn and pass in a callback to perform tasks when the element is resized. // Better function resizeObserver (node, callback) { const observer = new ResizeObserver(observerFn) function observerFn (entries) { for (let entry of entries) { callback(entry) } } observer.observe(node); } To use this, we can pass callback into the resizeObserver — this makes resizeObserver operate somewhat like an event listener which we are already familiar with. // Usage of the resizeObserver function const node = document.querySelector('#some-element') const obs = resizeObserver(node, entry => { // Do something with each entry }) We can make the callback slightly better by providing both entry and entries. There’s no performance hit for passing an additional variable so there’s no harm providing more flexibility here. function resizeObserver (element, callback) { const observer = new ResizeObserver(observerFn) function observerFn (entries) { for (let entry of entries) { callback({ entry, entries }) } } observer.observe(element); } Then we can grab entries in the callback if we need to. // Usage of the resizeObserver function // ... const obs = resizeObserver(node, ({ entry, entries }) => { // ... }) Next, it makes sense to pass the callback as an option parameter instead of a variable. This will make resizeObserver more consistent with the mutationObserver and intersectionObserver functions that we will create in the next article. function resizeObserver (element, options = {}) { const { callback } = options const observer = new ResizeObserver(observerFn) function observerFn (entries) { for (let entry of entries) { callback({ entry, entries }) } } observer.observe(element); } Then we can use resizeObserver like this. const obs = resizeObserver(node, { callback ({ entry, entries }) { // Do something ... } }) The observer can take in an option too ResizeObserver‘s observe method can take in an options object that contains one property, box. This determines whether the observer will observe changes to content-box, border-box or device-pixel-content-box. So, we need to extract these options from the options object and pass them to observe. function resizeObserver (element, options = {}) { const { callback, ...opts } = options // ... observer.observe(element, opts); } Optional: Event listener pattern I prefer using callback because it’s quite straightforward. But if you want to use a standard event listener pattern, we can do that, too. The trick here is to emit an event. We’ll call it resize-obs since resize is already taken. function resizeObserver (element, options = {}) { // ... function observerFn (entries) { for (let entry of entries) { if (callback) callback({ entry, entries }) else { node.dispatchEvent( new CustomEvent('resize-obs', { detail: { entry, entries }, }), ) } } } // ... } Then we can listen to the resize-obs event, like this: const obs = resizeObserver(node) node.addEventListener('resize-obs', event => { const { entry, entries } = event.detail }) Again, this is optional. Unobserving the element One final step is to allow the user to stop observing the element(s) when observation is no longer required. To do this, we can return two of the observer methods: unobserve: Stops observing one Element disconnect: Stops observing all Elements function resizeObserver (node, options = {}) { // ... return { unobserve(node) { observer.unobserve(node) }, disconnect() { observer.disconnet() } } } Both methods do the same thing for what we have built so far since we only allowed resizeObserver to observe one element. So, pick whatever method you prefer to stop observing the element. const obs = resizeObserver(node, { callback ({ entry, entries }) { // Do something ... } }) // Stops observing all elements obs.disconect() With this, we’ve completed the creation of a better API for the ResizeObserver — the resizeObserver function. Code snippet Here’s the code we’ve wrote for resizeObserver export function resizeObserver(node, options = {}) { const observer = new ResizeObserver(observerFn) const { callback, ...opts } = options function observerFn(entries) { for (const entry of entries) { // Callback pattern if (callback) callback({ entry, entries, observer }) // Event listener pattern else { node.dispatchEvent( new CustomEvent('resize-obs', { detail: { entry, entries, observer }, }) ) } } } observer.observe(node) return { unobserve(node) { observer.unobserve(node) }, disconnect() { observer.disconnect() } } } Using this in practice via Splendid Labz Splendid Labz has a utils library that contains an enhanced version of the resizeObserver we made above. You can use it if you wanna use a enhanced observer, or if you don’t want to copy-paste the observer code into your projects. import { resizeObserver } from '@splendidlabz/utils/dom' const node = document.querySelector('.some-element') const obs = resizeObserver(node, { callback ({ entry, entries }) { /* Do what you want here */ } }) Bonus: The Splendid Labz resizeObserver is capable of observing multiple elements at once. It can also unobserve multiple elements at once. const items = document.querySelectorAll('.elements') const obs = resizeObserver(items, { callback ({ entry, entries }) { /* Do what you want here */ } }) // Unobserves two items at once const subset = [items[0], items[1]] obs.unobserve(subset) Found this refactoring helpful? Refactoring is ultra useful (and important) because its a process that lets us create code that’s easy to use or maintain. If you found this refactoring exercise useful, you might just love how I teach JavaScript to budding developers in my Learn JavaScript course. In this course, you’ll learn to build 20 real-world components. For each component, we start off simple. Then we add features and you’ll learn to refactor along the way. That’s it! Hope you enjoyed this piece and see you in the next one. A Better API for the Resize Observer originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
-
LHB Linux Digest #25.13: lsattr, chattr Commands, Self-host Obsidian, Vert and More
by: Abhishek Prakash Fri, 13 Jun 2025 19:18:07 +0530 Another week, another chance to pretend you're fixing something important by typing furiously in the terminal. You do that, right? Or is it just me? 😉 This week's highlights are: lsattr, chatter and grep commandsbrace expansionsVERT converterAnd your regular dose of news, memes and tips❇️ Explore DigitalOcean with $100 free creditDigitalOcean is my favorite alternative to the likes of AWS and Azure and Google Cloud. I use it to host Linux Handbook and pretty happy with their performance and ease of deployment. Try their servers and marketplace apps for free with $100 credit which is applicable to new accounts. DigitalOcean – The developer cloudHelping millions of developers easily build, test, manage, and scale applications of any size – faster than ever before.Explore our productsGet started on DigitalOcean with a $100, 60-day credit for new users. This post is for subscribers only Subscribe now Already have an account? Sign in
-
Self-host Obsidian using Docker and Access it Via Web Browser
by: Abhishek Kumar Fri, 13 Jun 2025 18:48:38 +0530 Note-taking has come a long way from crumpled sticky notes and scattered .txt files. Today, we want our notes to be searchable, linked, visualized, and ideally, available anywhere. That’s where Obsidian shines. Source: Obsidian.mdBuilt around plain-text Markdown files, Obsidian offers local-first knowledge management with powerful graph views, backlinks, and a thriving plugin ecosystem. For many, it has become the go-to app for personal knowledge bases and second brains. While Obsidian does offer Obsidian Sync, a proprietary syncing service that lets you keep your notes consistent across devices, it’s behind a paywall. That’s fair for the convenience, but I wanted something different: A central Obsidian server, running in my homelab, accessible via browser, no desktop clients, no mobile apps, just one self-hosted solution available from anywhere I go. And yes, that’s entirely possible. Thanks to LinuxServer.io, who maintain some of the most stable and well-documented Docker images out there, setting this up was a breeze. I’ve been using their containers for various services in my homelab, and they’ve always been rock solid. Let me walk you through how I deployed Obsidian this way. PrerequisitesWe assume you have: A Linux system with Docker and Docker Compose installed.A basic understanding of terminal commands.Familiarity with editing YAML files.💡If you're new to Docker or Compose, check out our beginner Docker series and how to set up Docker Compose articles first.Setting up ObsidianIf you prefer keeping your self-hosted apps neatly organized (like I do), it's a good idea to create separate folders for each container. This not only helps with manageability, but also makes it easier to back up or migrate later. 1. Create a data directory for ObsidianLet’s start by creating a folder for Obsidian data: mkdir -p ~/docker/obsidian cd ~/docker/obsidian You can name it whatever you like, but I’m sticking with obsidian to keep things clear. 2. Create a docker-compose.yml FileNow, we’ll set up a Docker Compose file, this is the file that tells Docker how to run Obsidian, what image to use, which ports to open, and other important stuff. You don’t need to write the whole thing from scratch. I’m using the official example from the LinuxServer.io image page, but with a few changes tailored to my system. Just copy the following into a new file named docker-compose.yml: version: "3.8" services: obsidian: image: ghcr.io/linuxserver/obsidian:latest container_name: obsidian security_opt: - no-new-privileges:false - seccomp:unconfined healthcheck: test: timeout 10s bash -c ':> /dev/tcp/127.0.0.1/3000' || exit 1 interval: 10s timeout: 5s retries: 3 start_period: 90s ports: - "3000:3000" shm_size: "2gb" volumes: - ./config:/config:rw environment: CUSTOM_USER: yourusername PASSWORD: yourpassword PUID: 1000 PGID: 1000 TZ: Asia/Kolkata restart: unless-stopped Let’s break down a few important parts of this: image: We're using the latest Obsidian image provided by LinuxServer.io.volumes: Maps a config folder in your current directory to Obsidian’s internal config directory, this is where all your Obsidian data and settings will live.ports: The app will be available on port 3000 of your machine. You can change this if you prefer a different port.shm_size: Allocates shared memory; useful for apps with a UI like Obsidian.environment: This is where you set up your user, password, timezone, and file ownership.Make sure you replace the following placeholders with your own values: yourusername: The username you'll use to log in to Obsidian.yourpassword: Choose a strong password.TZ: Use your local timezone. (Example: Asia/Kolkata)PUID and PGID: These should match your user’s UID and GID on the host system. To find them, run:id yourusername You'll get something like this: uid=1000(yourusername) gid=1000(yourusername) groups=1000(yourusername),27(sudo),... Use those values in your Compose file. 3. Deploy the ContainerOnce the docker-compose.yml file is ready and the values are customized, go ahead and start the container: docker-compose up -d This command tells Docker to: Pull the Obsidian image (if it’s not already downloaded)Create a container using the settings we definedRun it in detached mode (-d), so it continues running in the backgroundGive it a minute or two, the first time you run this, Docker needs to download the entire image and set everything up. After that, it’ll be much faster on subsequent restarts. Accessing Obsidian in your browserOnce it's done, you should be able to open Obsidian in your browser at: http://localhost:3000 Or replace localhost with your server's IP if you’re not running it locally. 💡Optional: If you plan to access this instance from outside your local network, we strongly recommend putting it behind a reverse proxy like Caddy or NGINX with HTTPS and authentication. You can even pair it with a secure tunneling solution (like Cloudflare Tunnel or Tailscale Funnel) if you're behind CGNAT.Log in using the CUSTOM_USER and PASSWORD you set earlier. Once inside, it will look like this: Here you can: Create a new vault.Open an existing vault in the config volume.Explore the graph view, plugins, and everything else, right from the browser.Creating new vaultFor this tutorial, we’ll keep things simple, I’m just going to create a new vault to get started. Click on "Create", give your vault a name (anything you like - "secondbrain", "mynotes", "vault", etc.), and Obsidian will take care of the rest. It’ll create a new folder inside the mounted config directory we set up in Docker earlier. This means all your notes and settings will be saved persistently on your machine, even if the container is stopped or restarted. After you name and create the vault, Obsidian will drop you straight into the note-taking interface. And that’s it, you’re in! You can now start writing notes, creating folders, and playing around with features like: Graph view to visualize links between notesCommand palette to quickly access featuresThemes and plugin settings to customize your environmentEverything is accessible from the left sidebar, just like in the desktop app. No extra setup needed, just start typing and let your ideas flow. Final thoughtsSetting up Obsidian inside Docker was surprisingly easy, it didn’t take much time, and before I knew it, I had the full desktop-like experience running in my browser. This setup is especially great for people on the go or students like me who love using Obsidian but can’t always afford the Sync feature just yet. Now, I personally don’t mind paying for good software and I think Obsidian Sync is a solid service but those little costs start stacking up fast. I’ve also seen quite a few Reddit threads where folks have built their own syncing setups using Syncthing to keep notes in sync across devices, and that seems like a solid workaround as well. For me, this self-hosted browser version of Obsidian fits somewhere in the middle. It gives you the full experience without the limitations of a mobile app or the need to sync through someone else’s servers. And if you're already in the self-hosting ecosystem, it’s just another powerful tool you can add to your stack.
-
Breaking Boundaries: Building a Tangram Puzzle With (S)CSS
by: Sladjana Stojanovic Thu, 12 Jun 2025 13:58:38 +0000 For years, I believed that drag-and-drop games — especially those involving rotation, spatial logic, and puzzle solving — were the exclusive domain of JavaScript. Until one day, I asked AI: The answer: “No — not really. You’ll need JavaScript.” That was all the motivation I needed to prove otherwise. CodePen Embed Fallback But first, let’s ask the obvious question: Why would anyone do this? Well… To know how far CSS can be pushed in creating interactive UIs. To get better at my CSS skills. And it’s fun! Fair enough? Now, here’s the unsurprising truth: CSS isn’t exactly made for this. It’s not a logic language, and let’s be honest, it’s not particularly dynamic either. (Sure, we have CSS variables and some handy built-in functions now, hooray!) In JavaScript, we naturally think in terms of functions, loops, conditions, objects, comparisons. We write logic, abstract things into methods, and eventually ship a bundle that the browser understands. And once it’s shipped? We rarely look at that final JavaScript bundle — we just focus on keeping it lean. Now ask yourself: isn’t that exactly what Sass does for CSS? Why should we hand-write endless lines of repetitive CSS when we can use mixins and functions to generate it — cleanly, efficiently, and without caring how many lines it takes, as long as the output is optimized? So, we put it to the test and it turns out Sass can replace JavaScript, at least when it comes to low-level logic and puzzle behavior. With nothing but maps, mixins, functions, and a whole lot of math, we managed to bring our Tangram puzzle to life, no JavaScript required. Let the (CSS-only) games begin! 🎉 The game The game consists of seven pieces: the classic Tangram set. Naturally, these pieces can be arranged into a perfect square (and many other shapes, too). But we need a bit more than just static pieces. So here’s what I am building: A puzzle goal, which is the target shape the player has to recreate. A start button that shuffles all the pieces into a staging area. Each piece is clickable and interactive. The puzzle should let the user know when they get a piece wrong and also celebrate when they finish the puzzle. The HTML structure I started by setting up the HTML structure, which is no small task, considering the number of elements involved. Each shape was given seven radio buttons. I chose radios over checkboxes to take advantage of their built-in exclusivity. Only one can be selected within the same group. This made it much easier to track which shape and state were currently active. The start button? Also a radio input. A checkbox could’ve worked too, but for the sake of consistency, I stuck with radios across the board. The puzzle map itself is just a plain old <div>, simple and effective. For rotation, we added eight radio buttons, each representing a 45-degree increment: 45°, 90°, 135°, all the way to 360°. These simulate rotation controls entirely in CSS. Every potential shadow position got its own radio button too. (Yes, it’s a lot, I know.) And to wrap it all up, I included a classic reset button inside a <form> using <button type="reset">, so players can easily start over at any point. Given the sheer number of elements required, I used Pug to generate the HTML more efficiently. It was purely a convenience choice. It doesn’t affect the logic or behavior of the puzzle in any way. Below is a sample of the compiled HTML. It might look overwhelming at first glance (and this is just a portion of it!), but it illustrates the structural complexity involved. This section is collapsed to not nuke your screen, but it can be expanded if you’d like to explore it. Open HTML Code <div class="wrapper"> <div class="tanagram-box"></div> <div class="tanagram-box"></div> <form class="container"> <input class="hide_input start" type="checkbox" id="start" autofocus /> <button class="start-button" type="reset" id="restart">Restart</button> <label class="start-button" for="start">Start </label> <div class="shadow"> <input class="hide_input" type="radio" id="blueTriangle-tan" name="tan-active" /> <input class="hide_input" type="radio" id="yellowTriangle-tan" name="tan-active" /> <!-- Inputs for others tans --> <input class="hide_input" type="radio" id="rotation-reset" name="tan-active" /> <input class="hide_input" type="radio" id="rotation-45" name="tan-rotation" /> <input class="hide_input" type="radio" id="rotation-90" name="tan-rotation" /> <!--radios for 90, 225, 315, 360 --> <input class="hide_input" type="checkbox" id="yellowTriangle-tan-1-135" name="tan-rotation" /> <input class="hide_input" type="checkbox" id="yellowTriangle-tan-1-225" name="tan-rotation" /> <!-- radio for every possible shape shadows--> <label class="rotation rot" for="rotation-45" id="rot45">⟲</label> <label class="rotation rot" for="rotation-90" id="rot90">⟲</label> <!--radios for 90, 225, 315, 360 --> <label class="rotation" for="rotation-reset" id="rotReset">✘</label> <label class="blueTriangle tans" for="blueTriangle-tan" id="tanblueTrianglelab"></label> <div class="tans tan_blocked" id="tanblueTrianglelabRes"></div> <!-- labels for every tan and disabled div --> <label class="blueTriangle tans" for="blueTriangle-tan-1-90" id="tanblueTrianglelab-1-90"></label> <label class="blueTriangle tans" for="blueTriangle-tan-1-225" id="tanblueTrianglelab-1-225"></label> <!-- labels radio for every possible shape shadows--> <div class="shape"></div> </div> </form> <div class="tanagram-box"></div> <div class="tanagram-box"></div> <div class="tanagram-box"></div> <div class="tanagram-box"></div> <div class="tanagram-box"></div> </div> Creating maps for shape data Now that HTML skeleton is ready, it’s time to inject it with some real power. That’s where our Sass maps come in, and here’s where the puzzle logic starts to shine. Note: Maps in Sass hold pairs of keys and values, and make it easy to look up a value by its corresponding key. Like objects in JavaScript, dictionaries in Python and, well, maps in C++. I’m mapping out all the core data needed to control each tangram piece (tan): its color, shape, position, and even interaction logic. These maps contain: the background-color for each tan, the clip-path coordinates that define their shapes, the initial position for each tan, the position of the blocking div (which disables interaction when a tan is selected), the shadow positions (coordinates for the tan’s silhouette displayed on the task board), the grid information, and the winning combinations — the exact target coordinates for each tan, marking the correct solution. $colors: ( blue-color: #53a0e0, yellow-color: #f7db4f, /* Colors for each tan */ ); $nth-child-grid: ( 1: (2, 3, 1, 2, ), 2: ( 3, 4, 1, 2, ), 4: ( 1, 2, 2, 3, ), /* More entries to be added */); $bluePosiblePositions: ( 45: none, 90: ( (6.7, 11.2), ), 135: none, 180: none, /* Positions defined up to 360 degrees */); /* Other tans */ /* Data defined for each tan */ $tansShapes: ( blueTriangle: ( color: map.get($colors, blue-color), clip-path: ( 0 0, 50 50, 0 100, ), rot-btn-position: ( -20, -25, ), exit-mode-btn-position: ( -20, -33, ), tan-position: ( -6, -37, ), diable-lab-position: ( -12, -38, ), poss-positions: $bluePosiblePositions, correct-position: ((4.7, 13.5), (18.8, 13.3), ), transform-origin: ( 4.17, 12.5,), ), ); /* Remaining 7 combinations */ $winningCombinations: ( combo1: ( (blueTriangle, 1, 360), (yellowTriangle, 1, 225), (pinkTriangle, 1, 180), (redTriangle, 4, 360), (purpleTriangle, 2, 225), (square, 1, 90), (polygon, 4, 90), ), ); You can see this in action on CodePen, where these maps drive the actual look and behavior of each puzzle piece. At this point, there’s no visible change in the preview. We’ve simply prepared and stored the data for later use. CodePen Embed Fallback Using mixins to read from maps The main idea is to create reusable mixins that will read data from the maps and apply it to the corresponding CSS rules when needed. But before that, we’ve elevated things to a higher level by making one key decision: We never hard-coded units directly inside the maps. Instead, we built a reusable utility function that dynamically adds the desired unit (e.g., vmin, px, etc.) to any numeric value when it’s being used. This way, when can use our maps however we please. @function get-coordinates($data, $key, $separator, $unit) { $coordinates: null; // Check if the first argument is a map @if meta.type-of($data) == "map" { // If the map contains the specified key @if map.has-key($data, $key) { // Get the value associated with the key (expected to be a list of coordinates) $coordinates: map.get($data, $key); } // If the first argument is a list } @else if meta.type-of($data) == "list" { // Ensure the key is a valid index (1-based) within the list @if meta.type-of($key) == "number" and $key > 0 and $key <= list.length($data) { // Retrieve the item at the specified index $coordinates: list.nth($data, $key); } // If neither map nor list, throw an error } @else { @error "Invalid input: First argument must be a map or a list."; } // If no valid coordinates were found, return null @if $coordinates == null { @return null; } // Extract x and y values from the list $x: list.nth($coordinates, 1); $y: list.nth($coordinates, -1); // -1 gets the last item (y) // Return the combined x and y values with units and separator @return #{$x}#{$unit}#{$separator}#{$y}#{$unit}; } Sure, nothing’s showing up in the preview yet, but the real magic starts now. CodePen Embed Fallback Now we move on to writing mixins. I’ll explain the approach in detail for the first mixin, and the rest will be described through comments. The first mixin dynamically applies grid-column and grid-row placement rules to child elements based on values stored in a map. Each entry in the map corresponds to an element index (1 through 8) and contains a list of four values: [start-col, end-col, start-row, end-row]. @mixin tanagram-grid-positioning($nth-child-grid) { // Loop through numbers 1 to 8, corresponding to the tanam pieces @for $i from 1 through 8 { // Check if the map contains a key for the current piece (1-8) @if map.has-key($nth-child-grid, $i) { // Get the grid values for this piece: [start-column, end-column, start-row, end-row] $values: map.get($nth-child-grid, $i); // Target the nth child (piece) and set its grid positions &:nth-child(#{$i}) { // Set grid-column: start and end values based on the first two items in the list grid-column: #{list.nth($values, 1)} / #{list.nth($values, 2)}; // Set grid-row: start and end values based on the last two items in the list grid-row: #{list.nth($values, 3)} / #{list.nth($values, 4)}; } } } } We can expect the following CSS to be generated: .tanagram-box:nth-child(1) { grid-column: 2 / 3; grid-row: 1 / 2; } .tanagram-box:nth-child(2) { grid-column: 3 / 4; grid-row: 1 / 2; } CodePen Embed Fallback In this mixin, my goal was actually to create all the shapes (tans). I am using clip-path. There were ideas to use fancy SVG images, but this test project is more about testing the logic rather than focusing on beautiful design. For this reason, the simplest solution was to cut the elements according to dimensions while they are still in the square (the initial position of all the tans). So, in this case, through a static calculation, the $tansShapes map was updated with the clip-path property: clip-path: (0 0, 50 50, 0 100); This contains the clip points for all the tans. In essence, this mixin shapes and colors each tan accordingly. @mixin set-tan-clip-path($tanName, $values) { // Initialize an empty list to hold the final clip-path points $clip-path-points: (); // Extract the 'clip-path' data from the map, which contains coordinate pairs $clip-path-key: map.get($values, clip-path); // Get the number of coordinate pairs to loop through $count: list.length($clip-path-key); // Loop through each coordinate point @for $i from 1 through $count { // Convert each pair of numbers into a formatted coordinate string with units $current-point: get-coordinates($clip-path-key, $i, " ", "%"); // Add the formatted coordinate to the list, separating each point with a comma $clip-path-points: list.append($clip-path-points, #{$current-point}, comma); } // Style for the preview element (lab version), using the configured background color #tan#{$tanName}lab { background: map.get($values, color); clip-path: polygon(#{$clip-path-points}); // Apply the full list of clip-path points } // Apply the same clip-path to the actual tan element .#{$tanName} { clip-path: polygon(#{$clip-path-points}); } } and output in CSS should be: .blueTriangle { clip-path: polygon(0% 0%, 50% 50%, 0% 100%); } /* other tans */ CodePen Embed Fallback Start logic Alright, now I’d like to clarify what should happen first when the game loads. First, with a click on the Start button, all the tans “go to their positions.” In reality, we assign them a transform: translate() with specific coordinates and a rotation. .start:checked ~ .shadow #tanblueTrianglelab { transform-origin: 4.17vmin 12.5vmin; transform: translate(-6vmin,-37vmin) rotate(360deg); cursor: pointer; } CodePen Embed Fallback So, we still maintain this pattern. We use transform and simply change the positions or angles (in the maps) of both the tans and their shadows on the task board. When any tan is clicked, the rotation button appears. By clicking on it, the tan should rotate around its center, and this continues with each subsequent click. There are actually eight radio buttons, and with each click, one disappears and the next one appears. When we reach the last one, clicking it makes it disappear and the first one reappears. This way, we get the impression of clicking the same button (they are, of course, styled the same) and being able to click (rotate the tan) infinitely. This is exactly what the following mixin enables. @mixin set-tan-rotation-states($tanName, $values, $angles, $color) { // This mixin dynamically applies rotation UI styles based on a tan's configuration. // It controls the positioning and appearance of rotation buttons and visual feedback when a rotation state is active. @each $angle in $angles{ & ~ #rot#{$angle}{ transform: translate(get-coordinates($values,rot-btn-position,',',vmin )); background: $color;} & ~ #rotation-#{$angle}:checked{ @each $key in map.keys($tansShapes){ & ~ #tan#{$key}labRes{ visibility: visible; background:rgba(0,0,0,0.4); } & ~ #tan#{$key}lab{ opacity:.3; } & ~ #rotReset{ visibility: visible; } } } } } And the generated CSS should be: #blueTriangle-tan:checked ~ #rotation-45:checked ~ #tanblueTrianglelab { transform: translate(-6vmin,-37vmin) rotate(45deg); } #blueTriangle-tan:checked ~ #rotation-45:checked ~ #tanblueTrianglelabRes { visibility: hidden; } OK, the following mixins use the set-clip-path and set-rotation mixins. They contain all the information about the tans and their behavior in relation to which tan is clicked and which rotation is selected, as well as their positions (as defined in the second mixin). @mixin generate-tan-shapes-and-interactions($tansShapes) { // Applies styling logic and UI interactions for each individual tan shape from the $tansShapes map. @each $tanName, $values in $tansShapes{ $color: color.scale(map.get($values, color), $lightness: 10%); $angles: (45, 90, 135, 180, 225, 270, 315, 360); @include set-tan-clip-path($tanName, $values); ##{$tanName}-tan:checked{ & ~ #tan#{$tanName}Res{ visibility:hidden; } & ~ #tan#{$tanName}lab{opacity: 1 !important;background: #{$color};cursor:auto;} @each $key in map.keys($tansShapes){ & ~ #tan#{$tanName}Res:checked ~ #tan#{$key}labRes{visibility: visible;} } & ~ #rot45{display: flex;visibility: visible;} & ~ #rotReset{ transform: translate(get-coordinates($values, exit-mode-btn-position,',', vmin)); } @include set-tan-rotation-states($tanName, $values, $angles, $color); } } } @mixin set-initial-tan-position($tansShapes) { // This mixin sets the initial position and transformation for both the interactive (`lab`) and shadow (`labRes`) versions // of each tan shape, based on coordinates provided in the $tansShapes map. @each $tanName, $values in $tansShapes{ & ~ .shadow #tan#{$tanName}lab{ transform-origin: get-coordinates($values, transform-origin,' ' ,vmin); transform: translate( get-coordinates($values,tan-position,',', vmin)) rotate(360deg) ; cursor: pointer; } & ~ .shadow #tan#{$tanName}labRes{ visibility:hidden; transform: translate(get-coordinates($values,diable-lab-position,',',vmin)); } } } CodePen Embed Fallback As mentioned earlier, when a tan is clicked, one of the things that becomes visible is its shadow — a silhouette that appears on the task board. These shadow positions (coordinates) are currently defined statically. Each shadow has a specific place on the map, and a mixin reads this data and applies it to the shadow using transform: translate(). When the clicked tan is rotated, the number of visible shadows on the task board can change, as well as their angles, which is expected. Of course, special care was taken with naming conventions. Each shadow element gets a unique ID, made from the name (inherited from its parent tan) and a number that represents its sequence position for the given angle. Pretty cool, right? That way, we avoid complicated naming patterns entirely! @mixin render-possible-tan-positions( $name, $angle, $possiblePositions, $visibility, $color, $id, $transformOrigin ) { // This mixin generates styles for possible positions of a tan shape based on its name, rotation angle, and configuration map. // It handles both squares and polygons, normalizing their rotation angles accordingly and applying transform styles if positions exist.} @if $name == 'square' { $angle: normalize-angle($angle); // Normalizujemo ugao ako je u pitanju square } @else if $name == 'polygon'{ $angle: normalize-polygon-angle($angle); } @if map.has-key($possiblePositions, $angle) { $values: map.get($possiblePositions, $angle); @if $values != none { $count: list.length($values); @for $i from 1 through $count { $position: get-coordinates($values, $i, ',', vmin); & ~ #tan#{$name}lab-#{$i}-#{$angle} { @if $visibility == visible { visibility: visible; background-color: $color; opacity: .2; z-index: 2; transform-origin: #{$transformOrigin}; transform: translate(#{$position}) rotate(#{$angle}deg); } @else if $visibility == hidden { visibility: hidden; } &:hover{ opacity: 0.5; cursor: pointer; } } } } } } The generated CSS: #blueTriangle-tan:checked ~ #tanblueTrianglelab-1-360 { visibility: visible; background-color: #53a0e0; opacity: 0.2; z-index: 2; transform-origin: 4.17vmin 12.5vmin; transform: translate(4.7vmin,13.5vmin) rotate(360deg); } This next mixin is tied to the previous one and manages when and how the tan shadows appear while their parent tan is being rotated using the button. It listens for the current rotation angle and checks whether there are any shadow positions defined for that specific angle. If there are, it displays them; if not — no shadows! @mixin render-possible-positions-by-rotation { // This mixin applies rotation to each tan shape. It loops through each tan, calculates its possible positions for each angle, and handles visibility and transformation. // It ensures that rotation is applied correctly, including handling the transitions between various tan positions and visibility states. @each $tanName, $values in $tansShapes{ $possiblePositions: map.get($values, poss-positions); $possibleTansColor: map.get($values, color); $validPosition: get-coordinates($values, correct-position,',' ,vmin); $transformOrigin: get-coordinates($values,transform-origin,' ' ,vmin); $rotResPosition: get-coordinates($values,exit-mode-btn-position ,',' ,vmin ); $angle: 0; @for $i from 1 through 8{ $angle: $i * 45; $nextAngle: if($angle + 45 > 360, 45, $angle + 45); @include render-position-feedback-on-task($tanName,$angle, $possiblePositions,$possibleTansColor, #{$tanName}-tan, $validPosition,$transformOrigin, $rotResPosition); ##{$tanName}-tan{ @include render-possible-tan-positions($tanName,$angle, $possiblePositions,hidden, $possibleTansColor, #{$tanName}-tan,$transformOrigin) } ##{$tanName}-tan:checked{ @include render-possible-tan-positions($tanName,360, $possiblePositions,visible, $possibleTansColor, #{$tanName}-tan,$transformOrigin); & ~ #rotation-#{$angle}:checked { @include render-possible-tan-positions($tanName,360, $possiblePositions,hidden, $possibleTansColor, #{$tanName}-tan,$transformOrigin); & ~ #tan#{$tanName}lab{transform:translate( get-coordinates($values,tan-position,',', vmin)) rotate(#{$angle}deg) ;} & ~ #tan#{$tanName}labRes{ visibility: hidden; } & ~ #rot#{$angle}{ visibility: hidden; } & ~ #rot#{$nextAngle}{ visibility: visible } @include render-possible-tan-positions($tanName,$angle, $possiblePositions,visible, $possibleTansColor, #{$tanName}-tan,$transformOrigin); } } } } } CodePen Embed Fallback When a tan’s shadow is clicked, the corresponding tan should move to that shadow’s position. The next mixin then checks whether this new position is the correct one for solving the puzzle. If it is correct, the tan gets a brief blinking effect and becomes unclickable, signaling it’s been placed correctly. If it’s not correct, the tan simply stays at the shadow’s location. There’s no effect and it remains draggable/clickable. CodePen Embed Fallback Of course, there’s a list of all the correct positions for each tan. Since some tans share the same size — and some can even combine to form larger, existing shapes — we have multiple valid combinations. For this Camel task, all of them were taken into account. A dedicated map with these combinations was created, along with a mixin that reads and applies them. CodePen Embed Fallback At the end of the game, when all tans are placed in their correct positions, we trigger a “merging” effect — and the silhouette of the camel turns yellow. At that point, the only remaining action is to click the Restart button. Well, that was long, but that’s what you get when you pick the fun (albeit hard and lengthy) path. All as an ode to CSS-only magic! Breaking Boundaries: Building a Tangram Puzzle With (S)CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
-
FOSS Weekly #25.24: Nano Tips, Wayland-only Ubuntu, End of Mint 20 and 13 Years of It's FOSS
by: Abhishek Prakash Thu, 12 Jun 2025 04:28:41 GMT It's FOSS is turning 13 this week! It was created on 14th June, 2012 as a personal notebook where I shared my Linux discoveries. I didn't know that it will become a force to reckon with, a place to look up to for suggestions and advice on using Linux. In the 13 years, it's been viewed over 200 million times and formed a community of hundreds of thousands of Linux lovers from all parts of the world, with the US, Germany, Russia, UK and India taking the top 5 spots. I would like this opportunity to express my gratitude to all of you for your continued support 🙏 We shall continue to grow together and help the growth of the Linux community and open source software movement 💪 As a token of appreciation, I would like to unveil the new It's FOSS Plus website. This portal organizes the existing resources from the main website into course format which can be enjoyed by our paid members. Thank you for supporting us. Explore It's FOSS Plus To celebrate 13 years of It's FOSS, I have brought back the lifetime membership option with reduced pricing of $76 instead of the usual $99. If you ever wanted to support us with Plus membership but didn't like the recurring subscription, this is your chance 😃 Get It's FOSS Lifetime Membership 💬 Let's see what else you get in this edition Ubuntu ditching Xorg.Linux Mint 20.x reaching EOL.Nano editor tips.Tower cases for your Raspberry Pi.And other Linux news, tips, and, of course, memes!📰 Linux and Open Source NewsThe CrowPi 3 is now available on Kickstarter.The OpenInfra Foundation has a new home now.Canonical has decided to retire Bazaar from Launchpad.A new open source compiler is here to challenge LLVM.Langfuse, the popular LLM analytics platform, goes open.After Fedora, now Ubuntu opts for Wayland-only release.Linux Mint 20.x has reached end of life. Here's what you can do about it. Attention! Linux Mint 20 Has Reached Its EndIt’s time to upgrade! Linux Mint 20.x has reached end of life.It's FOSS NewsSourav Rudra🧠 What We’re Thinking AboutBig Tech doesn't like self-hosting and media server content. Heck, even posting about it on social media results in post removal. Self-Hosting and Media Servers are Big Tech’s Next TargetYouTube is actively silencing legitimate self-hosting content. They don’t want you to own your data?It's FOSS NewsSourav Rudra🧮 Linux Tips, Tutorials and MoreBenchmark your Linux system to see how it's performing.App is a cross-platform package management tool that rules them all.Why there is no IPv5?Explore the not-so-known features of the magnificent Nano editor. 10 Tips to Get More Out of Nano EditorLearn and use these tips and tricks to utilize lesser known Nano editor features.It's FOSSSreenath Desktop Linux is mostly neglected by the industry but loved by the community. For the past 12 years, It's FOSS has been helping people use Linux on their personal computers. And we are now facing the existential threat from AI models stealing our content. If you like what we do and would love to support our work, please become It's FOSS Plus member. It costs $24 a year (less than the cost of a burger meal each month) and you get an ad-free reading experience with the satisfaction of helping the desktop Linux community. Join It's FOSS Plus 👷 Homelab and Maker's CornerLevel up your Raspberry Pi 5 with a gaming tower case. Raspberry Pi 5 Tower Cases to Give it Desktop Gaming Rig LookPi 5 is a remarkable device and it deserves an awesome case. Transform your Raspberry Pi 5 into a miniature desktop tower PC with these cases.It's FOSSAbhishek PrakashIf that doesn't interest you, how about an open source accessible keyboard that you can build. ✨ Project HighlightPacket is a Quick Share client for Linux that facilitates wireless file transfers from Android devices. GitHub - nozwock/packet: Quick Share client for LinuxQuick Share client for Linux. Contribute to nozwock/packet development by creating an account on GitHub.GitHubnozwock📽️ Videos I am Creating for YouLike the terminal customization video, I made another detailed one about transforming the looks of Linux Mint. Subscribe to It's FOSS YouTube Channel🧩 Quiz TimeArch users, can you beat the Pacman Command Quiz? Pacman Command QuizBTW, do you use Arch Linux? If yes, can you answer all these questions correctly?It's FOSSAbhishek Prakash💡 Quick Handy TipIn the Dolphin file manager, you can open a folder while dragging a file to it. This is helpful if you want to drag and drop a file into a nested folder arrangement. To enable this, click on the Top-right Hamburger menu ⇾ Configure ⇾ Configure Dolphin. Here, go to the View section, select the General tab and toggle the Open folders during drag operations checkbox. Now, you can open a folder by dragging files and hovering them over it. 🤣 Meme of the WeekAn unbreakable bond! 🫂 🗓️ Tech TriviaOn June 10, 1977, Apple began shipping the Apple II, a home computer that quickly became a hit, especially in schools, thanks to its user-friendly design and color graphics. 🧑🤝🧑 FOSSverse CornerPro FOSSer Neville, is wondering whether ChatGPT has access to books? How does ChatGPT access books?I have not tried this, but there is a suggestion here that ChatGPT can reproduce material from a copyrighted book. Books were definitely included in their information intake, but I wonder how far they went. I bet they did not access older books that are only available in libraries or collections. If that is so, their information is biased toward modern material. There was a Google project many years ago to photocopy every book and make them freely available online. They were stopped by…It's FOSS Communitynevj❤️ With lovePlease share it with your Linux-using friends and encourage them to subscribe (hint: it's here). Share the articles in Linux Subreddits and community forums. Follow us on Google News and stay updated in your News feed. Opt for It's FOSS Plus membership and support us 🙏 Enjoy FOSS 😄
-
Chris Corner: For The Sake of It
by: Chris Coyier Mon, 09 Jun 2025 16:37:09 +0000 I love weird design ideas. Probably because so much of what we need to do as web designers is, appropriately, somewhat serious. We want things to be simple, clear, professional, so that people understand them and in many cases pay for them. So when the constraints relax, so can we. It’s unlikely that Taylor’s homepage would “perform well” in any sort of UX testing, but who cares? It’s not impossible to use, it’s just unusual. And crucially, it’s fun and memorable, which is likely a leg up on the last “dashboard” you saw. It’s cool of Blackle Mori to have documented The Lost CSS Tricks of Cohost.org, a social network ultimately too cool for this world. I sort of suspect a lot of this trickery is available in advanced email clients too, where you definitely don’t have JavaScript, but do have access to more-modern-than-you’d-think HTML and CSS. And high on the tranfixingly-weird scale is Amit Sheen’s CSS Spotlight Effect. Don’t write it off as a black page with a transparent circle moving with the mouse. I mean, it kinda is, but the filtering and scaling affects that come along for the ride are extremely cool. I actually just got to hang with Amit a bit at CSS Day in Amsterdam this past week. His talk was about building logic gates in CSS was pretty wild, and the whole thing end with him just showing off random amazing Pens of his on stage. Sometimes design can feel impressive because of the extreme constraints of where you’re seeing it. I’m at an airport lounge right now where I’ve seen an exhibit of sculptures carved into the lead tips of pencils. It’s that same kind of feeling I get when I see art happen in the terminal, a place usually not regarded for it’s beauty. Like seeing a daisy grow from the cracks of a busted up sidewalk. I like serious design as well. Certainly there is more money in it. I’m allowed to like them both, just like I enjoy both fine dining and fast food. I’ll just hit you with some quicker links though as I bet you’re tired of my going on. Chris Nager weighs in on Design Engineering from his experience with that title at Carta. “The most important skill design engineers possess is the ability to communicate with both designers and frontend engineers. They’re able to give feedback to both sides, and can act as translators between the two worlds through prototypes.” Emphasis mine, naturally. Lea Verou looks critically at a design change at GitHub in Minimalist Affordances: Making the right tradeoffs. Not only was it interesting, it showcases the power of blogging and making coherent points: GitHub noticed, talked with her, and improved the design. Grant Slatton on How to write a good design document. “Think of a design document like a proof in mathematics. The goal of a proof is to convince the reader that the theorem is true. The goal of a design document is to convince the reader the design is optimal given the situation.”
-
Creating an Auto-Closing Notification With an HTML Popover
by: Preethi Mon, 09 Jun 2025 12:58:37 +0000 The HTML popover attribute transforms elements into top-layer elements that can be opened and closed with a button or JavaScript. Most popovers can be light-dismissed, closing when the user clicks or taps outside the popup. Currently, HTML popover lacks built-in auto-close functionality, but it’s easy to add. Auto closing popups are useful for user interfaces like banner notifications — the new-message alerts in phones, for instance. A picture demo, is worth a thousand words, right? Click on the “Add to my bookmarks” button in the following example. It triggers a notification that dismisses itself after a set amount of time. CodePen Embed Fallback Let’s start with the popover The HTML popover attribute is remarkably trivial to use. Slap it on a div, specify the type of popover you need, and you’re done. <div popover="manual" id="pop">Bookmarked!</div> A manual popover simply means it cannot be light-dismissed by clicking outside the element. As a result, we have to hide, show, or toggle the popover’s visibility ourselves explicitly with either buttons or JavaScript. Let’s use a semantic HTML button. <button popovertarget="pop" popovertargetaction="show"> Add to my bookmarks </button> <div popover="manual" id="pop">Bookmarked!</div> The popovertarget and popovertargetaction attributes are the final two ingredients, where popovertarget links the button to the popover element and popovertargetaction ensures that the popover is show-n when the button is clicked. Hiding the popover with a CSS transition OK, so the challenge is that we have a popover that is shown when a certain button is clicked, but it cannot be dismissed. The button is only wired up to show the popover, but it does not hide or toggle the popover (since we are not explicitly declaring it). We want the popover to show when the button is clicked, then dismiss itself after a certain amount of time. The HTML popover can’t be closed with CSS, but it can be hidden from the page. Adding animation to that creates a visual effect. In our example, we will hide the popover by eliminating its CSS height property. You’ll learn in a moment why we’re using height, and that there are other ways you can go about it. We can indeed select the popover attribute using an attribute selector: [popover] { height: 0; transition: height cubic-bezier(0.6, -0.28, 0.735, 0.045) .3s .6s; @starting-style { height: 1lh; } } When the popover is triggered by the button, its height value is the one declared in the @starting-style ruleset (1lh). After the transition-delay (which is .6s in the example), the height goes from 1lh to 0 in .3s, effectively hiding the popover. Once again, this is only hiding the popover, not closing it properly. That’s the next challenge and we’ll need JavaScript for that level of interaction. Closing the popover with JavaScript We can start by setting a variable that selects the popover: const POPOVER = document.querySelector('[popover]'); Next, we can establish a ResizeObserver that monitors the popover’s size: const POPOVER = document.querySelector('[popover]'); const OBSERVER = new ResizeObserver((entries) => { if(entries[0].contentBoxSize[0].blockSize == 0) OBSERVER.unobserve((POPOVER.hidePopover(), POPOVER)); }); And we can fire that off starting when the button to show the popover is clicked: const POPOVER = document.querySelector('[popover]'); const OBSERVER = new ResizeObserver((entries) => { if(entries[0].contentBoxSize[0].blockSize == 0) OBSERVER.unobserve((POPOVER.hidePopover(), POPOVER)); }); document.querySelector('button').onclick = () => OBSERVER.observe(POPOVER); The observer will know when the popover’s CSS height reaches zero at the end of the transition, and, at that point, the popover is closed with hidePopover(). From there, the observer is stopped with unobserve(). In our example, height and ResizeObserver are used to auto-close the notification. You can try any other CSS property and JavaScript observer combination that might work with your preference. Learning about ResizeObserver and MutationObserver can help you find some options. Setting an HTML fallback When JavaScript is disabled in the browser, if the popover type is set to any of the light-dismissible types, it acts as a fallback. Keep the popover visible by overriding the style rules that hide it. The user can dismiss it by clicking or tapping anywhere outside the element. If the popover needs to be light-dismissible only when JavaScript is disabled, then include that popover inside a <noscript> element before the manual popover. It’s the same process as before, where you override CSS styles as needed. <noscript> <div popover="auto" id="pop">Bookmarked!</div> </noscript> <div popover="manual" id="pop">Bookmarked!</div> <!-- goes where <head> element's descendants go --> <noscript> <style> [popover] { transition: none; height: 1lh; } </style> </noscript> When to use this method? Another way to implement all of this would be to use setTimeout() to create a delay before closing the popover in JavaScript when the button is clicked, then adding a class to the popover element to trigger the transition effect. That way, no observer is needed. With the method covered in this post, the delay can be set and triggered in CSS itself, thanks to @starting-style and transition-delay — no extra class required! If you prefer to implement the delay through CSS itself, then this method works best. The JavaScript will catch up to the change CSS makes at the time CSS defines, not the other way around. Creating an Auto-Closing Notification With an HTML Popover originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
-
Transform Your Raspberry Pi 5 Into Miniature Desktop Gaming Rig With These Tower Cases
by: Abhishek Prakash Sat, 07 Jun 2025 15:36:14 GMT The bare Raspberry Pi board has a different appeal but I prefer keeping my Pis under cover, in protective cases. Now, there are tons of interesting cases available. You can also build your own with a 3D printer. The official Raspberry Pi 5 case and other small box design cases are okay for protection and they don't cost much. Raspberry Pi 5 official case beside Pironman 5However, lately, I have been fascinated with the tower cases. With the semi-transparent design and RGB lightings, they look dope. Like those customized gaming rigs people spend hundreds of dollars on. Thankfully, the Raspberry Pi is a small device so their tower cases are also not that expensive. Let me share a few of such beautiful mini tower PC like protective cases you can get for your Raspberry Pi 5 in the buyer's guide. Pironman 5: Full mini PC experiencePironman 5 is the ultimate case that got me into the tower PC fetish. It's my prized Pi accessory, beautifully sitting there on my secondary work desk. The Pironman 5 case transforms your Raspberry Pi 5 into a sleek aluminum mini-tower with advanced cooling, NVMe M.2 SSD support, customizable RGB lighting, dual standard HDMI ports, and a secure power switch. Yes, you read that right. It upgrades your Pi 5's mini HDMI ports into full HDMI ports and also allows you to use NVMe M.2 SSD. Do check the list of supported SSDs. Key Features: Adds a NVMe M.2 slot for SSDTower coolerDual RGB fans with dust filters0.96" OLED display showing real-time system metricsSafe shutdown functionality and IR receiverDual full-size HDMI ports and external GPIO accessActive support and communityUS duties and EU VAT included in the pricing💸 Price: $79.99 Get Pironman 5 from official websiteGet Pironman 5 from AmazonTom's Hardware found it could handle overclocked Pi 5s at 3GHz while maintaining excellent temperatures. I didn't do such extensive testing but you can still read my full experience of Pironman 5 in the review I did earlier. Pironman 5 Review: Best Raspberry Pi 5 Case You Can getIt’s a cooling case with RGB lighting but it turns your Raspberry Pi into a mini PC.It's FOSSAbhishek PrakashPironman 5 Max: NAS/AI optionPironman 5 Max is a slight upgrade to the previous entry. What's different here? Well, it primarily adds an additional NVMe M.2 slot so that you can use it as NAS RAID 0/1 setup or add Hailo-8L AI accelerator. There might be a few small differences, like the OLED screen has the tap to wake feature, but the main difference is that Pironman 5 Max has an additional NVMe slot. Oh, the black design gives it a more badass look. Key Features: Dual expandable NVMe M.2 slots with RAID 0/1 supportAI accelerator compatibility (e.g., Hailo-8L) for advanced edge AI applicationsSmart OLED display with vibration wake-up and tap-to-wake functionalityAdvanced cooling with tower cooler and dual RGB fansSleeker black aluminum chassis with semi-transparent panelsDual full-size HDMI ports and external GPIO accessActive support and communitySafe shutdown functionality and IR receiver💸 Price: $94.99 (Early bird: $71.24 for first 500 units) Clearly, it is suitable for NAS builds, AI edge computing, and Home Assistant hubs. 💡 And at the moment, the pre-order discount makes it cheaper than its predecessor. Grab it before the pricing goes back to normal. Get Pironman 5 Max from official websiteGeeekPi Tower Kit: Classic Pi plus M.2 NVMeThe GeeekPi Tower kit comes into two variants: with and without N07 M.2 NVMe SSD PCIe peripheral. The design is not a lot different from Pironman cases, at least from the outside. But here, you DO NOT get full HDMI slots. You access the usual Pi 5 ports. That makes it cheaper than Pironman cases. You have one Ice tower cooler with RGB lights to keep the Pi cool. Key Features: ICE Tower Cooler with LED fan for effective temperature control0.96" OLED screen for displaying system status informationTwo acrylic panels offering clear view of internal componentsN07 M.2 NVMe support in the upgraded modelRGB lighting that cycles through colorsRegular Pi 5 ports, no full HDMI slots💸 Price: $49 for the basic model Get GeeekPi Tower Kit from AmazonYahboom CUBE Pi: Boxed TowerIgnore the quirky Yahboom brand name ;) The CUBE Pi features a boxy aluminum alloy construction with 270° panoramic view that clearly displays internal components. There is only one fan with blue light at the top but it has ducts at top and bottom for better ventilation. The top is covered by a magnetic mesh. You also get programmable RGB lighting to add the oomph factor. The mini-HDMI ports are converted into full HDMI, so that's a good thing. There is an OLED display to show you the system stats hidden inside the case instead of being on the exterior. The case has enough space for adding an active radiator or M.2 SSD, you have to make those purchases separately. Key Features: Metal chassis with three highly transparent acrylic side plates offering 270° panoramic viewBlue light cooling fan with dual cooling ductsFull HDMI portsDust-proof magnetic nets to effectively block dust intrusionRGB lightingOLED display inside the caseScope for NVMe M.2 SSD slot (sold separately)💸 Price: ~$49 Get CUBE PI from AmazonElectroCookie: The Minimalist ChampionSometimes less is more. ElectroCookie's aluminum mini tower combines a large heat dissipation structure with an RGB-lit PWM fan that automatically adjusts speed based on CPU temperature. There is scope for the NVMe SSD HAT but you have to purchase it separately. There is a separate model that comes with the HAT. And that's it. It's just a case and doesn't add extra ports or slots to it. There is no OLED display, either. However, the case comes in five different colors to choose from. Now that's something, right? Key Features: Large active cooler with RGB PWM fanCompatible M.2 HAT NVMe SSD support (sold separately)Easy access to GPIO pins, SD card slot, and all portsSoft-touch power buttonAvailable in silver, black, red, blue and pink colors0-40 (M.2 HAT sold separately)Price: ~$32 Get ElectroCookie from AmazonWhich one to choose?Pick Pironman 5 if you want the complete package with professional features and don't mind paying premium pricing. Pick Pironman 5 Max if you need extra storage slot for a NAS or AI options to an overall mini PC build and don't mind the price tag. GeeekPi if you want a cool looking mini tower PC with focus on tower cooling and not focused on additional slots. Pick Yahboom if you don't necessarily want extra features but agree to pay a premium price for just a beautiful RGB lit tower case. Pick ElectroCookie if you want a tower case in your choice of color and don't need fancy features to keep the pricing in check. All these cases transform your Pi 5 from exposed board to desktop-class computer. Well, a miniature desktop computer. The cooling performance across all options is pretty good - you cannot function a Raspberry Pi as a desktop computer without proper thermal management. I am a fan of the Pironman cases. They are on the expensive side when compared to the rest but they also provide more features than the rest of the lot.
-
Converter Between YAML and JSON Files
by: Abhishek Prakash Fri, 06 Jun 2025 20:40:46 +0530
-
Better CSS Shapes Using shape() — Part 3: Curves
by: Temani Afif Fri, 06 Jun 2025 13:52:42 +0000 If you’re following along, this is the third post in a series about the new CSS shape() function. We’ve learned how to draw lines and arcs and, in this third part, I will introduce the curve command — the missing command you need to know to have full control over the shape() function. In reality, there are more commands, but you will rarely need them and you can easily learn about them later by checking the documentation. Better CSS Shapes Using shape() Lines and Arcs More on Arcs Curves (you are here!) The curve command This command adds a Bézier curve between two points by specifying control points. We can either have one control point and create a Quadratic curve or two control points and create a Cubic curve. For many of you, that definition is simply unclear, or even useless! You can spend a few minutes reading about Bézier curves but is it really worth it? Probably not, unless your job is to create shapes all the day and you have a solid background in geometry. We already have cubic-bezier() as an easing function for animations but, honestly, who really understands how it works? We either rely on a generator to get the code or we read a “boring” explanation that we forget in two minutes. (I have one right here by the way!) Don’t worry, this article will not be boring as I will mostly focus on practical examples and more precisely the use case of rounding the corners of irregular shapes. Here is a figure to illustrate a few examples of Bézier curves. The blue dots are the starting and ending points (let’s call them A and B) and the black dots are the control points. And notice how the curve is tangent to the dashed lines illustrated in red. In this article, I will consider only one control point. The syntax will follow this pattern: clip-path: shape( from Xa Ya, curve to Xb Yb with Xc Yc ); arc command vs. curve command We already saw in Part 1 and Part 2 that the arc command is useful establishing rounded edges and corners, but it will not cover all the cases. That’s why you will need the curve command. The tricky part is to know when to use each one and the answer is “it depends.” There is no generic rule but my advice is to first see if it’s possible (and easy) using arc. If not, then you have to use curve. For some shapes, we can have the same result using both commands and this is a good starting point for us to understand the curve command and compare it with arc. Take the following example: CodePen Embed Fallback This is the code for the first shape: .shape { clip-path: shape(from 0 0, arc to 100% 100% of 100% cw, line to 0 100%) } And for the second one, we have this: .shape { clip-path: shape(from 0 0, curve to 100% 100% with 100% 0, line to 0 100%) } The arc command needs a radius (100% in this case), but the curve command needs a control point (which is 100% 0 in this example). Now, if you look closely, you will notice that both results aren’t exactly the same. The first shape using the arc command is creating a quarter of a circle, whereas the shape using the curve command is slightly different. If you place both of them above each other, you can clearly see the difference. CodePen Embed Fallback This is interesting because it means we can round some corners using either an arc or a curve, but with slightly different results. Which one is better, you ask? I would say it depends on your visual preference and the shape you are creating. In Part 1, we created rounded tabs using the arc command, but we can also create them with curve. CodePen Embed Fallback Can you spot the difference? It’s barely visible but it’s there. Notice how I am using the by directive the same way I am doing with arc, but this time we have the control point, which is also relative. This part can be confusing, so pay close attention to this next bit. Consider the following: shape(from Xa Ya, curve by Xb Yb with Xc Yc) It means that both (Xb,Yb) and (Xc,Yc) are relative coordinates calculated from the coordinate of the starting point. The equivalent of the above using a to directive is this: shape(from Xa Ya, curve to (Xa + Xb) (Ya + Yb) with (Xa + Xc) (Yb + Yc)) We can change the reference of the control point by adding a from directive. We can either use start (the default value), end, or origin. shape(from Xa Ya, curve by Xb Yb with Xc Yc from end) The above means that the control point will now consider the ending point instead of the starting point. The result is similar to: shape(from Xa Ya, curve to (Xa + Xb) (Ya + Yb) with (Xa + Xb + Xc) (Ya + Yb + Yc)) If you use origin, the reference will be the origin, hence the coordinate of the control point becomes absolute instead of relative. The from directive may add some complexity to the code and the calculation, so don’t bother yourself with it. Simply know it exists in case you face it, but keep using the default value. I think it’s time for your first homework! Similar to the rounded tab exercise, try to create the inverted radius shape we covered in the Part 1 using curve instead of arc. Here are both versions for you to reference, but try to do it without peeking first, if you can. CodePen Embed Fallback Let’s draw more shapes! Now that we have a good overview of the curve command, let’s consider more complex shapes where arc won’t help us round the corners and the only solution is to draw curves instead. Considering that each shape is unique, so I will focus on the technique rather than the code itself. Slanted edge Let’s start with a rectangular shape with a slanted edge. Getting the shape on the left is quite simple, but the shape on the right is a bit tricky. We can round two corners with a simple border-radius, but for the slanted edge, we will use shape() and two curve commands. The first step is to write the code of the shape without rounded corners (the left one) which is pretty straightforward since we’re only working with the line command: .shape { --s: 90px; /* slant size */ clip-path: shape(from 0 0, line to calc(100% - var(--s)) 0, line to 100% 100%, line to 0 100% ); } Then we take each corner and try to round it by modifying the code. Here is a figure to illustrate the technique I am going to use for each corner. We define a distance, R, that controls the radius. From each side of the corner point, I move by that distance to create two new points, which are illustrated above in red. Then, I draw my curve using the new points as starting and ending points. The corner point will be the control point. The code becomes: .shape { --s: 90px; /* slant size */ clip-path: shape(from 0 0, Line to Xa Ya, curve to Xb Yb with calc(100% - var(--s)) 0, line to 100% 100%, line to 0 100% ); } Notice how the curve is using the coordinates of the corner point in the with directive, and we have two new points, A and B. Until now, the technique is not that complex. For each corner point, you replace the line command with line + curve commands where the curve command reuses the old point in its with directive. If we apply the same logic to the other corner, we get the following: .shape { --s: 90px; /* slant size */ clip-path: shape(from 0 0, line to Xa Ya, curve to Xb Yb with calc(100% - var(--s)) 0, line to Xc Yc, curve to Xd Yd with 100% 100%, line to 0 100% ); } Now we need to calculate the coordinates of the new points. And here comes the tricky part because it’s not always simple and it may require some complex calculation. Even if I detail this case, the logic won’t be the same for the other shapes we’re making, so I will skip the math part and give you the final code: .box { --h: 200px; /* element height */ --s: 90px; /* slant size */ --r: 20px; /* radius */ height: var(--h); border-radius: var(--r) 0 0 var(--r); --_a: atan2(var(--s), var(--h)); clip-path: shape(from 0 0, line to calc(100% - var(--s) - var(--r)) 0, curve by calc(var(--r) * (1 + sin(var(--_a)))) calc(var(--r) * cos(var(--_a))) with var(--r) 0, line to calc(100% - var(--r) * sin(var(--_a))) calc(100% - var(--r) * cos(var(--_a))), curve to calc(100% - var(--r)) 100% with 100% 100%, line to 0 100% ); } I know the code looks a bit scary, but the good news is that the code is also really easy to control using CSS variables. So, even if the math is not easy to grasp, you don’t have to deal with it. It should be noted that I need to know the height to be able to calculate the coordinates which means the solution isn’t perfect because the height is a fixed value. CodePen Embed Fallback Arrow-shaped box Here’s a similar shape, but this time we have three corners to round using the curve command. CodePen Embed Fallback The final code is still complex but I followed the same steps. I started with this: .shape { --s: 90px; clip-path: shape(from 0 0, /* corner #1 */ line to calc(100% - var(--s)) 0, /* corner #2 */ line to 100% 50%, /* corner #3 */ line to calc(100% - var(--s)) 100%, line to 0 100% ); } Then, I modified it into this: .shape { --s: 90px; clip-path: shape(from 0 0, /* corner #1 */ line to Xa Ya curve to Xb Yb with calc(100% - var(--s)) 0, /* corner #2 */ line to Xa Ya curve to Xb Yb with 100% 50%, /* corner #3 */ line to Xa Yb curve to Xb Yb with calc(100% - var(--s)) 100%, line to 0 100% ); } Lastly, I use a pen and paper to do all the calculations. You might think this technique is useless if you are not good with math and geometry, right? Not really, because you can still grab the code and use it easily since it’s optimized using CSS variables. Plus, you aren’t obligated to be super accurate and precise. You can rely on the above technique and use trial and error to approximate the coordinates. It will probably take you less time than doing all the math. Rounded polygons I know you are waiting for this, right? Thanks to the new shape() and the curve command, we can now have rounded polygon shapes! Here is my implementation using Sass where you can control the radius, number of sides and the rotation of the shape: CodePen Embed Fallback If we omit the complex geometry part, the loop is quite simple as it relies on the same technique with a line + curve per corner. $n: 9; /* number of sides*/ $r: .2; /* control the radius [0 1] */ $a: 15deg; /* control the rotation */ .poly { aspect-ratio: 1; $m: (); @for $i from 0 through ($n - 1) { $m: append($m, line to Xai Yai, comma); $m: append($m, curve to Xbi Ybi with Xci Yci, comma); } clip-path: shape(#{$m}); } Here is another implementation where I define the variables in CSS instead of Sass: CodePen Embed Fallback Having the variables in CSS is pretty handy especially if you want to have some animations. Here is an example of a cool hover effect applied to hexagon shapes: CodePen Embed Fallback I have also updated my online generator to add the radius parameter. If you are not familiar with Sass, you can easily copy the CSS code from there. You will also find the border-only and cut-out versions! Conclusion Are we done with the curve command? Probably not, but we have a good overview of its potential and all the complex shapes we can build with it. As for the code, I know that we have reached a level that is not easy for everyone. I could have extended the explanation by explicitly breaking down the math, but then this article would be overly complex and make it seem like using shape() is harder than it is. This said, most of the shapes I code are available within my online collection that I constantly update and optimize so you can easily grab the code of any shape! If you want a good follow-up to this article, I wrote an article for Frontend Masters where you can create blob shapes using the curve command. Better CSS Shapes Using shape() Lines and Arcs More on Arcs Curves (you are here!) Better CSS Shapes Using shape() — Part 3: Curves originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
-
LHB Linux Digest #25.12: Vim Mouse Mode, Dir Command, Sticky Bits and More
by: Abhishek Prakash Fri, 06 Jun 2025 17:33:26 +0530 Lesser known... that's the theme of this week's newsletter. Hope you like it 😄 Here are the highlights of this edition : Lesser known mouse mode in VimLesser known dir command in LinuxLesser known special file permissionsAnd your regular dose of better known memes, tips and news ;)🚀 Level up your coding skills and build your own botsHarness the power of machine learning to create digital agents and more with hot courses like Learning LangChain, The Developer's Playbook for Large Language Model Security, Designing Large Language Model Applications, and more. Part of the purchase goes to Code for America! Check out the ebook bundle here. Humble Tech Book Bundle: Machine Learning, AI, and Bots by O’Reilly 2025Master machine learning with this comprehensive library of coding and programming courses from the pros at O’Reilly.Humble Bundle This post is for subscribers only Subscribe now Already have an account? Sign in
-
Using Tabbed Interface in Vim
by: Abhishek Prakash Fri, 06 Jun 2025 16:15:07 +0530 Think of Vim tabs like browser tabs for your code editor - each tab holds one or more windows, letting you organize multiple files into logical workspaces. Unlike window splits that divide your screen, tabs stack contexts you can flip between instantly. Three files opened in separate tabs in VimLet's see how you can use tabs in Vim. Essential Vim tab commands at a glanceHere are the most common actions you can use while dealing with tabs in Vim. Command Action Memory Hook vim -p file1 file2 Opens files in tabs Vim in pages :tabnew filename Open file in new tab Tab new :tabedit filename Open file for editing in new tab Tab edit gt Next tab Go to next gT Previous tab Go to previous {n}gt Jump to tab number n Go to specific :tabfirst Jump to first tab Self-explanatory :tabclast Jump to last tab Self-explanatory :tabclose Close current tab Self-explanatory :tabonly Close all other tabs Keep only this :tabs List all tabs Show tabs Interesting, right? Let's see it in details. Opening files in tabs in VimLet's start by opening files in tabs first. Start Vim with multiple files opened in tabsLaunch Vim with multiple tabs instantly: vim -p file1.py file2.py file3.py 0:00 /0:13 1× Open two existing files in tabs while starting Vim How can you open just one file in tab? Well... if it's one file, what's the point of tab, right? 📋Vim tabs aren't file containers - they're viewport organizers. Each tab can hold multiple split windows, making tabs perfect for grouping related files by project, feature, or context. It's like having separate desks for different projects.Open a file in a new tab in the current Vim sessionWhen you are already inside Vim and want to open a file in a new tab, switch to normal mode by pressing Esc key and use the command: :tabnew filenameThis will load the file in a new tab. If the file doesn't exist, it will create a new one. Filename is optional. If you don't provide it, it will open a new file without any name: :tabnew 0:00 /0:11 1× Opening existing or new files in tabs from existing Vim session 💡If you use tabedit instead of tabnew, it open the file in Edit mode (insert mode) in the new tab.Search for files and open them in tabsSearch the current directory for filename matching the given pattern and open it in a new tab: :tabf filename*This only works if the search results into a single file. If there are more than one file matched, it will throw an error: E77: Too many file names💡While you can open as many tabs as you want, only 10 tabs are shown by default. You can change this by setting tabpagemax in your vimrc to something like set tabpagemax=12Navigating between tabsYou can move between opened tabs using: :tabn: for next tab:tabp: for previous tabTyping the commands could be tedious, so you can use the following key combinations in the nomral mode: gt: To go to the next tabgT (i.e. press g and shift and t keys together) To go to the previous tabIf there are too many tabs opened, you can use: :tabfirst: Jump to first tab:tablast: Jump to last tab💡You can enable mouse mode in Vim and that makes navigating between tabs easier with mouse clicks.In many distributions these days, Vim is preconfigured to show the tab labels on the top. If that's not the case, add this to your vimrc: set showtabline=2You can list all the opened tabs with: :tabs💡If you are particular about putting the opened tabs in specific order, you can move the current tab to Nth position with :tabm N. This tabm is short for tabmove. Note that Vim starts numbering at 0.Closing tabsHow do you close a tab? If the tab has a single filed opened, the regular save/exit Vim commands work. But it will be an issue if you have multiple split windows opened in a tab. :tabclose: Close current tab:tabonly: Only keep the current tab opened, close all others 0:00 /0:14 1× Tab closing operation in Vim 💡Accidentally closed a tab? :tabnew | u creates a new tab and undoes in one motion - your file returns.Bulk tab operationsWith :tabdo command, you can run the same operations in all the tabs. For example, :tabdo w will save file changes in all tabs, :tabdo normal! gg=G auto-indents every file. Similarly, tabdo %s/oldvar/newvar/g executes search-replace across every tab simultaneously. Parallel processing for repetitive changes. You get the idea. tabdo is the key here. 💡You can save your meticulously crafted tab layout. :mksession project.vim then vim -S project.vim restores exact tab layout.ConclusionWhile it is good to enjoy tabs in Vim, don't create dozens of them - they become harder to navigate than helpful. Use buffers for file switching and tabs for context switching. As you can see, with the tab feature, you get one inch closer to having the IDE like experience in Vim.
-
Exploring the CSS contrast-color() Function… a Second Time
by: Daniel Schwarz Thu, 05 Jun 2025 13:45:56 +0000 In many countries, web accessibility is a human right and the law, and there can be heavy fines for non-compliance. Naturally, this means that text and icons and such must have optimal color contrast in accordance with the benchmarks set by the Web Content Accessibility Guidelines (WCAG). Now, there are quite a few color contrast checkers out there (Figma even has one built-in now), but the upcoming contrast-color() function doesn’t check color contrast, it outright resolves to either black or white (whichever one contrasts the most with your chosen color). Right off the bat, you should know that we’ve sorta looked at this feature before. Back then, however, it was called color-contrast() instead of contrast-color() and had a much more convoluted way of going about things. It was only released in Safari Technology Preview 122 back in 2021, and that’s still the case at the time I’m writing this (now at version 220). You’d use it like this: button { --background-color: darkblue; background-color: var(--background-color); color: contrast-color(var(--background-color)); } CodePen Embed Fallback Here, contrast-color() has determined that white contrasts with darkblue better than black does, which is why contrast-color() resolves to white. Pretty simple, really, but there are a few shortcomings, which includes a lack of browser support (again, it’s only in Safari Technology Preview at the moment). We can use contrast-color() conditionally, though: @supports (color: contrast-color(red)) { /* contrast-color() supported */ } @supports not (color: contrast-color(red)) { /* contrast-color() not supported */ } The shortcomings of contrast-color() First, let me just say that improvements are already being considered, so here I’ll explain the shortcomings as well as any improvements that I’ve heard about. Undoubtedly, the number one shortcoming is that contrast-color() only resolves to either black or white. If you don’t want black or white, well… that sucks. However, the draft spec itself alludes to more control over the resolved color in the future. But there’s one other thing that’s surprisingly easy to overlook. What happens when neither black nor white is actually accessible against the chosen color? That’s right, it’s possible for contrast-color() to just… not provide a contrasting color. Ideally, I think we’d want contrast-color() to resolve to the closest accessible variant of a preferred color. Until then, contrast-color() isn’t really usable. Another shortcoming of contrast-color() is that it only accepts arguments of the <color> data type, so it’s just not going to work with images or anything like that. I did, however, manage to make it “work” with a gradient (basically, two instances of contrast-color() for two color stops/one linear gradient): CodePen Embed Fallback <button> <span>A button</span> </button> button { background: linear-gradient(to right, red, blue); span { background: linear-gradient(to right, contrast-color(red), contrast-color(blue)); color: transparent; background-clip: text; } } The reason this looks so horrid is that, as mentioned before, contrast-color() only resolves to black or white, so in the middle of the gradient we essentially have 50% grey on purple. This problem would also get solved by contrast-color() resolving to a wider spectrum of colors. But what about the font size? As you might know already, the criteria for color contrast depends on the font size, so how does that work? Well, at the moment it doesn’t, but I think it’s safe to assume that it’ll eventually take the font-size into account when determining the resolved color. Which brings us to APCA. APCA (Accessible Perceptual Contrast Algorithm) is a new algorithm for measuring color contrast reliably. Andrew Somers, creator of APCA, conducted studies (alongside many other independent studies) and learned that 23% of WCAG 2 “Fails” are actually accessible. In addition, an insane 47% of “Passes” are inaccessible. Not only should APCA do a better job, but the APCA Readability Criterion (ARC) is far more nuanced, taking into account a much wider spectrum of font sizes and weights (hooray for me, as I’m very partial to 600 as a standard font weight). While the criterion is expectedly complex and unnecessarily confusing, the APCA Contrast Calculator does a decent-enough job of explaining how it all works visually, for now. contrast-color() doesn’t use APCA, but the draft spec does allude to offering more algorithms in the future. This wording is odd as it suggests that we’ll be able to choose between the APCA and WCAG algorithms. Then again, we have to remember that the laws of some countries will require WCAG 2 compliance while others require WCAG 3 compliance (when it becomes a standard). That’s right, we’re a long way off of APCA becoming a part of WCAG 3, let alone contrast-color(). In fact, it might not even be a part of it initially (or at all), and there are many more hurdles after that, but hopefully this sheds some light on the whole thing. For now, contrast-color() is using WCAG 2 only. Using contrast-color() Here’s a simple example (the same one from earlier) of a darkblue-colored button with accessibly-colored text chosen by contrast-color(). I’ve put this darkblue color into a CSS variable so that we can define it once but reference it as many times as is necessary (which is just twice for now). button { --background-color: darkblue; background-color: var(--background-color); /* Resolves to white */ color: contrast-color(var(--background-color)); } And the same thing but with lightblue: button { --background-color: lightblue; background-color: var(--background-color); /* Resolves to black */ color: contrast-color(var(--background-color)); } First of all, we can absolutely switch this up and use contrast-color() on the background-color property instead (or in-place of any <color>, in fact, like on a border): button { --color: darkblue; color: var(--color); /* Resolves to white */ background-color: contrast-color(var(--color)); } Any valid <color> will work (named, HEX, RGB, HSL, HWB, etc.): button { /* HSL this time */ --background-color: hsl(0 0% 0%); background-color: var(--background-color); /* Resolves to white */ color: contrast-color(var(--background-color)); } Need to change the base color on the fly (e.g., on hover)? Easy: button { --background-color: hsl(0 0% 0%); background-color: var(--background-color); /* Starts off white, becomes black on hover */ color: contrast-color(var(--background-color)); &:hover { /* 50% lighter */ --background-color: hsl(0 0% 50%); } } CodePen Embed Fallback Similarly, we could use contrast-color() with the light-dark() function to ensure accessible color contrast across light and dark modes: :root { /* Dark mode if checked */ &:has(input[type="checkbox"]:checked) { color-scheme: dark; } /* Light mode if not checked */ &:not(:has(input[type="checkbox"]:checked)) { color-scheme: light; } body { /* Different background for each mode */ background: light-dark(hsl(0 0% 50%), hsl(0 0% 0%)); /* Different contrasted color for each mode */ color: light-dark(contrast-color(hsl(0 0% 50%)), contrast-color(hsl(0 0% 0%)); } } CodePen Embed Fallback The interesting thing about APCA is that it accounts for the discrepancies between light mode and dark mode contrast, whereas the current WCAG algorithm often evaluates dark mode contrast inaccurately. This one nuance of many is why we need not only a new color contrast algorithm but also the contrast-color() CSS function to handle all of these nuances (font size, font weight, etc.) for us. This doesn’t mean that contrast-color() has to ensure accessibility at the expense of our “designed” colors, though. Instead, we can use contrast-color() within the prefers-contrast: more media query only: button { --background-color: hsl(270 100% 50%); background-color: var(--background-color); /* Almost white (WCAG AA: Fail) */ color: hsl(270 100% 90%); @media (prefers-contrast: more) { /* Resolves to white (WCAG AA: Pass) */ color: contrast-color(var(--background-color)); } } Personally, I’m not keen on prefers-contrast: more as a progressive enhancement. Great color contrast benefits everyone, and besides, we can’t be sure that those who need more contrast are actually set up for it. Perhaps they’re using a brand new computer, or they just don’t know how to customize accessibility settings. Closing thoughts So, contrast-color() obviously isn’t useful in its current form as it only resolves to black or white, which might not be accessible. However, if it were improved to resolve to a wider spectrum of colors, that’d be awesome. Even better, if it were to upgrade colors to a certain standard (e.g., WCAG AA) if they don’t already meet it, but let them be if they do. Sort of like a failsafe approach? This means that web browsers would have to take the font size, font weight, element, and so on into account. To throw another option out there, there’s also the approach that Windows takes for its High Contrast Mode. This mode triggers web browsers to overwrite colors using the forced-colors: active media query, which we can also use to make further customizations. However, this effect is quite extreme (even though we can opt out of it using the forced-colors-adjust CSS property and use our own colors instead) and macOS’s version of the feature doesn’t extend to the web. I think that forced colors is an incredible idea as long as users can set their contrast preferences when they set up their computer or browser (the browser would be more enforceable), and there are a wider range of contrast options. And then if you, as a designer or developer, don’t like the enforced colors, then you have the option to meet accessibility standards so that they don’t get enforced. In my opinion, this approach is the most user-friendly and the most developer-friendly (assuming that you care about accessibility). For complete flexibility, there could be a CSS property for opting out, or something. Just color contrast by default, but you can keep the colors you’ve chosen as long as they’re accessible. What do you think? Is contrast-color() the right approach, or should the user agent bear some or all of the responsibility? Or perhaps you’re happy for color contrast to be considered manually? Exploring the CSS contrast-color() Function… a Second Time originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
-
The State of CSS 2025 Survey is out!
by: Juan Diego Rodríguez Thu, 05 Jun 2025 11:13:00 +0000 The State of CSS 2025 Survey dropped a few days ago, and besides waiting for the results, it’s exciting to see a lot of the new things shipped to CSS over the past year reflected in the questions. To be specific, the next survey covers the following features: calc-size() shape() Scroll-driven animations Container scroll-state queries CSS Carousels text-box-edge and text-box-trim field-sizing ::target-text @function display: contents Advanced attr() if() sibling-index() and sibling-count() Again, a lot! However, I think the most important questions (regarding CSS) are asked at the end of each section. I am talking about the “What are your top CSS pain points related to ______?” questions. These sections are optional, but help user agents and the CSS Working Group know what they should focus on next. By nature of comments, those respondents with strong opinions are most likely to fill them in, skewing data towards issues that maybe the majority doesn’t have. So, even if you don’t have a hard-set view on a CSS pain point, I encourage you to fill them — even with your mild annoyances. The State of CSS 2025 Survey is out! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
-
FOSS Weekly #25.23: Helwan Linux, Quarkdown, Konsole Tweaks, Keyboard Shortcuts and More Linux Stuff
by: Abhishek Prakash Thu, 05 Jun 2025 04:30:10 GMT There is some interesting news from this month's desktop Linux market share update. Linux has reached an all-time high market share of 2.69% in the latest Steam Survey. Also, CachyOS has found itself a spot in Linux-specific desktop market share in Steam Survey. 💬 Let's see what else you get in this edition Ubuntu keyboard shortcuts.The World Bank open sourcing a tool.Swiss privacy debacle.Obsidian plugins suggestion.And other Linux news, tips, and, of course, memes!This edition of FOSS Weekly is supported by ANY.RUN.🌟 OtterCookie: New Malware from North Korea Targets Tech and Finance ProfessionalsLazarus Group is currently distributing a stealer malware in fake job offer attacks. Here's how it goes: Lure via LinkedIn: Attackers offer freelance work to fix a minor DApp bug.Triggered Failure: An intentional app error fetches and executes malicious code from external APIMalware Execution: OtterCookie infects the system, stealing victims' browser data and crypto wallets.Read detailed analysis on ANY.RUN's blog. OtterCookie: Analysis of New Lazarus Group MalwareExplore in-depth technical analysis of OtterCookie, a new North Korean Lazarus APT malware that steals victims’ crypto and credentials.ANY.RUN's Cybersecurity BlogMauro Eldritch📰 Linux and Open Source NewsThe World Bank has open sourced Metadata Editor.KDE is getting its own GNOME Boxes counterpart.Ubuntu is adopting the monthly snapshots approach.Organic Maps has been forked over governance concerns.PeerTube is raising funds for further development of its mobile app./e/OS 3.0 has debuted with refined parental controls and new privacy tools.A recent kernel drama sees Linus Torvalds catch a string of suspicious pull requests.Arch Linux is working on a way to handle sponsorships. Sponsorships Seem to Be Coming to Arch Linux!A proposal looks to introduce a transparent sponsorship process to Arch Linux.It's FOSS NewsSourav Rudra🧠 What We’re Thinking AboutSwitzerland is going down a very dystopian path. I hope the law doesn't pass. No More Safe Haven for Privacy? Switzerland Drifts Toward a Surveillance State Due to New Controversial LawsProposed privacy law changes, if passed in parliament, threaten Switzerland’s reputation as a privacy haven.It's FOSS NewsGourav Patnaik🧮 Linux Tips, Tutorials and MoreYou can easily run JavaScript in VS Code.Here are 13 tips and tweaks to get more out of Konsole.Tweaks to get more out of Nautilus file manager.Plugins to enhance your Obsidian knowledge base.If you are up for an experiment, then you could try running macOS on Linux.I Installed macOS on Linux in a VM (for fun sake)Installing macOS as a virtual machine in a Linux system? Well, let’s do it for the sake of some ‘virtual fun’It's FOSSPranav Krishna Desktop Linux is mostly neglected by the industry but loved by the community. For the past 12 years, It's FOSS has been helping people use Linux on their personal computers. And we are now facing the existential threat from AI models stealing our content. If you like what we do and would love to support our work, please become It's FOSS Plus member. It costs $24 a year (less than the cost of a burger meal each month) and you get an ad-free reading experience with the satisfaction of helping the desktop Linux community. Join It's FOSS Plus 👷 Homelab and Maker's CornerLooks like a cyberdeck. Works like a classroom. The CrowPi 3 surprised me with its versatility. CrowPi 3: An All-in-one AI Learning Kit With Cyberdeck FeelA Swiss Army knife for coding education as this kit transforms the programming concepts into tangible experiences. Learn from more than a hundred interactive projects.It's FOSSAbhishek Prakash✨ Project HighlightMarkdown meets the power of LaTeX in this modern typesetting system with Quarkdown. GitHub - iamgio/quarkdown: 🪐 Markdown with superpowers — from ideas to presentations, articles and books.🪐 Markdown with superpowers — from ideas to presentations, articles and books. - iamgio/quarkdownGitHubiamgioHelwan Linux is a distro out of Egypt that feels quite nice. Helwan Linux: A Made-in-Egypt Linux DistributionCheck out what this distro out of Egypt has to offer.It's FOSS NewsGourav Patnaik📽️ Videos I am Creating for YouEssential Ubuntu keyboard shortcuts in action in this week's video. Subscribe to It's FOSS YouTube Channel🧩 Quiz TimeThis time, we have a puzzle for deciphering scrambled application names. Decipher The Scrambled WordsCan you guess the scrambled words? Give it a try!It's FOSSAnkush Das💡 Quick Handy TipIn VirtualBox, you can send keyboard shortcuts like CTRL+ALT+DEL to logout, and a bunch of other keyboard shortcuts to the guest (the virtual machine), without affecting the host (your computer). To send CTRL+ALT+DEL, use Host+Delete. The Host key is usually the Right CTRL key on a keyboard. In the top menu, go to Input ⇾ Keyboard → Keyboard Settings to view and customize additional keyboard shortcuts for interacting with the virtual machine. If you need more input options, you can use the Input ⇾ Keyboard → Soft Keyboard menu to open a virtual keyboard for the guest VM. 🤣 Meme of the WeekLinux users, together, strong! 🤜🤛 🗓️ Tech TriviaBefore Spotify or iTunes, there was Napster. Launched on June 1, 1999, by 18-year-old Shawn Fanning, who built it in his college dorm room, Napster quickly attracted over 20 million users (and viruses). 🧑🤝🧑 FOSSverse CornerOne of our regular FOSSers, Laura, is looking to compare notes on building Linux and open source software from source. Building Linux and FLOSS programs from source codeStarting a thread on discussion of building parts of Linux, LFS and various packages from source code. This is a spot to compare notes on building from source. We can also discuss pros and cons of various packages/projects to figure out which might be most useful to build from source and which might have less dependencies.It's FOSS CommunityLaura_Michaels❤️ With lovePlease share it with your Linux-using friends and encourage them to subscribe (hint: it's here). Share the articles in Linux Subreddits and community forums. Follow us on Google News and stay updated in your News feed. Opt for It's FOSS Plus membership and support us 🙏 Enjoy FOSS 😄
-
SSH Config Generator
by: Abhishek Prakash Wed, 04 Jun 2025 20:37:04 +0530 This tool lets you generate the SSH config entries quickly. Fill the fields and hit the generate button and copy the entries to your SSH config file. /* Catppuccin Mocha Color Palette */ :root { --ctp-base: #1e1e2e; --ctp-mantle: #181825; --ctp-surface0: #313244; --ctp-surface1: #45475a; --ctp-surface2: #585b70; --ctp-overlay0: #6c7086; --ctp-overlay1: #7f849c; --ctp-text: #cdd6f4; --ctp-subtext1: #bac2de; --ctp-subtext0: #a6adc8; --ctp-blue: #89b4fa; --ctp-lavender: #b4befe; --ctp-mauve: #cba6f7; --ctp-red: #f38ba8; --ctp-green: #a6e3a1; --ctp-yellow: #f9e2af; } * { margin: 0; padding: 0; box-sizing: border-box; } .ssh-config-widget { background: var(--ctp-base); color: var(--ctp-text); font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; border-radius: 12px; border: 1px solid var(--ctp-surface1); max-width: 600px; width: 100%; padding: 24px; margin: 0 auto; } .widget-header { margin-bottom: 24px; } .widget-header h2 { font-size: 1.25rem; font-weight: 600; color: var(--ctp-lavender); margin-bottom: 4px; } .widget-header p { font-size: 0.875rem; color: var(--ctp-subtext0); } .form-group { margin-bottom: 16px; } .form-group label { display: block; font-size: 0.875rem; font-weight: 500; color: var(--ctp-text); margin-bottom: 6px; } .required { color: var(--ctp-red); } .form-group input { width: 100%; padding: 10px 12px; background: var(--ctp-surface0); border: 1px solid var(--ctp-surface1); border-radius: 8px; color: var(--ctp-text); font-size: 0.875rem; transition: all 0.2s ease; } .form-group input:focus { outline: none; border-color: var(--ctp-blue); box-shadow: 0 0 0 2px rgba(137, 180, 250, 0.2); } .form-group input::placeholder { color: var(--ctp-overlay0); } .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } .help-text { font-size: 0.75rem; color: var(--ctp-subtext0); margin-top: 4px; } .error-message { color: var(--ctp-red); font-size: 0.75rem; margin-top: 4px; display: none; } .error-message.show { display: block; } .form-group input.error { border-color: var(--ctp-red); box-shadow: 0 0 0 2px rgba(243, 139, 168, 0.2); } .advanced-section { margin-top: 20px; border: 1px solid var(--ctp-surface1); border-radius: 8px; overflow: hidden; } .advanced-toggle { background: var(--ctp-surface0); padding: 12px 16px; cursor: pointer; display: flex; align-items: center; gap: 8px; font-size: 0.875rem; font-weight: 500; color: var(--ctp-text); transition: background 0.2s ease; user-select: none; } .advanced-toggle:hover { background: var(--ctp-surface1); } .toggle-icon { font-size: 0.75rem; transition: transform 0.2s ease; color: var(--ctp-overlay1); } .toggle-icon.expanded { transform: rotate(90deg); } .advanced-content { padding: 20px; background: var(--ctp-mantle); border-top: 1px solid var(--ctp-surface1); } .checkbox-group { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; margin-top: 12px; } .checkbox-item { display: flex; align-items: center; gap: 8px; } .checkbox-item input[type="checkbox"] { width: 16px; height: 16px; background: var(--ctp-surface0); border: 1px solid var(--ctp-surface2); border-radius: 4px; accent-color: var(--ctp-blue); } .checkbox-item label { font-size: 0.875rem; color: var(--ctp-text); cursor: pointer; margin: 0; } .generate-btn { width: 100%; background: var(--ctp-blue); color: var(--ctp-base); border: none; padding: 12px 16px; font-size: 0.875rem; font-weight: 600; border-radius: 8px; cursor: pointer; transition: all 0.2s ease; margin-top: 24px; } .generate-btn:hover { background: var(--ctp-lavender); } .generate-btn:active { transform: translateY(1px); } .output-section { margin-top: 24px; border: 1px solid var(--ctp-surface1); border-radius: 8px; overflow: hidden; } .output-header { background: var(--ctp-surface0); padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--ctp-surface1); } .output-header h3 { font-size: 0.875rem; font-weight: 600; color: var(--ctp-text); } .copy-btn { background: var(--ctp-green); color: var(--ctp-base); border: none; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 0.75rem; font-weight: 500; transition: background 0.2s ease; } .copy-btn:hover { background: var(--ctp-yellow); } .copy-btn.copied { background: var(--ctp-mauve); } .config-output { background: var(--ctp-mantle); color: var(--ctp-text); padding: 16px; font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace; font-size: 0.8rem; line-height: 1.5; white-space: pre-wrap; overflow-x: auto; } .output-footer { padding: 12px 16px; background: var(--ctp-surface0); font-size: 0.75rem; color: var(--ctp-subtext0); } .output-footer code { background: var(--ctp-surface1); padding: 2px 6px; border-radius: 4px; font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace; color: var(--ctp-blue); } @media (max-width: 640px) { .form-row { grid-template-columns: 1fr; } .checkbox-group { grid-template-columns: 1fr; } } Generate SSH config file entries Generate valid ~/.ssh/config entries Host Alias * Connection nickname Hostname/IP * Server address Username Leave empty for current user Port Default: 22 (use 1-65535) Identity File If you have multiple keys ▶ Advanced Options Proxy Jump Host Jump through this host first Local Forward local:remote:port Remote Forward remote:local:port Additional Options Forward X11 Forward SSH Agent Strict Host Key Checking Keep Alive Generate Config Generated Config Copy Add to ~/.ssh/config then connect with: ssh myserver With the ~/.ssh/config file, you can save the details about various servers you regularly connect to via SSH. It generates one set of entries for a server at a time. If you have multiple servers, you need to manually add the generated entries one after another in ~/.ssh/config file. Learn more about using SSH config file in detail. How to Use SSH Config File [Beginner’s Guide]Using SSH profiles can help you in cases where you regularly connect to various servers. No need to remember the IP address and other such details for SSH connection.Linux HandbookAbhishek Prakash
-
My Favorite Obsidian Plugins and Their Hidden Settings
by: Sreenath Wed, 04 Jun 2025 12:59:54 GMT In an earlier article, I wrote about using plugins in Obsidian. In this one, let me share a few of my favorite plugins. I recommend them but only use the ones that fit your needs. Just to recall, Obsidian has two kinds of plugins: Core plugins: Officially developed and maintained by the Obsidian team.Community Plugins: Created by users in the Obsidian community🚧Note that some plugins may make your Markdown notes fully readable only in Obsidian as they add extra features that are not available in usual Markdown. This can be a vendor lock in. Use plugins only according to your needs.Essential Core PluginsAt the time of writing this article, I see 28 core plugins in my Obsidian installation. I have picked only a handful of them. It doesn't mean others are not good. All core plugins have some use case for a particular set of users! Several of the plugins I discuss here are enabled by default. But these plugins have settings of their own and I share these settings that have enhanced my note management experience in Obsidian. ✋Non-FOSS Warning! Obsidian is not an open source software but it is loved and used by many open source developers and Linux users.BacklinksThe backlinks are among Obsidian's greatest features. It is crucial for managing interconnected notes and data. I know that the backlink plugin is enabled by default but there is a useful feature that you'll have to manually enable. It is “Show backlinks at the bottom of notes” option. Enable it by going to Backlinks plugin settings. Enable backlinksNow, under each note, backlinks will be shown. 0:00 /0:18 1× Backlinks in Obsidian It is particularly useful if you are creating new notes from a single place like Daily Notes, which is our next plugin! Daily NotesDaily Notes is like diary pages. It will create a Markdown page for each day and you can write your thoughts here. By default, you can access the daily notes from the Obsidian ribbon menu. But a more efficient way is to open daily notes whenever you open Obsidian. Go to the Daily Notes settings. Here, enable the "Open daily note on startup" toggle button. Daily Notes SettingsIn the screenshot above, you can see some other settings have been changed. Date Format: How the title of the daily note appear. You can get the date format options here.New file location: I have created a separate folder called Journals in my Obsidian vault to store all the daily notes.Page PreviewThis is enabled by default for you. With this plugin, you can hover over a note while pressing the CTRL key to get a preview. You can also quickly edit the note in the preview or go to another sub-preview, etc. Very useful tool if you are deep into note interlinking. 0:00 /0:23 1× Preview page in Obsidian Slash CommandsThis plugin is disabled by default. Go to the Core Plugins in Obsidian settings and enable this plugin. Once enabled, you can press the / key when typing a note to access commands. For example, insert attachment, insert code block, etc. A simple preview is shown in the below video. 0:00 /0:24 1× Slash command in Obsidian Notion, Ghost and many modern editors use this feature.| Web ViewerThis is a cool plugin that allows you to visit web links from within Obsidian. More than that, you can save a website to vault using this core plugin. It is not enabled by default, so do that first. Once enabled, click on the settings gear adjacent to the plugin to go to the plugin settings. Web viewer settings buttonHere, you can set further options like where to save the page by default, search engine, etc. Web Viewer SettingsYou can see some examples in the video below. Web viewer in Obsidian Interesting community plugins I likeNow, let's take a look at some cool community plugins that can enhance your knowledge base, as they do for me. CalendarIf you are a daily notes writer, this is a must-have plugin. Even if you are not into diary writing, it is still pretty cool to have a calendar placed on Obsidian. Calendar ViewYou can visit notes of any date simply by clicking on that date. If there is no note, it will prompt you to create one! There are many more features that you can explore, like a meter to track how much you have written on a particular day. 💡If you press CTRL key and hover over a date, those day's notes will be shown in a preview.Calendar PluginQuickAddQuickAdd is a much needed automation tool in Obsidian. It offers features like templates, captures, macros, multis, etc. which essentially allow users to create notes quickly. For example, the template feature can create a note based on a given template in a specified directory. All you have to do is invoke the command. The screenshot below shows three templates created by me for my use cases. Created TemplatesUse the settings gear to change additional settings like where to create a note, open the note automatically, etc. The video below shows how it quickly creates a note on a specified location. 0:00 /0:13 1× QuickAdd Working With macros, you can even assign key bindings to make your workflow even faster! QuickAddIconizeEmojis and icons are all the rage these days. From GitHub to changelogs, you'll see them everywhere. How about adding them to Obsidian? Obsidian organizes notes into folders and subfolders. With the Iconize plugin, you can set icons to folders. Icons applied to foldersYou can add new icon packs by going to the Settings → Community plugins → Installed plugins -→Iconize -→Settings gear → Icon packs. Icon packs added in IconizeRight-click on a folder or file and use the Change icon option to add a new icon to that folder/file. IconizeHighlightrRemember highlighting important stuff in a book? You can do the same in your notes on Obsidian. Highlight text in Vimeo It also provides different styles of highlighting, all selectable from the plugin settings. HighlightrCallout Manager and Callout SuggestionsThese are two different plugins which, when used together, are a great way to add callouts. 📋If you are not aware, callout blocks can improve your notes by making specific visually separated blocks for tips, warnings, etc. Like this 'note' callout block I used for telling you about callouts.By default, obsidian has some callouts like Note, Tips, Warnings, etc. Callout Manager allows you to create more callout blocks. Say you want to create a new callout block called "Read Later" and assign a particular color and icon. You can do that with this plugin. Callout blocks from Callout ManagerThe Callout Suggestions plugins will help you access these defined callout blocks easily in your notes. You can press >! and a dropdown menu will appear asking what block to use. 0:00 /0:23 1× Inserting Callouts in Obsidian PDF++Annotating a PDF document is a must-have feature in any PDF viewer. How about doing it in Obsidian? PDF++ is a great tool for this purpose. You can add your PDF notes to your vault and start annotating! Once the plugin is installed and enabled, make sure you have enabled the PDF editing feature. PDF++ plugin settingsNow, you can select text and then right-click to get the annotation menu. Unlike other plugins, this has a slight learning curve and plenty of options to tweak. Use it carefully. Annotate PDF in ObsidianPDF++LanguageTool IntegrationThis is for those who want to create notes without grammatical errors or spelling mistakes. LanguageTool is a proofreading software that checks the grammar, style, and spelling in over 20 languages. With this plugin, you can get error notifications for your text in Obsidian. If you have a premium subscription for LanguageTool, you can use it here as well. Spell check in Obsidian🚧You should disable the Obsidian spell check (Settings → Editor → Behavior → Spell Check) feature if you want to use this plugin.LanguageTool IntegrationTasksYou can use Obsidian as a task/to-do manager. That's no secret. However, Tasks is plugin that can do a lot more than just simple to-dos. It supports scheduling tasks, recurring tasks etc. You can also list all the tasks, today's tasks, etc. by using simple tasks specific queries. To create a task, you can enter CTRL+P (open command in Obsidian) and search for Tasks. Using the Tasks plugin to create tasksYou can retrieve tasks as shown in the small video below: 0:00 /0:28 1× Retrieve tasks in Obsidian TasksExcalidrawExcalidraw is a plugin to edit and view Excalidraw drawings in Obsidian. This sketching solution can make wonderful diagrams within Obsidian, embed drawings into your documents and much more. An Excalidraw drawing in ObsidianYou can find a huge list of settings for this plugin in the Obsidian settings. If you are into creative note-taking, look no further. ExcalidrawHonorable mentionsStyle Settings: Allows you to tweak several themes in Obsidian. One such theme that I am using and is heavily customizable is Border.Git: Allows you to version control your notes. You can pull changes from and push changes to GitHub, GitLab, etc.Dataview: Dataview is a live index and query engine over your personal knowledge base. You can query data from your Obsidian vault.QuickAdd: QuickAdd is like a super-smart shortcut button in Obsidian that lets you quickly create new notes or add stuff to existing ones using pre-made templates and automated steps you set up.Kanban: This plugins created a Markdown-based Kanban board.There are many other plugins, enabled/disabled in a default Obsidian installation. What I mentioned above are a couple of special ones. Don't forget to read the descriptions and try others too. Now I let you share your favorite Obsidian plugin in the comments.
-
Using Mouse Mode in Vim
by: Abhishek Prakash Wed, 04 Jun 2025 12:11:12 +0530 I hope I am not committing blasphemy but you can use the mouse in Vim. Press Esc to go to command mode in Vim and use: :set mouse=a It will enable mouse mode immediately in all Vim modes, i.e. normal, insert and visual. To disable the mouse mode, use this: :set mouse=If you want to use mouse mode all the time, I am not judging you, add this entry to your ~/.vimrc file: set mouse=aSave, restart Vim, and your mouse now works for clicking, scrolling, and selecting text. What does mouse mode do in Vim?First thing first, the mouse mode has limited usage in Vim. Don't expect to get a notepad like experience just because you enabled mouse mode. It does add some functionality and ease of use like: Jump to exact locations without counting lines or using searchWheel scrolling while dealing with long filesEasy text selection: double click to select a word, triple click to select an entire line, use mouse click and drag for the desired text selectionClick between panes and resize them easily while using split windows in VimWhile using tabs, click on other tabs to switch, click on X (should be visible at top right in mouse mode) to close a tab, double click on tab bar to create new empty files in new tabsUse middle click to paste from system clipboardThe video below shows some of the above discussed featured in action: 0:00 /0:21 1× Vim Mouse Mode demonstration Mouse mode doesn't replace Vim's keyboard efficiency - it complements it. Think of it as having both a sports car's manual controls and cruise control available. 📋Mouse mode behavior varies by terminal and some terminal emulators may require specific settings. If you are using tmux, ensure both Vim and tmux have mouse mode enabled for proper interaction.Understanding mouse mode OptionsVim's mouse configuration works like a permission system: set mouse=a " All modes (normal, visual, insert, command) set mouse=n " Normal mode only set mouse=v " Visual mode only set mouse=i " Insert mode only set mouse=c " Command mode only set mouse=nv " Normal and visual modesMostly, you would want mouse=a - which is like having universal access rather than mode-specific restrictions. 💡You can refer to Vim's official mouse documentation: :help mouse-usingConclusionI am not a Vim purist and I won't judge you if you use mouse in Vim, which is known for its keyboard-centric approach to productivity. Mouse mode doesn't diminish Vim's keyboard efficiency - it provides additional options for specific scenarios. If you feel too uncomfortable with Vim at the beginning, opt for the hybrid approach where you use mouse for navigation and positioning, keyboard for editing operations. I welcome your comments.
-
Windows Subsystem for Linux is now Open Source
by: Adnan Shabbir Wed, 04 Jun 2025 04:44:46 +0000 Windows Subsystem for Linux (WSL) allows you to run Linux distros within the Windows operating system. WSL is available in multiple versions: WSL1 (older but still supported) and WSL2 (newer with continuous development support). Recently, on May 19, 2025, Microsoft conducted a 2025 Build Conference where they announced the open-source nature of WSL for Windows, which is huge news for Linux users and the open-source community. In today’s guide, I will elaborate on the recent development in the WSL project, the open-source nature of the WSL, how mature its open-source nature is, and Microsoft’s journey towards open-source. So, let’s get started: Windows Subsystem for Linux (WSL) is now Open Source Road to Open Source WSL How can I Contribute to Open-Source WSL? Basic Understanding of the WSL Architecture Bonus: Keep Your WSL Updated Conclusion Windows Subsystem for Linux (WSL) is now Open Source Yes, you heard that right, Microsoft has recently declared WSL as an open-source component of Windows. Microsoft has officially released the source code of WSL on May 19, 2025, at the Microsoft Build 2025 Conference. As per the announcement of Microsoft, the WSL is completely open-sourced except for the following three components: “Lxcore.sys”: Kernel-side driver that powers WSL1, i.e., component that translates Linux commands for Windows in WSL1. “P9rdr.sys” and “p9n9.dll”: These 9P protocol components refer to the filesystem redirection from Windows to Linux. The above three components are tightly integrated with Windows core processes. For instance, the “Lxcore.sys” has to interact with the Windows kernel. Similarly, the 9P protocol components handle Windows files and directories. These deep integrations of WSL’s components with Windows might be the reasons behind keeping them closed-source. This was about Microsoft’s recent announcement. Now, let me take you through Microsoft’s path towards open-source WSL. Road to Open Source WSL In 2019, Microsoft started maintaining its own Linux Kernel specifically for WSL, and it was declared an open-source component. Since then, the WSL community has started growing and contributing to the WSL project. Later on, the WSLg component became open source, which provides the graphical applications support for the Linux applications in the WSL. Microsoft gradually moved WSL to an open-source project. In 2021, Microsoft separated the Windows code base from the WSL code base. The newly released WSL was then available on the Microsoft Store for Windows 11 users. However, in 2022, it was made available for Windows 10 users too. Apart from that, the development and upgradation of WSL keep going, i.e., the latest available WSL is “2.5.7.0”. How can I Contribute to Open-Source WSL? Microsoft has provided the source code Git Repo of WSL, where you can contribute to reporting the bugs/issues or developing the source code from scratch. A Linux enthusiast may love to look into the source code and contribute according to their interest. If you are facing any technical issues relevant to WSL, you can ask for assistance on WSL’s Git repo. Similarly, if you are a regular user, you must keep on visiting the WSL repo and contribute to any other issues of the users in the comments. Basic Understanding of the WSL Architecture Since two different operating systems are collaborating, the component functionalities would differ in both environments. Let’s have a bird’s eye view of the WSL architecture: Windows Host: The components hosted on the Windows side are the entry points to interact with the WSL. wsl.exe: Command line utility to launch or manage the WSL. wslconfig.exe: This mostly belongs to WSL 1 and is used to configure WSL 1. wslg.exe: Launch and manage the GUI apps from Windows. wslservice.exe: This is the background process, i.e., launch the VM, start/stop the distros attached, mount the filesystem, etc. WSL VM Service: These components work inside the WSL VM and are triggered when the user executes Windows components. init: The initial process that starts in the Linux environment. It initializes the necessary processes and manages the distribution at the WSL end. gns (Networking): Manages DNS and network integration with Windows. localhost (port forwarding): The localhost running inside the Linux VM is the port forwarding. Plan9 Server: The server is responsible for file sharing from Windows to Linux. Bonus: Keep Your WSL Updated When you install WSL from the command line, it might fetch and install a bit lower version. Or even if it is up to date as per your installation, you must check for updates and get the latest available version: Before updating: wsl -v Updating WSL: wsl --update Both the Linux Kernel and WSL versions are updated: wsl -v That’s all from this post. Conclusion On May 19, 2025, Microsoft announced the WSL as an open-source project. Not 100% open-source, yet a major advancement in the open-source world. Microsoft has long been working on this. Microsoft has held three components closed-source because of their deep integration with the Windows core processes. In today’s post, I have talked about the open-source nature of WSL and Microsoft’s path to open-source WSL development. FAQs: Q1: Is WSL completely open source now? Yes, except for only three components, which are “Lxcore.sys”, “P9rdr.sys”, and “p9n9.dll”. Q2: How can I contribute to WSL? Microsoft has provided the source code Git Repo of WSL, where you can contribute to reporting the bugs/issues or developing the source code from scratch. Q3: Is WSL a replacement for Linux? WSL cannot be referred to as a full replacement for Linux. WSL is just a subsystem within Windows. Q4: Can WSL open a GUI? Yes, WSL can open a Linux GUI application with the help of WSLg, i.e., introduced in 2021 for Windows 11 and in 2022 for Windows 10.
-
Getting Creative With HTML Dialog
by: Andy Clarke Tue, 03 Jun 2025 14:39:04 +0000 Like ’em or loath ’em, whether you’re showing an alert, a message, or a newsletter signup, dialogue boxes draw attention to a particular piece of content without sending someone to a different page. In the past, dialogues relied on a mix of divisions, ARIA, and JavaScript. But the HTML dialog element has made them more accessible and style-able in countless ways. So, how can you take dialogue box design beyond the generic look of frameworks and templates? How can you style them to reflect a brand’s visual identity and help to tell its stories? Here’s how I do it in CSS using ::backdrop, backdrop-filter, and animations. Design by Andy Clarke, Stuff & Nonsense. Mike Worth’s website will launch in June 2025, but you can see examples from this article on CodePen. I mentioned before that Emmy-award-winning game composer Mike Worth hired me to create a highly graphical design. Mike loves ’90s animation, and he challenged me to find ways to incorporate its retro style without making a pastiche. However, I also needed to achieve that retro feel while maintaining accessibility, performance, responsiveness, and semantics. A brief overview of dialog and ::backdrop Let’s run through a quick refresher. Note: While I mostly refer to “dialogue boxes” throughout, the HTML element is spelt dialog. dialog is an HTML element designed for implementing modal and non-modal dialogue boxes in products and website interfaces. It comes with built-in functionality, including closing a box using the keyboard Esc key, focus trapping to keep it inside the box, show and hide methods, and a ::backdrop pseudo-element for styling a box’s overlay. The HTML markup is just what you might expect: <dialog> <h2>Keep me informed</h2> <!-- ... --> <button>Close</button> </dialog> This type of dialogue box is hidden by default, but adding the open attribute makes it visible when the page loads: <dialog open> <h2>Keep me informed</h2> <!-- ... --> <button>Close</button> </dialog> I can’t imagine too many applications for non-modals which are open by default, so ordinarily I need a button which opens a dialogue box: <dialog> <!-- ... --> </dialog> <button>Keep me informed</button> Plus a little bit of JavaScript, which opens the modal: const dialog = document.querySelector("dialog"); const showButton = document.querySelector("dialog + button"); showButton.addEventListener("click", () => { dialog.showModal(); }); Closing a dialogue box also requires JavaScript: const closeButton = document.querySelector("dialog button"); closeButton.addEventListener("click", () => { dialog.close(); }); Unless the box contains a form using method="dialog", which allows it to close automatically on submit without JavaScript: <dialog> <form method="dialog"> <button>Submit</button> </form> </dialog> The dialog element was developed to be accessible out of the box. It traps focus, supports the Esc key, and behaves like a proper modal. But to help screen readers announce dialogue boxes properly, you’ll want to add an aria-labelledby attribute. This tells assistive technology where to find the dialogue box’s title so it can be read aloud when the modal opens. <dialog aria-labelledby="dialog-title"> <h2 id="dialog-title">Keep me informed</h2> <!-- ... --> </dialog> Most tutorials I’ve seen include very little styling for dialog and ::backdrop, which might explain why so many dialogue boxes have little more than border radii and a box-shadow applied. Out-of-the-box dialogue designs I believe that every element in a design — no matter how small or infrequently seen — is an opportunity to present a brand and tell a story about its products or services. I know there are moments during someone’s journey through a design where paying special attention to design can make their experience more memorable. Dialogue boxes are just one of those moments, and Mike Worth’s design offers plenty of opportunities to reflect his brand or connect directly to someone’s place in Mike’s story. That might be by styling a newsletter sign-up dialogue to match the scrolls in his news section. Mike Worth concept design, designed by Andy Clarke, Stuff & Nonsense. Or making the form modal on his error pages look like a comic-book speech balloon. Mike Worth concept design, designed by Andy Clarke, Stuff & Nonsense. dialog in action Mike’s drop-down navigation menu looks like an ancient stone tablet. Mike Worth, designed by Andy Clarke, Stuff & Nonsense. I wanted to extend this look to his dialogue boxes with a three-dimensional tablet and a jungle leaf-filled backdrop. Mike Worth, designed by Andy Clarke, Stuff & Nonsense. This dialog contains a newsletter sign-up form with an email input and a submit button: <dialog> <h2>Keep me informed</h2> <form> <label for="email" data-visibility="hidden">Email address</label> <input type="email" id="email" required> <button>Submit</button> </form> <button>x</button> </dialog> I started by applying dimensions to the dialog and adding the SVG stone tablet background image: dialog { width: 420px; height: 480px; background-color: transparent; background-image: url("dialog.svg"); background-repeat: no-repeat; background-size: contain; } Then, I added the leafy green background image to the dialogue box’s generated backdrop using the ::backdrop pseudo element selector: dialog::backdrop { background-image: url("backdrop.svg"); background-size: cover; } Mike Worth, designed by Andy Clarke, Stuff & Nonsense. I needed to make it clear to anyone filling in Mike’s form that their email address is in a valid format. So I combined :has and :valid CSS pseudo-class selectors to change the color of the submit button from grey to green: dialog:has(input:valid) button { background-color: #7e8943; color: #fff; } I also wanted this interaction to reflect Mike’s fun personality. So, I also changed the dialog background image and applied a rubberband animation to the box when someone inputs a valid email address: dialog:has(input:valid) { background-image: url("dialog-valid.svg"); animation: rubberBand 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; } @keyframes rubberBand { from { transform: scale3d(1, 1, 1); } 30% { transform: scale3d(1.25, 0.75, 1); } 40% { transform: scale3d(0.75, 1.25, 1); } 50% { transform: scale3d(1.15, 0.85, 1); } 65% { transform: scale3d(0.95, 1.05, 1); } 75% { transform: scale3d(1.05, 0.95, 1); } to { transform: scale3d(1, 1, 1); } } Tip: Daniel Eden’s Animate.css library is a fabulous source of “Just-add-water CSS animations” like the rubberband I used for this dialogue box. Changing how an element looks when it contains a valid input is a fabulous way to add interactions that are, at the same time, fun and valuable for the user. Mike Worth, designed by Andy Clarke, Stuff & Nonsense. That combination of :has and :valid selectors can even be extended to the ::backdrop pseudo-class, to change the backdrop’s background image: dialog:has(input:valid)::backdrop { background-image: url("backdrop-valid.svg"); } Try it for yourself: CodePen Embed Fallback Conclusion We often think of dialogue boxes as functional elements, as necessary interruptions, but nothing more. But when you treat them as opportunities for expression, even the smallest parts of a design can help shape a product or website’s personality. The HTML dialog element, with its built-in behaviours and styling potential, opens up opportunities for branding and creative storytelling. There’s no reason a dialogue box can’t be as distinctive as the rest of your design. Andy Clarke Often referred to as one of the pioneers of web design, Andy Clarke has been instrumental in pushing the boundaries of web design and is known for his creative and visually stunning designs. His work has inspired countless designers to explore the full potential of product and website design. Andy’s written several industry-leading books, including ‘Transcending CSS,’ ‘Hardboiled Web Design,’ and ‘Art Direction for the Web.’ He’s also worked with businesses of all sizes and industries to achieve their goals through design. Visit Andy’s studio, Stuff & Nonsense, and check out his Contract Killer, the popular web design contract template trusted by thousands of web designers and developers. Getting Creative With HTML Dialog originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
-
Chris’ Corner: Type Stuff!
by: Chris Coyier Mon, 02 Jun 2025 17:01:09 +0000 Let’s do typography stuff! Video: “A live demo by me of early font editors on a real Macintosh Plus” by Mark Simonson Font: “Is this font easy for you to read? Good—that’s the idea.” Hyperlegible is the name of the font, designed for people with low vision. Technique: “Fluid typography means thinking in terms of type scales and flexible spacing across your defined design space.” Richard Rutter goes retrofitting a new type sizing technique into an old layout. Miriam is also thinking about all this. Richard also has a good one on avoiding faux bold, which has afflicted me many times. My hot take is that browsers shouldn’t do faux nuthin. Font: “To help young readers of all skill levels, we’re introducing Kermit, a child-friendly typeface created by the type design studio Underware.” It’s fairly pricey but I can imagine it being perfect for some projects. Like Comic Sans but cooler. P.S. you should really try Comic Code on CodePen, it’s awesome. Fonts: “UNCUT.wtf is a free typeface catalogue, focusing on somewhat contemporary type.” A lot of them are super similar which makes me wonder if many of them come from students taking a design class or something. Journey: “One day, I saw what felt like Gorton on a ferry traversing the waters Bay Area. A few weeks later, I spotted it on a sign in a national park. Then on an intercom. On a street lighting access cover. In an elevator. At my dentist’s office. In an alley.” Marcin Wichary on Manhattan’s hardest working typeface. Performance: “… file sizes of web fonts? I personally don’t have a gut feeling how much is too much and how much is to be expected.” Stoyan Stefanov reckons 20k is fair. Behind the Scenes: “This meant that on web we could simply start our font stacks with Verdana, pick a couple of reasonable fallbacks, and get IKEA branding effectively for free.” IKEA didn’t end up using Verdana, but I wish they did, I kinda love it at small sizes. This is a great look at a major typographic choice at a major brand from Robin Whittleton. Technique: “…we added text-wrap: balance on WordPress.org, and quickly got community feedback that it led to awkward, unexpected breaks in Japanese and Korean.” Kelly Choyce-Dwan whips out stuff like word-break: auto-phrase; for the win.
-
CrowPi 3: An All-in-one AI Learning Kit With Cyberdeck Feel
by: Abhishek Prakash Mon, 02 Jun 2025 14:28:22 GMT Most educational programs keep students trapped behind screens, manipulating virtual objects and producing digital outputs. This is why the Raspberry Pi and Arduino like devices provide such a great learning environment. Adding hardware components to the board gives meaning to the software. The CrowPi takes this forward by giving an entire learning laboratory that transforms abstract programming concepts into tangible, interactive experiences. Elecrow has been known for creating great Raspberry Pi products. They are launching their latest product, CrowPi 3. They sent me the device to test and share the experience and that's what I am going to do in this article. What is CrowPi 3?Think of the CrowPi 3 as a Swiss Army knife for coding education - it packs dozens of sensors, programming environments, and learning tools into one portable, briefcase. Instead of juggling separate breadboards, sensors, and computers, everything lives in a single ecosystem. The CrowPi 3 transforms abstract programming concepts into tangible experiences. Write Python code to detect motion → PIR sensor triggers alarm. Code an NFC reader → tap a card to spawn TNT blocks in Minecraft. It's programming with immediate, visible consequences. The core components are: Raspberry Pi 5 as the brain of the kit (advance kit includes the pi)20+ sensors pre-wired and ready4.3" touchscreen + HDMI connections for external monitorsBuilt-in breadboards and GPIO accessArduino and Pi Pico can be attached on top of it128 GB micro SD card with custom operating systemThis custom operating system is a customized version of Raspbian OS and has numerous ready-to-explore projects across multiple programming environments like Python, Scratch etc. A dedicated section of AI projects to try beginner level AI projects using the on-board camera and various sensors. No prizes for guessing that there were at least two more such CrowPi kits in the past and considering the fact that this is the third such inastallment, I would think that the first two devices met with success. Who is this kit for?The primary audience for CrowPi 3 is children aged 8-16 who are learning programming and AI coding fundamentals. Of course, it is not just limited to children. People new to Raspberry Pi/Arduino would also benefit from the guided experiences of such a kit. I also think that it is suitable for teachers that need turnkey STEM curriculum tools. The kit has streamlined learning of all levels with a mix of both software and hardware. Start with drag-drop Scratch blocks, graduate to Python sensor control, eventually tackle more intense AI projects. But all this can be assembled...Sure, you may not need or use all of the sensors provided in the kit. And yes, you can get the sensors and accessories separately and use them with tons of open source projects available online but kits like CrowPi, make it all streamlined. This is ideal for schools and for people who want to explore programming the hardware devices without struggling with the hardware assembly. Hardware assembly could be fun, too, but it could be frustrating and time-consuming. These kits flatten the learning curve a little, letting the students enjoy dipping their toes in the vast ocean of electronic geekery. CrowPi 3 Kit contents📋The kit I received for this review is the basic one. The advanced kit comes with a Raspberry Pi 5 16 GB already fixed in, batteries for extra portability, an additional SD card with retro games on it, game controllers and a few more accessories. The basic kit has the following item in addition to the pre-wired ones on the board already: Power Supply x1Screwdriver x1Stepped Motor x1Infrared Receiver Head x1RFID Card + Tag x1IR Remote Control x1User Manual x1Motor + Fan Blade x1USB A to Micro B Cable x1Type-C to Type-C Cable x1NFC Card x1TF Card Reader x1A Swiss Army knife for coding education as this kit transforms the programming concepts into tangible experiences. Learn from more than a hundred interactive projects.Crowtail-9G Servo x1Components Pack (with wires, LED and more) x1128G TF Card with Customized System x1Accessories in CrowPi 3 Basic kitThe advanced kit has these items in addition to everything in the basic kit: NFC Tag x10Raspberry Pi 5 (16GB) x1Laptop Tote x12.4G Wireless Keyboard + Mouse x132G TF Card with Customized System x1Minecraft paper x5Crowtail - I2C HUB x118650 Lithium Battery x2Game Controller x2Experiencing CrowPi 3The CrowPi kit comes in a briefcase styled box which kind of gives me the cyberdeck feel. The case looks good with the frosted glass. CrowPi 3 Kit with frosted glass coverLet me share my experience using this open source AI education kit. The buildThis is a well-thought and well-built device. It comes in a briefcase styled box with a fold-out handle that makes it easy to carry. The frosted glass cover doesn't come off easily as it has strong magnets and all the sensors and components are firmly fixed in the kit. I didn't do a fall test by dropping it to the ground to check if it can survive something like that. If you ever do this, accidentally or otherwise, do share the data with us 😜 There is an empty slot at the bottom that could hold a few tiny accessories. It's a small thing but Built-in touchscreenThe small screen has touch capability and the touch response is pretty decent but I would only use it when there are no other options available. My fat fingers and eyes are not suitable for tiny screens anymore. Okay, I could access pretty much most of the things with this tiny screen and it becomes even easier to use when I connected proper keyboard and mouse to it. Still, it is tiny which is okay for retro gaming and a few small stuff to check the output or see debug info but you'll need a proper screen to make use of it. This is evident as the customized learning interface is not properly displayed in the tiny screen. CrowPi 3's small screen doesn't properly display its customized learning interfaceRaspbian OS customized for interactive learning📋I still don't have a capture card for Raspberry Pi (a shame, I know) and hence I cannot share good quality images here. Apologies as I order a capture card.The interface, when experienced on a proper monitor, shows the IDE tools and interactive project to choose from. I used their CrowView Notebook for this purpose, although it can be connected to any external monitor. Select interactive project from left or use IDE from the right to code on your ownTo use the interactive projects, you'll have to create an account. I think it's a local user account that stays on your system. This way, more than one people can save their progress. A local user account saves your progress on the deviceIn the image below, you can see that it provides 39 Python projects that teach you the basic Python programming that also interact with various sensors and components on the kit. Interactive Python project interfaceWhen you select a project, you have instructions on half the screen and the code editor or tool in the other half. And the programs you run can also impact the hardware (most of the time) and you can see it on CrowPi 3 kit. For example, in the screenshot above (if it is visible), it creates a simple program that sounds the buzzer on the board for the specified time. See, this allows an easier way of learning with both software and hardware in the mix as you have pre-built scenario with instructions and their impact is visible on the hardware. You are not restricted to this customized interface. You can also use the classic Raspbian interface and access the editors and projects from there as well. The sensory board experienceThe kit features two breadboards positioned strategically on the main board, eliminating the frustrating "where do I connect this?" moments. While breadboards are available for custom circuits, most sensors come pre-connected and ready to use. There are easy sample projects to test the functionality of these sensors and they are fun to experiment even for adults. The cooling fanThe cooling fan is loud and runs continuouslyThere is a visible cooling fan with RGB lighting. While it seemed cool at the beginning, it started to annoy me later. Don't get me wrong. I know that Raspberry Pi like devices must have some sort of cooling system. But this fan is much too loud for its size. And it runs continuously, unnecessarily. There is no on/off button for that and I don't expect such a feature. Now, this could be a good programming challenge to write a script that turns the fan off when the CPU reaches a certain temperature threshold. Bottom lineThe CrowPi 3 solves the "hello world" problem in programming education. Instead of printing text to screens, students control real-world devices and see immediate physical results. I mean, those variables are more meaningful when they represent real sensor readings. The conditional logic makes more sense when you can control lights and motors with it. The ready-to-use kit eliminates the setup frustration that could arise from manually connecting all those sensors, fans and lights. This is suitable for classrooms, as well as motivated self-learning at home. It is also a good Christmas gift candidate if you have children in your family that might be interested in learning AI, coding and electronics. I don't have the pricing at the time of writing this review. Elecrow is planning to run a pre-order campaign through Kickstarter very soon. Please check it for pricing and release dates. CrowPi 3 on Kickstarter