RSS FeedTwitterMastodonBlueskyShare IconHeart IconGithub IconArrow IconClock IconGUI Challenges IconHome IconNote IconBlog IconCSS IconJS IconHTML IconMedia IconGit IconSpeaking IconTools Icon
Background grid of 2023's and the GUI Skull logo in the middle
A series of images of an avatar doing a bunch of skateboard tricks.

New Year, New Site

12 min read
cssjs

I've joined the indie web #

Viva RSS, viva owning your content, viva to free expression. Join that Indie Web y'all 🤘🏻💀.

let life = new Year().next()

Instead of joining a different social network, investing in some new walled garden (Mastadon 👀), I've decided to take the popular UX/UI patterns, like a social feed, and build them here, where they can't be taken away or stifled.

Ain't no one gonna take my site. Adam Argyle

the stack #

After much testing and research, I chose to invest in Deno and their Fresh framework. I appreciate their investment in web standards and focus on being minimal.

It's also a server side rendered framework, at the edge, which means I can fix my site within seconds and don't have to wait for long static build and deploy times. Deno Fresh also caches at the edge, just in time 😍

SSR and progressive enhancement FTW.

the styles #

Fresh is pretty minimal out of the box, especially in regards to styling. It only offers a just in time atomic stylesheet setup, but I wanted to use Open Props (naturally). So I ended up writing my own file system watcher task that compiles PostCSS and pops it out to the static/ directory for cached serving.

import { debounce } from '$std/async/mod.ts'

export async function watchAndBuildStyles() {
  const watcher = Deno.watchFs([
    './styles/', 
    './components/', 
    './islands/',
  ])

  const protectedBuildCall = debounce(buildStyles, 200)
  
  for await (const _event of watcher)
    protectedBuildCall()
}

This route also means I'm in full control of the stylesheet. That was very critical to me as I want to use my site as a playground for new CSS features, progressively enhancing UX when available but otherwise serving a great static experience.

Here's my list of plugins if you're curious:

import cssNesting from 'npm:postcss-nesting'
import customMediaPlugin from 'npm:postcss-custom-media'
import mqRanges from 'npm:postcss-media-minmax'

import inlineImports from 'npm:postcss-import'
import importUrl from 'npm:postcss-import-url'
import importGlob from 'npm:postcss-import-ext-glob'
import cssnano from 'npm:cssnano'

import OpenProps from 'npm:open-props'
import jitProps from 'npm:postcss-jit-props'

A small preview of my index.css file. Spoiler, it's layers all the way, and I looooved it.

@import "https://unpkg.com/open-props/normalize.min.css" layer(base.normalize);
@import "https://unpkg.com/open-props/theme.light.switch.min.css" layer(base.theme);
@import "https://unpkg.com/open-props/theme.dark.switch.min.css" layer(base.theme);
@import "utilities.css" layer(base.utilities);
@import "nojs.css" layer(base.nojs);

@import "toast.css" layer(components.toast);
@import "markdown.css" layer(components.markdown);
@import "syntax-highlighting.css" layer(components.syntax);
@import "neon.css" layer(components.p3);
@import "quotes.css" layer(components.quote);

@import-glob "../components/**/*.css" layer(components.fresh);
@import-glob "../islands/**/*.css" layer(components.fresh);

@layer base.normalize-overrides {…}
@layer overrides {…}

It's 3 layers: base, components, overrides. Named sublayers for easier debugging and organization.

With that in place, I match a classname to a component name and that's pretty much it. I have global styles and component styles, all sharing the props and JIT Props makes sure I only ship the props I use. Good stuff.

Try the site in Chrome with #experimental-web-platform-features enabled. I'll be constantly trialing new features there! I'm currently trialing Scroll Linked Animations!

light n' dark #

Hopefully you noticed there was no "flash of an unwanted color scheme" (FOAUCS) when the page loaded.

So many sites strobe light my face with the light theme when the page loads (I'm generally in dark mode), and it makes me feel like a vampire who just got blasted with a sun ray. Sometime they blast me on every single page load… 😱

To make this feature, I followed my own Theme Switch GUI Challenge! I just integrated it into Fresh 🙂

A few features of it that make me happy:

  1. Works without JS
  2. Remembers your choice
  3. Syncs with the system as it changes
  4. Has a rad animation between a sun and moon with SVG and transforms
  5. No FOAUCS anywhere
  6. Has accessibility considerations

Also, don't miss that adaptive favicon 😎

comments and likes #

I really like the idea of aggregating mentions of my site's work onto this site itself, and WebMentions.io let me do that. Once set, with Deno I server side fetch mentions for any sub pages and send the data to some components to handle for rendering.

I think the result is cool. I feel like it's peer to peer in nature but with a good amount of optional content filtering from the owner side.

I'm not in any webrings yet… should I be?

multiple personas #

Sometimes people have multiple accounts with a service so they can provide different branded feeds, maybe a personal and a business one for example. I have 6 personas I can be on my site lol:

  1. admin: when I'm making site updates or announcements
  2. google: when posts are related to my work at Google
  3. argyleink: when the posts are rando Adam thoughts or comments
  4. csspodcast: when new episodes or moments happen for the CSS Podcast
  5. guichallenges: when new episodes or moments happen for the GUI Challenges
  6. pops: dad updates

I even have an open feature request, to myself, about co-tweeting personas. lol, taking multiple personalities to a new site level.

404 #

Make a _404.tsx handler in the routes/ directory and you too can make a custom 404 page. Want to see it? Visit https://nerdy.dev/you-wont-find-this.

Open Props made this page easy to style because I could easily bring in the normalize and props from a CDN and use them in the template.

localized dates #

With a little custom middleware I parse preferred languages from the request headers and then provide a getter to components on the server.

req.headers.get('accept-language')

This feature is both for localization but also for accuracy. The dates shows how long ago the post was made, and by knowing where you are in the world I can provide a date relative to you.

logical properties #

I used logical properties everywhere, which means the site can do rad stuff like this:

and i didnt have to do anything, the browser adapts it for me.

Turned out Media Queries made this hard though, and Container Queries saved the day. Blog post will def come out about it.

pretty URLs #

I really like minimal URLs and with Deno + Fresh it was really easy. I appreciate that it was the default.

analytics #

No client side analytics.

I went with Pirsch, and am happy! Cost seems right so they stay alive, great APIs and SDKs, and a really nice dashboard that's simple but powerful.

I reeeeeeally didn't want to run some open source containers on the cloud and host my own analytics… just not my kind of Tuesday night activity. Aka, I'm down with a couple extra bucks for a managed solution.

progressive web app #

This site is also a PWA. Go ahead, install it or add it to your homescreen. It launches with a nice splash image, is full screen, and can really feel like a system application.

I've implemented the following PWA features so far:

  1. manifest.json
  2. A service worker, mine is very lightweight
  3. Custom icon
  4. Custom install banner image

It's also got a great landscape layout when in fullscreen 🤓

forced colors #

Where there are shadows and colors to help distinguish UI elements, they're replaced with a transparent 1px border so that in forced-colors-mode there are visual affordances for distinguishing elements.

I also think the retro colors are super rad. Sometimes I use this mode if a site doesn't have a dark theme, it'll force a cool retro one!

works without javascript #

The site tries to only use JavaScript to enhance the experience, not relying on it for the baseline behavior.

uses some GUI Challenges #

You'll find the toast, dialog, theme switch, adaptive favicon and more to come.

Checkout all the GUI Challenges

keyboard navigation #

Give it a shot, try navigating around with the keyboard. There's a skip link in the nav bar, special scroll snap UX in the home feed, arrow key support in the filter aside, and great focus styles.

rss #

I freakin love RSS. Peer to peer social interation, nothin in the middle, so good. I've been an RSS reader for over 10 years, it's by far the best place for me to get meaningful information. Well, now I have a feed!

The RSS feed is the backbone of the site, it's an artifact that allows the content to travel and adapt to reader's preferences. You can subscribe from an RSS reader, from Chrome using the follow feature, or just follow along on Twitter where I syndicate the content.

Subscribe why don't ya?!

media #

Everything is currently uploaded into Cloudinary and then I've created a few authoring conveniences in Fresh and when writing Markdown that utilize all their great features.

I try to be respectful with the media delivery:

  • reliable alt content
  • lazy loading
  • async decoding
  • client hints
  • multiple formats (webp, avif, etc)
  • compressed
  • delivered from CDNs near you
  • videos always offer controls and only loop on demand

pride moment #

this can't be found in my styles:

body {
  overflow-x: hidden;
}

🤓

Mentions #

Join the conversation on

184 likes
12 reposts
  • Mike-麥-Mai/index.html
  • Craig Webb
  • nullptr
  • Jem Young
  • Denis TRUFFAUT ⭐️
  • WebPerformance Report
  • Bramus
  • 🄰🄻🄸
  • Fronteers
  • L!on 💙💛
  • Povilas
  • DevLobster🐻⛓
1 pingbacks

Crawl the CSS Webring?

previous sitenext site
a random site