CSS Variables are cool

(Technically they're called properties, but everyone calls them variables because of the var(--mycolor) syntax -- and I'm a descriptivist not a prescriptivist, so I'm gonna call 'em variables too.)

Now... CSS Variables are COOL.

The hooded figures who lurk near the Forbidden Dog Park don't want you to know about CSS Variables. The Sherriff's Secret Police would rather we did not discuss CSS Variables. But I Must Press On With This Blog Post.

I started using CSS Variables when revamping NimbleText with dark mode.

But I didn't even begin to scratch the surface then. I didn't get it (I didn't need to) but now the deeper I dig, the more power I find. No wonder shadowy helicopters hover outside the window of my office tower, even now.

Basic usage

First, I used Css Variables as a way to make sure I could define useful colors "just once", and reuse them consistently.

e.g. you can define a "main" background color like this:

:root {
	--main-bg: #FFF;
}

And apply it to any selectors you want:

body {
	background-color: var(--main-bg);
}

And you can override that... for example for your dark theme:

@media (prefers-color-scheme: dark) {
	:root {
		--main-bg: #222;
	}
}

And that's fun. But the fun doesn't stop there!

You may have noticed, above, that we applied our variable definition to a :root selector... what is that strange thing? and what other options do we have?

The colon prefix tells us it's a "pseudo-class" (like :hover or :focus), in this case it's a special pseudo-class meaning the root of the document. So the variable we created applies to the whole document.

We could also have "scoped" the variable to just a part of the document...

footer {
	--bg: #222;
	background-color:var(--bg);
}

I'm not using that piece of knowledge yet, but I mention it because many authors gloss over it, and also, perhaps because a skeletal figure just dragged his ragged body across the floor of my office, rose to his bony feet, brandished a glistening metallic weapon with a bright logo from "Cranial Extracto Matic" and screamed, in a voice like a thousand metal polar bears shrieking in agony as they are fed into a terrible grinding machine, that it would perhaps be wisest if I refrained from mentioning ":pseudo elements" in this particular blog post. And I am a bit of a contrarian on these things.

Now for something cool

The cool thing I've noticed more recently (and why I'm writing to you about this now) is this:

You can reference variables from variables.

🤯

... and referencing variables from variables must have many uses, an infinite number of uses.

For me, an immediate and powerful use is for doing custom themes properly.

In a real world theme, on a real world site, you don't end up with just 2 or 3 colors. You end up with many, many slightly different variations on a smaller number of hues. (A good recent article on the topic is here). If a button has a gradient, then it has several variations of the initial color. A panel may have a less saturated version of a background color, pushing it into the distance. Many variations, many colors -- but very few hues.

With Css Variables we can reuse an underlying hue with different saturation, lightness or opacity.

Instead of putting an entire color into a variable, start by putting your hue into a variable.

For example:

:root {
	--main-hue: 124; /* a green hue */
}

If we have a "greenish" theme, perhaps a variation on the hue of the green slime that has begun to bleed from the shadowy apparition who hovers even now abaft the cavernous black void that threatens the rusted children's play equipment in the boarded up park across the road, then we can store our "hue" and make sure that every "greenish" color on our site is based upon that hue.

Write your colors in hsla format... e.g:

:root {
	--main-hue: 124; /* a green hue */
	--main-bg: hsla(var(--main-hue),100%, 13%, 1); /* background */
	--main-fg: hsla(var(--main-hue), 17%, 86%,1); /* foreground */
}

... and so on.

Thus your whole theme can build up and having many colors based on a small number of hues.

The dark theme might even use the same hue, but flip the saturation/lightness of the colors that build upon it. It's flexible enough to achieve anything you need. And all the while, a single fact such as the base hues of your theme are not repeated.

It's DRY, it's separation of concerns. All those "principles" people used to go on about back in the Olden times of Before.

With your underlying hue defined in a variable, you can change a single line of CSS and completely alter your theme.

Now make it dynamic...

Oh but wait, it's cooler than that.

In one line you can also change those variables with javascript.

var root = document.documentElement;
root.style.setProperty('--main-hue', 0); // now the theme color is pure red (`hue = 0`), not that slime-like green.

Or if -- like me -- you're a fan of taking things too far, you can have a timer that randomly tries out different hues, and a stop button for when you're happy.

Or you can have several hues all rotated by various amounts from each other, in triads, or complements etc. You can "AB test" different values, or survey a small group to see the best possible themes.

This is much closer to how things should be (and it's also much more satisfying than using a pre-processor). The stylesheet encodes the underlying relationships between the properties, instead of just hard-coding the final values everywhere.

The art of science is the science of turning art into a science and the art of turning science into an art of science. Or something.

All of this is powerful stuff that no one is talking about. Maybe because THE MAN wants to keep us DOWN, or because BIG CSS wants us to be silent and happy with our rems and our ems. But I'm not going to be quiet about this anymore. Variables are there: do something extraordinary with them today.

Oh and bonus points if you also use calc() with them -- i'd love to hear about some interesting uses of calc with var.

My lawyer also wishes me to make it known that certain elements of this blog post would not have been possible if I had not recently begun listening to far too much "Welcome to Night Vale"; a very, very, very good podcast. There are also books.

 

Stashy is a Really simple Key Value store

In my various websites and web applications, I often need to store some things and later retrieve them.

Mostly for this I use a really simple key value store called "Stashy", that I built, oh, decades ago.

Imagine I have a "plain old class object", and I want to save it. It might be an object called "myNewBlogPost".

I will call:

stashy.Save<BlogPost>(myNewBlogPost, myNewBlogPost.Slug);

Later, if I want to load that blog post, I will need to know the key I used, let's say it was "hello-world":

var myNewBlogPost = stashy.Load<BlogPost>("hello-world");

And there's also a method for loading all objects of a given type:

var myBlogPosts = stashy.LoadAll<BlogPost>();

And for deleting a single object:

stashy.Delete<BlogPost>("hello-world");

And that's it!

It's not distributed. It doesn't have a query language. It doesn't have built in caching semantics. It's not even async.

I tell you what it does have: utility. It's bloody useful! I would definitely not consider it for cases with millions of records, but 1000 or under? It's fine! And one less thing to worry about.

The Stashy class is an implementation of the IStashy interface. This makes it easier to test, and helps me with the dependency injection stuff. Mostly it serves to make sure I'm keeping the contract small.

The IStashy interface is just this:

public interface IStashy<K>
{
    void Save<T>(T t, K id);
    T Load<T>(K id);
    IEnumerable<T> LoadAll<T>();
    void Delete<T>(K id);
    K GetNewId<T>();
}

The type K is the type of key you want to use. I usually use strings as my key. But you can use integers or guids, or anything else, if you like.

As I tweeted recently, The heart and soul of my most used key-value store fits in a single tweet. Easily!

I've used a couple of different implementations of this interface over the years, but currently what it does is serialize the object as JSON and then store it in a file named after the key, in a sub-folder named after the type of the object. (I also have a completely in-memory implementation, for testing etc.)

Here's an implementation I use at the moment in one of my projects. It is not glorious beautiful wondrous code, but it is working code that I just... well I just never have to look at it. But I rely on it all the time:

see gist: FileStashy.cs

It's about 100 lines of code, written years ago, and relies on Newtonsoft.Json to turn objects into Json and vice-versa.

(10 years ago my FileStashy used XML, on Windows... Now it's all Json, and runs anywhere that .net core runs. Maybe one day, one special day, we'll all switch to CSV.)

Sometimes I build other stuff on-top of Stashy, such as indexes, for faster lookups, and "revisions", so I can look at old versions of an object. That was easy enough to write (and very fun of course!) but I wouldn't want to do too much with it, or I'd switch to a real key value store... but then get all the headaches that come from those.

Got any crufty little things you've rolled yourself instead of using something "better"?

 

Tips For Working From Home

Now that everyone else has to work from home let me share my tips from 5 happy years of this way of life:

Get some exercise! Measure your steps and make sure you do a healthy amount every day. Super important. With no incidental walking your fitness can really drop.

Music! Music is crucial for me and maybe for you too. I bought a spotify subscription after a few months, just to get rid of the ads. You need a lot of playlists to fill up a week. Different music is needed at different times of day, and for different moods. Monday mornings and friday afternoons are two extreme opposites, and there many variations in between.

Look after your eyes! Home office is smaller than work office. Make sure your eyes get to look into the middle distance often. If you stare at just monitor/close walls all day, eyes never relax and you go blind! So stare out the window while you're day dreaming.

Plan B. You may need to be prepared in case your home internet connection goes down. (This happened to me yesterday, after a garbage truck took out the NBN for my whole neighborhood.) Make sure you can tether your laptop to your phone, or that you can work fully offline if needed.

The duck. Without a colleague to talk to you may find yourself talking to a rubber duck, explaining all your technical problems. This is a great technique and has been shown to work. Don't let your boss find out though or they'll fire you and keep the duck.

Headset. You'll need a good headset for remote meetings.

some kind of weird helmet that provides oxygen to an office worker

Diet. Don't let high sugar foods enter the house. It's the only way to avoid the temptation and deadly snaccidents.

Environment. Do whatever you can to create a dedicated workspace. It will help you separate work from home and improve your focus. This way too, you can finally get the home office just the way you want it.

finally got the home office just the way i want it

Keyboard, Mouse, Monitor(s). Maybe your setup isn't perfect, but where possible, don't just work directly on your laptop. Use a real keyboard. I think wireless is a waste of time with keyboards, you hardly move it, just used a wired one! And I'm not one of those fetishists who spends all their time and money on them, though I don't wish to shame those who are, well not too much. More monitors are always better, but you may not have the space, the funds, or the opportunity to get that in place right now. I don't have strong opinions on mice either, but with a real keyboard you will also need a real mouse.

Habits. The whole experience hinges on your ability to construct your own habits that create a good life. You can't rely on following the momentum of those around you. You're free to build up or destroy the structure of your day. It doesn't take will power, it just takes sensible routines.

 

Productivity with Powershell: Grab your agenda from outlook as plain text

I use a plain text file to manage my daily todo items. (Gina Trapani championed this idea with the "todo.txt" movement) and I use some little bits of console goodness to make it work my way.


I like to jam with the console cowboys in cyberspace.

At the start of each day I run a powershell script, "today" that does a bunch of calculations, opens up my todo.txt file in notepad++, and puts a bunch of text into my clipboard as a starting point. Then I paste that list at the bottom of the document. I also scroll up to yesterday's list and run a notepad++ macro that "sweeps" any remaining tasks from yesterday into today's list. Then I get to work.

But there is this big problem inside companies called "meetings" and they tend to get in the way of this thing called "personal productivity". I work remotely and don't have a lot of meetings, so I don't have the habit of checking my calendar, or living in my calendar, as some people do. Hence I have a bad habit of missing meetings. Bosses don't like this habit.

To alleviate the problem, I've improved my "today" script so that it will grab today's meeting schedule from outlook. I've shared the script I wrote for this as a github "gist", here:

Get today's schedule from Outlook, using COM, and format as text for my TODO.txt file

It's not very "elegant" as it is an example of "meware" -- code built for an audience of one.

Heck I'll just post it inline, here's my ugly baby:

# Get a list of meetings occurring today.
function get-meetings() {
	$olFolderCalendar = 9
	$ol = New-Object -ComObject Outlook.Application
	$ns = $ol.GetNamespace('MAPI')
	$Start = (Get-Date).ToShortDateString()
	$End = (Get-Date).ToShortDateString()
	$Filter = "[MessageClass]='IPM.Appointment' AND [Start] > '$Start' AND [End] < '$End'"
	$appointments = $ns.GetDefaultFolder($olFolderCalendar).Items
	$appointments.IncludeRecurrences = $true
	$appointments.Restrict($Filter) |  
	% {
		if ($_.IsRecurring -ne $true) {
			# send the meeting down the pipeline
			$_; 
		} else {
			#"RECURRING... see if it occurs today?"
			try {
				# This will throw an exception if it's not on today. (Note how we combine today's *date* with the start *time* of the meeting)
				$_.GetRecurrencePattern().GetOccurrence( ((Get-Date).ToString("yyyy-MM-dd") + " " + $_.Start.ToString("HH:mm")) )
				# but if it is on today, it will send today's occurrence down the pipeline.
			} 
			catch
			{
				#"Not today"
			}
		}
	} | sort -property Start | % { 
		# split up the names of the attendees to have just 1 firstname/surname and less space.
		$arrr = ($_.RequiredAttendees.split(';') | % { $_.Trim() } | % { $_.split(' ')[1] + ' ' + $_.split(' ')[0] } )
		$attendees = ($arrr -join " ").Replace(", ",",").TrimEnd(',')
		# this is the formatted string that we return, ready for use in 'today'
		("`n`t`t[ ] " + $_.Start.ToString("HH:mm") + " - " + $_.Subject.ToUpper() + " with: " + $attendees )  
	}
}

The resulting text looks like this:

		[ ] 09:00 - STANDUP with: Keena Maloney,Rhea Zamora,Ed Jackson
		[ ] 10:00 - DESIGN REVIEW with: Steph Perkins,Rhea Zamora
		[ ] 14:30 - CUSTOMER COMPLAINTS with: Della Bouchard,Steph Perkins

The way recurring meetings work was the tricky bit. I couldn't find any simple way to query a recurring appointment to ask it "Does it actually occur today?"

As always, I'm very open to any feedback or improvements!

 

2019 By The Numbers

instagram images from 2019

Well it's the roaring 20's at last and here's the blogpost where I look back on the annual output of a machine I call "me".

I spend a lot of time berating myself for the things I didn't do, so it's pretty odd to look back and see that a lot of things did get out the door.

Numbers in parens, e.g. "(3,2,1)", are the figures from last year, the year before that and the year before that.

  • 1 new product launched TimeSnapper for Mac. Jon Schneider did all the work here, but that won't stop me from listing it as my biggest achievement of the year. Top work Jon! (And Atli continues to put in the long hours of keeping TimeSnapper for Windows alive. We're hoping to update the application and the website this year too.)
  • 1 Kanban Simulation game (0): DevShop
  • 0 books published (1,1,0) Joseph and I slowed down work on Evergreen Skills for Developers. The brilliant thing about Evergreen Skills is that it'll still be fresh info even if it takes us 10 year to complete. I compiled 1 complete book in 2019, but haven't launched it due to Amazon "issues", and outlined a few more. See 'how to get nothing done'
  • 13 blog posts written (10, 13, 17) — Most popular thing I published was probably this html quine.
  • 15 new wiki articles completed (14,10,50) — Best new article there is Protective Factors for Mental Health
  • 48 Weekly Project Update emails shared with Richard Mason of Aussie Bushwalking (19,0). These keep the both of us productive all year long.
  • ~200 pictures at insta/secretGeek (~200)
  • 100 Today-I-Learned Entries written (73, 133, 233) — 30142 words (19761, 35289, 42352), 15 new topics (10,21, 44)
  • 389 contributions at Github/secretGeek (156,189,292)
  • 0 talks delivered (0,1,1) phew! 1 conference attended as always: DDD Brisbane.
  • Daily step goal met every day unless gravely ill.
  • Millions of tweets @secretGeek. This seems to be the one that more people saw:

Previously (2018), Previously (2017) previously (2016)

 

5 minutes to import CSV to a database: What do you do?

I challenged my twitter peeps:

Challenge: you have a csv file and you need to import it into a new table in a database. You have 5 mins to get it done. What tools do you use?

@secretgeek

and was overwhelmed with the responses! So many different ways people approach the problem, and so many different tools.

It reminds me of Miles' Law:

Where you stand on an issue depends on where you sit.

  • If you sit in python all day, you'll write a python script.
  • If you sit in powershell all day, you'll use powershell
  • If you sit in SQL Server all day, you might use T-SQL itself to do the work.
  • If you sit in an editor all day, you might use search/replace to transform the data into sql statements.

I put all the solutions I saw into my "today I learneds" site, here's a table of contents for the topic:

In related CSV-news I should mention that I made a git repo: "Awesome CSV" where I collect all the coolest CSV tools, articles and specs from around the planet. It's a happening place for cool people to hang out and learn cool things.

And my own modest tool NimbleText treats everything as delimited text, lets you do powerful things quickly, and is arguably* the greatest invention in the history of the universe.

I also have an Awesome GUID repo, where I store awesome things related to GUIDs. There are surprisingly few things there. Hmmm.


* Whenever someone says 'arguably' it is safe to replace it with 'not'. This is arguably true in every circumstance.

 

Desert Skies - exclusive inside information from the developers

Unless you've been living under a rock like some disgusting sand-sneaking Seeker -- you're aware of the awesome sensation taking the gaming community by storm: Desert Skies.

Here's a video of the game play in case you're not familiar.

I recently sat down for an intense lunch with one of the minds from White Rabbit Games - the creative duo behind this Indy gaming sensation, and I found out some exclusive information.

Although the company is called "White Rabbit Games" the logo is of a cat.

white rabbit games logo... a white cat

SEE!?

What's that all about?

T: we have a fluffy white cat, and it looks like a bunny so we call it "bunny".

Thus the white rabbit is actually a cat.

Woah.

That's enough revelations for one day.

It's a really awesome game. I find it pretty exciting because it's a simple idea that is popular enough that it will get a chance to expand in some really interesting directions. Keep an eye on this!

 

What is NimbleText (a story about cheese)

A good friend asked me the other day, "Wait a second, what's nimble text?" and I stammered out an answer. I'm not good at elevator pitches. Waving my arms around didn't make it any clearer. I tried waving them in bigger circles and pointing as I talked. It still didn't help.

I decided to email him an answer. It turns out I don't have his email address, so I'm writing it here, and I'll transmit the URL some other way. You get to read my reply in public.

What then, is NimbleText?

NimbleText is an adhoc code generator.

For example, say you have a list of "keywords", (he's a digital advertising specialist... it takes all kinds) and you want to do something with each keyword, maybe insert it into a database.

Here's your list of keywords (these are French cheeses)

Abondance
Banon
Beaufort
Bleu
Brie
Brillat-Savarin
Brocciu Cara or Brocciu
Cabécou
Cancoillotte
Camembert de Normandie
Chabichou du Poitou
Chaource
Chevrotin
Comté

...you paste that into NimbleText, and type in a pattern... how about this:

Insert into Keywords(Keyword) Values ('$0')

NimbleText will *magically* create this SQL query for you:

Insert into Keywords(Keyword) Values ('Abondance')
Insert into Keywords(Keyword) Values ('Banon')
Insert into Keywords(Keyword) Values ('Beaufort')
Insert into Keywords(Keyword) Values ('Bleu')
Insert into Keywords(Keyword) Values ('Brie')
Insert into Keywords(Keyword) Values ('Brillat-Savarin')
Insert into Keywords(Keyword) Values ('Brocciu Cara or Brocciu')
Insert into Keywords(Keyword) Values ('Cabécou')
Insert into Keywords(Keyword) Values ('Cancoillotte')
Insert into Keywords(Keyword) Values ('Camembert de Normandie')
Insert into Keywords(Keyword) Values ('Chabichou du Poitou')
Insert into Keywords(Keyword) Values ('Chaource')
Insert into Keywords(Keyword) Values ('Chevrotin')
Insert into Keywords(Keyword) Values ('Comté')

Two seconds later, you run the SQL and insert all the keywords into the database.

You can play with that example here: https://nimbletext.com/Live/398054487/

If the input had more than one column, you would refer to the next column as $1, then $2, etc.

So if the keyword data included, I don't know... some search traffic numbers?...

Abondance,1000
Banon,3000
Beaufort,200
Bleu,12
Brie,6
Brillat-Savarin,90

We could use a pattern like this:

Insert into KeywordResult (Keyword,Result) Values ('$0', $1)

To generate this result:

Insert into KeywordResult (Keyword,Result) Values ('Abondance', 1000)
Insert into KeywordResult (Keyword,Result) Values ('Banon', 3000)
Insert into KeywordResult (Keyword,Result) Values ('Beaufort', 200)
Insert into KeywordResult (Keyword,Result) Values ('Bleu', 12)
Insert into KeywordResult (Keyword,Result) Values ('Brie', 6)
Insert into KeywordResult (Keyword,Result) Values ('Brillat-Savarin', 90)

(See this example here: https://nimbletext.com/Live/284342338/)

But it's not just for generating SQL queries, it's for text in general; and it's not just for keywords and it's not even specific to cheeses. There are many non-cheese uses.

It helps you anytime you want to rearrange some data and do something to every row, to generate a result.

There's an online version, and a downloadable version (with extra features). You can embed javascript inside your patterns to do really tricky formatting stuff. It's versatile.

It's just this thing, you know. For stuff.

 

Secrets of Mastering Excel

Inspired by a comment thread at hacker news I made this mini site:

excel.secretgeek.net

I often want to share Joel Spolsky's famous "You Suck at Excel" video tutorial with "important" business people inside the large enterprise where I spend a lot of time.

But it would be easily misconstrued as trolling if I sent a business customer a URL that literally tells them, right in the heading, that they "suck" at excel.

So, as advised by user TuringTest (in the thread mentioned above) I created a mini site, on its own sub-domain, that is palatable to a business mindset, and brutally overlaid a heading that says "Secrets of Mastering Excel" right over the top of the "You Suck at Excel" heading.

Now I've got something I can recommend to business people without causing disharmony. And I can leave a link to it in my corporate email signature, to nudge others to improve their Excel game.

Ideally the page would detect when the video starts and remove the label, so it doesn't obscure the video. Also, I'd like to write up a set of notes about the talk, but ain't nobody got time for that, so I've linked to the notes from Max Masnick. Perfect is the mortal enemy of done and they are locked in an ultimate death embrace and that is how storm clouds create lightning but I digress.

The whole thing from idea to delivery was about an hour. I was really pleased with the speed on this one.

 

The Market for Ideas (and is contenteditable really terrible?)

Some people invest in gold, others bitcoin. I put my money on ideas. The volatility can be wild.

Take this idea -- the idea that "Contenteditable" attribute is a terrible basis for a Wysiwyg editor, as expressed by this article from medium engineering.

5 years ago, this idea was only worth 3 points and 1 comment.

(I bought all I could! I liquidated all my other ideas so I could go long on this one....)

One year later -- it's worth had grown to 75 points and 9 comments:

(I was tempted to cash out... but some stubborn streak... I held my nerve)

It hit its peak 3 years ago when its worth grew to 155 points and 76 comments

(I sold it all, by god, I cashed it all in, right at the peak! It was glorious!)

Soon after: the idea came crashing down. A year ago it was old news, back down to 3 points and 0 comments.

I guess what I'm saying is... sometimes an idea is before it's time. Not enough people understand the fundamentals to be able to engage with the idea. Then the audience has caught up, and they understand the idea. And then, having grokked it, the idea doesn't captivate them any longer.

A different though related thought is "It's better to be wrong than early" as described at hackernoon, a sentiment that occurs a few times in Ray Dalio's Principles.

contenteditable

I don't really invest in ideas. Not explicitly. (At a deep level, just about everything we do is an investment in an idea.)

p.s. I built something with contenteditable this week... I agree it's terrible. A page that lets you edit its style, without using javascript