<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>jama - Jan Maslov&apos;s Personal Website &amp; Portfolio</title><description>I design and code digital experiences and smart software.</description><link>https://jama.me/</link><language>en-us</language><item><title>jama - Own a Domain</title><link>https://jama.me/blog/own-a-domain/</link><guid isPermaLink="true">https://jama.me/blog/own-a-domain/</guid><description>A domain is an easy way to future-proof your online identity and has lots of everyday tangible benefits. Even for non-techies.</description><pubDate>Thu, 28 Aug 2025 00:00:00 GMT</pubDate><content:encoded>
&lt;div&gt;&lt;aside aria-label=&quot;Update 2025-09-14&quot; style=&quot;--colorBg: var(--color-fg-rgb);--colorFg: var(--color-fg-inverted);&quot;&gt;  &lt;section style=&quot;--colorBg: var(--color-fg-rgb);--colorFg: var(--color-fg-inverted);&quot;&gt; &lt;p&gt;Removed references to Namecheap, because Namecheap is going to be acquired by &lt;a href=&quot;https://archive.is/i9vOk&quot;&gt;CVC Capital Partners&lt;/a&gt;. I expect
service quality to eventually suffer as a result.&lt;/p&gt; &lt;/section&gt; &lt;/aside&gt; &lt;/div&gt; 
&lt;p&gt;Lately I’ve been getting asked about privacy and life online by friends, coworkers, even grandparents.&lt;/p&gt;
&lt;p&gt;Planning a wedding without inbox chaos or drowning in group chats, launching a professional side-gig, trying to end the suffering of spam from years of neglecting digital hygiene, looking to de-google.&lt;/p&gt;
&lt;p&gt;Things anyone might want to do these days.&lt;/p&gt;
&lt;p&gt;If you want a fuller picture on privacy, &lt;a href=&quot;https://www.privacyguides.org&quot;&gt;Privacy Guides&lt;/a&gt; is great.
But for most people that rabbit hole (which only begins on Privacy Guides) can wait until later.&lt;/p&gt;
&lt;p&gt;Start with the basic thing that will keep paying off:&lt;/p&gt;
&lt;h2 id=&quot;get-your-own-domain&quot;&gt;Get Your Own Domain&lt;/h2&gt;
&lt;p&gt;No hype.
It’s cheap, quick, and you don’t need to be a techie.
It gets you tangible benefits.&lt;/p&gt;
&lt;p&gt;Let’s walk through some of them.&lt;/p&gt;
&lt;h3 id=&quot;email&quot;&gt;Email&lt;/h3&gt;
&lt;p&gt;This is probably one of the most boring and one of the most “thank yourself later” things you can do with your own domain.
It’s also one of the most impactful.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You get lots of spam because your address has been sold to data brokers and leaked in data breaches&lt;/li&gt;
&lt;li&gt;Your &lt;em&gt;xxxAlucardSoHot2283xxx​@hotmail.com&lt;/em&gt; address doesn’t quite cut it for your resumé, or it got too cringy for your taste&lt;/li&gt;
&lt;li&gt;You get married and sharing an &lt;em&gt;alice_and_joe5124​@gmail.com&lt;/em&gt; address with others leads to typos and is inconvenient&lt;/li&gt;
&lt;li&gt;You don’t like how Gmail does email&lt;/li&gt;
&lt;li&gt;You don’t want your email provider scraping your emails for data&lt;/li&gt;
&lt;li&gt;You were algorithmically banned out of the blue and your account is unrecoverable because of nonexistent support&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those are some of the email things you can resolve with your own domain.&lt;/p&gt;
&lt;p&gt;If you’ve been using an email address for a long time all over the web, I’d say that’s a growing privacy concern.
Over time, your email address is getting linked with more and more services, some of which may be directly connected with your identity.
And changing email addresses is such an incredible chore if you’re been using it everywhere.
As always, privacy and convenience oppose each other.&lt;/p&gt;
&lt;p&gt;Changing from your ancient or cringy address to another really only gets you a new start with the same outcome.
So I get that it’s not worth the effort.&lt;/p&gt;
&lt;h4 id=&quot;aliases&quot;&gt;Aliases&lt;/h4&gt;
&lt;p&gt;On the other hand, I’d argue that changing to &lt;em&gt;firstname​@lastname.com&lt;/em&gt; (or whatever domain you go with) will probably be the last time you’ll switch emails.
Why?
Because you won’t be using that address everywhere.
And I don’t mean that you’ll keep using your old address alongside the new one.
You’ll be using aliases instead.&lt;/p&gt;
&lt;p&gt;I found that I get the best of both privacy and convenience using &lt;a href=&quot;https://fastmail.com&quot;&gt;Fastmail&lt;/a&gt;, but there’s lots of other options.
Fastmail allows me to create masked email addresses, so I can sign up with a different address everywhere.
Every service I sign up for gets a different one, like &lt;em&gt;sweet.yard9366​@mydomain.com&lt;/em&gt;.
Various online stores, social networks, Google, Apple, my internet provider, my cellular provider, my bank, my landlord, some person I’m selling something to, and so on.
And they all go into my single mailbox.&lt;/p&gt;
&lt;p&gt;If a service leaks my data and I start getting bombarded with spam, I can easily just nuke that one address.
Because all the addresses are different from each other, I also get insight into who leaked or sold my data.
At the same time, I have stable addresses (like the one in the legals on this site) for handing out to people, and I don’t sign up anywhere with those.&lt;/p&gt;
&lt;p&gt;And this isn’t just mumbo-jumbo for techies, there are very practical uses too.
Creating a mailbox for yourself and your partner each and having an alias forwarding email to both of you is a nice way of managing finances and the household.&lt;/p&gt;
&lt;h4 id=&quot;the-infrastructure&quot;&gt;The Infrastructure&lt;/h4&gt;
&lt;p&gt;Aside from a domain, you should explore where to host your emails, because your Gmail doesn’t allow custom domains for free.&lt;/p&gt;
&lt;p&gt;Fastmail delivers mail well and has a good spam filter.
The featureset and high service quality just makes it comparatively expensive.
If Fastmail is too expensive for your taste, take a look at &lt;a href=&quot;https://forwardemail.net/en/private-business-email&quot;&gt;Forward Mail&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Most domain providers also offer email hosting.
Beware though, those services are of varying quality, and you don’t want your email to be bad.
Most of them don’t offer a masked email feature like Fastmail, but they usually let you manually create aliases like &lt;em&gt;bank​@yourdomain.com&lt;/em&gt; or &lt;em&gt;amazon​@yourdomain.com&lt;/em&gt;.
Not as convenient, but the result is effectively the same.
In any case, if you like the idea of aliases, avoid services that let you create a limited amount of them.&lt;/p&gt;
&lt;p&gt;Now, after investing all that work into changing my email provider and setting up all the aliases, what if the provider goes under?
In the unlikely event that Google decided to end Gmail, or suddenly banned you for whatever reason, you’d be forced to go through the change completely unprepared.&lt;/p&gt;
&lt;p&gt;If your email hoster ended their service or banned you, you’d still have your domain, and could move over to another service without changing any addresses.
If your domain provider goes under, you can also just move to another.
It’s all independent from one another.
I previously changed my email hosting from Google Workspace (one of the paid business plans) to &lt;a href=&quot;https://www.mailcheap.co/&quot;&gt;Mailcheap&lt;/a&gt; to Fastmail, completely without disruptions.&lt;/p&gt;
&lt;h3 id=&quot;professional-benefits&quot;&gt;Professional Benefits&lt;/h3&gt;
&lt;p&gt;Aside from the obvious fact that your custom email address can look more professional and trustworthy, having a domain lets you host websites on the internet, like the one you’re currently visiting.
In your case that might be a blog, but it might also be your CV for example.
You’re not limited by &lt;em&gt;yourdomain.com&lt;/em&gt; as well.
You can also easily set up subdomains like &lt;em&gt;cv.yourdomain.com&lt;/em&gt; and host many things.&lt;/p&gt;
&lt;p&gt;See it as your little home on the internet - professional and personal.
We had Geocities websites and MySpace profiles back in the day (man, I’m dating myself aren’t I), and this is something similar.&lt;/p&gt;
&lt;p&gt;You don’t have to &lt;a href=&quot;https://jama.me/blog/new-home&quot;&gt;build it yourself&lt;/a&gt; either.
There’s lots of platforms you can use to create a good personal website without having to learn to code.
For a personal website, check out &lt;a href=&quot;https://bearblog.dev/&quot;&gt;Bear&lt;/a&gt;, &lt;a href=&quot;https://ghost.org/&quot;&gt;Ghost&lt;/a&gt;, &lt;a href=&quot;https://micro.blog/&quot;&gt;micro.blog&lt;/a&gt;, or &lt;a href=&quot;https://carrd.co/&quot;&gt;Carrd&lt;/a&gt; if you’re looking for something simple that just works.
If you want to dig deeper, take a look at &lt;a href=&quot;https://www.framer.com/&quot;&gt;Framer&lt;/a&gt;, &lt;a href=&quot;https://webflow.com/&quot;&gt;Webflow&lt;/a&gt;, and &lt;a href=&quot;https://home.omg.lol/&quot;&gt;omg.lol&lt;/a&gt;, or really do learn a bit of HTML and CSS and build it yourself.&lt;/p&gt;
&lt;p&gt;There’s &lt;a href=&quot;https://nownownow.com/&quot;&gt;lots&lt;/a&gt; &lt;a href=&quot;https://uses.tech/&quot;&gt;of&lt;/a&gt; &lt;a href=&quot;https://projects.kwon.nyc/internet-is-fun/&quot;&gt;excellent&lt;/a&gt; &lt;a href=&quot;https://jamesg.blog/2024/02/19/personal-website-ideas&quot;&gt;inspiration&lt;/a&gt; for what to put on your website.&lt;/p&gt;
&lt;h3 id=&quot;self-hosting&quot;&gt;Self-hosting&lt;/h3&gt;
&lt;p&gt;I’d say this one’s pretty niche, but interesting nonetheless.
Of course, if you can host websites, you can also host other kinds of services.&lt;/p&gt;
&lt;p&gt;A common one may be backing up your family’s photo library and letting them access it remotely.
Same for wedding photos for example.
The easiest way to do that would probably be hosting &lt;a href=&quot;https://immich.app/&quot;&gt;Immich&lt;/a&gt; on &lt;a href=&quot;https://www.pikapods.com/&quot;&gt;PikaPods&lt;/a&gt;.
Immich has a nice web interface as well as polished apps.&lt;/p&gt;
&lt;p&gt;Among others, I host &lt;a href=&quot;https://actualbudget.com/&quot;&gt;Actual Budget&lt;/a&gt; to keep track of my finances and &lt;a href=&quot;https://www.navidrome.org/&quot;&gt;Navidrome&lt;/a&gt; as my personal music streaming service.&lt;/p&gt;
&lt;p&gt;My setup is more involved, but using PikaPods you get up and running in a matter of minutes.
That can get you pretty far, and you’re not locked into any particular service.
In any case, should you choose to move the hosting, your domain will stay the same.
If you shared the address to your family photos with anyone, they won’t even know the hosting changed.&lt;/p&gt;
&lt;h2 id=&quot;how-to-get-a-domain&quot;&gt;How to Get a Domain&lt;/h2&gt;
&lt;p&gt;Now, I’ve dropped a few recommendations here and there throughout this post.
But how do you actually set it up?
Is it really that easy and cheap?&lt;/p&gt;
&lt;h3 id=&quot;choosing-a-domain-name&quot;&gt;Choosing a Domain Name&lt;/h3&gt;
&lt;p&gt;Chances are you’re not the only person in the world who thinks that getting &lt;em&gt;lastname.com&lt;/em&gt; is a good idea.
Probably that one’s already taken if you look it up.
Michael Lynch has some good advice on &lt;a href=&quot;https://mtlynch.io/couples-email-domain/&quot;&gt;selecting a domain name&lt;/a&gt; for use with your partner, but it’s applicable to any personal domain I’d say.
He suggests going for something unrelated to your name that is easily pronounceable and clear.&lt;/p&gt;
&lt;p&gt;You’ll likely need to choose not only the domain name, but also the ending.
I went for the Montenegran &lt;em&gt;.me&lt;/em&gt; to signal that this is my personal website for example.&lt;/p&gt;
&lt;p&gt;If you don’t plan to move away from the country you’re in, and if that country is somewhat stable, it may make sense to get the two-letter cTLD (Country Top Level Domain).
Otherwise, maybe go for something completely different.
Just keep in mind that TLDs have reputations, which can influence how often emails you send will go into spam.
If you want to be extra sure, check out Spamhaus’ regular &lt;a href=&quot;https://info.spamhaus.com/domain-reputation-updates&quot;&gt;Domain Reputation Report&lt;/a&gt; for TLDs to avoid.
A .com is, of course, most preferable, as it’s not tied to any country and is well-known.&lt;/p&gt;
&lt;p&gt;To quickly find available domains, I like to use &lt;a href=&quot;https://tld-list.com/&quot;&gt;TLD-List&lt;/a&gt;.
It’s also great for comparing prices between domain registrars.&lt;/p&gt;
&lt;p&gt;Prices vary wildly between TLDs.
A .com should go for around $10-15 USD/year.
My &lt;em&gt;jama.me&lt;/em&gt; domain currently costs me $17.27 USD/year on Porkbun.&lt;/p&gt;
&lt;h3 id=&quot;choosing-a-domain-registrar&quot;&gt;Choosing a Domain Registrar&lt;/h3&gt;
&lt;p&gt;There are lots of them, and for personal use you should go with one of the cheaper ICANN-accredited ones that maybe people you know trust, or that are generally reputable.
I personally had good experiences with &lt;a href=&quot;https://porkbun.com/&quot;&gt;Porkbun&lt;/a&gt; as well as &lt;a href=&quot;https://www.netim.com/&quot;&gt;Netim&lt;/a&gt; and &lt;a href=&quot;https://www.hover.com/&quot;&gt;Hover&lt;/a&gt; (though that one’s quite expensive).&lt;/p&gt;
&lt;p&gt;I’d prefer companies whose main business is domains instead of ones that offer a wide range of services, like Ionos, or Cloudflare.
These are larger platforms with varying levels of human support, and it really stings to lose critical infrastructure such as domains.
You really want to be able to quickly talk to a knowledgeable human in case things go sideways.&lt;/p&gt;
&lt;h3 id=&quot;now-what&quot;&gt;Now What?&lt;/h3&gt;
&lt;p&gt;Once you’ve found an available domain name and the registrar you want to purchase the domain with, let’s go step by step:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sign up at the domain registrar with your old email address. You’ll change that once you’re done&lt;/li&gt;
&lt;li&gt;Purchase your domain&lt;/li&gt;
&lt;li&gt;Your domain registrar will let you set DNS records for your domain. Find that list, you’ll need to edit it&lt;/li&gt;
&lt;li&gt;Sign up with the email hoster
&lt;ul&gt;
&lt;li&gt;Fastmail lets you immediately sign up with a custom domain. Others may not, so you’ll sign up with your old email address there too.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The email hoster will provide you with instructions on which DNS records to set in your domain. Add the records as instructed
&lt;ul&gt;
&lt;li&gt;Here’s &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360060591153-Manual-DNS-configuration&quot;&gt;Fastmail’s article&lt;/a&gt; on that explaining what the records do.
The records are going to be similar for other email hosters as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Wait for a little while (usually an hour, can be up to a day)
&lt;ul&gt;
&lt;li&gt;This is because DNS, the phone book of the internet, is a distributed system.
When you set records in your domain, the registrar will notify DNS servers around the world about the change.
It can take a hot minute for all the providers to have heard about it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add your new email address to email apps on your devices, verify it works
&lt;ul&gt;
&lt;li&gt;If you added the SRV type records as well, it should magically auto-configure like Gmail or Outlook.&lt;/li&gt;
&lt;li&gt;Send yourself an email, send friends and family one, let them write you!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Create an email alias&lt;/li&gt;
&lt;li&gt;Change the account email at your domain registrar to the alias you created
&lt;ul&gt;
&lt;li&gt;If you had to sign up with your old email address at your new email hoster, change the address to an alias there as well, or keep the old address as a backup.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Start your journey of changing email addresses at all the relevant services you signed up with over the years
&lt;ul&gt;
&lt;li&gt;If you have been using a password manager, or at least have saved logins in your browser, this might be a lot easier than otherwise.&lt;/li&gt;
&lt;li&gt;If you plan to abandon the old email address, this might also be the time to decide which services you don’t use anymore.&lt;/li&gt;
&lt;li&gt;Here’s a list of websites and services to start with:
&lt;ol&gt;
&lt;li&gt;Shopping websites (Amazon, eBay, Craigslist, etc.)&lt;/li&gt;
&lt;li&gt;Financial services (Bank, Trading platform, Crypto exchange, etc.)&lt;/li&gt;
&lt;li&gt;Social networks (Facebook, Instagram, TikTok, X, Reddit, Discord, Bluesky, Mastodon, etc.)&lt;/li&gt;
&lt;li&gt;Entertainment services (YouTube, Patreon, Netflix, Spotify, Audible, Steam, etc.)&lt;/li&gt;
&lt;li&gt;Device platform accounts (Google, iCloud, Microsoft/Xbox, Nintendo, Sony, etc.)&lt;/li&gt;
&lt;li&gt;Infrastructure providers (Home internet, cell service, utilities, etc.)&lt;/li&gt;
&lt;li&gt;Smart home devices (Eufy, Philips, Nest, Ubiquiti, Sonos, Lutron, etc.)&lt;/li&gt;
&lt;li&gt;Software services (Adobe, Microsoft, Backblaze, Notion, 1Password, YNAB, etc.)&lt;/li&gt;
&lt;li&gt;Sports and health services (Health insurance, Gym, Peloton, etc.)&lt;/li&gt;
&lt;li&gt;Educational services (School/university, Duolingo, Udemy, etc.)&lt;/li&gt;
&lt;li&gt;Transportation (Car insurance, public transport card, car infotainment, etc.)&lt;/li&gt;
&lt;li&gt;Old-school forums&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Make more use of your little place on the web
&lt;ul&gt;
&lt;li&gt;Host your CV, a website, and other stuff!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Whether it’s to de-google your life a bit, to combat chaos in your inbox, to present yourself professionally, or if it’s to set up a side gig -
having your own domain is a good way to bring structure and hygiene into your and your close ones’ digital life.&lt;/p&gt;
&lt;p&gt;Yes, it’s a pain in the ass to switch email addresses.
But with full control over your email address, this time will probably be your last.
Do yourself a favor and get on it.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - The Deprofessionalization of the Games Industry</title><link>https://jama.me/blog/deprofessionalization-gamedev/</link><guid isPermaLink="true">https://jama.me/blog/deprofessionalization-gamedev/</guid><description>While layoffs are rampant under big-budget behemoths, I hope the future might favor teams daring to dream mid-sized again.</description><pubDate>Wed, 04 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I found my calling at the tender age of 10 years old when I stumbled upon the then newly released &lt;a href=&quot;https://web.archive.org/web/20051013071311/http://www.fpscreator.com:80/index.html&quot;&gt;FPS Creator&lt;/a&gt; in 2005.
“Design your own first person shooter games - No coding required!” read the marketing back then.
And from the moment I saw my first little level, made in about a day, render in real-time, I knew this would be something I wanted to do long-term.&lt;/p&gt;

&lt;p&gt;I mean, for crying out loud, I have a Bachelor’s in Digital Games focused on Game Design and I’m wrapping up a Master’s in the same.
I’m really invested in the industry and I love to see it thrive and innovate.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/mirrors-edge.Ei1OEk2v_Z1fV48p.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/mirrors-edge.Ei1OEk2v_1cJNiu.jxl, https://jama.me/_astro/mirrors-edge.Ei1OEk2v_Z14d8gs.jxl 1.5x, https://jama.me/_astro/mirrors-edge.Ei1OEk2v_1tIFTl.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/mirrors-edge.Ei1OEk2v_Z2oHQWA.avif, https://jama.me/_astro/mirrors-edge.Ei1OEk2v_ovkho.avif 1.5x, https://jama.me/_astro/mirrors-edge.Ei1OEk2v_1jwSJm.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/mirrors-edge.Ei1OEk2v_f9Mm9.webp, https://jama.me/_astro/mirrors-edge.Ei1OEk2v_Z21N9cN.webp 1.5x, https://jama.me/_astro/mirrors-edge.Ei1OEk2v_2dC4Fh.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/mirrors-edge.Ei1OEk2v_Z22SAtV.png&quot; srcset=&quot;https://jama.me/_astro/mirrors-edge.Ei1OEk2v_KkAK3.png 1.5x, https://jama.me/_astro/mirrors-edge.Ei1OEk2v_2eFXEO.png 2x&quot; alt=&quot;Mirror&apos;s Edge (2008) screenshot. First person view of player walking balancing on a pipe above drop.&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;Mirror’s Edge (2008) by DICE&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;aaa-is-in-a-rut&quot;&gt;AAA Is In a Rut&lt;/h2&gt;
&lt;p&gt;Back in 2005, I couldn’t have predicted how massive the games industry would become.
It’s by far the biggest entertainment sector.
Of course, alongside new models of monetization, there was lots of innovation and transformation in the space, but also lots of consolidation.
Acquiring Zenimax and ABK, Microsoft absorbed &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_Microsoft_Gaming_studios&quot;&gt;over 60 game studios&lt;/a&gt; and powerful multimedia publishing arms.
Importantly, they also bought their intellectual properties.&lt;/p&gt;
&lt;p&gt;There’s no such thing as infinite growth though.
Matthew Ball’s &lt;a href=&quot;https://www.matthewball.co/all/stateofvideogaming2025&quot;&gt;The State of Video Gaming in 2025&lt;/a&gt; paints a picture of the industry reaching some kind of ceiling.
And until more innovation brings in more monetization, we’re beginning to see how that ceiling looks like.
The massive layoffs across the industry, to me, show that art is one of the first things on the chopping block when the going gets tough.
Still, despite it all, we had a couple years of incredible releases and disappointments - the industry is very much alive.&lt;/p&gt;
&lt;p&gt;While that’s going on, we &lt;a href=&quot;https://screenrant.com/balatro-simple-games-best-op-ed/&quot;&gt;see&lt;/a&gt; &lt;a href=&quot;https://www.wired.com/story/concord-death-future-of-video-games-bleak/&quot;&gt;concrete&lt;/a&gt; &lt;a href=&quot;https://gameworldobserver.com/2024/10/16/indie-games-revenue-steam-vs-aaa-titles-vg-insights&quot;&gt;examples&lt;/a&gt; pointing out a still rather small, but distinct &lt;a href=&quot;https://www.gamedeveloper.com/business/-deprofessionalization-is-bad-for-video-games&quot;&gt;shift of focus towards indie games&lt;/a&gt;.
A deprofessionalization one may call it.
And it doesn’t appear to be looking good for the big and established publishers and everybody working there.&lt;/p&gt;
&lt;p&gt;While I too find the mass layoffs devastating to follow - the livelihoods of so many in the industry at risk - I find that the current panic is bound to become an overcorrection following years of unbelievable growth and naively expecting it to keep going forever.
Honestly, call me naive, but it seems fundamentally misaligned for publicly traded companies to be in the business of art.
But aside from an overcorrection, I also find this to be a chance to see what’s up ahead and a chance to bring a broad new wave of creativity in.&lt;/p&gt;
&lt;p&gt;As in many other spheres, the &lt;a href=&quot;https://www.wired.com/story/concord-death-future-of-video-games-bleak/#:~:text=If%20you%20have%20a%20stable%20parent%20company,people%20in%20high-GDP%20countries.&quot;&gt;“middle of the market […] disintegrating”&lt;/a&gt; is definitely something I’ve observed for the last decades in gaming.
It appears to have been the reason &lt;a href=&quot;https://www.videogameschronicle.com/news/ps5-japan-studios-closed-because-the-double-a-market-has-disappeared-says-shuhei-yoshida/&quot;&gt;Sony closed Japan Studio&lt;/a&gt;.
But contrary to that, I think it’s worth asking whether we really need to keep betting the house on massive, multi-hundred-million-dollar projects?
Of course, supporting a workforce hundreds (or thousands) strong demands scale, and publishers looking for further growth won’t be lining up for your average artsy sidescroller.
But that is not the situation big-name developers find themselves in.
They’re increasingly swinging for the fences each project.
Concord actually was a great example of putting all eggs in one basket to a devastating flop.&lt;/p&gt;
&lt;p&gt;Put bluntly, what I want is the unlikely thing of A and AA games to more broadly make a return.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/pacific-drive.BTHXRcVe_1rAMOf.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/pacific-drive.BTHXRcVe_Z1agXQv.jxl, https://jama.me/_astro/pacific-drive.BTHXRcVe_1rqmAm.jxl 1.5x, https://jama.me/_astro/pacific-drive.BTHXRcVe_ZyUDIm.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/pacific-drive.BTHXRcVe_Z1ksL1u.avif, https://jama.me/_astro/pacific-drive.BTHXRcVe_1hezqn.avif 1.5x, https://jama.me/_astro/pacific-drive.BTHXRcVe_ZEHXXU.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/pacific-drive.BTHXRcVe_ZqnA5z.webp, https://jama.me/_astro/pacific-drive.BTHXRcVe_2bjKmi.webp 1.5x, https://jama.me/_astro/pacific-drive.BTHXRcVe_1STPPR.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/pacific-drive.BTHXRcVe_ZpjG62.png&quot; srcset=&quot;https://jama.me/_astro/pacific-drive.BTHXRcVe_2cnElP.png 1.5x, https://jama.me/_astro/pacific-drive.BTHXRcVe_2qSMBD.png 2x&quot; alt=&quot;Pacific Drive (2024) screenshot. Dark and foggy view of a road. Car is shown from behind. A gas station sign in the distance ominously illuminates the fog.&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;Pacific Drive (2024) by Ironwood Studios&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Thanks to democratized dev tools, we’ve seen the value delivered by talented solo developers and small teams, with projects inching closer to filling the bottom part of that void.
But they naturally have a hard time reaching the same level of ambition a mid-sized team with a decent budget could afford - quality mid-sized projects that would fit into that middle segment.
Teams of less than 100 people with not out-of-this-world budgets, Sandfall Interactive’s &lt;em&gt;Clair Obscur: Expedition 33&lt;/em&gt; &lt;a href=&quot;https://www.thegamer.com/clair-obscur-expedition-33-sandfall-interactive-dev-team-size-baldurs-gate-3/&quot;&gt;being a recent example&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the last decade I feel a divide in creativity between big budget titles and indies has formed (while the definition of “big budget” definitely also has changed).
We’re seeing remakes and remasters one after the other, banking on nostalgia.
We’re seeing one &lt;em&gt;Call of Duty&lt;/em&gt; and &lt;em&gt;Assassin’s Creed&lt;/em&gt; after the other, banking on an established name.
That’s not to discredit the skill or creativity of those teams - the production quality is clearly extremely high - but the fatigue with the big franchises is growing to be real, and it’ll be hard to shake off.
At the same time, indies can afford to be weird and experimental, and they’re a lot less aggressive about the monetization.
But most importantly, they’ve been shaking off their “small project” stigmata in the general zeitgeist.&lt;/p&gt;
&lt;p&gt;In short, I think a renewed focus on creativity and smaller projects could spark new franchises to milk for the decade(s) to come.
It’s all a cycle anyway.
Right now it’s sloping into a rut, but we need to think of the fuel that will propel the rise later.
And I have a feeling that it won’t be yet another &lt;em&gt;Call of Duty&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I want to be excited for the next experimental horror series like Silent Hill back in the day, a return to the gimmicky shooter à la Half-Life, tactical espionage and spy games, 3D platformers, immersive sims and life sims, fresh VR concepts, and completely new experiences.
But at this rate, they’re not going to come from the big names who in time will be scrambling to buy the interesting developers and franchises and molding them into safe, profitable, gray goo.&lt;/p&gt;
&lt;p&gt;So actually I would prefer a little bit of deprofessionalization of the games industry.&lt;/p&gt;
&lt;p&gt;Give me that &lt;a href=&quot;https://en.wikipedia.org/wiki/Shrek_(video_game)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;weird movie tie-in game&lt;/a&gt; that introduces new rendering tech.
A horror experience with a similar level of love and care as &lt;a href=&quot;https://en.wikipedia.org/wiki/Alien:_Isolation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Alien: Isolation&lt;/a&gt;.
A wonderfully cute soft-body physics platformer like &lt;a href=&quot;https://en.wikipedia.org/wiki/LocoRoco&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;LocoRoco&lt;/a&gt;.
A brand-centric game with an integration that makes sense, like &lt;a href=&quot;https://en.wikipedia.org/wiki/Need_for_Speed:_Porsche_Unleashed&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Need for Speed: Porsche Unleashed&lt;/a&gt;.
That spiritual successor to &lt;a href=&quot;https://en.wikipedia.org/wiki/No_One_Lives_Forever_2:_A_Spy_in_H.A.R.M.%27s_Way&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;No One Lives Forever&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/Tom_Clancy%27s_Splinter_Cell_(video_game)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Splinter Cell&lt;/a&gt;.
There’s surely more to explore from where &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Last_Guardian&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;The Last Guardian&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Alien_Swarm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Alien Swarm&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/Beat_Saber&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Beat Saber&lt;/a&gt; have left off.
Or combine turn-based combat with cards like &lt;a href=&quot;https://en.wikipedia.org/wiki/Metal_Gear_Acid&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Metal Gear Acid&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/talos-principle.B-wDJgkJ_Rv4Tg.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/talos-principle.B-wDJgkJ_Z1RcPHs.jxl, https://jama.me/_astro/talos-principle.B-wDJgkJ_1vUDz9.jxl 1.5x, https://jama.me/_astro/talos-principle.B-wDJgkJ_26Q781.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/talos-principle.B-wDJgkJ_gq0cc.avif, https://jama.me/_astro/talos-principle.B-wDJgkJ_Z1pCDk8.avif 1.5x, https://jama.me/_astro/talos-principle.B-wDJgkJ_2o0BLc.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/talos-principle.B-wDJgkJ_Z1u1t5m.webp, https://jama.me/_astro/talos-principle.B-wDJgkJ_1T71cf.webp 1.5x, https://jama.me/_astro/talos-principle.B-wDJgkJ_Z1lWNWJ.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/talos-principle.B-wDJgkJ_1sQPYb.jpg&quot; srcset=&quot;https://jama.me/_astro/talos-principle.B-wDJgkJ_ZdbMx9.jpg 1.5x, https://jama.me/_astro/talos-principle.B-wDJgkJ_ZWVdnM.jpg 2x&quot; alt=&quot;The Talos Principle 2 (2023) screenshot. Player is solving a puzzle using lasers and portals. Sunlit delapidated concrete structures in the background.&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;The Talos Principle 2 (2023) by Croteam&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Once in a while there’s something pretty out there coming along, like &lt;a href=&quot;https://en.wikipedia.org/wiki/High_on_Life_(video_game)&quot;&gt;High on Life&lt;/a&gt;.
While not praised to high heaven (and I’m also not a fan), it’s at least trying to be very different.
And, with a bit of help, it seems to have gotten into &lt;a href=&quot;https://x.com/highonlifegame/status/1651662548528144398&quot;&gt;quite a few&lt;/a&gt; players’ hands.&lt;/p&gt;
&lt;p&gt;My thoughts go out to everyone who lost their dream job in this chaos.
Even if the longest running studios that suffered closures have changed a lot since their glory days,
&lt;a href=&quot;https://www.gamesindustry.biz/volition-games-closes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;it&lt;/a&gt;
&lt;a href=&quot;https://www.ign.com/articles/ea-cancels-black-panther-game-closes-cliffhanger-games&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;still&lt;/a&gt;
&lt;a href=&quot;https://www.videogameschronicle.com/news/sources-playstation-is-winding-down-sony-japan-studio/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;just&lt;/a&gt;
&lt;a href=&quot;https://www.androidcentral.com/gaming/virtual-reality/ready-at-dawn-studios-closing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;sucks&lt;/a&gt;
&lt;a href=&quot;https://kotaku.com/monolith-productions-wonder-woman-multiversus-cancel-wb-1851766400&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;so&lt;/a&gt;
&lt;a href=&quot;https://www.ign.com/articles/microsoft-closes-redfall-developer-arkane-austin-hifi-rush-developer-tango-gameworks-and-more-in-devastating-cuts-at-bethesda&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;bad&lt;/a&gt;.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - Sanding Early</title><link>https://jama.me/blog/sanding-early/</link><guid isPermaLink="true">https://jama.me/blog/sanding-early/</guid><description>Realizing that I wasn&apos;t polishing things in the way I always said, I think I can put my ruminations to rest.</description><pubDate>Wed, 05 Mar 2025 00:00:00 GMT</pubDate><content:encoded>
&lt;p&gt;From time to time, I’m confronted with the notions of “technical debt is acceptable this time”, “let’s make the simplest possible version of this” and “no one will notice - we’ll make it good and pretty later”.&lt;/p&gt;
&lt;p&gt;Of course, life is complex, and from time to time it makes sense.
From time to time.
I think users can be more discernable and critical than those sentiments give them credit for.&lt;/p&gt;
&lt;p&gt;As probably most software developers, I’m not a huge fan of the appetite to make software cheaper, especially at the cost of quality.
It’s a modus operandi in enterprise software and I find it quite mean to give people who don’t love their job more than anything else in the world more reasons for it to be that way, for example by having them use subpar software.
It just makes for a more depressing reality.&lt;/p&gt;
&lt;p&gt;I definitely appreciate the fact that sometimes rapidly shipping features is impressive and can shift the project into the spotlight, securing future investment for example.
Still, my perspective on it is that there are things that are almost equally as impressive, but which at the same time make developing things feel less like work.
And being impressive in enterprise software doesn’t always require taking on a massive challenge.
Sometimes it’s the little things that count.&lt;/p&gt;
&lt;h2 id=&quot;polishing-early&quot;&gt;Polishing Early?&lt;/h2&gt;
&lt;p&gt;For a long time I’ve called myself a chronic polisher and a perfectionist, struggling to let go of things until I found them to be presentable.
But that doesn’t mean I obsessed over every detail until it was superbly immaculate.
It was never about endlessly refining for the sake of it, and not about adding &lt;a href=&quot;https://en.wikipedia.org/wiki/Game_feel&quot;&gt;juice&lt;/a&gt;.
Instead, I wanted the result on the whole to be solid enough to not be embarrassing.
That’s what I really meant.&lt;/p&gt;
&lt;p&gt;Reflecting on it from time to time, I’ve come to realize that I likely have a strong sensitivity to quality.
I take a pragmatic, almost clinical approach to spending money.
Before making a purchase, I create a mental checklist of criteria, and thoroughly research before allowing myself to get excited about it.
Yet, sometimes even without knowing the brand or the price, I gravitate toward the higher-end options in a given category.
It’s both blessing and curse, especially when I’m shopping offline, where I somehow always end up picking the most expensive items before checking the price tag.&lt;/p&gt;
&lt;p&gt;On the one hand, I might just be naive and impressionable, not noticing how I’m getting sweet-talked by marketing.
On the other, I like to believe it’s also having a keen eye for quality and paying close attention to the subtle details that signal care and craftsmanship.&lt;/p&gt;
&lt;p&gt;It’s also what makes working in enterprise software frustrating for me, because not only is it a race to the bottom - achieving the set objectives as cheaply as possible - but, usually, nobody with enough control over the project appreciates it when you polish.
Requirements change, polishing happens in places nobody cares about, polish can introduce things that slow down workflows, I’m painfully aware.&lt;/p&gt;
&lt;p&gt;But what I realized over time is that, even working on my own projects with full creative control, I don’t polish immediately.
What I actually do in the spur of the moment is not polishing, it’s &lt;em&gt;sanding&lt;/em&gt;.
When I’m forced to let go of things that aren’t &lt;em&gt;sanded&lt;/em&gt;, that’s when I feel truly dissatisfied.&lt;/p&gt;
&lt;h2 id=&quot;sanding-everything&quot;&gt;Sanding Everything&lt;/h2&gt;
&lt;p&gt;I really appreciate Jim Nielsen’s &lt;a href=&quot;https://blog.jim-nielsen.com/2024/sanding-ui/&quot;&gt;blog post on sanding UI&lt;/a&gt; (and &lt;a href=&quot;https://blog.jim-nielsen.com/2025/sanding-ui-pt-ii/&quot;&gt;part the second&lt;/a&gt;).
In a very simple way it put a word to what I’ve been doing for years and years and made it tangible for me.
As the saying goes, words mean things, and it doesn’t have to carry the baggage of &lt;em&gt;polish&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;While I think that creating user interfaces is a very good vehicle to exemplify sanding, I see it also taking place in all kinds of different spheres and ways.
Because I like making many different things in the digital realm, I find myself confronted with sanding across many disciplines.&lt;/p&gt;
&lt;p&gt;To me, sanding is an exercise in getting to know what the low hanging fruits and the acceptable quality bar in a certain discipline are, and interspersing my time working on something to find and implement little improvements - what Nielsen calls “feeling for splinters”.
It spans across basically everything I find myself producing - software architecture, hard and soft surface 3D modelling and texturing, printed goods design, web development, level design, shader programming, music and sound mixing, and so on - often right from the conceptualizing phase.&lt;/p&gt;
&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://jama.me/_astro/tlou2-block.EHX0KTM2_zT9dI.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/tlou2-block.EHX0KTM2_1Ite2m.jxl, https://jama.me/_astro/tlou2-block.EHX0KTM2_24wkeF.jxl 1.5x, https://jama.me/_astro/tlou2-block.EHX0KTM2_2p00Dh.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tlou2-block.EHX0KTM2_Z1YLddU.avif, https://jama.me/_astro/tlou2-block.EHX0KTM2_Z1DI71B.avif 1.5x, https://jama.me/_astro/tlou2-block.EHX0KTM2_Z1jfqC0.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tlou2-block.EHX0KTM2_Z1pwuJP.webp, https://jama.me/_astro/tlou2-block.EHX0KTM2_Z14toxw.webp 1.5x, https://jama.me/_astro/tlou2-block.EHX0KTM2_ZJ0I8U.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/tlou2-block.EHX0KTM2_Z1xvKDz.jpg&quot; srcset=&quot;https://jama.me/_astro/tlou2-block.EHX0KTM2_Z1csErg.jpg 1.5x, https://jama.me/_astro/tlou2-block.EHX0KTM2_ZQYY2E.jpg 2x&quot; alt=&quot;The Last of Us: Part II in-development screenshot, generic colorful blocks standing in for level geometry&quot; fetchpriority=&quot;auto&quot; width=&quot;300&quot; height=&quot;169&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;https://jama.me/_astro/tlou2-done.C0JZQyjH_v1RpF.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/tlou2-done.C0JZQyjH_Z2uMCUC.jxl, https://jama.me/_astro/tlou2-done.C0JZQyjH_2i1ICU.jxl 1.5x, https://jama.me/_astro/tlou2-done.C0JZQyjH_1b0TBX.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tlou2-done.C0JZQyjH_Z1KHhrM.avif, https://jama.me/_astro/tlou2-done.C0JZQyjH_Z2353Hb.avif 1.5x, https://jama.me/_astro/tlou2-done.C0JZQyjH_1U6g5N.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tlou2-done.C0JZQyjH_Z1MLWus.webp, https://jama.me/_astro/tlou2-done.C0JZQyjH_Z259IJQ.webp 1.5x, https://jama.me/_astro/tlou2-done.C0JZQyjH_1S1A38.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/tlou2-done.C0JZQyjH_Z1HPOCE.jpg&quot; srcset=&quot;https://jama.me/_astro/tlou2-done.C0JZQyjH_Z20dAS3.jpg 1.5x, https://jama.me/_astro/tlou2-done.C0JZQyjH_1WWHTV.jpg 2x&quot; alt=&quot;The Last of Us: Part II screenshot, post-apocalyptic overgrown guitar store, highly detailed&quot; fetchpriority=&quot;auto&quot; width=&quot;300&quot; height=&quot;169&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;p&gt;A comparison of a rough block out of a game scene, and how it looks finished, by Michael Barclay from The Last of Us: Part II. (&lt;a href=&quot;https://mikebarclay.co.uk/blocktober-2020/&quot;&gt;Source&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;The block out can be sanded to improve gameplay.
The finished product has already been sanded and polished lots to improve visual details for example.
The work that happens between the two states is decidedly not sanding itself.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Unsurprisingly, I’m not satisfied with my work when I have to painstakingly unravel the horrors of numerous thoughtless split-second decisions that led to interdependent permanent temporary band-aids.
Sanding is no guarantee for that not happening, but it requires taking another look (and maybe a different look) at the thing in development, and that heightens the chance of catching missteps.&lt;/p&gt;

&lt;p&gt;Sanding is simultaneously an improvement to the thing we’re creating, and an appreciation for what we have achieved so far.
It requires and communicates to others that I truly understand what I’m making, and test-driving it myself while seeing it improve in rapid feedback cycles feels rewarding.&lt;/p&gt;
&lt;h2 id=&quot;sanding-is-thoughtful&quot;&gt;Sanding Is Thoughtful&lt;/h2&gt;
&lt;p&gt;There are a myriad of takes on why we make software, but for the purposes of this section, I’ll focus on software for the workplace.&lt;/p&gt;
&lt;p&gt;Ideally, we make software to make life better - help people achieve something, reduce frustration, but at the very least to &lt;a href=&quot;https://pluralistic.net/2023/01/21/potemkin-ai/#hey-guys&quot;&gt;make line go up&lt;/a&gt;.
Best-case scenario, work becomes easier, more fulfilling, and leads to happier people who are happy to be highly productive.&lt;/p&gt;
&lt;p&gt;Slightly more realistically, to this end, work should be a little bit of fun.
Maybe not so much as to detract from why we’re doing the work in the first place, or to distract us so much that we lose our focus, but enough fun to keep us engaged.&lt;/p&gt;
&lt;p&gt;That’s why I never play the pissed off customer.
I want good service, so I try to be as helpful and friendly as I possibly can, because I want people to do something for me.
Making someone’s day worse rarely nets the best result if I’m depending on them.
Their willingness to be engaged in our interaction will sink.
Conversely, kindness often leads to fast, excellent service, and smiles all around.&lt;/p&gt;

&lt;p&gt;This is one way in which much of enterprise software lacks.
It generally treats users badly.
I don’t lament that there’s no fun in it.
It just completely lacks a sense for what’s engaging, and shows not the least bit of sympathy.
Maybe it’s asking too much, because people ought to work more, not enjoy the process.&lt;/p&gt;
&lt;p&gt;As a result, often enough, users have to deal with ridiculous situations: Nested remote desktops, torturous performance, confusing UIs that are direct representations of their complicated backing data structures, or that don’t communicate their state clearly (looking at you, &lt;a href=&quot;https://answers.microsoft.com/en-us/msteams/forum/all/teams-is-the-worst-software-i-have-ever-used/252542f6-c4a9-4736-9bec-2136793d50fd&quot;&gt;Teams&lt;/a&gt;), stability issues, glacial maintenance.
A cursory selection of the usual state of affairs in the typical workplace.&lt;/p&gt;
&lt;p&gt;Sanding on its own can’t fix any of that, but it can make the experience more bearable at least, even if the rest is a flaming hellscape.
It implies that at least a modicum of thought was spent on how a thing will be used.&lt;/p&gt;
&lt;h2 id=&quot;why-i-sand-early&quot;&gt;Why I Sand Early&lt;/h2&gt;
&lt;p&gt;I determine the best time for and amount of sanding by trying to understand how technically experienced the one(s) judging the work is, and how central an individual aspect under consideration is to the whole experience.
Let’s take software development for example and throw in game development for good measure.&lt;/p&gt;
&lt;p&gt;Working as a freelancer and later for a software dev consultancy, I got the privilege to work with all kinds of different people from different lines of work, and wildly varying experience with software development.
Some of these people are completely oblivious to how the sausage is made.
The same is true for gamers.
Somewhat less so for PC gamers than ones primarily on consoles or mobile platforms.
But the fact of the matter is that everyone’s different, and everyone has different interests and priorities.&lt;/p&gt;
&lt;p&gt;I like to account for that, so if at all possible, the amount of effort I put into sanding is somewhat inversely proportional to that experience level.&lt;/p&gt;
&lt;h3 id=&quot;in-software&quot;&gt;In Software&lt;/h3&gt;
&lt;p&gt;Project managers often insist that “the minimum working thing is more than enough”.
But when it’s implemented precisely to that specification, they will often get caught up on details and be disappointed.
Demanding improvements is totally fine, but unfortunately that all-important first impression is now soured.
And, in the worst case, it will silently weigh on your reputation.&lt;/p&gt;
&lt;p&gt;That’s why, when building UIs, I follow Nielsen’s advice - I constantly play around with what I’m making and I try to break it, finding and fixing small deficiencies.
Most of the time, I do that as I’m implementing it.
Regularly, the results go unnoticed because it “just works”, but sometimes it’s visible enough that clients are grateful for it.&lt;/p&gt;
&lt;p&gt;An example came up recently.
A feature to manage project avatar images was deprioritized mid-implementation, and the sidebar where those projects could be switched between looked absolutely dreadful as a result, because it had no avatar fallbacks in place.
I avoided showing projects without avatars in demos until we finally tackled the issue.&lt;/p&gt;
&lt;p&gt;The fix was planned to display a plain background and a generic icon - the simplest possible version of a fallback.
Suspecting it to stick if I let it, I decided to ignore the requirements that time.&lt;/p&gt;

&lt;p&gt;I took the extra time to implement an algorithm to generate stable, colorful, and contrasty fore- and background colors based on project names.
It would spit out enough different combinations that even having the whole sidebar populated, the avatars were distinct enough for it all to be easily distinguishable.
I also made it generate its colors in the &lt;a href=&quot;https://oklch.com&quot;&gt;&lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;oklch&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; notation&lt;/a&gt;, so the avatars would pop on wide-gamut displays (in moderation), and ensure enough contrast, making use of its consistent lightness property.
The two or three letters derived from the project name would scale to fit inside the avatar with a consistent padding for visual cohesion.
It made the rest of the quite enterprisey and boring interface a lot nicer looking.&lt;/p&gt;
&lt;p&gt;The PO was stunned by how the austere UI suddenly came to life when I showed him.
He didn’t mind the extra time taken at all.&lt;/p&gt;
&lt;p&gt;I was later told that users were tempted to have the sidebar chock-full with projects, just to make their workspace more engaging.
The avatar fallback took me half a day to implement, and the other half to sand and test.
A small detail in the grand scheme of things, but it added just that little bit of playfulness and fun.&lt;/p&gt;
&lt;p&gt;It was of course also a case of &lt;a href=&quot;https://allenpike.com/2022/giving-a-shit&quot;&gt;giving a shit&lt;/a&gt; or not.
But what isn’t, really.&lt;/p&gt;
&lt;h3 id=&quot;in-games&quot;&gt;In Games&lt;/h3&gt;
&lt;p&gt;I observe the same thing in games.&lt;/p&gt;
&lt;p&gt;Game development is a very complex and highly multi-disciplinary thing.
To put it in a way that doesn’t do it justice, the complexity mostly comes from cohesion being very important for a gaming experience as a whole to feel well-made, and from designing the thing so it steers emotions in a way that is desirable.
There’s just so much stuff that has to intertwine in the right ways (see &lt;a href=&quot;https://lizengland.com/blog/2014/04/the-door-problem/&quot;&gt;the Door Problem&lt;/a&gt; for a glimpse) for it to not be off-putting.&lt;/p&gt;
&lt;p&gt;When working on games, I like to lead with getting the atmosphere dialed in, and letting much of the rest fall into place from there, because getting the atmosphere right requires that cohesion on multiple parts of the production.
That’s easier said than done though, because for it to not be a jumbled mess of loosely fitting parts, it requires trying to form an all-encompassing vision of what something will be like to experience.
The art direction, a potential story beat, the resolution of the volumetric fog, the ad hoc emotion players should experience, the thoughts they should have if they were to deeply engage with it in that moment, the footstep sound for damp carpet #6, the voice of our silent protagonist, the room tone-like droning music, the shape and color of light sources, the backstory of our goofy comedic relief character.
The list goes on.&lt;/p&gt;
&lt;p&gt;I really hammer that vision into my brain and do my best to keep the memory fresh for as long as I take to make it happen.
It’s like a target clip that I have in mind, enriched with the technicalities that would come up when implementing it.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=-7damwzqa4s&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/mgs-concept.BbY7Xt-3_wkifd.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;491&quot;&gt;&lt;/a&gt; &lt;script type=&quot;module&quot; src=&quot;https://jama.me/Users/janmaslov/Documents/repos/maslov2024/node_modules/@astro-community/astro-embed-youtube/YouTube.astro?astro&amp;amp;type=script&amp;amp;index=0&amp;amp;lang.ts&quot;&gt;&lt;/script&gt; &lt;figcaption&gt;&lt;p&gt;An early announcement trailer for Metal Gear Solid (1998) from 1996 showing some target footage of the vision for the game.
The game itself was of course quite different to that.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=R4DcDdTpfJQ&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/ac3-concept.DlJ7K_ia_ZWXuVf.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;410&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Target footage for Assassin’s Creed 3 (2012) from 2010.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Like any other software, games go through stages of development, and the first couple level block outs and gameplay prototypes are usually the point where we try to judge the interplay of it all, to understand whether it’s valuable enough to make in earnest.
And usually, neither testers nor stakeholders, or even developers are able to withstand early-stage jank skewing their perception:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;They all say they are good at looking past the gray boxes, the bugs, the crashes, the ear flicks.
But then they are grateful when a buggy playtest ends, or grateful when the whole playtest is cancelled, because it is a painful experience.&lt;/p&gt;
&lt;p&gt;— &lt;cite&gt;Greg Street for Polygon (&lt;a href=&quot;https://www.polygon.com/analysis/482369/push-to-talk-apex-legends-caravan-sandwitch-age-of-empires&quot;&gt;Source&lt;/a&gt;)&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And it is painful for different reasons.
Testers may say they didn’t like the gameplay when in reality they’d have a kick-ass time with the final art assets and lighting in place.
Stakeholders and owners might have second thoughts about their investments into this buggy mess, when it’s just the character controller getting stuck on slopes because its maximum climbing angle isn’t configured correctly.&lt;/p&gt;
&lt;p&gt;One is vastly, &lt;em&gt;vastly&lt;/em&gt; more elaborate to fix, with hundreds of layers of sanding and polish involved - years into the future probably.
The other is maybe a couple of hours of sanding if we discount external playtesting.&lt;/p&gt;
&lt;p&gt;Developers should have the best chance to see past it all, but I’ve experienced firsthand teams that pushed MVP-thinking in the same way I outlined previously.
When we didn’t have time to sand, it made internal playtests disappointing, showing off the thing to others embarrassing, and nobody involved could precisely pinpoint why exactly.
Well duh, because it wasn’t quite good enough!
But looking at a jumbled mess that’s already happened and determining what tiny improvements will magically open the pearly gates is way more painful than sanding.&lt;/p&gt;
&lt;p&gt;While the concept was great as it was in my mind, &lt;a href=&quot;https://jama.me/games/abf&quot;&gt;A Breathtaking Flight&lt;/a&gt; was bogged down by its scope because of deadlines that were breathing down our necks the entire way, which made us forego sanding completely.
Through sheer will, working ourselves to the bone, and not having a ton of fun, we nevertheless forced it to work, but it was far from refined.
It was functioning, and showed the concept’s potential, so it achieved its goal.
But as I wrote in the case study, if the whole thing was to be made for real, we would’ve had to throw it all out and start fresh.&lt;/p&gt;
&lt;p&gt;For my personal projects before that, and for &lt;em&gt;Fey: Distant Daydream&lt;/em&gt;, I’ve embraced more early sanding, where I get closer to the target I set in my mind, work on the little things some more, have it in a more presentable state early, so that everyone has an easier time to grasp the vision and believe in it.
It usually yields a lasting boost to morale.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Sanding everything and sanding early isn’t the one and only solution, but it helps if you can somehow squeeze it into the alloted time budget.
It makes working on things more fun, doesn’t have to cost a lot of time, but you require experience to judge when and where sanding time is best spent.&lt;/p&gt;
&lt;p&gt;While people try to get away with proofs of concept as much as possible, they aren’t oblivious to recognizing quality.
They usually notice and appreciate when something is made with care.
And that is often enough the sum of all kinds of little thoughtful improvements made along the way.&lt;/p&gt;
&lt;p&gt;One of the things I often get flak for is that I love giving new projects a pretty name and making a logo for them.
To me, it makes the endeavour feel more official, and not like some kind of grassroots backyard operation.
It’s probably one of the more superfluous ways of adding to a project, but it nevertheless motivates me.
And later, when it comes to presenting the thing to outsiders, people are excited that we have a catchy name and a logo to make fancy presentations with.&lt;/p&gt;
&lt;p&gt;Because of the double-standard that people are hell-bent on making MVPs to go as fast and cheap as possible, but at the same time are openly grateful for things not being subpar, I believe sanding and being smart about it is a good skill to have.&lt;/p&gt;
&lt;p&gt;Happier people do better work.
And that’s true for everyone.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thanks again to &lt;a href=&quot;https://www.jim-nielsen.com/&quot;&gt;Jim Nielsen&lt;/a&gt; for his blog post!&lt;/em&gt;&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - Wishing Game Tech Demos to Come Back</title><link>https://jama.me/blog/wishing-back-tech-demos/</link><guid isPermaLink="true">https://jama.me/blog/wishing-back-tech-demos/</guid><description>These days, advances in video game tech often happen behind closed doors. I wish they were celebrated more.</description><pubDate>Fri, 11 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The GeForce 256 turns &lt;a href=&quot;https://blogs.nvidia.com/blog/first-gpu-gaming-ai/&quot;&gt;25 years old&lt;/a&gt; today, so I figured I’d reminisce a little about the tech driving our beloved games.&lt;/p&gt;
&lt;p&gt;Everyone playing video games has their favorite game, and everyone following the games industry probably has a favorite year in video games.
For me, it was 2004 - &lt;a href=&quot;https://www.reddit.com/r/gaming/comments/1kl0or/2004_one_of_the_best_years_in_gaming/&quot;&gt;arguably&lt;/a&gt; &lt;a href=&quot;https://www.goonhammer.com/the-best-year-in-gaming-the-final-matchup/&quot;&gt;one&lt;/a&gt; &lt;a href=&quot;https://gamerant.com/best-year-in-gaming/&quot;&gt;of the&lt;/a&gt; &lt;a href=&quot;https://www.pcgamer.com/what-was-the-best-year-for-pc-gaming/&quot;&gt;most&lt;/a&gt; &lt;a href=&quot;https://www.gamespot.com/articles/why-2004-was-the-best-year-in-gaming/1100-6424377/&quot;&gt;exciting&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/2004_in_video_games&quot;&gt;years&lt;/a&gt; in video games.
I was a little too young at the time to fully appreciate the most exciting of the releases that year, though I thoroughly enjoyed &lt;em&gt;Need for Speed: Underground&lt;/em&gt;, &lt;em&gt;The Sims 2&lt;/em&gt;, &lt;em&gt;Pokémon LeafGreen&lt;/em&gt; and like a billion amazing demos towards the end of the year.&lt;/p&gt;
&lt;p&gt;Seeing the E3 show coverage the year before, where some of the games released in 2004 were announced, is what got me interested in game development.
Specifically, this screenshot of &lt;em&gt;S.T.A.L.K.E.R. Shadow of Chernobyl&lt;/em&gt; (back then still called &lt;em&gt;S.T.A.L.K.E.R. Oblivion Lost&lt;/em&gt;) shown exclusively in the E3 coverage of the German GameStar magazine’s 07/2003 issue (&lt;a href=&quot;https://bk.gamestar.de/frontend/getcatalog.do?catalogId=8523&amp;amp;catalogVersionId=123983&amp;amp;catalogVersion=1&amp;amp;preview=true&quot;&gt;p. 22&lt;/a&gt;):&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/stalker-ol.3mPoqID0_Z2cCS0A.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/stalker-ol.3mPoqID0_Z1CEgHh.jxl, https://jama.me/_astro/stalker-ol.3mPoqID0_syMyj.jxl 1.5x, https://jama.me/_astro/stalker-ol.3mPoqID0_1BzBFT.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/stalker-ol.3mPoqID0_Z250edz.avif, https://jama.me/_astro/stalker-ol.3mPoqID0_1dP31.avif 1.5x, https://jama.me/_astro/stalker-ol.3mPoqID0_Z26EOzn.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/stalker-ol.3mPoqID0_1ngkGP.webp, https://jama.me/_astro/stalker-ol.3mPoqID0_Z1AGIPv.webp 1.5x, https://jama.me/_astro/stalker-ol.3mPoqID0_Z1wq76i.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/stalker-ol.3mPoqID0_HY3iS.jpg&quot; srcset=&quot;https://jama.me/_astro/stalker-ol.3mPoqID0_Z2fY1es.jpg 1.5x, https://jama.me/_astro/stalker-ol.3mPoqID0_Z1Epn02.jpg 2x&quot; alt=&quot;Stalker: Oblivion Lost video game screenshot, first person. Player and soldiers fighting mutant&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;450&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;The screenshot that started my game development journey. &lt;a href=&quot;https://stalker.fandom.com/ru/wiki/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Vadyanchikus/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D1%81%D0%BA%D1%80%D0%B8%D0%BD%D1%88%D0%BE%D1%82%D0%BE%D0%B2_S.T.A.L.K.E.R.:_Oblivion_Lost&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Despite the miniscule size it was printed at in the magazine, I was completely spellbound by it.
Especially the rifle’s scope (an &lt;a href=&quot;https://en.wikipedia.org/wiki/Dragunov_SVU&quot;&gt;&lt;em&gt;OTs-03 SVU-A&lt;/em&gt;&lt;/a&gt;-lookalike) caught my eye, the folds in the rubber and the irregular shape of it, the reflection in the glass, the little embossed writings on it.
Not being able to make out much more, I projected this level of realism onto the whole game in my mind.
And it got me interested in learning how people achieved this and birthed a new kind of respect for their work.
Naturally, &lt;em&gt;S.T.A.L.K.E.R.&lt;/em&gt; with its troubled development history at that point still needed a couple more years in the oven and leaks to happen before being released.
Since its humble beginnings it was used to demonstrate the capabilities of GSC Game World’s in-house engine Xray, with numerous trailers showing their &lt;a href=&quot;https://www.youtube.com/watch?v=LzVIkgJoPmI&quot;&gt;advanced AI system&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=cXtwlCmUc5c&quot;&gt;physics simulation&lt;/a&gt;, &lt;a href=&quot;https://youtu.be/dCYjWQryK5o?feature=shared&amp;amp;t=852&quot;&gt;rendering tech&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=oqQt-C6KB_8&quot;&gt;gameplay systems&lt;/a&gt; like the &lt;a href=&quot;https://www.youtube.com/watch?v=BviZaGRFuM0&quot;&gt;day/night cycle&lt;/a&gt; and more.&lt;/p&gt;
&lt;p&gt;But I digress.
Of course, not to forget, at E3 2003 there were also &lt;em&gt;Doom 3&lt;/em&gt;’s and &lt;em&gt;Far Cry&lt;/em&gt;’s new trailers and the legendary &lt;em&gt;Half-Life 2&lt;/em&gt; demonstration, which was highly realistic in a different way:&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=3ZYho9nu60w&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/hl2-demo-poster.aXJnKpHw_1rBnEH.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt; &lt;script type=&quot;module&quot; src=&quot;https://jama.me/Users/janmaslov/Documents/repos/maslov2024/node_modules/@astro-community/astro-embed-youtube/YouTube.astro?astro&amp;amp;type=script&amp;amp;index=0&amp;amp;lang.ts&quot;&gt;&lt;/script&gt; &lt;figcaption&gt;&lt;p&gt;A clean version of the presentation made from an original recording released by Valve for Half-Life 2’s 20th anniversary and a reconstructed commentary audio track.
There’s also the original &lt;a href=&quot;https://www.youtube.com/watch?v=CaHtOISsLT4&quot;&gt;clean version&lt;/a&gt; without the commentary.
Alternatively, &lt;a href=&quot;https://valvearchive.com/archive/Other%20Files/Conferences%20and%20Expos/E3%202003/&quot;&gt;Valve Archive&lt;/a&gt; has original files released back in 2003.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;While &lt;em&gt;S.T.A.L.K.E.R.&lt;/em&gt; initially impressed me solely with its image quality, &lt;em&gt;Half-Life 2&lt;/em&gt;’s demo displayed a skillful stylization of the realistic and added never-before-seen character interactions and physics into the mix.
This demo touches on a great many rabbit holes the team at Valve had to deep dive into to make the technological advances they deemed necessary for &lt;em&gt;Half-Life 2&lt;/em&gt; happen.
Not understanding even a little bit of the technical details, it was also the first time I was confronted with the idea that games actually go through a process of creation, where each and every detail is meticulously crafted, rather than magically appearing on store shelves when the time is right.&lt;/p&gt;
&lt;p&gt;Back then, games were proudly marketed with their technological and technical advancements, whereas today it appears to be more implicit.
I feel that putting the hard work that went into games on display got lost or pushed into the background somewhere between our expectations rising over time, the supply of low-hanging fruits drying up, as well as gaming becoming more mainstream.
After having been through &lt;a href=&quot;https://www.youtube.com/watch?v=xNter0oEYxc&quot;&gt;that&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=QBcPS6XVABA&quot;&gt;much&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=ROWbxuYQMws&quot;&gt;backlash&lt;/a&gt; for downgrading games’ graphics between their pre-release material and the actual released product, understandably we also rarely see testing levels and other less polished footage of games.&lt;/p&gt;
&lt;p&gt;Hard to say exactly why though.
My guess is that, while the craft and art behind video games still holds a very high value, the industry has become split between large productions that are playing it safe trying to appease shareholders, and independent developers who generally lack the funds to push the envelope in the same way as back then.
Pushing the envelope simply got very expensive and time-consuming.
And despite being on a big budget, a mainstream series like &lt;em&gt;Call of Duty&lt;/em&gt; wouldn’t be able to ship some bleeding edge features, because it has different priorities.
It needs to run well on a wide range of systems to retain its players, so the studios working on it have to progress in lockstep with their slowest targeted systems.
Undoubtedly, graphics is also generally not one of &lt;em&gt;Call of Duty&lt;/em&gt;’s main attractions, it was never marketed primarily on that.
In my eyes, &lt;em&gt;Call of Duty&lt;/em&gt; is more importantly an exercise in storytelling (aside from the very popular multiplayer), where the teams appear to show increasingly impressive animation work.&lt;/p&gt;
&lt;p&gt;Just recently, &lt;em&gt;Death Stranding 2: On The Beach&lt;/em&gt; was widely covered for its TGS presentation, where its photo mode featuring their impressive character tech was shown off, going above even the first &lt;em&gt;Death Stranding&lt;/em&gt;, which was already mindblowing.&lt;/p&gt;
&lt;a href=&quot;https://youtube.com/watch?v=MHE69MiVh7A&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/ds2-poster.v6En_KJ-_Z1D88Ph.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  
&lt;p&gt;Everything about this presentation looks really convincing, and it was one of the &lt;a href=&quot;https://www.pcgamer.com/games/action/death-stranding-2s-lavish-and-slightly-leery-photo-mode-is-what-happens-when-kojima-has-a-big-budget-and-no-oversight/&quot;&gt;main&lt;/a&gt; &lt;a href=&quot;https://www.forbes.com/sites/paultassi/2024/09/29/death-stranding-2s-photo-mode-must-be-seen-to-be-believed/&quot;&gt;talking&lt;/a&gt; &lt;a href=&quot;https://www.resetera.com/threads/death-stranding-2-will-have-a-unique-photo-mode-that-apparently-impacts-the-story.995535/&quot;&gt;points&lt;/a&gt; in the coverage of it.
Though, officially, discussion of the tech behind it all is of course absent, aside from shots from motion capturing for the game.&lt;/p&gt;
&lt;p&gt;The last time I remember there being somewhat deeper technical discussion was around the release of Unreal Engine 5, introducing their new technologies &lt;a href=&quot;https://www.youtube.com/watch?v=qC5KtatMcUw&quot;&gt;Nanite and Lumen&lt;/a&gt;, and later, developer-centric demos that were published by gaming outlets, like a &lt;a href=&quot;https://www.youtube.com/watch?v=-lkEOEEKYD0&quot;&gt;demo&lt;/a&gt; of the procedural generation tools in Unreal Engine 5.2.
Players were discussing the implications for games in development because of Epic’s promises of a relatively easy upgrade path.&lt;/p&gt;
&lt;p&gt;On the other hand, these days you have small studios, and even individual creators who provide lots of insight into their development process.
But their strengths lie elsewhere, as they most often innovate in terms of gameplay and storytelling, rather than rendering tech for example.&lt;/p&gt;
&lt;p&gt;While I’m happy that the &lt;a href=&quot;https://www.pouet.net&quot;&gt;demoscene&lt;/a&gt; is still living and outlets like &lt;a href=&quot;https://www.digitalfoundry.net/&quot;&gt;Digital Foundry&lt;/a&gt; exist to cover some of the advances made as far as they can with a view from the outside, I would love for the industry to return to boasting their technical achievements more.
And to celebrate what’s already there, I made a list of my favorite tech showcases:&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=iBVI89dvO-c&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/mgs2-poster.-hqc9FZw_ZGv6AI.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Metal Gear Solid 2: Sons of Liberty E3 2000 trailer (2000)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=iUyGXGqsDC0&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/ati-radeons-ark-poster.D2SVyPP4_27zv35.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;ATI Radeon’s Ark tech demo (2000)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=XIHUPPhJGNE&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/shrek-poster.DJ7TEBKa_ZdaeTG.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;480&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Janky pre-release gameplay footage of Shrek (Xbox), the first commercial game using deferred rendering (2001)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=9K8masjDBrI&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/nvidia-nalu-poster.CgtgRpC0_Z2qTlQn.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;468&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Nvidia Nalu tech demo (2004)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=yoUEHAeN7O8&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/stalker-toughest-enemy-poster.BBoa4Osi_ZNAmuQ.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;480&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;The Toughest Enemy DirectX9 demonstration trailer for S.T.A.L.K.E.R. Shadow of Chernobyl (2005)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=oeZRV89I4PQ&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/heavy-rain-poster.DCeblvad_ZUkUwg.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Heavy Rain E3 2006 tech demo trailer (2006)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=SwagpVtkq_A&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/metro2033-poster.DgoX0jMG_ZQYEtu.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Metro 2033: The Last Refuge E3 2006 tech demo trailer (2006)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=_6OhmobOmb4&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/nvidia-froggy-poster.DK-7aCfD_Z1uSIlH.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;480&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Nvidia Froggy tech demo (2006)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=QupjwYqXqlE&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/nvidia-smoke-poster.Bv3R4p9x_Z2l1LQX.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;365&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Nvidia Box of Smoke tech demo (2006)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=a_mMwbJzu-g&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/mgs4-demo-poster.C4f00iMw_iJD5y.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;307&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Metal Gear Solid 4 TGS 2006 tech demo (2006)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=3frnW_2t2v4&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/cellfactor-poster.Bkt4OCEm_Z1D0gzw.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;450&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Trailer for CellFactor: Revolution - the first game using the AGEIA PhysX physics accelerator card (2006)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=f4yPVLXkiZM&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/euphoria-poster.Da5chR2-_Z18pXMj.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;400&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Naturalmotion’s Euphoria tech demo (2008)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=pJNdvRjtJQU&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/farcry2-poster.C5eGIfiK_ZPsXGK.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;370&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Dunia Engine tech demonstration trailer for Far Cry 2 (2008)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=CfKQCAxizrA&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/fromdust-poster.DEyPLg2D_Z1aizcR.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;From Dust tech demo (2010)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=JWvgETOo5ek&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/crysis3-poster.Ckb_rUyg_ZbOfmj.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Crysis 3 tech demo trailer (2012)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=bI1_quVr_3w&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/nvidia-new-dawn-poster.DBm18o0l_wbKx2.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;325&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Nvidia A New Dawn tech demo (2012)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=4dnw9dWISvw&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/geteven-poster.BvXFXp8F_Z1V7JXK.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Get Even “What is real” trailer (2014)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=D-epev7cT30&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/deus-ex-poster.BW8t0cOw_ZBk74g.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Deus Ex: Mankind Divided E3 2015 tech demo trailer (2015)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=UVX0OUO9ptU&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/agnis-philosophy-poster.yxjWVgXD_ZzJ8M0.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Agni’s Philosophy - Final Fantasy tech demo (2016)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=GCuCQSyqeXM&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/teardown-poster.CLXMlm___ZtmV8w.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Teardown announcement (2019)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=NgcYLIvlp_k&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/nvidia-marbles-poster.B1LEoGIe_1VgraD.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Nvidia Marbles at Night RTX tech demo (2020)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=VeFF344NbZ4&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/superrt-poster.CY8r10IG_1sv5iG.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;436&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;SuperRT SNES raytracing demo (2020)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=pXZEMfaq69A&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/portal-rtx-poster.BAb-dFlb_Zu9tpe.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Portal with RTX reveal trailer (2022)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://youtube.com/watch?v=NCYMNmkjRS4&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/hellblade2-poster.COfJPp5M_2dyk5n.webp&quot; alt=&quot;&quot; fetchpriority=&quot;auto&quot; width=&quot;640&quot; height=&quot;400&quot;&gt;&lt;/a&gt;  &lt;figcaption&gt;&lt;p&gt;Hellblade 2: Senua’s Saga Metahuman demo (2023)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - A New Home for My Website</title><link>https://jama.me/blog/new-home/</link><guid isPermaLink="true">https://jama.me/blog/new-home/</guid><description>I decided to move domains, hosting and tech for my personal website, or: how I discovered I sometimes like my training wheels.</description><pubDate>Thu, 26 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Lots of news about this website you’re visiting.&lt;/p&gt;
&lt;div&gt;&lt;aside aria-label=&quot;Update 2024-10-05&quot; style=&quot;--colorBg: var(--color-fg-rgb);--colorFg: var(--color-fg-inverted);&quot;&gt;  &lt;section style=&quot;--colorBg: var(--color-fg-rgb);--colorFg: var(--color-fg-inverted);&quot;&gt; &lt;p&gt;The UK announced that the sovereignty of Chagos Islands &lt;a href=&quot;https://www.bbc.com/news/articles/c98ynejg4l5o&quot;&gt;will be returned to Mauritius&lt;/a&gt;.
This puts .io-domains in murky waters.
Randomly good timing for my domain move.&lt;/p&gt; &lt;/section&gt; &lt;/aside&gt; &lt;/div&gt; 
&lt;div&gt;&lt;aside aria-label=&quot;Update 2024-12-01&quot; style=&quot;--colorBg: var(--color-fg-rgb);--colorFg: var(--color-fg-inverted);&quot;&gt;  &lt;section style=&quot;--colorBg: var(--color-fg-rgb);--colorFg: var(--color-fg-inverted);&quot;&gt; &lt;p&gt;Hetzner &lt;a href=&quot;https://news.ycombinator.com/item?id=42264427&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;announced to its users&lt;/a&gt; &lt;a href=&quot;https://adriano.fyi/posts/hetzner-raises-prices-while-significantly-lowering-bandwidth-in-us/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;price increases and traffic decreases&lt;/a&gt; for US deployments.
The comparison table didn’t require an update though.&lt;/p&gt; &lt;/section&gt; &lt;/aside&gt; &lt;/div&gt; 
&lt;p&gt;My previous 2021 website redesign was targeted at making me appear professional and focus on my skills in web development.
As such, I went for a very technical and reduced look.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/maslov-io-2021.z7hs08o2_Z24UMc5.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/maslov-io-2021.z7hs08o2_Z2e3W4z.jxl, https://jama.me/_astro/maslov-io-2021.z7hs08o2_26JHY0.jxl 1.5x, https://jama.me/_astro/maslov-io-2021.z7hs08o2_1UAj94.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/maslov-io-2021.z7hs08o2_Z2jQhk8.avif, https://jama.me/_astro/maslov-io-2021.z7hs08o2_20WnIr.avif 1.5x, https://jama.me/_astro/maslov-io-2021.z7hs08o2_Z10WXKd.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/maslov-io-2021.z7hs08o2_eLxuE.webp, https://jama.me/_astro/maslov-io-2021.z7hs08o2_ZuAUfH.webp 1.5x, https://jama.me/_astro/maslov-io-2021.z7hs08o2_2iLFLa.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/maslov-io-2021.z7hs08o2_LKugq.png&quot; srcset=&quot;https://jama.me/_astro/maslov-io-2021.z7hs08o2_2n1v4.png 1.5x, https://jama.me/_astro/maslov-io-2021.z7hs08o2_Z1gxwIG.png 2x&quot; alt=&quot;Website screenshot, maslov.io 2021 website&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;429&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;At the same time, it was a departure in terms of the technology behind it.
For the first time, I tried static site generators and was immediately enamored with them.
They reminded me of my humble beginnings in creating websites and starting out as a freelancer.
I love the simplicity behind the jamstack - especially coming from a custom node.js-based server with &lt;a href=&quot;https://ejs.co&quot;&gt;EJS&lt;/a&gt; for templating behind my personal site at the time.&lt;/p&gt;
&lt;p&gt;Having checked out multiple static site generators back in 2021, &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; among them, I went with &lt;a href=&quot;https://11ty.dev&quot;&gt;Eleventy&lt;/a&gt;.
Both still needed some time to mature, but Eleventy appeared to be further along.
And both turned out to be venerable alternatives in the space.&lt;/p&gt;
&lt;h2 id=&quot;moving-to-astro&quot;&gt;Moving to Astro&lt;/h2&gt;
&lt;p&gt;Since then, I’ve built multiple websites with Astro - the &lt;a href=&quot;https://secret.industries&quot;&gt;secret industries&lt;/a&gt; website among them.
And I liked the experience of building and maintaining so much that I decided to rebuild the new website with Astro instead of Eleventy.&lt;/p&gt;
&lt;p&gt;Notice the lack of blog posts in the last years?
That’s mostly a result of me concocting an Eleventy setup that was too fragile, complicated and suffering from a bad editing experience as a result.
The JavaScript ecosystem and Eleventy have come quite a ways since then, making TypeScript and bundling easier and more ubiquitous than ever with tools like &lt;a href=&quot;https://vitejs.dev&quot;&gt;Vite&lt;/a&gt; and the developer experience has gotten radically better, even if more complex in many respects.&lt;/p&gt;
&lt;p&gt;So this definitely isn’t a knock against Eleventy.
It’s more of a knock against my poor choice of templating engine to use with Eleventy (EJS), which was made in a different time and with different priorities in mind.
And a knock against me not walking the walk and fixing it.&lt;/p&gt;
&lt;p&gt;Even though Astro railroads by being pretty opinionated about how you build websites with it, it rewards with quality tooling.
And it turned out that that is exactly what I wanted for my website.&lt;/p&gt;
&lt;p&gt;I want it to be easy to edit and add new content to, so it takes less of what little free time I have.
Astro nudges here and there in certain directions (by that I mean architectural decisions, recommendations for tools that work best with it, and the breakneck speed at which its community formed and produced integrations) that make the experience as a whole feel almost magical.&lt;/p&gt;
&lt;h2 id=&quot;moving-to-cloudflare&quot;&gt;Moving to Cloudflare&lt;/h2&gt;
&lt;p&gt;I’m currently hosting several small-scale projects via Netlify, like the secret industries website, a couple of landing pages for previous clients and &lt;a href=&quot;https://jama.me/projects/russiandoll&quot;&gt;Russian Doll’s Booking frontend&lt;/a&gt;.
But my starry-eyes-ness about CDN hosting has been waning ever since Netlify sent out a &lt;a href=&quot;https://old.reddit.com/r/webdev/comments/1b14bty/netlify_just_sent_me_a_104k_bill_for_a_simple/&quot;&gt;$104,500 bill to a user&lt;/a&gt; on their free Starter plan.&lt;/p&gt;
&lt;p&gt;Netlify since then &lt;a href=&quot;https://x.com/i/web/status/1762518910107033798&quot;&gt;apologized&lt;/a&gt; and, after some community pressure, will introduce some spending management features (unreleased yet at the time of writing).
While I understand that their policy - to not let anyone hanging through the hardest of times and dutifully serving every last request - can be seen as admirable, this is not something I require for non-commercial or non-crucial websites, like my own personal website.
Likewise, I don’t think that the handling of the situation was adequate given how Netlify first reduced that huge bill to 20% and then 5% of the initial sum before the user complained publicly.&lt;/p&gt;
&lt;p&gt;My previous website, even though it was also a static site, was hosted on a VPS on Vultr in Frankfurt, because that’s where the one before that one was and I had a couple of other things running on it.
But now came the time to move my personal website to one of these hosts as well, and my decision fell on Cloudflare.&lt;/p&gt;
&lt;p&gt;The main reason was traffic cost.
All providers tout transparent and predictable pricing, but egress cost is by its very nature unpredictable.
You can’t know exactly how much traffic there will be.
Here’s a comparison of egress traffic costs for some hosting providers I considered:&lt;/p&gt;
&lt;div&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;Provider&lt;/th&gt;&lt;th&gt;Free quota&lt;/th&gt;&lt;th&gt;After that&lt;/th&gt;&lt;th&gt;Per TiB&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;Azure Static Web App&quot; href=&quot;https://azure.microsoft.com/en-us/pricing/details/app-service/static/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Azure Static Web App&lt;/a&gt; &lt;/td&gt;&lt;td&gt; 100GB&lt;sup&gt;1&lt;/sup&gt; &lt;/td&gt;&lt;td&gt; $0.00 &lt;/td&gt;&lt;td&gt; $0.00 &lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;Cloudflare&quot; href=&quot;https://www.cloudflare.com/plans/developer-platform/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cloudflare&lt;/a&gt; &lt;/td&gt;&lt;td&gt; &quot;Unlimited&quot;&lt;sup&gt;2, 3&lt;/sup&gt; &lt;/td&gt;&lt;td&gt; $0.00 &lt;/td&gt;&lt;td&gt; $0.00 &lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;OVH&quot; href=&quot;https://www.ovhcloud.com/en/vps/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;OVH&lt;/a&gt; &lt;/td&gt;&lt;td&gt; Depends on region, &quot;Unlimited&quot;&lt;sup&gt;2&lt;/sup&gt; or at least 1TB at full speed&lt;sup&gt;5&lt;/sup&gt; &lt;/td&gt;&lt;td&gt; $0.00 &lt;/td&gt;&lt;td&gt; $0.00 &lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;surge.sh&quot; href=&quot;https://surge.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;surge.sh&lt;/a&gt; &lt;/td&gt;&lt;td&gt; &quot;Unlimited&quot;&lt;sup&gt;2, 4&lt;/sup&gt; &lt;/td&gt;&lt;td&gt; $0.00 &lt;/td&gt;&lt;td&gt; $0.00 &lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;UpCloud&quot; href=&quot;https://upcloud.com/pricing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;UpCloud&lt;/a&gt; &lt;/td&gt;&lt;td&gt; Pooled between VPS, at least 1TB at full speed&lt;sup&gt;5&lt;/sup&gt; &lt;/td&gt;&lt;td&gt; $0.00 – $0.011/GB &lt;/td&gt;&lt;td&gt; $0.00 – $11.26 &lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;Hetzner&quot; href=&quot;https://www.hetzner.com/cloud/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hetzner&lt;/a&gt; &lt;/td&gt;&lt;td&gt; Depends on region, at least 1TB or 20TB &lt;/td&gt;&lt;td&gt; $1.12 – $8.26/TB &lt;/td&gt;&lt;td&gt; $1.15 – $8.46 &lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;Linode&quot; href=&quot;https://www.linode.com/pricing/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Linode&lt;/a&gt; &lt;/td&gt;&lt;td&gt; Pooled between VPS, at least 1TB &lt;/td&gt;&lt;td&gt; $0.005 – $0.015/GB &lt;/td&gt;&lt;td&gt; $5.12 – $15.36 &lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;DigitalOcean&quot; href=&quot;https://www.digitalocean.com/pricing/droplets#basic-droplets&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DigitalOcean&lt;/a&gt; &lt;/td&gt;&lt;td&gt; Pooled between VPS, at least 500GB &lt;/td&gt;&lt;td&gt; $0.01/GiB &lt;/td&gt;&lt;td&gt; $10.00 &lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;Vultr&quot; href=&quot;https://getdeploying.com/vultr&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Vultr&lt;/a&gt; &lt;/td&gt;&lt;td&gt; Pooled between VPS, at least 500GB &lt;/td&gt;&lt;td&gt; $0.01/GB &lt;/td&gt;&lt;td&gt; $10.24 &lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;Fly.io&quot; href=&quot;https://fly.io/docs/about/pricing/#data-transfer-pricing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Fly.io&lt;/a&gt; &lt;/td&gt;&lt;td&gt; Depends on region, 30GB - 100GB &lt;/td&gt;&lt;td&gt; $0.02 – $0.12/GB &lt;/td&gt;&lt;td&gt; $20.48 – $122.88 &lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;Vercel&quot; href=&quot;https://getdeploying.com/vercel&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Vercel&lt;/a&gt; &lt;/td&gt;&lt;td&gt; Depends on plan, 100GB - 1TB &lt;/td&gt;&lt;td&gt; $0.15 – $0.47/GB &lt;/td&gt;&lt;td&gt; $153.60 – $481.28 &lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;Render&quot; href=&quot;https://render.com/pricing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Render&lt;/a&gt; &lt;/td&gt;&lt;td&gt; Depends on plan, 100GB - 1TB &lt;/td&gt;&lt;td&gt; $30.00/100GB &lt;/td&gt;&lt;td&gt; $307.20 &lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt; &lt;a slot=&quot;Netlify&quot; href=&quot;https://www.netlify.com/pricing/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Netlify&lt;/a&gt; &lt;/td&gt;&lt;td&gt; Depends on plan, 100GB - 1TB &lt;/td&gt;&lt;td&gt; $55.00/100GB &lt;/td&gt;&lt;td&gt; $563.20 &lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt;  &lt;/table&gt; &lt;/div&gt; 
&lt;small&gt;&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt; Azure will stop serving when the quota is exceeded with no recourse, other than upgrading to a paid plan for it and paying $0.20 per GB ($204.80/TiB).
Also beware, the maximum deployment size for the free plan is only 250MB.&lt;/p&gt;&lt;p&gt;&lt;sup&gt;2&lt;/sup&gt; “Unlimited” of course still means that there’s some secret limit that may be enforced indiscriminately.&lt;/p&gt;&lt;p&gt;&lt;sup&gt;3&lt;/sup&gt; Cloudflare has a history of simply blocking access if the traffic gets too much.
There’s the risk of randomly getting banned though.
Cloudflare Pages has a deployment limit of &lt;a href=&quot;https://developers.cloudflare.com/pages/platform/limits/#files&quot;&gt;20000 files at a maximum of 25MB per file&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;sup&gt;4&lt;/sup&gt; surge.sh doesn’t mention any limits at all, but as of 2019, they apparently had a limit of &lt;a href=&quot;https://github.com/sintaxi/surge/issues/341#issuecomment-550035031&quot;&gt;4500 files and 430MB&lt;/a&gt; maximum deployment size.
I imagine there are higher limits in place with their $30.00/month &lt;a href=&quot;https://surge.sh/pricing&quot;&gt;Professional plan&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;sup&gt;5&lt;/sup&gt; OVH provides limited high-speed egress traffic of at least 1TB in their Sydney and Singapore regions.
They throttle the bandwidth to 10 Mb/s after the quota is exceeded.
UpCloud provides limited high-speed egress traffic of at least 1TB for all their offerings.
They throttle the bandwidth to &lt;a href=&quot;https://upcloud.com/fair-transfer-policy&quot;&gt;100 Mb/s&lt;/a&gt; after the quota is exceeded, except if you’d like to pay as per the table.&lt;/p&gt;&lt;/small&gt; 
&lt;hr&gt;
&lt;p&gt;Traffic with providers hosting on a CDN is mighty expensive!
And Netlify tops that list.
There’s a noticeable divide between those providers and the ones providing a VPS - where edge web hosting isn’t baked in as part of the service.
These providers hover around $10.00 per TiB, whereas traffic with serverless providers can cost multiple orders of magnitude more.&lt;/p&gt;
&lt;p&gt;The three that stand out are Azure, Cloudflare, and surge.sh, which would all provide predictable pricing.
As stated in the fine print, Azure will stop serving your website when the 100GB free quota is exceeded, with no way to get more traffic if needed.
100GB should be plenty for me, but the miniscule maximum deployment size of 250MB ruins the offer.
It’s designed for enterprise-y SPAs with little content, as the 500MB you get with the step up paid plan are still far too little for a media-heavy website.
At the time of writing, this website’s deployment comes in at 244MB, and it’s far from heavy on the media front (though the originals of all images are part of the bundle, which accounts for most of the size).
surge.sh also disqualifies itself for the same reason, though in both cases one workaround would be to host the media elsewhere.
It’s just not as convenient.&lt;/p&gt;
&lt;p&gt;What I lack in this whole comparison is a serverless hosting provider that isn’t free, but that also isn’t expensive.
I get that the generous free offerings are likely financially sustainable because of economies of scale, but I &lt;a href=&quot;https://allenpike.com/2022/giving-a-shit&quot;&gt;don’t see a reason not to pay for a good service&lt;/a&gt; - especially if I get the additional benefit of having to move hosting providers less often.
The offerings of the usual suspects are either free, or $20.00, which is a little too rich for me.
I wonder if that’s really the price necessary for these services to be sustainable.&lt;/p&gt;
&lt;p&gt;So I went with Cloudflare.
Of course, it’s also &lt;a href=&quot;https://robindev.substack.com/p/cloudflare-took-down-our-website&quot;&gt;not always sunny&lt;/a&gt; in &lt;del&gt;Phil&lt;/del&gt; Cloudflare city, but I’ll be on the lookout for another solution.
At least the infrastructure is some of the most advanced in the world and my personal website has horribly overkill peerings and points of presence all over the globe.
Can’t be mad at that, can I?&lt;/p&gt;
&lt;p&gt;Fingers crossed I don’t get my Cloudflare account banned out of nowhere.&lt;/p&gt;
&lt;h2 id=&quot;jamame---moving-to-a-new-domain&quot;&gt;jama.me - Moving To a New Domain&lt;/h2&gt;
&lt;p&gt;Honestly, I’m still somewhat conflicted with this decision.&lt;/p&gt;
&lt;h3 id=&quot;deliberations&quot;&gt;Deliberations&lt;/h3&gt;
&lt;p&gt;It’s not that I decided on a whim to have a change of scenery, or that it was “just time” to change it up.
There hasn’t been &lt;a href=&quot;https://tvtropes.org/pmwiki/pmwiki.php/Main/ImportantHaircut&quot;&gt;character development&lt;/a&gt; that would warrant such a change.
&lt;em&gt;maslov.io&lt;/em&gt; has been my home for a pretty long, but not that long of a time, but I still decided to basically reserve it for family use.
For things like email addresses, private services and stuff like that.
No idea whether anyone in the family will actually make use of it for something, but it’ll be there when they need it.
I’ll probably have this domain for life at this point, or until the registry goes under.&lt;/p&gt;
&lt;p&gt;Carving out a little space for myself in the namespace and moving to &lt;em&gt;jan.maslov.io&lt;/em&gt; felt awkward, especially because that’s also almost exactly an email address I use professionally, which would’ve made it confusing.
I also wanted to emphasize the personal website aspect more with whatever address I ended up with.&lt;/p&gt;
&lt;p&gt;And then I got lucky enough to snatch away &lt;em&gt;jama.me&lt;/em&gt; just as it wasn’t renewed by the previous owner who just sat on it!
Sure, it’s no &lt;em&gt;.com&lt;/em&gt;, but the Montenegran &lt;em&gt;.me&lt;/em&gt; ain’t shabby for a personal thingy I think.&lt;/p&gt;
&lt;h3 id=&quot;the-actual-move&quot;&gt;The Actual Move&lt;/h3&gt;
&lt;p&gt;Luckily, the previous Vultr VPS didn’t have any important services running anymore, so I could transition very cleanly.
I only had to make sure to have good redirects in place.&lt;/p&gt;
&lt;p&gt;I don’t plan on using Cloudflare Pages’ build infrastructure, since it’s enough for me to build the page locally and upload it.
So the plan was:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create Cloudflare account&lt;/li&gt;
&lt;li&gt;Onboard the new domain into Cloudflare, configure their nameservers to use jama.me’s apex
&lt;ul&gt;
&lt;li&gt;Register the domain in Cloudflare and wait until the nameserver change is recognized&lt;/li&gt;
&lt;li&gt;Transfer any DNS entries over via Cloudflare’s automatic onboarding. But beware, it missed a couple entries for me&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Using &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;wrangler&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, bring the website online (&lt;a href=&quot;https://developers.cloudflare.com/workers/wrangler/commands/&quot;&gt;Cloudflare Docs&lt;/a&gt;)
&lt;ul&gt;
&lt;li&gt;Deploying to Cloudflare Pages &lt;a href=&quot;https://community.cloudflare.com/t/static-website-uploaded-to-pages-renders-as-text-only/425847/29&quot;&gt;is buggy&lt;/a&gt; with Firefox&lt;/li&gt;
&lt;li&gt;Create the Cloudflare Pages project
&lt;pre style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;wrangler&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; pages&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; project&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; create&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; PROJECT_NAME&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Upload the Astro build output into the project via wrangler
&lt;br&gt;I added this as a deploy script to my project’s &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;package.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;:
&lt;pre style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; build&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;npx&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; wrangler&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; pages&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; deploy&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; ./dist&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; --project-name&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; PROJECT_NAME&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The new website is online! Yay! Go through all of Cloudflare’s gazillion settings and set everything up&lt;/li&gt;
&lt;li&gt;Make Cloudflare’s domain inaccessible (&lt;a href=&quot;https://developers.cloudflare.com/pages/how-to/redirect-to-custom-domain/&quot;&gt;Cloudflare Docs&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Add &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;_redirects&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; file (&lt;a href=&quot;https://developers.cloudflare.com/pages/configuration/redirects/&quot;&gt;Cloudflare Docs&lt;/a&gt;)
&lt;ul&gt;
&lt;li&gt;I changed a couple URLs for the blog posts and I stopped providing multiple languages (the blog was already English-only, hence no redirects for &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/de/blog/:title&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;), so this is the current redirects file:
&lt;pre style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/de&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/de/&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/en&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/en/&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/de/blog&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /blog&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/de/blog/&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /blog&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/en/blog&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /blog&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/en/blog/&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /blog&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/en/blog/hello-world-and-this-blogs-rules&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /blog/hello-world&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/en/blog/hello-world-and-this-blogs-rules/&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /blog/hello-world&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/en/blog/my-hackintosh-part-1-hardware-and-undervolting&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /blog/hackintosh-undervolting&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/en/blog/my-hackintosh-part-1-hardware-and-undervolting/&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /blog/hackintosh-undervolting&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/en/blog/the-newbie-performance-of-the-razer-blade-15-2018&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /blog/razer-blade-comparison&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/en/blog/the-newbie-performance-of-the-razer-blade-15-2018/&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /blog/razer-blade-comparison&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/en/blog/:title&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /blog/:title&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;/en/blog/:title/&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; /blog/:title&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 301&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Disconnect the apex and www DNS records of the old domain from the Vultr VPS&lt;/li&gt;
&lt;li&gt;Setup HTTP redirects for apex and www from the old to the new domain via domain registrar&lt;/li&gt;
&lt;li&gt;Onboard new domain into Google Search Console and trigger an address change from the old to the new domain&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And aside from Google not being cooperative the first couple of days, it worked flawlessly this time!
What I didn’t do is redirect files and images.
Frankly, a bit too much for me, since Astro outputs images with generated filenames.&lt;/p&gt;
&lt;h2 id=&quot;moving-on&quot;&gt;Moving on&lt;/h2&gt;
&lt;a href=&quot;https://jama.me/_astro/jama-me-2024.CoIsWVJ4_1GiMjJ.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/jama-me-2024.CoIsWVJ4_ZjQpD1.jxl, https://jama.me/_astro/jama-me-2024.CoIsWVJ4_1Mf2fD.jxl 1.5x, https://jama.me/_astro/jama-me-2024.CoIsWVJ4_2hNsA9.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/jama-me-2024.CoIsWVJ4_18R2TP.avif, https://jama.me/_astro/jama-me-2024.CoIsWVJ4_Z1OdD0r.avif 1.5x, https://jama.me/_astro/jama-me-2024.CoIsWVJ4_27BFqa.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/jama-me-2024.CoIsWVJ4_Z1hrqzm.webp, https://jama.me/_astro/jama-me-2024.CoIsWVJ4_OE1ji.webp 1.5x, https://jama.me/_astro/jama-me-2024.CoIsWVJ4_Z23uhrQ.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/jama-me-2024.CoIsWVJ4_1uGjnu.png&quot; srcset=&quot;https://jama.me/_astro/jama-me-2024.CoIsWVJ4_Z1somwM.png 1.5x, https://jama.me/_astro/jama-me-2024.CoIsWVJ4_Z22qnsj.png 2x&quot; alt=&quot;Website screenshot, jama.me 2024 website&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;429&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Loosely following web simplicity &lt;a href=&quot;https://motherfuckingwebsite.com/&quot;&gt;good practices&lt;/a&gt; (wouldn’t exactly call them the &lt;em&gt;best&lt;/em&gt; practices though), I kept the site really simple in this first iteration.
I think it looks good and reads well in both dark and light mode and on desktop and mobile screens.
Of course, being a static website, it performs well according to Google:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_Z1Jzvr9.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_Z1mMah0.jxl, https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_ZGMnnO.jxl 1.5x, https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_1gfxMd.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_KPFCE.avif, https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_1qPsvP.avif 1.5x, https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_1xp3qo.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_ZYAMDT.webp, https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_ZjB0KI.webp 1.5x, https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_Z2cynix.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_vg7Eb.png&quot; srcset=&quot;https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_1bfTxm.png 1.5x, https://jama.me/_astro/jama-me-pagespeed.jV9IdznL_Z1hs3Xp.png 2x&quot; alt=&quot;Website screenshot, PageSpeed Insights overall score 100&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;133&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;I also made a slight adjustment to my logo.
Initially, when I just introduced the logo around 2017, I heard that it looks too generic.
Lately, I asked again, and overwhelmingly heard that it’s &lt;em&gt;the&lt;/em&gt; symbol representing me, it looks cool, and it shouldn’t change under any circumstances.
So I kept it simple, only introducing a slant and a little refinement.&lt;/p&gt;
&lt;p&gt;&lt;svg width=&quot;1.59em&quot; height=&quot;1em&quot;&gt;   &lt;symbol id=&quot;ai:local:maslov&quot; viewBox=&quot;0 0 378 239&quot;&gt;&lt;path fill=&quot;currentColor&quot; fill-rule=&quot;evenodd&quot; d=&quot;M17.83 238.75a15.5 15.5 0 0 1-14.32-5.34c-3.55-4.19-4.5-9.77-2.43-14.4l80.23-179.6a12.54 12.54 0 0 1 9.59-7.2c4.32-.7 8.9.63 12.38 3.6l60.85 52.15c6 5.13 13.7 7.86 21.34 7.55 7.63-.31 14.55-3.63 19.15-9.2l67.55-81.7c2.9-3.5 7.59-5.18 12.42-4.44 4.84.75 9.15 3.8 11.43 8.11l79.6 150.1a13.3 13.3 0 0 1 .49 11.87 12.55 12.55 0 0 1-9.33 6.97z&quot;&gt;&lt;/path&gt;&lt;/symbol&gt;&lt;use href=&quot;#ai:local:maslov&quot;&gt;&lt;/use&gt;  &lt;/svg&gt;&lt;/p&gt;
&lt;p&gt;I think it looks less bland and more dynamic this way while retaining the shape.&lt;/p&gt;
&lt;p&gt;Aside from me being conflicted about the domain change, I’m now finally happy with my website’s setup and am motivated to write more.
The hosting will have to prove itself first before I really trust it.
And I’ll be on the lookout for a CDN static site hosting provider that fits the bill.&lt;/p&gt;
&lt;p&gt;Anyway, here’s to lots more years!&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - Russian Doll Hair Extensions</title><link>https://jama.me/projects/russiandoll/</link><guid isPermaLink="true">https://jama.me/projects/russiandoll/</guid><description>A bespoke customer-facing appointment booking system and an online store.</description><pubDate>Thu, 01 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Visit the website here: &lt;a href=&quot;https://www.russiandoll.com.au&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;russiandoll.com.au&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I’ve been approached by the team at Russian Doll Hair Extensions, a hair extensions studio in Sydney, Australia.
They were having troubles with organizing appointments.
Since the salon grew from a one-person operation, the processes around it weren’t adjusted in accordance with the team size.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/booking.oAFZYt3a_Z1A0MVL.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/booking.oAFZYt3a_2sIPFB.jxl, https://jama.me/_astro/booking.oAFZYt3a_Z1BWCfE.jxl 1.5x, https://jama.me/_astro/booking.oAFZYt3a_xr0ND.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/booking.oAFZYt3a_Z1DTbVY.avif, https://jama.me/_astro/booking.oAFZYt3a_ZEow4j.avif 1.5x, https://jama.me/_astro/booking.oAFZYt3a_ZcpHki.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/booking.oAFZYt3a_1sP9qQ.webp, https://jama.me/_astro/booking.oAFZYt3a_2skOjw.webp 1.5x, https://jama.me/_astro/booking.oAFZYt3a_6PpdR.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/booking.oAFZYt3a_nawGT.png&quot; srcset=&quot;https://jama.me/_astro/booking.oAFZYt3a_1mFczz.png 1.5x, https://jama.me/_astro/booking.oAFZYt3a_1OAf6V.png 2x&quot; alt=&quot;Website screenshot, Russian Doll Hair Extensions booking website&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;398&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;h2 id=&quot;my-involvement&quot;&gt;My Involvement&lt;/h2&gt;
&lt;p&gt;The whole process of booking appointments had to be done from scratch, since they were managed via direct private text messages with customers beforehand.
I handled the whole project from start to finish, incorporating feedback as I went:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating an exhaustive list of requirements&lt;/li&gt;
&lt;li&gt;Solution architecture&lt;/li&gt;
&lt;li&gt;Application architecture&lt;/li&gt;
&lt;li&gt;UX design&lt;/li&gt;
&lt;li&gt;Security engineering&lt;/li&gt;
&lt;li&gt;Backend engineering&lt;/li&gt;
&lt;li&gt;Frontend engineering&lt;/li&gt;
&lt;li&gt;Testing and bringing the solution online&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, I heavily modified a Shopify theme and worked out a concept of presenting the products as required and brought the store to launch.
I also created a rudimentary company landing page website that served as a substitute until the Shopify store launched.&lt;/p&gt;
&lt;h2 id=&quot;hair-extensions-are-tough&quot;&gt;Hair Extensions Are Tough!&lt;/h2&gt;
&lt;p&gt;I knew from the start that time was a major consideration in this project, so I wouldn’t be able to engineer a whole appointment booking system.
I assembled a list of potential appointment booking systems that I evaluated based on the given requirements.&lt;/p&gt;
&lt;p&gt;Hair extensions turned out to be a pretty difficult thing to book appointments for!&lt;/p&gt;
&lt;p&gt;The requirements included handling of payments via &lt;a href=&quot;https://squareup.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Square&lt;/a&gt;, being able to ask potential customers a pretty extensive list of questions, so the team knew before the appointment what materials they had to have prepared.
But the real kicker was that they required customers getting a cost estimate for their appointment configuration.
Usually, that’d be a given. However, as it turned out, appointment times and the material costs can vary &lt;strong&gt;a lot&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;All the systems I explored offered a questionnaire to fill out in order to book an appointment.
None of them allowed answers to those questions to influence the cost estimate.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://acuityscheduling.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Acuity Scheduling&lt;/a&gt; was the only one that allowed for a list of options, but those also
weren’t connected to the questionnaire.&lt;/p&gt;
&lt;h2 id=&quot;booking-the-solution&quot;&gt;Booking: The Solution&lt;/h2&gt;
&lt;p&gt;I went with Acuity Scheduling for its &lt;a href=&quot;https://developers.acuityscheduling.com/reference&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;API access&lt;/a&gt; and Square integration.
I expected that would yield the best chances for being a workable choice. Most systems didn’t even offer a publicly available API.&lt;/p&gt;
&lt;p&gt;To be able to share types and have a smoother development experience (in the name of time), I decided on using a node.js backend with a React frontend.
A very traditional choice for the frontend, but that made getting out a first version easier, which could then be ported to Preact for performance reasons.
The whole solution does not require a database. Everything needed simply lives in application memory.&lt;/p&gt;
&lt;p&gt;To reduce development time further, the backend API is based on a &lt;a href=&quot;https://tinyhttp.v1rtl.site/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tinyhttp&lt;/a&gt; server.
To avoid spending too much time on building components for the frontend, I decided on &lt;a href=&quot;https://mui.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mui&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-backend&quot;&gt;The Backend&lt;/h3&gt;
&lt;p&gt;In the beginning of the project I tried to engineer the backend API on &lt;a href=&quot;https://bun.sh&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Bun&lt;/a&gt; and &lt;a href=&quot;https://elysiajs.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Elysia&lt;/a&gt; to try the framework’s automatic validation feature, but Bun turned out to be unstable at that point.
I had to port the project over as a result. The solution was a little too bleeding edge. Thankfully I started testing early, so I noticed when the project wasn’t as far along at that point.&lt;/p&gt;
&lt;h3 id=&quot;acute-woes&quot;&gt;Acute Woes&lt;/h3&gt;
&lt;p&gt;It was clear that Acuity Scheduling’s integrated booking interface wouldn’t provide all required features, so I had to build a custom booking frontend.&lt;/p&gt;
&lt;h4 id=&quot;the-estimate&quot;&gt;The Estimate&lt;/h4&gt;
&lt;p&gt;The idea was pretty simple: The custom-built parts of the solution would handle the business logic of deciding on the cost and time estimates.
Then, a fitting appointment would be created in Acuity.&lt;/p&gt;
&lt;p&gt;Unfortunately, it turned out that Acuity’s API didn’t allow booking appointments that weren’t pre-configured.
Additionally, if answers to the questionnaire were to be stored alongside the appointment for the team to see, they had to be created in Acuity beforehand.
On top of all this, the API also didn’t allow setting custom appointment lengths and prices. They must consist of data that’s living in Acuity’s system.
This threw a wrench into my plans.&lt;/p&gt;
&lt;p&gt;Thankfully Acuity offers appointment options. Usually meant for things loosely related to the appointment, like a coffee for your appointment at the barber, options allow prolonging appointments and raising their price.
When creating an appointment, the backend will calculate the estimate and then assemble a combination of appointment options previously defined in Acuity that will closely approximate the estimate’s time.
That would allow for the rest of the systems at play to work, like letting Acuity finding the best times for the appointment based on working hours and other existing appointments.&lt;/p&gt;
&lt;p&gt;But money was another issue. While appointment options offered to also increase the price of an appointment, the configuration options would vary so wildly that I wasn’t able to approximate an appointment’s price using only appointment options created in Acuity.
Because Acuity’s API also didn’t allow setting a custom price, I had to drop using Acuity’s Square integration and implement it myself in the backend.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/booking-summary.D9hSV8Wa_2lzsMt.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/booking-summary.D9hSV8Wa_ZhfDcE.jxl, https://jama.me/_astro/booking-summary.D9hSV8Wa_ZkiyJl.jxl 1.5x, https://jama.me/_astro/booking-summary.D9hSV8Wa_Z1VEYMK.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/booking-summary.D9hSV8Wa_1QncH0.avif, https://jama.me/_astro/booking-summary.D9hSV8Wa_1Nkhaj.avif 1.5x, https://jama.me/_astro/booking-summary.D9hSV8Wa_Z1Evu9z.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/booking-summary.D9hSV8Wa_5UIpr.webp, https://jama.me/_astro/booking-summary.D9hSV8Wa_2RMRK.webp 1.5x, https://jama.me/_astro/booking-summary.D9hSV8Wa_ZkhM4z.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/booking-summary.D9hSV8Wa_1AMDIw.png&quot; srcset=&quot;https://jama.me/_astro/booking-summary.D9hSV8Wa_1xJIbP.png 1.5x, https://jama.me/_astro/booking-summary.D9hSV8Wa_zNwfy.png 2x&quot; alt=&quot;Website screenshot, customer sees the summary of their appointment configuration&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;436&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;h4 id=&quot;complex-appointments&quot;&gt;Complex Appointments&lt;/h4&gt;
&lt;p&gt;Another requirement that cropped up later on was handling of what I call complex appointments.
Doing hair extensions requires a lot of effort. The appointments are not only pretty expensive, but can also take a whole workday (or even more).&lt;/p&gt;
&lt;p&gt;Some types of appointments require multiple workers for a single customer. And after re-evaluating all appointment booking systems, none of them support that.
So choosing Acuity over some other system made no difference here, but the problem still had to be solved.&lt;/p&gt;
&lt;p&gt;The biggest problem was performance. Customers must be able to see at a glance what days in a given month their appointment configuration can be booked. Blocked days should be grayed out.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/booking-dates.MQ8jpOxn_10qsMs.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/booking-dates.MQ8jpOxn_Z22S0Et.jxl, https://jama.me/_astro/booking-dates.MQ8jpOxn_1N8sNP.jxl 1.5x, https://jama.me/_astro/booking-dates.MQ8jpOxn_2g8NBc.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/booking-dates.MQ8jpOxn_Z2d4MOs.avif, https://jama.me/_astro/booking-dates.MQ8jpOxn_1CVFDQ.avif 1.5x, https://jama.me/_astro/booking-dates.MQ8jpOxn_2altlD.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/booking-dates.MQ8jpOxn_Z1iYBSx.webp, https://jama.me/_astro/booking-dates.MQ8jpOxn_2x1QzL.webp 1.5x, https://jama.me/_astro/booking-dates.MQ8jpOxn_ZlcOCv.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/booking-dates.MQ8jpOxn_Z1hUHT0.png&quot; srcset=&quot;https://jama.me/_astro/booking-dates.MQ8jpOxn_Z2w6neC.png 1.5x, https://jama.me/_astro/booking-dates.MQ8jpOxn_bL78g.png 2x&quot; alt=&quot;Website screenshot, customer is selecting a date and time for an appointment&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;436&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;The naive way would have been to query the working hours and appointments of each worker for every day in a given month and find possible time slots that way.
But not only does Acuity Scheduling’s API not provide working hour information, but the API is also made in a way that doesn’t allow for bulk querying. This would mean querying days one-by-one.
That would’ve been unacceptable, since it would take a pretty long time (on the order of tens of seconds) per employee.&lt;/p&gt;
&lt;p&gt;Using Puppeteer I built an automation that would periodically log into Acuity Scheduling using an account created specifically for this purpose and scrape working hours from the website there.
This information would be cached and later used when assembling possible time slots for complex appointments.&lt;/p&gt;
&lt;p&gt;When requesting possible days for an appointment requiring multiple employees, the per-day working hours data of each employee able to handle this appointment would be used to create all possible time slots for a month.
Then, thankfully, Acuity’s API allows for querying an employee’s existing appointments for up to a year in advance. This data is cached periodically as well.
In combination, these datasets allow to filter all theoretically possible time slots down to what’s actually possible, per employee.&lt;/p&gt;
&lt;p&gt;Finally, we can then determine the best combination of employees for each day in the month the customer requested in the frontend, by prioritizing employees with the most time that day.&lt;/p&gt;
&lt;p&gt;Getting to that point was a doozy.&lt;/p&gt;
&lt;h3 id=&quot;performance&quot;&gt;Performance&lt;/h3&gt;
&lt;p&gt;As mentioned previously, performance was a pretty important consideration of the booking website.
While React wasn’t the best choice in that regard, porting to Preact will improve the already pretty acceptable performance, as shown by PageSpeed Insights:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/booking-pagespeed.gcCUS7VG_ZTdPV6.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/booking-pagespeed.gcCUS7VG_Uvksz.jxl, https://jama.me/_astro/booking-pagespeed.gcCUS7VG_Z1Uh9vr.jxl 1.5x, https://jama.me/_astro/booking-pagespeed.gcCUS7VG_ZGILvJ.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/booking-pagespeed.gcCUS7VG_Zw3nfH.avif, https://jama.me/_astro/booking-pagespeed.gcCUS7VG_1Hlgzd.avif 1.5x, https://jama.me/_astro/booking-pagespeed.gcCUS7VG_3pil2.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/booking-pagespeed.gcCUS7VG_4jlIO.webp, https://jama.me/_astro/booking-pagespeed.gcCUS7VG_2iI0yJ.webp 1.5x, https://jama.me/_astro/booking-pagespeed.gcCUS7VG_Z1MOpar.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/booking-pagespeed.gcCUS7VG_Z1UEFVI.png&quot; srcset=&quot;https://jama.me/_astro/booking-pagespeed.gcCUS7VG_iIWSc.png 1.5x, https://jama.me/_astro/booking-pagespeed.gcCUS7VG_2jf6lV.png 2x&quot; alt=&quot;Website screenshot, Russian Doll Hair Extensions booking website&apos;s PageSpeed Insights overall score of 86.&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;436&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Because customers will often come to the booking site with an intent to at least get to the summary to see the price estimate, the booking website has a lesser role in converting customers.
But it nevertheless shouldn’t deter them by making the process of booking take a long time. So after the considerations described above, getting available days and times for a complex appointment with two employees takes under a second in most cases.&lt;/p&gt;
&lt;p&gt;The website is fully static, so it’s served via a CDN. The backend is hosted on a VPS in Sydney though, because customers can upload photos, and they’re being stored on there for cost reasons.&lt;/p&gt;
&lt;h2 id=&quot;russian-dolls-company-website&quot;&gt;Russian Doll’s Company Website&lt;/h2&gt;
&lt;p&gt;Aside from enabling customers to book appointments and generally representing the company online, one of the main goals all along was to sell high quality hair (the same used in the salon) and related accessories via an online store.
But taking the pressure off in terms of managing appointments and employees had the highest priority.
Figuring out the proper way to segment and display products had to come later.&lt;/p&gt;
&lt;p&gt;Knowing that, I prepared parts of the store, like the general design language, and some of the preliminary texts in advance.&lt;/p&gt;
&lt;p&gt;But a lightweight landing page had to come before that to represent them on the web, and to steer customers towards the new appointment booking website.&lt;/p&gt;
&lt;h3 id=&quot;the-landing-page&quot;&gt;The Landing Page&lt;/h3&gt;
&lt;p&gt;To give the website more credence, we needed media to show off the place, the equipment and the people.
Because I’m operating from Switzerland, I had the team order a photographer and take detailed pictures of the salon.
Then, I directed them as to the “glamour” photos they should take, the angles and actions portrayed in them.
I think they turned out great!&lt;/p&gt;
&lt;p&gt;I incorporated them into the Shopify store.
Then I literally saved the store’s partially done homepage via the browser, converted it into an &lt;a href=&quot;https://astro.build/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Astro&lt;/a&gt; one-pager and polished it up.
That would make the eventual transition to the store under the same address less jarring for visitors, as the visuals would remain preserved.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/website.iJfs-pms_1JcsO9.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/website.iJfs-pms_D73FV.jxl, https://jama.me/_astro/website.iJfs-pms_ecNOD.jxl 1.5x, https://jama.me/_astro/website.iJfs-pms_ZAuXN0.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/website.iJfs-pms_1AF9Rh.avif, https://jama.me/_astro/website.iJfs-pms_1bKU0Y.avif 1.5x, https://jama.me/_astro/website.iJfs-pms_Z1lmGVV.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/website.iJfs-pms_ZlLCxO.webp, https://jama.me/_astro/website.iJfs-pms_ZKFRp7.webp 1.5x, https://jama.me/_astro/website.iJfs-pms_Z126znL.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/website.iJfs-pms_Z1rrfhL.png&quot; srcset=&quot;https://jama.me/_astro/website.iJfs-pms_Z1Qlu94.png 1.5x, https://jama.me/_astro/website.iJfs-pms_FDfui.png 2x&quot; alt=&quot;Website screenshot, Russian Doll Hair Extensions landing website&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;436&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Of course, I paid utmost attention to optimization, so the page loaded quickly and didn’t waste time to show potential customers what they can expect.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/website-pagespeed.DVOgqX8u_ZNB9oQ.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/website-pagespeed.DVOgqX8u_Z3kWz9.jxl, https://jama.me/_astro/website-pagespeed.DVOgqX8u_2b3GfL.jxl 1.5x, https://jama.me/_astro/website-pagespeed.DVOgqX8u_ZB74Yu.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/website-pagespeed.DVOgqX8u_Z1uTFiq.avif, https://jama.me/_astro/website-pagespeed.DVOgqX8u_ItXwu.avif 1.5x, https://jama.me/_astro/website-pagespeed.DVOgqX8u_91YRh.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/website-pagespeed.DVOgqX8u_ZTwViT.webp, https://jama.me/_astro/website-pagespeed.DVOgqX8u_1jQHw1.webp 1.5x, https://jama.me/_astro/website-pagespeed.DVOgqX8u_Z1HcHDc.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/website-pagespeed.DVOgqX8u_2aF9Ou.png&quot; srcset=&quot;https://jama.me/_astro/website-pagespeed.DVOgqX8u_ZF7k9w.png 1.5x, https://jama.me/_astro/website-pagespeed.DVOgqX8u_2oQMSb.png 2x&quot; alt=&quot;Website screenshot, Russian Doll Hair Extensions landing website&apos;s PageSpeed Insights overall score of 99&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;436&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Astro made the transition really easy.
The hardest part was stripping stuff that was useless for the one-pager, like all of the store features and related styling, without breaking the general look.&lt;/p&gt;
&lt;h3 id=&quot;the-shopify-store&quot;&gt;The Shopify Store&lt;/h3&gt;
&lt;p&gt;It turned out that hair of a quality that people actually want to have on 24/7 is hard to come by, especially in Australia.
So Russian Doll decided to start distributing it in an easy way, since they are already sourcing the products they use for appointments anyway.&lt;/p&gt;
&lt;p&gt;Knowing how the team used &lt;a href=&quot;https://squareup.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Square&lt;/a&gt; as a payment processor (POS and now also digital) extensively because of their mostly painless setup process, I knew that the best path forward would be to use &lt;a href=&quot;https://www.shopify.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Shopify&lt;/a&gt; as the undercarriage.
This would not only provide good enough performance and UX (especially Shopify’s checkout is excellent), but also absolve me of many hosting and maintenance-related tasks in the long run.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/store.Bx-0K1fB_Z2r8YCi.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/store.Bx-0K1fB_ZvSQ1D.jxl, https://jama.me/_astro/store.Bx-0K1fB_Ovgjb.jxl 1.5x, https://jama.me/_astro/store.Bx-0K1fB_1bUeTf.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/store.Bx-0K1fB_2d2C28.avif, https://jama.me/_astro/store.Bx-0K1fB_Z1vJoqY.avif 1.5x, https://jama.me/_astro/store.Bx-0K1fB_Zpfr5K.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/store.Bx-0K1fB_Z1Iy37Q.webp, https://jama.me/_astro/store.Bx-0K1fB_Zn8UM2.webp 1.5x, https://jama.me/_astro/store.Bx-0K1fB_ZD6SvO.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/store.Bx-0K1fB_21dX0n.png&quot; srcset=&quot;https://jama.me/_astro/store.Bx-0K1fB_Z1Hy3sJ.png 1.5x, https://jama.me/_astro/store.Bx-0K1fB_Z1khDac.png 2x&quot; alt=&quot;Website screenshot, Russian Doll Hair Extensions Shopify store&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;398&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Because I needed to quickly set up the store to then convert parts of it into the one-pager mentioned previously, I chose to go with a theme and modify it.
The theme that ticked the most boxes turned out to be Shopify’s own &lt;a href=&quot;https://themes.shopify.com/themes/refresh/styles/default&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Refresh&lt;/a&gt; - a pretty fully-featured theme in terms of the basics in my opinion.
It’s not exactly lightweight, but far from heavy as far as store themes go (counting in things like WooCommerce, Shopware, etc.), and it has lots of customization features to boot.
I wish Shopify placed a little more emphasis on &lt;a href=&quot;https://shopify.dev/docs/storefronts/headless/hydrogen/getting-started&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hydrogen and Oxygen&lt;/a&gt;, their headless backend offering, to build up its legacy like it did over the years with the server-side rendered Liquid-based themes.&lt;/p&gt;
&lt;p&gt;For the setup that went online in the end, the theme still required quite a few source code changes.
That was in part to accommodate the “Luxe” hair extensions products that are condensed to a single product page, with flexible configuration options and the ability to purchase on a per-gram basis.&lt;/p&gt;
&lt;p&gt;But nevertheless, the end product still performs admirably:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/store-pagespeed.BG3CId-G_105HQ6.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/store-pagespeed.BG3CId-G_Z1dUKcO.jxl, https://jama.me/_astro/store-pagespeed.BG3CId-G_Z1TbcJ0.jxl 1.5x, https://jama.me/_astro/store-pagespeed.BG3CId-G_16I6z7.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/store-pagespeed.BG3CId-G_TH5GP.avif, https://jama.me/_astro/store-pagespeed.BG3CId-G_erDaE.avif 1.5x, https://jama.me/_astro/store-pagespeed.BG3CId-G_1nRBdi.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/store-pagespeed.BG3CId-G_ZPJnzI.webp, https://jama.me/_astro/store-pagespeed.BG3CId-G_Z1vYP6T.webp 1.5x, https://jama.me/_astro/store-pagespeed.BG3CId-G_Z2m5OvD.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/store-pagespeed.BG3CId-G_E7wIm.png&quot; srcset=&quot;https://jama.me/_astro/store-pagespeed.BG3CId-G_Z17TMO.png 1.5x, https://jama.me/_astro/store-pagespeed.BG3CId-G_Z1qYvbv.png 2x&quot; alt=&quot;Website screenshot, Russian Doll Hair Extensions store&apos;s PageSpeed Insights overall score of 92&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;429&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;h2 id=&quot;verdict&quot;&gt;Verdict&lt;/h2&gt;
&lt;p&gt;I’m glad I took on this project. I got to try Bun in a real production project (and ended up empty handed), but more importantly it was a great exercise in integrating with not exactly favorable external systems.&lt;/p&gt;
&lt;p&gt;The business requirements meant that performance optimizations were very important and appreciated.
I’m happy that I was able to find workarounds to make it possible.
The custom work done to make questionnaire answers influence the time and cost estimates probably makes this the only booking website capable of providing deeper insights for both customers and employees.&lt;/p&gt;
&lt;p&gt;In any case, the team has been using Acuity Scheduling and the new booking website since the beginning of 2024, and has been delighted by it.
After they onboarded all customers that were currently still handled via direct messages, and started directing repeat customers to the booking page, word quickly spread about it, and the team is regularly booked out as a result.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - KateMotive</title><link>https://jama.me/projects/katemotive/</link><guid isPermaLink="true">https://jama.me/projects/katemotive/</guid><description>A brand from scratch, plus a full-stack production management application.</description><pubDate>Fri, 01 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the beginning of the COVID-19 lockdown, a sudden demand for face masks appeared.
I’ve been consulted to help out in my family.
My stepmother is a studied clothing engineer, and she, together with my father, decided to chime in.
She came up with a face mask design that surpassed other available ones in comfort, but they were pretty complex to make.&lt;/p&gt;
&lt;p&gt;They organized the production on paper cards though.
And as demand started to rise, they could barely keep up between organizing and actually making the masks, working morning until night every day.&lt;/p&gt;
&lt;h2 id=&quot;my-involvement&quot;&gt;My involvement&lt;/h2&gt;
&lt;p&gt;Throughout my helping them out, I was involved in all aspects of the newly founded business, starting with the business registration and with handling taxes.
I handled all digital aspects, like managing the Amazon and Etsy stores, doing convincing product photography, customer support (that there was a lot of), and more.
On the non-digital front I was involved in procurement of production materials, logistics, packaging products for shipping, and more.&lt;/p&gt;
&lt;p&gt;The most interesting tasks though, were building a brand from zero and the production management software I made.&lt;/p&gt;
&lt;h2 id=&quot;the-brand&quot;&gt;The brand&lt;/h2&gt;
&lt;p&gt;We decided that, since the product was high quality, it’d be beneficial to build a brand reputation.
But because the business originated literally from a living room, budget was extremely tight.
So we decided to start at the touchpoints with customers.
That meant standardizing the product photography and product pages in online stores, and introducing a brand with the potential to be recognized.
The latter had to happen on the cheap.&lt;/p&gt;
&lt;p&gt;The name was already decided upon, so I had to work with it. After some iteration I came up with the right combination of the logo and wordmark:&lt;/p&gt;
&lt;p&gt;&lt;svg width=&quot;6.11em&quot; height=&quot;1em&quot;&gt;   &lt;symbol id=&quot;ai:local:katemotive-logo&quot; viewBox=&quot;0 0 5597 917&quot;&gt;&lt;path fill=&quot;currentColor&quot; fill-rule=&quot;evenodd&quot; d=&quot;M1195 776.7v-17.1l75-12.3V153l-75-11.4v-18h240v18l-75 11.4v293.9l265.3-271-89-34.3v-18h188.6v18l-67 34.2L1472.7 362l261.2 385.3 55.5 12.3v17.1h-240.8v-17.1l74.3-11.4-214-322.5-48.9 49.8v271.8l75.1 12.3v17.1h-240Zm756.8 8.2c-35.1 0-63.7-9-85.8-27.8-22-18-33.4-44.9-33.4-80.8 0-51.4 21.2-87.4 64.5-109.4q64.8-31.8 149.4-31.8h54.7v-62.9c0-28.6-3.3-50.6-9-66.1a64.3 64.3 0 0 0-29.4-32.7c-13-5.7-31-9-53-9-25.4 0-45 5-57.2 14.7a51.2 51.2 0 0 0-18.8 40.9c0 15.5 7.3 26 22.9 31 0 13.9-5 25.3-15.6 35.9a50 50 0 0 1-36.7 15.5c-15.5 0-27.7-4-36.7-13-9-8.2-14-21.3-14-38.4 0-31.9 15.6-57.2 47.4-76 31.9-18.7 71-28.5 118.4-28.5 108.6 0 163.3 47.3 163.3 142V722c0 12.3 2.5 21.3 8.2 27 4.9 6.5 12.2 9.8 22 9.8a28 28 0 0 0 22-9.8c5.8-5.7 8.2-14.7 8.2-25.3h17.2c0 18.7-5.7 34.2-18 44.9-12.2 10.6-29.4 16.3-51.4 16.3a96 96 0 0 1-63.7-22c-16.3-14-25.3-36.8-26.1-67a144.3 144.3 0 0 1-59.6 65.3 177.8 177.8 0 0 1-89.8 23.7m44.9-36c14.7 0 29.4-4 45.7-13a110 110 0 0 0 39.2-38.4 132 132 0 0 0 19.6-59.6V553h-54.7c-45 0-77.6 9.8-99.6 30.2-22 19.6-33.5 47.4-33.5 83.3 0 30.2 7.3 51.4 21.2 63.7 13.9 12.2 35.1 18.8 62 18.8Zm439.7 36c-35.9 0-64.5-9-85.7-27-22-18-32.7-46.5-32.7-85.7v-293l-58-8.2v-16.4h58V202l81.7-20.4v173h106.1V371l-106.1 8.1v291.5c0 58 20.4 86.5 62 86.5 9 0 17.2-1.6 25.3-4 7.4-2.5 14-5.8 18.8-8.2 4.1-2.5 7.4-5 8.2-5.7l10.6 13a127 127 0 0 1-88.2 32.7m318.4 0c-40 0-76.7-9-109.4-27.8a194.8 194.8 0 0 1-77.5-76.7 231.4 231.4 0 0 1-28.6-115.1c0-43.3 9.8-81.7 28.6-115.1 18.7-32.7 44-58 75.9-76.8 31.8-18 67-27 106.1-27a182 182 0 0 1 92.3 23 167 167 0 0 1 63.7 62.8c15.5 27 22.8 81.6 23.6 116.7h-300.4v57.2c0 48.2 10.6 86.5 32.7 116 21.2 30.1 52.2 44.8 93 44.8a163 163 0 0 0 99.6-31.8 132 132 0 0 0 53.1-82.5h18.8c-7.4 40-27 72.7-58 96.4-31 23.6-68.6 35.9-113.5 35.9m85-256.4c0-47.3-7.4-106.1-22.1-129.8-14.7-22.8-37.6-34.3-68.6-34.3-36.7 0-64.5 13.1-84.9 38.4s-31.8 83.3-34.3 125.7h209.8ZM3334.4 785l-243.3-632v574l78.4 32.7v17.1h-174.7v-17.1l76.7-32.7V153l-75.1-11.3v-18h180.4l205 524.1 209-524.1h164.9v18l-75.1 11.4v594.3l75 12.3v17.1h-240v-17.1l75.1-12.3V177.5l-241.6 607.4h-14.7Zm659.6 0a207 207 0 0 1-107-27.8c-31.7-18-57-44-75.8-76.7a233.7 233.7 0 0 1-28.6-115.1c0-43.3 9.8-80.9 28.6-114.3 18.7-33.5 44-58.8 75.9-77.6 31.8-18 67.8-27 107-27 39.1 0 75 9 106.9 27a212 212 0 0 1 76.7 77.6 228 228 0 0 1 27.8 114.3c0 43.2-9 80.8-27.8 115a209.7 209.7 0 0 1-76.7 76.8 207 207 0 0 1-107 27.8m0-18c40 0 71-14.7 90.7-44 21.2-29.4 31-68.6 31-116.8v-81.6c0-48.2-9.8-87.4-31-116.8-19.6-28.6-50.7-43.3-90.7-43.3s-70.2 14.7-90.6 43.3c-20.4 29.4-31 68.6-31 116.8V606c0 48.2 10.6 87.4 31 116.7q30.6 44.1 90.6 44.1Zm413.2 18c-36 0-64.5-9-85.7-27-22-18-32.7-46.5-32.7-85.7v-293l-58-8.2v-16.4h58V202l81.6-20.4v173h106.2V371l-106.2 8.1v291.5c0 58 20.4 86.5 62 86.5 9 0 17.2-1.6 25.4-4 7.3-2.5 13.9-5.8 18.8-8.2 4-2.5 7.3-5 8.1-5.7l10.6 13a127 127 0 0 1-88.1 32.7Zm233.4-552.7a40 40 0 0 1-29.3-12.3 37 37 0 0 1-12.3-28.5 40 40 0 0 1 12.3-29.4c7.3-7.4 18-11.5 29.3-11.5 11.5 0 20.5 4.1 28.6 11.5a42.8 42.8 0 0 1 0 58 38.3 38.3 0 0 1-28.6 12.2M4542 776.7v-16.3l57.1-8.2v-373L4542 371v-16.4h138.8v397.6l57.1 8.2v16.3h-196Zm429.4 16.4-174.7-414.8-53-8.2v-16.3h203.2v16.3l-58.8 10.7 123.3 307 110.2-296.4-71.8-20.4v-16.4h146.1V371l-51.4 19.6L4994.3 793h-22.9Zm450.7-8.2c-40 0-76.8-9-109.4-27.8a194.8 194.8 0 0 1-77.6-76.7 231.4 231.4 0 0 1-28.6-115.1c0-43.3 9.8-81.7 28.6-115.1 18.8-32.7 44.1-58 76-76.8 31.8-18 66.9-27 106-27a182 182 0 0 1 92.3 23 167 167 0 0 1 63.7 62.8c15.5 27 22.9 81.6 23.7 116.7h-300.4v57.2c0 48.2 10.6 86.5 32.6 116 21.2 30.1 52.3 44.8 93 44.8a163 163 0 0 0 99.7-31.8 132 132 0 0 0 53-82.5h18.8c-7.3 40-27 72.7-58 96.4-31 23.6-68.5 35.9-113.4 35.9m84.9-256.4c0-47.3-7.4-106.1-22-129.8-14.7-22.8-37.6-34.3-68.6-34.3-36.8 0-64.5 13.1-85 38.4-20.3 25.3-31.8 83.3-34.2 125.7zm-4833.7 3.6a274 274 0 0 1 127.9 188.3L821.6 837l-83-22.8a17.9 17.9 0 0 0-22.6 16.4l-4 86-104.6-55.5A278.4 278.4 0 0 1 502.5 764a302.6 302.6 0 0 0 29.3-79.3 35.8 35.8 0 0 0-4-70L517 563.4l55.5 76.4a18 18 0 0 0 3.1 3.2 35.7 35.7 0 1 0 28-20.3c-.6-1.4-1.3-2.7-2.2-4l-55.5-76.3 52.7 30.4a36 36 0 0 0 35.8 35.9 36 36 0 0 0 35.8-35.8 36 36 0 0 0-53.8-31l-53-30.6 68.3 7a35.8 35.8 0 0 0 40.7 14.3l1-.4Zm-68.1 259.7a18 18 0 0 0-2-25.1c-8.2-7-15.4-14.7-21.6-23.3a17.9 17.9 0 1 0-29 21c8 10.9 17.2 20.8 27.4 29.5a18 18 0 0 0 25.2-2Zm69.7 36a17.9 17.9 0 0 0-7.7-24l-17.7-9.3a17.9 17.9 0 1 0-16.4 31.7l17.7 9.2a17.9 17.9 0 0 0 24-7.6ZM494.2 671c1 1.3 2 2.6 3.2 3.8a276.5 276.5 0 0 1-141 186.3l-104.6 55.6-4-86.1a18 18 0 0 0-22.5-16.4L142.2 837l20.5-116.6c8.3-47.7 29-92.3 59.8-129.6a305 305 0 0 0 78.9 3.7 35.7 35.7 0 0 0 64-21.7l52.6-30.5-55.5 76.4c-.9 1.2-1.6 2.5-2.1 4a35.8 35.8 0 1 0 27.9 20.2c1.2-1 2.2-2 3-3.2l55.6-76.4-12.7 59.5a35.9 35.9 0 0 0 11 69.8 35.8 35.8 0 0 0 23.9-62.4l12.8-59.9 12.5 58.4a35.8 35.8 0 0 0-.2 42.3M239 739.8a18 18 0 0 0-17.6-20.9 18 18 0 0 0-17.6 15l-3.3 19.8a18 18 0 0 0 17.7 20.6 18 18 0 0 0 17.6-14.8zm27.7-80.4a17.8 17.8 0 0 0-29-21c-7.8 11-14.4 22.7-19.5 35.2a17.9 17.9 0 0 0 33 13.5c4-9.8 9.2-19 15.5-27.7m30-100.4a273.8 273.8 0 0 1-211.6-77.3L0 399.4 80.6 369a17.9 17.9 0 0 0 8.6-26.6l-47.3-72L159 254c48-6.8 97-1 142 17a303.7 303.7 0 0 0 20.6 76 35.7 35.7 0 0 0 40.5 54.1l45.2 40.7-89.8-29.2a18 18 0 0 0-4.4-.8 36 36 0 0 0-35.1-29 35.9 35.9 0 0 0-35.7 35.8 35.9 35.9 0 0 0 60 26 18 18 0 0 0 4.2 2l89.7 29.2-60.5 6.3a35.8 35.8 0 1 0 3.8 35.5l61-6.4-53.1 30.7a35.6 35.6 0 0 0-50.8 17.2ZM164.2 332.5a18 18 0 0 0 15.2-17.6 18 18 0 0 0-20.5-17.7l-19.7 3a18 18 0 0 0-15.3 17.7 18 18 0 0 0 20.6 17.6zm37.5-23.8a18 18 0 0 0 16.4 19.1c10.6.8 21 3 31.1 6.2a17.9 17.9 0 1 0 11-34c-12.7-4.1-26-6.8-39.4-7.8a18 18 0 0 0-19.1 16.5M362.2 333c-2.7-.8-5.4-1.4-8.2-1.6a277.4 277.4 0 0 1 8.2-225.1L414.2 0 468 67.3a17.9 17.9 0 0 0 27.9 0L549.7 0l52 106.4a277.9 277.9 0 0 1 26.8 151c-2.6 25.4-9 50.4-18.6 74.1a35.7 35.7 0 0 0-32.1 43l-45.5 40.9 24.8-56c18-1.8 32-17.2 32-35.4a35.9 35.9 0 0 0-35.7-35.7 35.9 35.9 0 0 0-29 56.7l-24.6 55.6V306c0-1.5-.2-3-.6-4.4a35.8 35.8 0 1 0-34.6 0 22 22 0 0 0-.5 4.4v94.4L439.3 345a35.8 35.8 0 1 0-32.6 14.6l24.9 56-45.5-41a35.8 35.8 0 0 0-24-41.4Zm222-156.2a17.9 17.9 0 0 0-35.1 4.4c0 1.3.2 2.7.5 4 2.5 10.3 3.7 21 3.7 31.5a18 18 0 0 0 17.9 17.9 18 18 0 0 0 17.9-17.9c0-13.4-1.6-26.8-4.8-39.9Zm-56.7-83.2a18 18 0 0 0-8 24l9 17.8a17.9 17.9 0 1 0 31.9-16l-9-17.8a18 18 0 0 0-23.9-8m-81.3 391a35.9 35.9 0 0 1 35.7-35.7 35.9 35.9 0 0 1 35.7 35.7 35.9 35.9 0 0 1-35.7 35.7 35.8 35.8 0 0 1-35.5-32v-.1zm437-115.7 80.6 30.4-85.1 82.3a278.2 278.2 0 0 1-124.6 69.6 305.5 305.5 0 0 0-57.6-46.8 35.7 35.7 0 0 0-67-22.4l-62-6.4 89.8-29.2c2-.7 3.8-1.6 5.5-3a35.7 35.7 0 1 0-12.5-31.8c-1.4.1-2.7.4-4 .8l-89.9 29.2 45.3-40.7a35.6 35.6 0 0 0 40.4-54c0-.2.1-.2.2-.3a311 311 0 0 0 20.6-76 279 279 0 0 1 141.8-16.8L922 270.5l-47.4 72a17.9 17.9 0 0 0 8.6 26.5Zm-77 118a17.9 17.9 0 0 0-18.7-30.5 132 132 0 0 1-28.8 13.3 17.9 17.9 0 1 0 11 34c12.9-4.1 25.1-9.8 36.6-16.9Zm61.5-54.5a17.9 17.9 0 0 0-25.1-25.5l-14.2 14a17.9 17.9 0 0 0 25 25.5z&quot;&gt;&lt;/path&gt;&lt;/symbol&gt;&lt;use href=&quot;#ai:local:katemotive-logo&quot;&gt;&lt;/use&gt;  &lt;/svg&gt;&lt;/p&gt;
&lt;p&gt;Of course, one of the main points was to use the branding in a print context, not only digitally. So the main colors were defined in CMYK, since spot colors would have been more expensive to print.&lt;/p&gt;
&lt;div style=&quot;height: 6em; color: #fff&quot;&gt;&lt;div style=&quot;background-color: #E482B2&quot;&gt;&lt;span&gt;CMYK&lt;br&gt;5, 62, 2, 0&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;background-color: #BDC4D6&quot;&gt;&lt;span&gt;CMYK&lt;br&gt;10, 20, 30, 0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Pink being my stepmother’s favorite color, this was a non-conditional. She wanted the brand perception to be equally playful and glamorous, with the goal of being an indication of the product quality.
Getting the two main colors right, so they looked as intended in both print and digitally, took lots of iteration and almost a dozen rejected samples from multiple print shops.&lt;/p&gt;
&lt;p&gt;In the end, we settled on FlyerAlarm for their affordable good quality prints.
Their packaging wasn’t quite perfect though, we had some minor rejects here and there.&lt;/p&gt;
&lt;p&gt;In any case, this is how most customers received their orders:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/order-packaging.D5iLpsJu_Z1Saw1r.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/order-packaging.D5iLpsJu_Z1PG4m2.jxl, https://jama.me/_astro/order-packaging.D5iLpsJu_Z1l5pMe.jxl 1.5x, https://jama.me/_astro/order-packaging.D5iLpsJu_owUom.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/order-packaging.D5iLpsJu_hVLxC.avif, https://jama.me/_astro/order-packaging.D5iLpsJu_Mxq7q.avif 1.5x, https://jama.me/_astro/order-packaging.D5iLpsJu_FGq2x.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/order-packaging.D5iLpsJu_Z1suGIV.webp, https://jama.me/_astro/order-packaging.D5iLpsJu_ZWT3a8.webp 1.5x, https://jama.me/_astro/order-packaging.D5iLpsJu_20U87x.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/order-packaging.D5iLpsJu_2mdz9.png&quot; srcset=&quot;https://jama.me/_astro/order-packaging.D5iLpsJu_wWR8W.png 1.5x, https://jama.me/_astro/order-packaging.D5iLpsJu_Z29aGmg.png 2x&quot; alt=&quot;Two colorful face masks on top of a pink envelope, and a brown recycled paper envelope beneath&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;448&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Customers received a brown shipping envelope made from recycled paper with a modest black logo print on the top left of the front, with the shipping address opposite of that at the bottom right.
Inside, they would find the so-called pretty envelope: A striking, inside and out custom printed, pink envelope with the logo in white on the front, also made from recycled paper.
Inside that, they would find each mask neatly folded inside a white paper bag.&lt;/p&gt;
&lt;p&gt;We received lots of unsolicited customer feedback specifically citing how they liked the packaging.
Regularly we read that customers used the pretty envelope as gift packaging, even for gifts other than the face masks.&lt;/p&gt;
&lt;p&gt;Implementing the branding into the packaging this way turned out to be a significant success on our journey to convince customers.
And it was really cheap, too. FlyerAlarm would make 500 pretty envelopes for about €100. 500 of the brown shipping envelopes would run in the neighborhood of €50-80, same for the paper bags.
For about €0.50-0.60 per order we had a value-add that didn’t appear to be offered by other sellers, and was actually appreciated by a significant number of customers.&lt;/p&gt;
&lt;p&gt;I believe that was part of the magic that made straight 4-5 star reviews on both Amazon and Etsy possible. The average across both platforms and all products was 4.6.&lt;/p&gt;
&lt;h2 id=&quot;katemotive-organizer&quot;&gt;KateMotive Organizer&lt;/h2&gt;
&lt;p&gt;The main reason I joined my stepmother and father was them organizing the whole production via hand-written cards.
Between the two of them, they were able to plan and manufacture about 10-15 masks per day.&lt;/p&gt;
&lt;p&gt;As a platform mostly for buying mass-produced things from stock, Amazon quickly imposes penalties for delayed shipments.
And over the years, customers learned to expect Amazon orders to be quick.
But as new and repeat orders started to really roll in, I noticed the air becoming thicker with stress.&lt;/p&gt;
&lt;p&gt;So I started building a piece of software that would help them ease their organizational troubles.
They were against it at first, but they didn’t imagine the barrage of orders that would follow soon after.&lt;/p&gt;
&lt;p&gt;Thankfully, there was not a lot of automation to do, which simplified my job.
Essentially, they needed an interface to track each face mask through a very simplified process.
Mostly, just to not forget any of them.&lt;/p&gt;
&lt;h3 id=&quot;the-solution&quot;&gt;The solution&lt;/h3&gt;
&lt;p&gt;For each order, a card that would have some basic information about the order, and the face mask designs and sizes contained within, would be pinned to a wall.
The inner and outer layer material for the face masks was cut out grouped by size and design (customers had lots of designs to choose from).
Then, a diagonal line would be put in the check-box on the card.
The four parts per face mask would then be grouped together by order, later to be sewn and ironed into the final product.
Lastly, the masks belonging to an order would be hung off the same pin as the corresponding card on the wall. They’d be ready for packaging.
In order for my stepmother to retain an overview from her “workstation”, she would draw the second line in the check-box on the card - crossing off masks she had finished for an order.&lt;/p&gt;
&lt;p&gt;I assumed that, since face masks were crossed off one-by-one on the physical cards, the application had to support that as well.
That means each face mask had three states of completion, pretty simple:
Not started, in progress, and done.
In actuality, the middle state went mostly unused because it created too much overhead synchronizing that with the state on the physical cards.
Instead, after shipping out the orders for the day, the cards were removed from the wall and taken to the computer running the app, to mark all masks in the shipped orders as done.&lt;/p&gt;
&lt;p&gt;In any case, I had to create a system that could support and accelerate this already established workflow.
That meant the killer feature would be creating card PDFs that supported orders of any size, with all the relevant information, all without wasting too much toner.
It took a little iteration on the layout, but soon the program I dubbed the “KateMotive Organizer” spat out well-behaved card PDFs:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/kmo-card.CHnA6_Hv_BFOSw.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/kmo-card.CHnA6_Hv_Z1R5oAz.jxl, https://jama.me/_astro/kmo-card.CHnA6_Hv_28dE1W.jxl 1.5x, https://jama.me/_astro/kmo-card.CHnA6_Hv_Qo4Sz.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/kmo-card.CHnA6_Hv_2sf14q.avif, https://jama.me/_astro/kmo-card.CHnA6_Hv_1nlUS1.avif 1.5x, https://jama.me/_astro/kmo-card.CHnA6_Hv_Z26ALmE.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/kmo-card.CHnA6_Hv_Z2iG0bl.webp, https://jama.me/_astro/kmo-card.CHnA6_Hv_1GC3rb.webp 1.5x, https://jama.me/_astro/kmo-card.CHnA6_Hv_Z2tNdDr.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/kmo-card.CHnA6_Hv_ZzVaih.png&quot; srcset=&quot;https://jama.me/_astro/kmo-card.CHnA6_Hv_Z1EOftG.png 1.5x, https://jama.me/_astro/kmo-card.CHnA6_Hv_Z6uNlK.png 2x&quot; alt=&quot;Screenshot of PDF document, layout for a card filled with example data with four face masks&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;431&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;The app’s backend is based on a Node.js &lt;a href=&quot;https://www.npmjs.com/package/express&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Express&lt;/a&gt; server with a now long-unmaintained &lt;a href=&quot;https://www.npmjs.com/package/nedb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NeDB&lt;/a&gt; database (maintained forks do exist).
Additionally, I implemented live synchronization via &lt;a href=&quot;https://www.npmjs.com/package/socket.io&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;socket.io&lt;/a&gt;. Mostly just for fun, but it turned out really useful at the end.
Since the app was needed only locally, I conceptualized it as a Windows application that would spawn a server that could be connected to from the local network.
The computer the server was running on was just another client using the app.&lt;/p&gt;
&lt;p&gt;Using &lt;a href=&quot;https://www.npmjs.com/package/pdfmake&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PDFMake&lt;/a&gt; I could create a table to fill, that would also automatically overflow to multiple pages if the space wasn’t enough.&lt;/p&gt;
&lt;h3 id=&quot;the-frontend&quot;&gt;The frontend&lt;/h3&gt;
&lt;p&gt;Creating the PDFs was one of the easier problems to solve. Creating an easy to use interface around it was the harder part.&lt;/p&gt;
&lt;p&gt;To make iteration on the layouts quick, I used partial server-side rendering for the frontend using &lt;a href=&quot;https://www.npmjs.com/package/ejs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;EJS&lt;/a&gt;, and &lt;a href=&quot;https://getbootstrap.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Bootstrap&lt;/a&gt; for the styling.
But since I knew that the app would quickly have to manage thousands of entries, server-side rendering wouldn’t be performant at some point.
At least not in combination with the socket.io implementation.
That’s why I decided to implement the client-side code without a framework, in pure JavaScript.&lt;/p&gt;
&lt;p&gt;The frontend would manage content diffs sent by the backend via socket.io to make the interface snappy.
They were data-only JSON divided into elements to be added, elements to be edited and elements to be removed.
I also later implemented infinite scrolling with a crude virtualization implementation.
That wasn’t performant enough on lower end devices, so in the end a simple server-side pagination had to suffice to make the page perform well on an ageing iPad Air 2.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/kmo-1.BiAifcrb_6SjPy.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/kmo-1.BiAifcrb_Z1ikl0C.jxl, https://jama.me/_astro/kmo-1.BiAifcrb_ZPlC1C.jxl 1.5x, https://jama.me/_astro/kmo-1.BiAifcrb_Z1Wnzt3.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/kmo-1.BiAifcrb_Z12VrEd.avif, https://jama.me/_astro/kmo-1.BiAifcrb_ZzWIFd.avif 1.5x, https://jama.me/_astro/kmo-1.BiAifcrb_LxSzI.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/kmo-1.BiAifcrb_MmNcf.webp, https://jama.me/_astro/kmo-1.BiAifcrb_1flwbf.webp 1.5x, https://jama.me/_astro/kmo-1.BiAifcrb_1U9meF.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/kmo-1.BiAifcrb_Zoi88f.png&quot; srcset=&quot;https://jama.me/_astro/kmo-1.BiAifcrb_3FzPK.png 1.5x, https://jama.me/_astro/kmo-1.BiAifcrb_zJexX.png 2x&quot; alt=&quot;Website screenshot, main webpage of the KateMotive Organizer&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;422&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;The main webpage of the KateMotive Organizer.
The main table containing face masks data located centrally, with colorful buttons to the right of each entry.
A red pulsating circle indicating a ‘live’ synchronization status is in the navigation bar on the top right.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;It’s pretty simple in concept. Once added, face masks would have a red dot to indicate the “not started” status.
On the right, three buttons per entry are located: Two to set the other two states and a delete button.
The two state set buttons would immediately trigger the state change, while the delete button triggers a multi-function modal that asks whether to delete only that entry, or all entries belonging to that order.&lt;/p&gt;
&lt;p&gt;I also implemented complex filtering for the list of face masks. Some filters are added by AND, some by OR logic - depending on feedback from my stepmother.&lt;/p&gt;
&lt;p&gt;This is how adding an order looks like:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/kmo-2.Boi9vOta_Z1S50zb.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/kmo-2.Boi9vOta_gMyiA.jxl, https://jama.me/_astro/kmo-2.Boi9vOta_ILhhA.jxl 1.5x, https://jama.me/_astro/kmo-2.Boi9vOta_17PdU9.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/kmo-2.Boi9vOta_wbrE0.avif, https://jama.me/_astro/kmo-2.Boi9vOta_YaaD0.avif 1.5x, https://jama.me/_astro/kmo-2.Boi9vOta_Z1dpqP1.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/kmo-2.Boi9vOta_2muHvs.webp, https://jama.me/_astro/kmo-2.Boi9vOta_Z2fHHjt.webp 1.5x, https://jama.me/_astro/kmo-2.Boi9vOta_Z4NXb4.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/kmo-2.Boi9vOta_1aOLaX.png&quot; srcset=&quot;https://jama.me/_astro/kmo-2.Boi9vOta_1CNu9X.png 1.5x, https://jama.me/_astro/kmo-2.Boi9vOta_Z1pe5QL.png 2x&quot; alt=&quot;Website screenshot, an order is being added in KateMotive Organizer&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;422&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;There wasn’t enough time to implement automatic pulling in of new orders from the different online store systems (though technically it would’ve been possible), so I tried to streamline this interface as much as possible.
In order to reduce errors, the app has a set of mask designs and sizes configurable via simple JSON configuration files, limiting the selection to the ones currently most relevant.&lt;/p&gt;
&lt;p&gt;After pressing enter on the keyboard, or clicking the “Next” button at the bottom of the modal, a second modal opens with a preview of the card about to be printed.
Here, the user would have the choice to only print, or print and save the masks they input. This served as a last check before saving.
Were that preview modal closed, the other would open back up with the input data retained. Pressing outside the modals to close them was specifically disabled to avoid losing any information.&lt;/p&gt;
&lt;p&gt;And here’s the stats page:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/kmo-3.BnKAAhUa_OxAPL.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/kmo-3.BnKAAhUa_1bar5P.jxl, https://jama.me/_astro/kmo-3.BnKAAhUa_1D9a4P.jxl 1.5x, https://jama.me/_astro/kmo-3.BnKAAhUa_Z1eIisP.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/kmo-3.BnKAAhUa_1qykrf.avif, https://jama.me/_astro/kmo-3.BnKAAhUa_1Sx3qf.avif 1.5x, https://jama.me/_astro/kmo-3.BnKAAhUa_1udazV.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/kmo-3.BnKAAhUa_Z1Njxve.webp, https://jama.me/_astro/kmo-3.BnKAAhUa_Z1lkOwe.webp 1.5x, https://jama.me/_astro/kmo-3.BnKAAhUa_Z2rnuz3.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/kmo-3.BnKAAhUa_25cDXd.png&quot; srcset=&quot;https://jama.me/_astro/kmo-3.BnKAAhUa_Z2x0KQI.png 1.5x, https://jama.me/_astro/kmo-3.BnKAAhUa_1iovyb.png 2x&quot; alt=&quot;Website screenshot, stats view in KateMotive Organizer with example data&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;422&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;It aggregates orders by interesting stats, like international orders, most prolific customers, grouped by platform, and so on.
The chart plots orders over time and uses &lt;a href=&quot;https://github.com/tradingview/lightweight-charts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Lightweight Charts&lt;/a&gt; for the visualization.&lt;/p&gt;
&lt;h3 id=&quot;getting-printing-to-work&quot;&gt;Getting printing to work&lt;/h3&gt;
&lt;p&gt;Printing the cards was a special challenge. The printer used for cards and printing the logo on the brown shipping envelopes was an HP LaserJet Pro M1132.
Unfortunately, its outdated driver didn’t play well with printing from Chromium-based browsers.
The browser’s printing function added unexpected margins to the A8-sized prints, while printing a PDF via the Windows print dialog worked perfectly.&lt;/p&gt;
&lt;p&gt;After some trials and tribulations, I settled on a slightly unorthodox solution based on &lt;a href=&quot;https://www.sumatrapdfreader.org&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Sumatra PDF&lt;/a&gt;.
It has helpful command line arguments, some of which are specifically meant to configure printing. The command actually used is pretty simple as a result:&lt;/p&gt;
&lt;pre style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;sumatrapdf.exe&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; -print-to-default&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; -print-settings&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;portrait,monochrome,paper=&quot;A8Card&quot;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;A8Card&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; is a paper size previously added to the Windows print dialog.
The backend would create the PDF as a physical file, start the print, and optionally delete the file after creating the print job.&lt;/p&gt;
&lt;h2 id=&quot;verdict&quot;&gt;Verdict&lt;/h2&gt;
&lt;p&gt;The project was packaged as a convenient standalone executable using &lt;a href=&quot;https://www.npmjs.com/package/pkg&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;pkg&lt;/a&gt;, and rolling out updates was as simple as replacing it and restarting the application.&lt;/p&gt;
&lt;p&gt;In short, without the KateMotive Organizer, the influx of orders wouldn’t have been possible to handle.
Most customers first ordered a couple face masks at most, and in subsequent orders ordered more and more.
The largest order was held by a company who ordered 46 face masks at once, all to be delivered in a short amount of time.&lt;/p&gt;
&lt;p&gt;In that frenzy, the KateMotive Organizer held up perfectly and allowed us to focus on other things, like procuring more materials to keep certain designs in stock.
And, most importantly, to provide high quality and comfortable face masks to customers.
Production could scale from 10-15 face masks a day to 40-50 at the peak.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/masks.BPv31k3A_Zqy2Iz.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/masks.BPv31k3A_2ucc1m.jxl, https://jama.me/_astro/masks.BPv31k3A_1HrIqc.jxl 1.5x, https://jama.me/_astro/masks.BPv31k3A_24KCAf.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/masks.BPv31k3A_8Vwgc.avif, https://jama.me/_astro/masks.BPv31k3A_ZCMVjX.avif 1.5x, https://jama.me/_astro/masks.BPv31k3A_szVAf.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/masks.BPv31k3A_1hwYU9.webp, https://jama.me/_astro/masks.BPv31k3A_uMwjY.webp 1.5x, https://jama.me/_astro/masks.BPv31k3A_eIuab.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/masks.BPv31k3A_2knj95.jpg&quot; srcset=&quot;https://jama.me/_astro/masks.BPv31k3A_1xCPxU.jpg 1.5x, https://jama.me/_astro/masks.BPv31k3A_2aBokx.jpg 2x&quot; alt=&quot;Three collections of fancy face masks with rhinestones, lined up for product photography&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;357&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;The brand and the decisions made for packaging played a part in putting the name out there, and the face masks appeared convincing enough to build trust.
For starting from zero, I believe this is a pretty good result. We’ll explore this brand further in the future.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - No More jQuery</title><link>https://jama.me/blog/no-more-jquery/</link><guid isPermaLink="true">https://jama.me/blog/no-more-jquery/</guid><description>I take a peek at life without jQuery and boost my website&apos;s PageSpeed Insight score by a massive 2 points!</description><pubDate>Thu, 30 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;jQuery is great. It offers handy features like DOM manipulation, AJAX, and animations with bad performance.
I’ve used jQuery since my early days of frontend development because it made things easier, and there were always tons of plugins for it to explore.&lt;/p&gt;
&lt;p&gt;For many tasks that jQuery handled in a single line, browsers lacked an equivalent.
And that’s not even considering cross-browser compatibility — jQuery unified all browsers under one codebase.
You didn’t need to write code differently for Safari and Firefox to get the same results.&lt;/p&gt;
&lt;p&gt;It also made code more readable and understandable.
Take AJAX, for example, which allows asynchronous data exchange between the browser and server.
Here’s how to send form data and handle the server’s response:&lt;/p&gt;
&lt;pre style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;// Post form data in native JavaScript via XMLHttpRequest&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;document.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;getElementById&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;button-submit&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;click&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt; formData &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt; FormData&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(document.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;getElementById&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;form-signin&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt; xhr &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt; XMLHttpRequest&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  xhr.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;https://example.com/signin&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  xhr.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;onreadystatechange&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(xhr.readyState &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 4&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt; xhr.status &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;      console.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(xhr.responseText); &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;// When all went well&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  xhr.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(formData);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, here’s the jQuery equivalent:&lt;/p&gt;
&lt;pre style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;// Post form data via jQuery&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;#button-submit&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;click&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  $.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;https://example.com/signin&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;#form-signin&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;serialize&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;success&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;      console.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(data); &lt;/span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;// When all went well&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The jQuery version is not only shorter but also easier to follow due to its clean API, which clearly shows what happens and when.
Plus, jQuery automatically detects XML or JSON responses, giving you a JavaScript object to work with — no manual parsing needed.&lt;/p&gt;
&lt;p&gt;In general, jQuery is a win-win:
it saves you from reinventing the wheel and offers concise solutions for common tasks.
It’s also so widely used that linking to it via a popular CDN, like Google’s, makes caching easy.&lt;/p&gt;
&lt;p&gt;However, my website still didn’t score a perfect 100 on Google PageSpeed Insights for mobile.
Sure, 98 is nearly perfect, but for a lightweight site like mine, it should be able to hit 100.
So, I set out to find a solution.&lt;/p&gt;
&lt;h2 id=&quot;searching-for-a-solution&quot;&gt;Searching For a Solution&lt;/h2&gt;
&lt;a href=&quot;https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_1wG6Vt.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_Z2dub3I.jxl, https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_Z2qA0Gf.jxl 1.5x, https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_d7o6w.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_Z4Qk94.avif, https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_ZhW9LA.avif 1.5x, https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_Z1erjBK.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_Z1PiNqC.webp, https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_Z23oD49.webp 1.5x, https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_ZD4zCe.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_ZkqS7x.png&quot; srcset=&quot;https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_ZxwHK4.png 1.5x, https://jama.me/_astro/jquery-google-cdn.BuaX_Fkh_2r8vva.png 2x&quot; alt=&quot;Google Chrome waterfall graph of the loading process of the jQuery library&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;509&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;I quickly realized that jQuery was taking longer to load from Google’s servers than the whole rest of the page.
The somewhat outdated version 3.3.1 is about 30KB compressed (85KB uncompressed).
85KB is not a trivial amount of JavaScript for the browser to parse, especially if we’re only using small parts of it!
In addition, using jQuery from Google’s CDN adds extra overhead from the DNS request and SSL handshake.&lt;/p&gt;
&lt;p&gt;One solution is to host jQuery myself.
Or better yet, not use it at all.
The latter solves both problems: no jQuery means no overhead downloading it, and no large script to parse.&lt;/p&gt;
&lt;p&gt;GitHub famously &lt;a href=&quot;https://github.blog/2018-09-06-removing-jquery-from-github-frontend/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;removed jQuery&lt;/a&gt; from its frontend and replaced its functions with native JavaScript.
So, I decided to also give life without jQuery a try.&lt;/p&gt;
&lt;h2 id=&quot;replacing-jquery&quot;&gt;Replacing jQuery&lt;/h2&gt;
&lt;p&gt;But jQuery has its perks!
True, but modern browsers have evolved, and the features I relied on now ship in browsers from the get-go.
For the rest, there are smaller libraries and wrappers that are much lighter than jQuery.
I’m essentially &lt;a href=&quot;https://en.wikipedia.org/wiki/Tree_shaking&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tree shaking&lt;/a&gt; it at this point.&lt;/p&gt;
&lt;p&gt;What I appreciated most about jQuery was the ease of event handling, DOM selection, class utilities, and AJAX.
So let’s take a look at replacing these features.&lt;/p&gt;
&lt;h3 id=&quot;events&quot;&gt;Events&lt;/h3&gt;
&lt;p&gt;jQuery’s API for event handling is excellent, especially for binding multiple events to elements and doing event delegation.
For instance, I want to add a red border to empty text input fields using a CSS class.&lt;/p&gt;
&lt;p&gt;Here’s the jQuery version:&lt;/p&gt;
&lt;pre style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;// Defining multiple events on multiple elements via jQuery&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;#name, #message&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;change input blur&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;  var&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt; el &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt; $&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(e.target);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(el.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;val&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;    el.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;addClass&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;form-control-error&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;    el.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;removeClass&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;form-control-error&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in native JavaScript:&lt;/p&gt;
&lt;pre style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;// Defining multiple events on multiple elements in native JavaScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;document.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;#name, #message&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;el&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  [&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;change&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;input&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;blur&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;    el.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(event, &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;.value &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt; &apos;&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;        this&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;.classList.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;form-control-error&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;      }&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;        this&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;.classList.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;remove&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;form-control-error&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For such a simple task, the native solution requires two loops.
There’s no real performance difference though, as jQuery hides these loops behind a cleaner API.&lt;/p&gt;
&lt;p&gt;jQuery also supports event delegation, which in native JavaScript requires manually distinguishing the clicked element.
For an alternative, check out &lt;a href=&quot;https://github.com/cferdinandi/events&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;events.js&lt;/a&gt; by Chris Ferdinandi.
It’s only 1.24KB and simplifies event handling, though I skipped it for my small site.&lt;/p&gt;
&lt;h3 id=&quot;class-utilities&quot;&gt;Class Utilities&lt;/h3&gt;
&lt;p&gt;This one’s straightforward.
The native solution to use now is &lt;a href=&quot;https://developer.mozilla.org/en/docs/Web/API/Element/classList&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ClassList&lt;/a&gt;, which offers a similar API to jQuery, as seen in the example above.
&lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(element).&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;addClass&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;example&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; becomes &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;element.classList.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;example&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;.
ClassList works &lt;a href=&quot;https://caniuse.com/#feat=classlist&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;with some limitations&lt;/a&gt; in IE10, but without issues in modern browsers.&lt;/p&gt;
&lt;h3 id=&quot;ajax&quot;&gt;AJAX&lt;/h3&gt;
&lt;p&gt;As shown earlier, AJAX via native XMLHttpRequest isn’t exactly elegant.&lt;/p&gt;
&lt;p&gt;On my site’s contact form, a server-side API checks if the email is real and whether it’s from a known disposable email provider.
To handle this check, I used &lt;a href=&quot;https://github.com/fdaciuk/ajax&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ajax.js&lt;/a&gt; by Fernando Daciuk, which is only 1.97KB.
It works similarly to jQuery’s &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;$.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;$.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, and &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;$.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;ajax&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; functions.
The corresponding AJAX call looks like this:&lt;/p&gt;
&lt;pre style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;// AJAX Get via ajax.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;ajax&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;/contact/checkmail&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  email: email.value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;}).&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(data &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;    // Email address is allowed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;    // Email address is not allowed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, there’s the built-in &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Fetch API&lt;/a&gt;, which requires a bit more manual handling:&lt;/p&gt;
&lt;pre style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;// AJAX Get via Fetch API&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;/contact/checkmail&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  cache: &lt;/span&gt;&lt;span style=&quot;color:#A5D6FF&quot;&gt;&apos;no-cache&apos;&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  body: &lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;({email: email.value})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;}).&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  response.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFA657&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;(data.json &lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79C0FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;      // Email address is allowed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;    }&lt;/span&gt;&lt;span style=&quot;color:#FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:#8B949E&quot;&gt;      // Email address is not allowed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fetch only accepts plain text, requiring you to serialize the data you want to send if it’s not already a string.
It allows more control over the response format, like transforming the response into an object using &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;response.&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#D2A8FF&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;.
I went with ajax.js for &lt;a href=&quot;https://caniuse.com/#feat=fetch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;broader browser support&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;result&quot;&gt;Result&lt;/h2&gt;
&lt;p&gt;The outcome was clear:
by removing jQuery, I reduced data transferred on page load, eliminated a request (including DNS and SSL overhead), and cut out a large, mostly unused script.
Now, my homepage hits 100 on the PageSpeed Insights test.&lt;/p&gt;
&lt;p&gt;With jQuery:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_Z2gVAqX.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_1863zW.jxl, https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_f6KIB.jxl 1.5x, https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_kFv2C.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_Z1Nsejk.avif, https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_2nJBDg.avif 1.5x, https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_BP0FN.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_1vhqd3.webp, https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_Ci8lH.webp 1.5x, https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_1W3HKN.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_Z242MhN.png&quot; srcset=&quot;https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_28a3EM.png 1.5x, https://jama.me/_astro/jquery-pagespeed.CWpVQdGn_Z2d26I0.png 2x&quot; alt=&quot;Google PageSpeed Insights score page for mobile devices with 98% score&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;427&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Without jQuery:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_Z1sgC7B.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_Z1ljJDF.jxl, https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_1OM4QI.jxl 1.5x, https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_1dA88z.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_2hiFqY.avif, https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_ndm8r.avif 1.5x, https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_1XJd0l.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_Z2cvInq.webp, https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_XA67X.webp 1.5x, https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_7uutR.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_RGmJX.png&quot; srcset=&quot;https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_Z11nVxz.png 1.5x, https://jama.me/_astro/nojquery-pagespeed.BqjuUOzB_ZPC7MG.png 2x&quot; alt=&quot;Google PageSpeed Insights score page for mobile devices with 100% score&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;427&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Google assumes slower processors on mobile devices take longer to parse the page with jQuery, versus without it.
Notably, the First Meaningful Paint (FMP) time dropped by a full second.
FMP marks when visible elements and web fonts are fully rendered — important for both user experience and search rankings.&lt;/p&gt;
&lt;p&gt;Here’s the load time comparison:&lt;/p&gt;
&lt;p&gt;First with jQuery:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/jquery-load.8SsTvCdv_Z2gLmAk.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/jquery-load.8SsTvCdv_Z19CwY1.jxl, https://jama.me/_astro/jquery-load.8SsTvCdv_Z1D3bg0.jxl 1.5x, https://jama.me/_astro/jquery-load.8SsTvCdv_63KE7.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/jquery-load.8SsTvCdv_WyJyG.avif, https://jama.me/_astro/jquery-load.8SsTvCdv_t96hH.avif 1.5x, https://jama.me/_astro/jquery-load.8SsTvCdv_1s0scL.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/jquery-load.8SsTvCdv_zmihT.webp, https://jama.me/_astro/jquery-load.8SsTvCdv_5VE0U.webp 1.5x, https://jama.me/_astro/jquery-load.8SsTvCdv_22faFQ.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/jquery-load.8SsTvCdv_Z27wqel.png&quot; srcset=&quot;https://jama.me/_astro/jquery-load.8SsTvCdv_2tf4iB.png 1.5x, https://jama.me/_astro/jquery-load.8SsTvCdv_Z2iqAOT.png 2x&quot; alt=&quot;Google Chrome waterfall graph of the page loading process with jQuery. Finishes in 292ms&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;566&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;And without jQuery:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/nojquery-load.DwIx_7kW_1nYWwL.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/nojquery-load.DwIx_7kW_ZP8fVM.jxl, https://jama.me/_astro/nojquery-load.DwIx_7kW_1GdoEi.jxl 1.5x, https://jama.me/_astro/nojquery-load.DwIx_7kW_2nwLUC.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/nojquery-load.DwIx_7kW_vNqAR.avif, https://jama.me/_astro/nojquery-load.DwIx_7kW_Z2222AY.avif 1.5x, https://jama.me/_astro/nojquery-load.DwIx_7kW_2dkYKD.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/nojquery-load.DwIx_7kW_16394W.webp, https://jama.me/_astro/nojquery-load.DwIx_7kW_Z1rMk7T.webp 1.5x, https://jama.me/_astro/nojquery-load.DwIx_7kW_Z1WKX7n.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/nojquery-load.DwIx_7kW_1Pyvn8.png&quot; srcset=&quot;https://jama.me/_astro/nojquery-load.DwIx_7kW_ZHgWOI.png 1.5x, https://jama.me/_astro/nojquery-load.DwIx_7kW_Z1VH47P.png 2x&quot; alt=&quot;Google Chrome waterfall graph of the page loading process without jQuery. Finishes in 228ms&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;524&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Another small optimization I made was inlining web fonts in the &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#7EE787&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:var(--color-on-dark-bg)&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; section of each page.
Thanks to HTTP/2, the fonts now load in parallel with the rest, without waiting for main.css.
Beyond the 30KB saved from removing jQuery, I shaved off about 10% of load time for first-time visitors, where DNS lookup and SSL handshake are needed.
On subsequent page loads, the savings are slightly higher.&lt;/p&gt;
&lt;p&gt;While these changes weren’t strictly necessary for my site, I’m pleased with the perfect score and the realization that jQuery isn’t really needed anymore.&lt;/p&gt;
&lt;p&gt;Thank you jQuery for your much needed service!
But I think I’ll avoid using it in future projects — certainly can’t hurt.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - Performance of the Razer Blade 15 (2018)</title><link>https://jama.me/blog/razer-blade-comparison/</link><guid isPermaLink="true">https://jama.me/blog/razer-blade-comparison/</guid><description>I compare the newcomer&apos;s performance to my previous laptop and a high-end gaming desktop.</description><pubDate>Sat, 10 Nov 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After my Bachelor’s degree, I decided it was time to replace my old laptop with something more modern and lightweight.
The 4.5 kg XMG P704 was becoming a hassle when traveling.&lt;/p&gt;
&lt;p&gt;Before diving into the details, let’s take a look at my outgoing XMG.
Aside from its hefty weight, it started to feel a bit dated.
It barely ran the games I developed during my studies, so there’s definitely room for improvement.&lt;/p&gt;
&lt;h2 id=&quot;the-old-hardware&quot;&gt;The Old Hardware&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;XMG P704 PRO (2014)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Intel Core i7 4710MQ&lt;/li&gt;
&lt;li&gt;Nvidia GeForce GTX 870M (6GB)&lt;/li&gt;
&lt;li&gt;2x8GB Crucial Ballistix Sport DDR3-1600&lt;/li&gt;
&lt;li&gt;2x SATA SSDs (1x 250GB, 1x 120GB)&lt;/li&gt;
&lt;li&gt;17.3” Full-HD TN screen (60Hz)&lt;/li&gt;
&lt;li&gt;230-watt power supply&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Based on the Clevo P177SM-A, the XMG offered solid performance at a reasonable price (~€1670).
It featured good I/O and performance comparable to high-end 2011 hardware, with a decent thermal solution.
Then Nvidia’s 10-series GPUs shook up the market by bringing laptop GPUs somewhat in line with their desktop counterparts, differing only in memory, clock speeds, and thermals, which resulted in a significant performance boost.&lt;/p&gt;
&lt;p&gt;The XMG served me well during my studies, but struggled with projects like &lt;em&gt;And Now&lt;/em&gt; and &lt;em&gt;Tea Shop&lt;/em&gt;.
The 6GB of video memory delayed the aging of the GTX 870M a little.
With some overclocking, and later undervolting, I managed to squeeze out 90MHz more clock speed, but Nvidia’s Kepler chips weren’t great for overclocking, especially with air cooling.
Laptops also got lesser GPU chips compared to their desktop counterparts due to power constraints.&lt;/p&gt;
&lt;p&gt;Unfortunately for me, just a month after I purchased the P704, XMG released the P705, with M.2 NVMe slots and an IPS display.
I guess I jumped the gun.&lt;/p&gt;
&lt;h2 id=&quot;the-new-hardware&quot;&gt;The New Hardware&lt;/h2&gt;
&lt;h3 id=&quot;the-candidates&quot;&gt;The Candidates&lt;/h3&gt;
&lt;p&gt;I considered the following new laptops for my upgrade:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MSI GS65 Stealth Thin - €2199 (&lt;a href=&quot;https://www.notebookcheck.com/Test-MSI-GS65-8RF-Stealth-Thin-i7-8750H-GTX-1070-Max-Q-Full-HD-Laptop.301762.0.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Notebookcheck’s review&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Gigabyte Aero 15X - €2499 (&lt;a href=&quot;https://www.notebookcheck.com/Test-Gigabyte-Aero-15X-v8-i7-8750H-GTX-1070-Max-Q-Full-HD-Laptop.293672.0.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Notebookcheck’s review&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Razer Blade 15 (Advanced) - €2449 (&lt;a href=&quot;https://www.notebookcheck.com/Test-Razer-Blade-15-i7-8750H-GTX-1070-Max-Q-Full-HD-Laptop.303674.0.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Notebookcheck’s review&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Apple MacBook Pro 15 (2018) - at least €3299 (&lt;a href=&quot;https://www.notebookcheck.com/Test-Apple-MacBook-Pro-15-2018-2-6-GHz-560X-Laptop.316698.0.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Notebookcheck’s review&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All four, except the MacBook, shared similar specs:
Intel Core i7 8750H, 16GB RAM, Nvidia GeForce GTX 1070 Max-Q, and a 144Hz Full-HD IPS display with 99% sRGB coverage.
The MacBook had an i7 8850H, an AMD Radeon Pro 560X (roughly equivalent to the RX560), and a 60Hz Retina Display.
Apple planned to include AMD Vega GPUs later, but I failed to see how they’d match Nvidia’s performance in gaming.&lt;/p&gt;
&lt;p&gt;All four were light (2-2.5 kg) and premium-priced, but two were eliminated quickly.
The MacBook, due to its high price point and weaker graphics, and the MSI for its reportedly poor build quality.&lt;/p&gt;
&lt;p&gt;This left the Gigabyte and the Razer.
A friend of mine had bought the Gigabyte Aero 15X based on my recommendation and was generally happy with it.
I’d still recommend it for solid performance and great battery life.
However, the Razer Blade 15 caught my eye just as it was being released.
I preferred its sleek, MacBook-like design with a touch of gaming flair, contrasting the Gigabyte with its tacky gamer-y keyboard font.
The initial reviews also highlighted the Razer’s strong build quality.&lt;/p&gt;
&lt;h3 id=&quot;razer-blade-15&quot;&gt;Razer Blade 15&lt;/h3&gt;
&lt;p&gt;My configuration for €2449 includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Intel Core i7 8750H&lt;/li&gt;
&lt;li&gt;Nvidia GeForce GTX 1070 Max-Q (8GB)&lt;/li&gt;
&lt;li&gt;2x8GB Samsung M471A1K43CB1-CTD (DDR4-2666 CL19)&lt;/li&gt;
&lt;li&gt;1x 256GB M.2 PCIe SSD&lt;/li&gt;
&lt;li&gt;15.6” Full-HD IPS screen (144Hz)&lt;/li&gt;
&lt;li&gt;230W power supply&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;testing-methodology&quot;&gt;Testing Methodology&lt;/h2&gt;
&lt;p&gt;My goal here isn’t a neutral comparison, but rather seeing how much performance I gained with the new laptop and how it compares to my desktop PC.&lt;/p&gt;
&lt;p&gt;The desktop has an Intel Core i7 7700K, 32GB DDR4-3000 CL15 RAM, and an EVGA GeForce GTX 1080Ti SC2.
I also tested my old laptop.
All three systems were running stable overclocks/undervolts used in daily scenarios.&lt;/p&gt;
&lt;p&gt;I tested them in three benchmarks: &lt;strong&gt;3DMark Fire Strike&lt;/strong&gt;, &lt;strong&gt;3DMark Time Spy&lt;/strong&gt;, and &lt;strong&gt;Unigine Superposition&lt;/strong&gt;.
Due to the differences between the systems, sensor information isn’t equally available, so the data can’t be as detailed as in previous tests.
Unfortunately, I couldn’t log the fan speeds on the laptops.
However, I still gathered as much useful data as possible.
For framerate comparisons I used the framerate output from the respective benchmarking software.&lt;/p&gt;
&lt;p&gt;This time, I also logged room temperature during the tests.&lt;/p&gt;
&lt;h2 id=&quot;benchmarks&quot;&gt;Benchmarks&lt;/h2&gt;
&lt;h3 id=&quot;3dmark-fire-strike&quot;&gt;3DMark Fire Strike&lt;/h3&gt;
&lt;a href=&quot;https://jama.me/_astro/fire-strike.NbsN67FZ_hULuM.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/fire-strike.NbsN67FZ_1LztAM.jxl, https://jama.me/_astro/fire-strike.NbsN67FZ_2s6gcH.jxl 1.5x, https://jama.me/_astro/fire-strike.NbsN67FZ_Zl9XLk.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/fire-strike.NbsN67FZ_Z1VEWEu.avif, https://jama.me/_astro/fire-strike.NbsN67FZ_Z1g9b3z.avif 1.5x, https://jama.me/_astro/fire-strike.NbsN67FZ_17ytLw.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/fire-strike.NbsN67FZ_Z1mqfbp.webp, https://jama.me/_astro/fire-strike.NbsN67FZ_ZFTszu.webp 1.5x, https://jama.me/_astro/fire-strike.NbsN67FZ_Z1iJYHF.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/fire-strike.NbsN67FZ_Z1upv59.jpg&quot; srcset=&quot;https://jama.me/_astro/fire-strike.NbsN67FZ_ZNSIte.jpg 1.5x, https://jama.me/_astro/fire-strike.NbsN67FZ_ZhaUHB.jpg 2x&quot; alt=&quot;3DMark Fire Strike screenshot&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Here are the results (average of three runs):&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt; &lt;script type=&quot;module&quot; src=&quot;https://jama.me/Users/janmaslov/Documents/repos/maslov2024/src/components/Chart/index.astro?astro&amp;amp;type=script&amp;amp;index=0&amp;amp;lang.ts&quot;&gt;&lt;/script&gt; 
&lt;p&gt;Obviously there’s a significant performance gap between the three systems.
The Razer Blade 15 allows the desktop with its GTX 1080Ti a lead of 1.8 times, while itself being 4.5 times faster than the XMG.
Thanks to its two extra CPU cores, the i7 8750H in the Blade 15 even edges out the i7 7700K in my desktop in the CPU stress test, which is impressive!&lt;/p&gt;
&lt;p&gt;Now, let’s dive into the rest of the data.&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;p&gt;The relation core temperature and power consumption of the GPUs is particularly interesting.
We’re comparing a GTX 1080Ti (300W power limit), a GTX 1070 Max-Q (90W), and a GTX 870M (100W, although I couldn’t log exact power usage for the latter).
All GPUs stay within their power limits, but the undervolted GTX 1080Ti doesn’t fully utilize its limit.&lt;/p&gt;
&lt;p&gt;For the desktop, maximum CPU and GPU temps hit 66°C and 68°C respectively, with an average of 35.6W and 192.1W power consumption respectively.
The Blade 15 reaches 96°C (CPU) and 66°C (GPU) at an average of 18.7W and 75.3W.
The P704 tops out at 73°C and 84°C, with a CPU average of 11.6W.&lt;/p&gt;
&lt;p&gt;Obviously, the graphics tests focus more on GPU stress, and CPU tests don’t stress the GPU as much, which is reflected in the average power values.
When gaming, the GPU is usually the limiting factor, and that’s the case with these systems in this benchmark, where GPU usage hovers around 100%, while CPU usage is much lower.&lt;/p&gt;
&lt;h3 id=&quot;3dmark-time-spy&quot;&gt;3DMark Time Spy&lt;/h3&gt;
&lt;a href=&quot;https://jama.me/_astro/time-spy.DSg4kZij_Z2azM0s.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/time-spy.DSg4kZij_Zm7HYN.jxl, https://jama.me/_astro/time-spy.DSg4kZij_1jsLAf.jxl 1.5x, https://jama.me/_astro/time-spy.DSg4kZij_Z24otHX.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/time-spy.DSg4kZij_Z16Yr8J.avif, https://jama.me/_astro/time-spy.DSg4kZij_yB3rj.avif 1.5x, https://jama.me/_astro/time-spy.DSg4kZij_2MMOJ.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/time-spy.DSg4kZij_ZMIjzz.webp, https://jama.me/_astro/time-spy.DSg4kZij_RRb0t.webp 1.5x, https://jama.me/_astro/time-spy.DSg4kZij_ZkoDr3.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/time-spy.DSg4kZij_eO3PW.jpg&quot; srcset=&quot;https://jama.me/_astro/time-spy.DSg4kZij_1Upyr0.jpg 1.5x, https://jama.me/_astro/time-spy.DSg4kZij_1rmo7B.jpg 2x&quot; alt=&quot;3DMark Time Spy screenshot&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Here are the average results of three runs:&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;p&gt;Time Spy, a DirectX 12 benchmark, clearly benefits from modern hardware.
The gains are not only from a faster GPU but also from optimizations for this API, supported by Nvidia’s Pascal architecture.
The i7 8750H again outperforms the i7 7700K while using less power.&lt;/p&gt;
&lt;p&gt;In this test, the GTX 1070 Max-Q delivers 3.7 times the performance of the GTX 870M in the XMG P704, while the GTX 1080Ti in the desktop doubles the performance of the Blade 15.
The GTX 1080Ti shows a massive 6.88x performance boost over the GTX 870M — an impressive result.&lt;/p&gt;
&lt;p&gt;Here’s the rest of the data:&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;p&gt;One issue stands out:
while the Blade 15’s GPU temps are manageable, its CPU runs too hot.
In the longer Time Spy benchmark, the CPU reaches 99°C, clearly thermal throttling and reducing clock speed.
Undervolting the Blade 15 could improve performance stability, though I haven’t had time to try that yet.&lt;/p&gt;
&lt;p&gt;Also, note the bouncy usage and temps at the beginning and end of each test.
This is due to the 3DMark Launcher, which stresses both the CPU and GPU a bit during menu transitions.&lt;/p&gt;
&lt;h3 id=&quot;unigine-superposition&quot;&gt;Unigine Superposition&lt;/h3&gt;
&lt;a href=&quot;https://jama.me/_astro/superposition.CH5XbHe6_mIJVQ.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/superposition.CH5XbHe6_Z23Q9B2.jxl, https://jama.me/_astro/superposition.CH5XbHe6_ZgsHN2.jxl 1.5x, https://jama.me/_astro/superposition.CH5XbHe6_PaC7T.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/superposition.CH5XbHe6_Z1i7lfR.avif, https://jama.me/_astro/superposition.CH5XbHe6_ug5x8.avif 1.5x, https://jama.me/_astro/superposition.CH5XbHe6_ZP31cG.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/superposition.CH5XbHe6_1zzHry.webp, https://jama.me/_astro/superposition.CH5XbHe6_Z1HdYyn.webp 1.5x, https://jama.me/_astro/superposition.CH5XbHe6_20roVD.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/superposition.CH5XbHe6_24Fzl7.jpg&quot; srcset=&quot;https://jama.me/_astro/superposition.CH5XbHe6_Z1d87EO.jpg 1.5x, https://jama.me/_astro/superposition.CH5XbHe6_22OQd7.jpg 2x&quot; alt=&quot;Unigine Superposition screenshot&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Finally, here are the Unigine Superposition benchmark results using the 1080p Medium preset.
The average scores of three runs:&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 2;&quot;&gt; &lt;canvas style=&quot;--aspect: 2;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;p&gt;And the remaining data:&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;p&gt;It’s clear that the Blade 15 is thermally limited.
Even under moderate CPU loads, temperatures rise quickly, though the GPU seems to handle heat better - likely due to its larger chip surface.
The CPU’s contact with the vapor chamber cooler or the quality of the thermal paste could be factors for its poor thermal performance.&lt;/p&gt;
&lt;p&gt;Still, the Blade 15 holds up well despite this issue.
While it doesn’t deliver ultra-high-end performance, it appears to be highly efficient.
In the Superposition benchmark, it achieves about 76% of the desktop’s performance while consuming far less power.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The Razer Blade 15 is a powerful device in a compact package.
There are some concerns about longevity, particularly because of Razer’s wonky track record with batteries.
However, after four months with the Blade, and especially after changing the thermal paste, I’m satisfied with its overall performance.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - tempcontrol</title><link>https://jama.me/projects/tempcontrol/</link><guid isPermaLink="true">https://jama.me/projects/tempcontrol/</guid><description>A comprehensive company rebranding and website.</description><pubDate>Mon, 01 Oct 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Visit the website here: &lt;a href=&quot;https://www.tempcontrol.info&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tempcontrol.info&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;tempcontrol partners with Petman, among other companies. After they saw &lt;a href=&quot;https://jama.me/projects/petman&quot;&gt;my work for Petman&lt;/a&gt;, they also requested a new website for themselves.
In addition, they requested a significant rebranding to modernize.&lt;/p&gt;
&lt;h2 id=&quot;my-involvement&quot;&gt;My involvement&lt;/h2&gt;
&lt;p&gt;And that’s what I did. First up was the logo and the brand colors.
The team wanted something fresh and modern looking, but that would also hold up over time.
They were coming from this logo and had several problems with it:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__Z17yeX3.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__1EeLzF.jxl, https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__Z8X5pK.jxl 1.5x, https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__287f2n.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__2pnQrr.avif, https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__Z27lkGx.avif 1.5x, https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__1XGdkc.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__y98UX.webp, https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__1lyh7V.webp 1.5x, https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__Z2cpJ1C.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__ZoXtlA.png&quot; srcset=&quot;https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__1NRG7g.png 1.5x, https://jama.me/_astro/tempcontrol-logo-old.B0s0RKe__212EYF.png 2x&quot; alt=&quot;temPControl&apos;s old logo (pre 2018)&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;77&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;As a company rooted in hardware and software design, they told me that investments into visual design were minimal.
In fact, that logo was done by one of the employees.
The main problem they had with it was that people were misreading the company name.
Because the main product’s software runs on PCs, they decided to stylize the company name to “temPControl”.&lt;/p&gt;
&lt;p&gt;And people pronounced it exactly like that: &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;tˈɛm pˈiː sˈiː ˈɑːntɹɑːl&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, or “tem PC ontrol”.
They must’ve really been trying, because that pronunciation is not exactly easy to wrap your head around.&lt;/p&gt;
&lt;p&gt;Of course, the intended pronunciation is &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;tˌɛmpkɔntɾˈoːl&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;, as a single word.
That was the most important functional change the new logo had to provide.&lt;/p&gt;
&lt;p&gt;Additionally, they lacked a negative version of the logo, so they used it exactly like above in print.
Prints were coming out less than pretty, with banding issues in color, and unintended differences in contrast in grayscale.
Lastly, they didn’t have a vector version, so large prints used for exhibition stands would turn out blurry.&lt;/p&gt;
&lt;p&gt;To my surprise, they liked my first attempt so much that it became their new logo. After a couple iterations of polish it was ready to go:&lt;/p&gt;
&lt;p&gt;&lt;svg width=&quot;6.87em&quot; height=&quot;1em&quot;&gt;   &lt;symbol id=&quot;ai:local:tempcontrol-logo&quot; viewBox=&quot;0 0 927 135&quot;&gt;&lt;path fill=&quot;currentColor&quot; fill-rule=&quot;evenodd&quot; d=&quot;M519.6 49.53v3.14a1 1 0 0 1-.93 1c-3.06.28-6.02 1.96-7.2 5.2-.3.82-.47 1.7-.54 2.57a1 1 0 0 1-1 .93h-8.43a1 1 0 0 1-1-1.05 19.5 19.5 0 0 1 4.48-11.67c3.34-3.9 8.37-6.15 13.56-6.42a1 1 0 0 1 1.04 1v.92a1519 1519 0 0 1 .01 4.37ZM32.04 37.8a1 1 0 0 0-1 1V78c0 2.8.67 4.83 2.02 6.08s3.63 1.87 6.83 1.87h8.75a1 1 0 0 1 1 1v15.7a1 1 0 0 1-1 1h-12.2q-26.55 0-26.55-25.8V38.8a1 1 0 0 0-1-1H1a1 1 0 0 1-1-1V20.82a.27.27 0 0 1 .27-.27c7.52 0 13.6-6.1 13.6-13.6V1a1 1 0 0 1 1-1h15.24a1 1 0 0 1 1 1v18.55a1 1 0 0 0 1 1h16.54a1 1 0 0 1 1 1V36.8a1 1 0 0 1-1 1h-16.6Zm657.88 0a1 1 0 0 0-1 1V78c0 2.8.68 4.83 2.03 6.08s3.62 1.87 6.82 1.87h8.75a1 1 0 0 1 1 1v15.7a1 1 0 0 1-1 1h-12.2q-26.55 0-26.55-25.8V38.8a1 1 0 0 0-1-1h-7.9a1 1 0 0 1-1-1V20.82a.27.27 0 0 1 .28-.27c7.51 0 13.6-6.1 13.6-13.6V1a1 1 0 0 1 1-1H688a1 1 0 0 1 1 1v18.55a1 1 0 0 0 1 1h16.53a1 1 0 0 1 1 1V36.8a1 1 0 0 1-1 1h-16.6ZM136.05 60.3c0 2.65-.16 5.07-.47 7.25a1 1 0 0 1-.99.85H75.3a1 1 0 0 0-.99 1.12c.68 5.47 2.88 9.8 6.35 12.98 3.7 3.4 8.6 5.1 14 5.1 7.49 0 13.3-3.1 16.67-9.28a1 1 0 0 1 1.21-.47l18.8 6.84a1 1 0 0 1 .45 1.55c-3.36 4.4-5.34 6.26-11.64 11.03-6.8 5.15-15.15 7.73-25.05 7.73-8 0-15.18-1.78-21.53-5.33A37.5 37.5 0 0 1 58.72 84.6c-3.55-6.5-5.32-14-5.32-22.5q0-12.9 5.25-22.65c5.25-9.75 8.4-11.5 14.7-15Q82.8 19.2 95.1 19.2c7.9 0 14.97 1.7 21.22 5.1a35.9 35.9 0 0 1 14.55 14.47c3.45 6.25 5.18 13.43 5.18 21.53m-22.29-6a1 1 0 0 0 1-1.06 16.67 16.67 0 0 0-5.96-11.92c-3.8-3.25-8.8-4.87-14.3-4.87-5.2 0-9.9 1.57-13.46 4.72-3.3 2.93-5.55 6.91-6.5 11.95a1 1 0 0 0 .99 1.18zm134.79-34.95c10.2 0 18.42 3.12 24.67 9.37s9.38 14.98 9.38 26.18v47.75a1 1 0 0 1-1 1h-19a1 1 0 0 1-1-1v-44.9c0-6.5-1.65-11.48-4.95-14.93s-7.8-5.17-13.5-5.17-10.23 1.72-13.58 5.17-5.02 8.43-5.02 14.93v44.9a1 1 0 0 1-1 1h-19a1 1 0 0 1-1-1v-44.9c0-6.5-1.65-11.48-4.95-14.93s-7.8-5.17-13.5-5.17c-5.8 0-10.38 1.72-13.72 5.17-3.35 3.45-5.03 8.43-5.03 14.93v44.9a1 1 0 0 1-1 1h-19a1 1 0 0 1-1-1v-81.1a1 1 0 0 1 1-1h19a1 1 0 0 1 1 1v9.05c2.7-3.5 6.17-6.25 10.43-8.25 4.25-2 8.92-3 14.02-3 6.5 0 12.3 1.37 17.4 4.12a28.9 28.9 0 0 1 11.85 11.78 30.1 30.1 0 0 1 11.78-11.55 33.5 33.5 0 0 1 16.72-4.35m66.75 13.2c2.7-3.8 6.42-6.97 11.17-9.53 4.75-2.55 10.18-3.82 16.28-3.82 7.1 0 13.52 1.75 19.27 5.25s10.3 8.47 13.65 14.93c3.35 6.44 5.03 13.92 5.03 22.42s-1.68 16.02-5.03 22.57-7.9 11.63-13.65 15.23a35.6 35.6 0 0 1-19.27 5.4c-6.1 0-11.48-1.25-16.13-3.75a34.6 34.6 0 0 1-11.32-9.45v41.3a1 1 0 0 1-1 1h-19a1 1 0 0 1-1-1V21.55a1 1 0 0 1 1-1h19a1 1 0 0 1 1 1zm45.28 29.25c0-5-1.07-9.4-3.12-13.06a21.7 21.7 0 0 0-8.22-8.37 22.3 22.3 0 0 0-11.03-2.87c-3.8 0-7.54 1-10.89 2.94a22.14 22.14 0 0 0-8.22 8.53c-2.05 3.7-3.12 8.13-3.12 13.13s1.07 9.43 3.12 13.13a22.14 22.14 0 0 0 8.22 8.53 21.56 21.56 0 0 0 21.92-.08c3.35-2 6.17-4.9 8.22-8.6s3.12-8.18 3.12-13.28m29.7.3c0-8.6 1.75-16.13 5.25-22.57a37.53 37.53 0 0 1 14.55-15c6.2-3.55 13.3-5.33 21.3-5.33 10.3 0 18.82 2.58 25.57 7.73 6.42 4.89 8.16 6.98 11.7 13.3a1 1 0 0 1-.52 1.43l-18.15 6.6a1 1 0 0 1-1.27-.57 17.46 17.46 0 0 0-6.13-7.56c-2.85-2.05-7.06-3.08-11.26-3.08-6 0-11.67 2.18-15.17 6.53s-5.6 10.52-5.6 18.52c0 7.9 2.1 14.03 5.6 18.38S425.32 87 431.32 87c8.15 0 14.59-3.5 17.4-10.49a1 1 0 0 1 1.26-.56l18.14 6.59a1 1 0 0 1 .52 1.45c-3.4 5.84-5.33 8.05-11.76 13.06q-10.2 7.95-25.5 7.95c-8 0-15.1-1.77-21.3-5.32a37.53 37.53 0 0 1-14.55-15c-3.5-6.45-5.25-13.98-5.25-22.58M527.1 20.93a1 1 0 0 1 1.17-.98 42.6 42.6 0 0 1 13.08 4.58 39.1 39.1 0 0 1 15.37 15.07c3.75 6.5 5.63 14 5.63 22.5s-1.93 16-5.78 22.5a40 40 0 0 1-15.6 15.08C534.42 103.23 527.1 105 519 105c-8 0-15.2-1.77-21.6-5.32a38.34 38.34 0 0 1-15.08-15.08c-3.65-6.5-5.47-14-5.47-22.5s1.87-16 5.62-22.5a39.1 39.1 0 0 1 15.38-15.07 42.8 42.8 0 0 1 13.08-4.58 1 1 0 0 1 1.17.98v16.9a1 1 0 0 1-.68.94 24.73 24.73 0 1 0 32.9 23.33 24.74 24.74 0 0 0-16.54-23.33 1 1 0 0 1-.68-.94zm90.75-1.58c9.9 0 17.9 3.13 24 9.38S651 43.7 651 54.9v47.75a1 1 0 0 1-1 1h-19a1 1 0 0 1-1-1v-44.9c0-6.6-1.65-11.67-4.95-15.22s-7.8-5.33-13.5-5.33c-5.8 0-10.38 1.77-13.73 5.33-3.35 3.55-5.02 8.62-5.02 15.22v44.9a1 1 0 0 1-1 1h-19a1 1 0 0 1-1-1v-81.1a1 1 0 0 1 1-1h19a1 1 0 0 1 1 1v9.35c2.8-3.6 6.37-6.42 10.72-8.47a33.2 33.2 0 0 1 14.33-3.08m122.4 14.1c2.7-4.4 6.22-9.05 10.57-11.55 4.1-2.35 8.73-2.58 13.93-2.6a1 1 0 0 1 1 1v20.1a1 1 0 0 1-1 1h-4.55c-6.6 0-11.58 1.55-14.93 4.65s-5.02 8.5-5.02 16.2v40.4a1 1 0 0 1-1 1h-19a1 1 0 0 1-1-1v-81.1a1 1 0 0 1 1-1h19a1 1 0 0 1 1 1zM813.9 105c-8 0-15.2-1.77-21.6-5.33a38.34 38.34 0 0 1-15.08-15.07c-3.65-6.5-5.47-14-5.47-22.5s1.87-16 5.62-22.5a39.1 39.1 0 0 1 15.38-15.08c6.5-3.55 13.75-5.32 21.75-5.32s15.25 1.77 21.75 5.32a39.1 39.1 0 0 1 15.37 15.08c3.75 6.5 5.63 14 5.63 22.5s-1.93 16-5.78 22.5a40 40 0 0 1-15.6 15.07C829.32 103.22 822 105 813.9 105m-.02-18.3c3.8 0 7.88-.93 11.23-2.77a21 21 0 0 0 8.4-8.33c2-3.7 3.14-8.2 3.14-13.5 0-7.9-2.37-13.98-6.52-18.23s-9.94-6.37-15.94-6.37-11.73 2.12-15.78 6.37-6.36 10.33-6.36 18.23 2.25 13.97 6.2 18.22 9.63 6.38 15.63 6.38M888.2 0a1 1 0 0 1 1 1v101.65a1 1 0 0 1-1 1h-19a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm26.29 0c2.3 0 4.36.52 6.16 1.55a11.23 11.23 0 0 1 4.25 4.31 12.34 12.34 0 0 1 1.55 6.16c0 2.28-.52 4.32-1.55 6.13a11.3 11.3 0 0 1-4.25 4.28A12.1 12.1 0 0 1 914.5 24c-2.3 0-4.37-.52-6.2-1.56a11.2 11.2 0 0 1-4.27-4.27 12.2 12.2 0 0 1-1.55-6.14c0-2.27.51-4.32 1.55-6.16a11.16 11.16 0 0 1 4.28-4.3A12.3 12.3 0 0 1 914.49 0m0 22.01c2.91 0 5.41-.93 7.21-2.8a9.97 9.97 0 0 0 2.76-7.19c0-2.9-.96-5.3-2.76-7.17-1.8-1.88-4.3-2.82-7.21-2.82-2.96 0-5.47.94-7.28 2.81a9.97 9.97 0 0 0-2.75 7.18c0 2.91.95 5.3 2.75 7.18s4.32 2.81 7.28 2.81m5.28-12.62c0 1-.28 1.83-.85 2.48a4.04 4.04 0 0 1-2.37 1.29l3.65 5.14-3.1.06-3.34-5.08h-1.4v5.08h-2.6V5.5h5.64c1.34 0 2.4.35 3.19 1.05a3.6 3.6 0 0 1 1.18 2.84m-7.4 1.74h2.85c.56 0 1.03-.14 1.4-.42q.54-.42.54-1.26c0-.84-.18-.97-.55-1.22a2.36 2.36 0 0 0-1.4-.4h-2.85v3.3Z&quot;&gt;&lt;/path&gt;&lt;/symbol&gt;&lt;use href=&quot;#ai:local:tempcontrol-logo&quot;&gt;&lt;/use&gt;  &lt;/svg&gt;&lt;/p&gt;
&lt;p&gt;Of course, now dropping the “PC” stylization of the wordmark, I decided to place slightly more focus on the temperature part by turning the &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;o&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt; closest to the middle into a thermometer bulb.
The logo uses a modified version of ITF’s &lt;a href=&quot;https://github.com/itfoundry/Poppins&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Poppins font&lt;/a&gt;.
I changed not only the spacing, but also rounded off the &lt;span style=&quot;background-color:var(--color-code-bg);color:var(--color-on-dark-bg)&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;s corners where the crossbar and ascender meet to give it a more &lt;a href=&quot;https://lineto.com/typefaces/circular/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Circular&lt;/a&gt; look.
I also rounded all corners a bit for a more friendly appearance, while retaining a clean and modern technical aesthetic.&lt;/p&gt;
&lt;p&gt;Next up were the brand colors. Here, the team requested something really flashy, but that would still work in the future, with the intent to stand out.
I decided to support this decision, so to reinforce the temperature theme, I chose a striking ultraviolet blue and white as the main colors, with an almost black blue color for use on white.&lt;/p&gt;
&lt;div style=&quot;height: 6em; color: #fff&quot;&gt;&lt;div style=&quot;background-color: #143cff&quot;&gt;&lt;span&gt;#143cff&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;background-color: #0c1928&quot;&gt;&lt;span&gt;#0c1928&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;background-color: #fff; color: #000&quot;&gt;&lt;span&gt;#ffffff&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Of course, that would mean a split for print colors, because there’s no way to replicate that blue color with CMYK or spot colors.
So we settled on the following colors for print:&lt;/p&gt;
&lt;div style=&quot;height: 6em; color: #fff&quot;&gt;&lt;div style=&quot;background-color: #0050b5&quot;&gt;&lt;span&gt;Pantone 2388 C&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;background-color: #0c1928&quot;&gt;&lt;span&gt;Pantone 296 C&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;background-color: #fff; color: #000&quot;&gt;&lt;span&gt;CMYK 0, 0, 0, 0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It’s a compromise in flashyness, but everything still looks alright.&lt;/p&gt;
&lt;h2 id=&quot;the-website&quot;&gt;The website&lt;/h2&gt;
&lt;p&gt;Of course, the team also required a modern website to back up their newly acquired logo.
In tight collaboration on the contents the team and I defined what the website had to offer, and I got developing.&lt;/p&gt;
&lt;p&gt;Because of the vibrant, but nevertheless limited color scheme, the website had the potential to become bland.
I decided to keep the text color on white solely to the dark blue, but interspersed lots of ultraviolet blue icons and put lots of effort into animated illustrations.&lt;/p&gt;
&lt;p&gt;For some of the marketing materials I created photorealistic 3D models of tempcontrol’s main product line: tBrix - accurate temperature sensors with extensive monitoring capabilities.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/tbrix-family.Bp_AzlTM_ZSDNdf.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/tbrix-family.Bp_AzlTM_2wLq2z.jxl, https://jama.me/_astro/tbrix-family.Bp_AzlTM_16ugnV.jxl 1.5x, https://jama.me/_astro/tbrix-family.Bp_AzlTM_p2HLy.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tbrix-family.Bp_AzlTM_Z14Gfdv.avif, https://jama.me/_astro/tbrix-family.Bp_AzlTM_Z2uXoR9.avif 1.5x, https://jama.me/_astro/tbrix-family.Bp_AzlTM_jfnw0.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tbrix-family.Bp_AzlTM_1zbp6e.webp, https://jama.me/_astro/tbrix-family.Bp_AzlTM_8TfrA.webp 1.5x, https://jama.me/_astro/tbrix-family.Bp_AzlTM_Z2ciUs9.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/tbrix-family.Bp_AzlTM_ZHQXJQ.png&quot; srcset=&quot;https://jama.me/_astro/tbrix-family.Bp_AzlTM_Z2998ou.png 1.5x, https://jama.me/_astro/tbrix-family.Bp_AzlTM_Z1EjXGn.png 2x&quot; alt=&quot;3D render, tempcontrol&apos;s tBrix family of products&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;531&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;And because these white-gray-ish boxes also look pretty bland, I thought to spice it up a little with a simple &lt;a href=&quot;https://github.com/mrdoob/three.js&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Three.js&lt;/a&gt; scene on the homepage:&lt;/p&gt;
&lt;video loop=&quot;&quot; autoplay=&quot;&quot; muted=&quot;true&quot; playsinline=&quot;&quot; controls=&quot;&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/home-av1.Ba8cVJpZ.webm&quot; type=&quot;video/webm; codecs=av01.0.05M.08&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/home-hevc.BCc8OkCz.mp4&quot; type=&quot;video/mp4; codecs=hvc1&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/home-avc.DgOiVnyj.mp4&quot; type=&quot;video/mp4; codecs=avc1.4D401E&quot;&gt;&lt;/video&gt;
&lt;p&gt;When visiting the site, an illumination texture flashes the LED on the sender unit.
The unit only starts to rotate after a few seconds of suspense. It can also be rotated by input from mouse or touch.&lt;/p&gt;
&lt;p&gt;The website also features other hero animations. This one’s for the tBrix product presentation page:&lt;/p&gt;
&lt;video loop=&quot;&quot; autoplay=&quot;&quot; muted=&quot;true&quot; playsinline=&quot;&quot; controls=&quot;&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/tbrix-av1.Dg0yDFqQ.webm&quot; type=&quot;video/webm; codecs=av01.0.05M.08&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/tbrix-hevc.D5Hfrs6Q.mp4&quot; type=&quot;video/mp4; codecs=hvc1&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/tbrix-avc.B2zSVCj1.mp4&quot; type=&quot;video/mp4; codecs=avc1.4D401E&quot;&gt;&lt;/video&gt;
&lt;p&gt;A little simpler, but still eye-catching. All illustrations are animated using only CSS animations for performance reasons, at most switching CSS classes via JavaScript.&lt;/p&gt;
&lt;p&gt;In that spirit, the focus was on performance, reflected in the PageSpeed Insights score:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/pagespeed.CJZJqI94_NlaiN.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/pagespeed.CJZJqI94_2hlWs9.jxl, https://jama.me/_astro/pagespeed.CJZJqI94_Z1amjfm.jxl 1.5x, https://jama.me/_astro/pagespeed.CJZJqI94_Z1dp7Ln.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/pagespeed.CJZJqI94_ZFCSN5.avif, https://jama.me/_astro/pagespeed.CJZJqI94_VOXil.avif 1.5x, https://jama.me/_astro/pagespeed.CJZJqI94_Z1EK5hF.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/pagespeed.CJZJqI94_Z13Pl4R.webp, https://jama.me/_astro/pagespeed.CJZJqI94_yCw1y.webp 1.5x, https://jama.me/_astro/pagespeed.CJZJqI94_1MvtCJ.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/pagespeed.CJZJqI94_1js4cO.png&quot; srcset=&quot;https://jama.me/_astro/pagespeed.CJZJqI94_Z28gcuG.png 1.5x, https://jama.me/_astro/pagespeed.CJZJqI94_ZD6ODV.png 2x&quot; alt=&quot;Website screenshot, tempcontrol&apos;s PageSpeed Insights overall score of 100&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;398&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;This is from the homepage. It’s afforded by recognizing the requesting device’s resolution, and only loading Three.js and the 3D model when it should be displayed.&lt;/p&gt;
&lt;p&gt;Technically, the website has a minimal Node.js backend using a &lt;a href=&quot;https://www.npmjs.com/package/polka&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Polka&lt;/a&gt; server and &lt;a href=&quot;https://www.npmjs.com/package/ejs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;EJS&lt;/a&gt;-based server-side rendering.
But there’s nothing too special about it, other than to be able to send email when contact forms are sent, and to be fast, of course.&lt;/p&gt;
&lt;h2 id=&quot;verdict&quot;&gt;Verdict&lt;/h2&gt;
&lt;p&gt;Because the team had a good grasp on what they wanted in terms of content, we had lots of effective teamwork.
What they lacked was a direction for their visuals. As a result, they were extremely happy with the website and the rebranding.&lt;/p&gt;
&lt;p&gt;With the rebranding they received significantly more visitors at trade shows and online ads were heaps more effective because of the professional look and salient colors.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/stand.CLy3bkVg_TNr0r.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/stand.CLy3bkVg_Z1YbAYK.jxl, https://jama.me/_astro/stand.CLy3bkVg_1un6h3.jxl 1.5x, https://jama.me/_astro/stand.CLy3bkVg_Ab9oi.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/stand.CLy3bkVg_JJR41.avif, https://jama.me/_astro/stand.CLy3bkVg_ZPRyt7.avif 1.5x, https://jama.me/_astro/stand.CLy3bkVg_Z10YwAH.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/stand.CLy3bkVg_1SlkHX.webp, https://jama.me/_astro/stand.CLy3bkVg_hHTaP.webp 1.5x, https://jama.me/_astro/stand.CLy3bkVg_Z1ePY1L.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/stand.CLy3bkVg_xVd2g.png&quot; srcset=&quot;https://jama.me/_astro/stand.CLy3bkVg_Z12GduR.png 1.5x, https://jama.me/_astro/stand.CLy3bkVg_Z1V1IF9.png 2x&quot; alt=&quot;Photo, small tempcontrol stand at a trade show&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;437&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Shortly after the website launched, they have received feedback from multiple potential customers, saying that tempcontrol was selected because they noticed the new website.
The rebranding and product presentation on the website were described as confidence-inspiring, and tempcontrol received numerous serious requests in a matter of weeks.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - My Hackintosh: Hardware and Undervolting</title><link>https://jama.me/blog/hackintosh-undervolting/</link><guid isPermaLink="true">https://jama.me/blog/hackintosh-undervolting/</guid><description>After six years with my i7 2600K system, I try some undervolting to get the temperatures of my new system under control.</description><pubDate>Tue, 22 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;temperatures-and-hardware&quot;&gt;Temperatures and hardware&lt;/h2&gt;
&lt;p&gt;Every Mac I’ve encountered ran hot due to timid fan speed curves and undersized heatsinks.
The trade-off, though, is quieter operation.
Traditionally, for processor performance, this didn’t matter much.
As long as core temperatures stayed below the Thermal Junction Max (TjMax), the CPU maintained its clock speed.
If temperatures rose above that, the CPU would throttle its speed or shut down to cool off.
High temperatures also slightly increase power consumption (Gamers Nexus noted a 4% rise for every 10°C) and shorten the component’s lifespan.
This behavior is seen in modern Intel and AMD CPUs, except for the newer Ryzen 2000 series, which, using Precision Boost 2, behaves more like a GPU.&lt;/p&gt;
&lt;p&gt;GPUs, especially modern ones, react differently to heat.
Nvidia’s GPU Boost dynamically increases clock speed based on core temperatures, power, and voltage, which reduces manual overclocking headroom compared to older generations (like Fermi) but boosts performance for users not interested in overclocking.
When temperatures are low, you get great clock speeds without effort.
As temperatures climb, clock speeds gradually drop to manage heat.
In games and GPU-heavy tasks, this can visibly affect performance.
In comparison, Apple lets CPUs run up to the 100°C TjMax under sustained load, while in my experience, dedicated GPUs in their systems hover around 80°C to 90°C.&lt;/p&gt;
&lt;h2 id=&quot;assembling-the-new-hardware&quot;&gt;Assembling the new hardware&lt;/h2&gt;
&lt;p&gt;By February 2017, I was still using my old system with these specs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Case:&lt;/strong&gt; Cooler Master CM 690II Nvidia Edition&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Motherboard:&lt;/strong&gt; Asus P8Z68 V-PRO&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU:&lt;/strong&gt; Intel i7 2600K (overclocked to 4.4GHz)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAM:&lt;/strong&gt; 2x8GB Corsair Vengeance DDR3-1600&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Graphics card:&lt;/strong&gt; EVGA GeForce GTX 680 2GB&lt;/li&gt;
&lt;li&gt;Hard disks, etc. irrelevant&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was generally happy with its performance (having come from an Intel Core 2 Quad Q6600 and EVGA GeForce 8800GT), but when the system wouldn’t yield playable performance in DOOM (2016), it was time to move on.
With the release of Intel’s 7th-gen processors (Kaby Lake) and the upcoming GTX 1080Ti, I figured it was the perfect time to build a new system.
By early March 2017, the new build was ready:&lt;/p&gt;
&lt;h3 id=&quot;case-lian-li-pc-q36&quot;&gt;Case: Lian Li PC-Q36&lt;/h3&gt;
&lt;a href=&quot;https://jama.me/_astro/pc-q36.D_V5jSL8_Z1zovyz.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/pc-q36.D_V5jSL8_14ky41.jxl, https://jama.me/_astro/pc-q36.D_V5jSL8_2kET4z.jxl 1.5x, https://jama.me/_astro/pc-q36.D_V5jSL8_Z1SRNgd.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/pc-q36.D_V5jSL8_1jIrpq.avif, https://jama.me/_astro/pc-q36.D_V5jSL8_Z2u8lnW.avif 1.5x, https://jama.me/_astro/pc-q36.D_V5jSL8_1z8DxI.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/pc-q36.D_V5jSL8_Z1U9qx3.webp, https://jama.me/_astro/pc-q36.D_V5jSL8_ZDO5wu.webp 1.5x, https://jama.me/_astro/pc-q36.D_V5jSL8_1lhc7E.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/pc-q36.D_V5jSL8_1o21pc.jpg&quot; srcset=&quot;https://jama.me/_astro/pc-q36.D_V5jSL8_Z2pOLob.jpg 1.5x, https://jama.me/_astro/pc-q36.D_V5jSL8_Z1N22vU.jpg 2x&quot; alt=&quot;Lian Li PC-Q36 computer case on white background&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;600&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;This time, I decided to build a Mini-ITX system to ditch the large tower.
There were several cases to consider, like the &lt;a href=&quot;https://ncases.com/products/m1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NCase M1&lt;/a&gt;, the &lt;a href=&quot;https://www.dan-cases.com/dana4.php&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DAN Cases A4-SFX&lt;/a&gt;, and the &lt;a href=&quot;https://www.louqe.com/ghost-s1/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Louqe Ghost S1&lt;/a&gt;, as well as older options like the Lian Li PC-Q36.&lt;/p&gt;
&lt;p&gt;I first saw the Lian Li PC-V359 in der8auer’s Burst-Fire prebuilt systems from Caseking, and I liked the smaller PC-Q36 even more.
Its design really appealed to me.
Lian Li is known for unique, well-built cases, often made from aluminum rather than cheaper steel.
The two-tier construction (PSU and hard disks at the bottom, motherboard on top) also suggested good airflow, ideal for the power-hungry components I planned to use.&lt;/p&gt;
&lt;p&gt;When I was buying parts, the case was out of production already.
Luckily, I found one from an Amazon Marketplace seller from Spain.&lt;/p&gt;
&lt;p&gt;While I love the case, the Slimline optical drive slot bothered me.
I don’t need an optical drive, but I also didn’t want the slot sitting empty.
A card reader seemed like the perfect solution to avoid a dangling USB one.
Just in time, I saw that &lt;a href=&quot;https://www.silverstonetek.com/en/product/info/accessories/FPS01/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Silverstone had released the FPS01&lt;/a&gt;, a Slimline card reader.&lt;/p&gt;
&lt;p&gt;So, the decision was made:
I’d go with the PC-Q36, mostly because of the looks.
The other cases were too small for larger CPU aircoolers and didn’t accommodate 3.5” hard drives.&lt;/p&gt;
&lt;h3 id=&quot;motherboard-asus-rog-strix-z270i-gaming&quot;&gt;Motherboard: ASUS ROG Strix Z270I Gaming&lt;/h3&gt;
&lt;p&gt;Choosing a motherboard was tough.
It came down to the ASRock Fatal1ty Z270 Gaming-ITX or this ASUS board.
The decision was between a Thunderbolt 3 port or two M.2 slots for another SSD.
I went with the latter.
Otherwise, the two boards are very similar, especially since I wasn’t aiming for extreme CPU overclocks.&lt;/p&gt;
&lt;h3 id=&quot;cpu-intel-core-i7-7700k&quot;&gt;CPU: Intel Core i7 7700K&lt;/h3&gt;
&lt;p&gt;Not much to add here.
It’s Intel’s overclockable high-end mainstream processor, slightly better than the 6700K.
I like the added H.265 decode support.&lt;/p&gt;
&lt;h3 id=&quot;ram-2x16gb-corsair-vengeance-lpx-ddr4-3000&quot;&gt;RAM: 2x16GB Corsair Vengeance LPX DDR4-3000&lt;/h3&gt;
&lt;p&gt;These 3000MHz CL15 sticks offered the best price/performance.
Dual rank limits potential overclocking, but that’s expected.&lt;/p&gt;
&lt;h3 id=&quot;graphics-card-nvidia-geforce-gtx-1080ti-founders-edition&quot;&gt;Graphics card: Nvidia GeForce GTX 1080Ti Founder’s Edition&lt;/h3&gt;
&lt;p&gt;This was the part I was most excited about.
The GTX 1080Ti offers a solid 30% performance bump over the GTX 1080, and coming from a GTX 680, it’s a massive upgrade.&lt;/p&gt;
&lt;h3 id=&quot;ssd-samsung-960-evo-m2-500gb&quot;&gt;SSD: Samsung 960 EVO M.2 500GB&lt;/h3&gt;
&lt;p&gt;The case had room for 3.5” and 2.5” drives, but with two M.2 slots on the motherboard, I opted for the 960 EVO for its price/performance ratio—just shy of the PRO model.&lt;/p&gt;
&lt;h3 id=&quot;cpu-cooler-bequiet-dark-rock-3&quot;&gt;CPU cooler: beQuiet! Dark Rock 3&lt;/h3&gt;
&lt;p&gt;I previously used a Scythe Ninja 3 Rev. B on my i7 2600K, which worked great but was huge.
While the case could fit a large aircooler, I chose the Dark Rock 3 based on reviews and its sleek black look.
In hindsight, I should’ve picked something else.&lt;/p&gt;
&lt;h3 id=&quot;fans-2x-blacknoise-nb-eloop-b12-ps&quot;&gt;Fans: 2x Blacknoise NB-eLoop B12-PS&lt;/h3&gt;
&lt;p&gt;I stuck with what I knew.
After replacing the old case fans in my Cooler Master with eLoops and being satisfied, I got two for this build - one for the CPU cooler and one for the exhaust.&lt;/p&gt;
&lt;h3 id=&quot;psu-corsair-rm650i&quot;&gt;PSU: Corsair RM650i&lt;/h3&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://web.archive.org/web/20190715172332/http://www.jonnyguru.com:80/blog/2015/09/21/corsair-rm650i-650w-power-supply/&quot;&gt;JonnyGuru (via Web Archive)&lt;/a&gt; recommended it, so I figured it was a solid choice.
In retrospect, I should have gone for the RM650x or a similarly priced Seasonic unit, as I don’t use the Corsair Link feature, but no big deal.&lt;/p&gt;
&lt;h2 id=&quot;first-start&quot;&gt;First Start&lt;/h2&gt;
&lt;p&gt;Alright!&lt;/p&gt;
&lt;p&gt;I got all the components, built the system, and powered it on.
In Windows, I didn’t notice much of a difference in general snappiness.
Makes sense, as the Windows Desktop Manager (WDM) isn’t that demanding.
The M.2 SSD didn’t immediately blow me away either, likely due to software limitations or system latency when loading small files, like during boot.
The upgrade wasn’t as dramatic as moving from a spinning hard drive to a SATA SSD.&lt;/p&gt;
&lt;p&gt;So I had quite tepid expectations, but then several problems cropped up:&lt;/p&gt;
&lt;h3 id=&quot;coil-whine&quot;&gt;Coil Whine&lt;/h3&gt;
&lt;p&gt;First, I checked the CPU and GPU temperatures and compared the old and new systems using Unigine’s Heaven Benchmark.
I knew from &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.youtube.com/watch?v=BipVR5-YM_Y&quot;&gt;Gamers Nexus’ review&lt;/a&gt; that the Founder’s Edition GTX 1080Ti would run warm - similar to the GTX 680 - so I was prepared for the inevitable noise.
However, I quickly noticed significant coil whine, even under a light load (even after lowering settings and enabling VSync).
With the PC meant to sit on my desk, enduring the whine wasn’t an option, so I reluctantly returned the card.&lt;/p&gt;
&lt;p&gt;And I didn’t order another immediately.
The reference design was loud and inefficient at cooling, holding 1780MHz at 84°C under load - better than my old card for sure, but I decided to wait for other options.
Eventually, more models arrived, but most were too thick (2.5 PCI slots).
Luckily, EVGA released 2-slot coolers for the GTX 1080Ti.
I settled on the EVGA GTX 1080Ti SC2, as the FTW3 model I wanted had no defined launch date.
Thankfully, when the new card arrived, I noticed that it was free of coil whine.&lt;/p&gt;
&lt;h3 id=&quot;temperatures&quot;&gt;Temperatures&lt;/h3&gt;
&lt;p&gt;From the start, the system had cooling issues I hadn’t experienced before.
Four factors were at play: the case, the graphics card, Intel, and ASUS.
And, of course, my ongoing quest for both performance and silence.&lt;/p&gt;
&lt;h4 id=&quot;intel&quot;&gt;Intel&lt;/h4&gt;
&lt;p&gt;The Core i7 7700K is notorious for poor thermal performance, prompting Intel to advise customers &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.pcgamer.com/intels-tells-core-i7-7700k-owners-to-stop-overclocking-to-avoid-high-temps/&quot;&gt;against overclocking&lt;/a&gt; their overclockable CPU if they dislike the high temps.
The culprit is the thermal paste Intel used between the silicon die and the heatspreader.
Older Intel and most AMD processors were soldered, which offered better heat conductivity but at a higher production cost.&lt;/p&gt;
&lt;p&gt;Since the 8th generation (Coffee Lake), Intel improved the paste, but temperature spikes persist.
I eventually delidded the 7700K and replaced the paste with Thermal Grizzly Conductonaut, which offers much better thermal conductivity at 73 W/(m·K).&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/delid.DDpVSylA_X6ifT.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/delid.DDpVSylA_EKztd.jxl, https://jama.me/_astro/delid.DDpVSylA_Z21avuv.jxl 1.5x, https://jama.me/_astro/delid.DDpVSylA_2uxRbP.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/delid.DDpVSylA_ZXwEXh.avif, https://jama.me/_astro/delid.DDpVSylA_1pImQV.avif 1.5x, https://jama.me/_astro/delid.DDpVSylA_2ngsoV.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/delid.DDpVSylA_pTNff.webp, https://jama.me/_astro/delid.DDpVSylA_Z2g1hIt.webp 1.5x, https://jama.me/_astro/delid.DDpVSylA_2r0TM.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/delid.DDpVSylA_HB7Ij.jpg&quot; srcset=&quot;https://jama.me/_astro/delid.DDpVSylA_Z1XjXfp.jpg 1.5x, https://jama.me/_astro/delid.DDpVSylA_Z18rmW3.jpg 2x&quot; alt=&quot;A delidded Intel Core i7 7700K&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;450&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;After reapplying the heatspreader with heat-resistant silicone, I saw temps reduced by about 19°C, and eliminated the fan speed spikes that happened by just moving the mouse on the desktop.
I also used Thermal Grizzly Kryonaut between the heatspreader and the CPU cooler for good measure.&lt;/p&gt;
&lt;h4 id=&quot;graphics-card&quot;&gt;Graphics Card&lt;/h4&gt;
&lt;p&gt;This was my third self-built system, but the first where I had a wide choice of aftermarket graphics card coolers.
Most modern graphics cards use open coolers with axial fans, which blow air through the fins and exhaust it into the case everywhere around them.
But in small cases like mine, this meant hot air from the GPU directly warmed the CPU cooler.&lt;/p&gt;
&lt;p&gt;With only two fans — one on the CPU cooler and one for exhaust — cooling was a challenge.
I tried replacing the eLoop exhaust fan with three airflow-optimized Noctua NF-S12A fans.
After mounting all three as case exhausts, CPU temps dropped significantly, making GPU overclocking feasible.&lt;/p&gt;
&lt;p&gt;Using MSI Afterburner, I raised the GPU and memory clock speeds by +70MHz and +550MHz, respectively.
This resulted in clock speeds of 2010-2110MHz, depending on temps, giving me a 4-8 FPS boost in games, though temps increased further.&lt;/p&gt;
&lt;h4 id=&quot;case&quot;&gt;Case&lt;/h4&gt;
&lt;p&gt;The Lian Li PC-Q36’s cooling design is deceptive.
While it appears open, the positioning of the fan mounts in the side panels of the case creates issues.
When case is closed, the graphics card would pull in air through the fan mounts right next to it.
But, while they are perfectly positioned on the CPU side, on the graphics card side the perforations are too high up, with half of the graphics card’s fans obstructed by solid aluminum.
This prevents proper airflow, and combined with dust filters, it’s basically a hotbox.&lt;/p&gt;
&lt;p&gt;Removing the side panel on the GPU side significantly reduces impedance, dropping temps from 74°C to 66°C and 30% less fan speed.
It’s a massive difference with high power draw components such as these.&lt;/p&gt;
&lt;h4 id=&quot;motherboard&quot;&gt;Motherboard&lt;/h4&gt;
&lt;p&gt;In order to combat the graphics card situation, I attempted to rotate the CPU cooler 90° to help exhaust heat from the graphics card, but the Strix Z270I Gaming’s VRM cooler was too tall to fit under the Dark Rock 3.
The same issue occurred with Noctua coolers, so I had to leave it as-is.&lt;/p&gt;
&lt;h3 id=&quot;interim-conclusion&quot;&gt;Interim Conclusion&lt;/h3&gt;
&lt;p&gt;The system has had its challenges, but it’s improved over time.
I played DOOM (2016) shortly after assembly and hit 200FPS consistently, though the graphics card was really loud.
Thankfully, I had closed-back headphones.&lt;/p&gt;
&lt;p&gt;Recently, I upgraded to Noctua’s NF-A12x25 fans, which have a smaller than normal gap between the blades and housing, improving airflow by being better equipped for pushing against the impedance of the case’s side panels.
These fans are incredibly quiet and lowered CPU temps by a few more degrees at the same noise level.&lt;/p&gt;
&lt;h2 id=&quot;performance&quot;&gt;Performance&lt;/h2&gt;
&lt;a href=&quot;https://jama.me/_astro/superposition.VID7cQlX_Z1HhjVO.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/superposition.VID7cQlX_Z2kBI9o.jxl, https://jama.me/_astro/superposition.VID7cQlX_sBt5A.jxl 1.5x, https://jama.me/_astro/superposition.VID7cQlX_Z1FwL4t.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/superposition.VID7cQlX_ZQSfAx.avif, https://jama.me/_astro/superposition.VID7cQlX_1VkVDr.avif 1.5x, https://jama.me/_astro/superposition.VID7cQlX_Z1PIyes.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/superposition.VID7cQlX_1LYoIc.webp, https://jama.me/_astro/superposition.VID7cQlX_ZtXwPK.webp 1.5x, https://jama.me/_astro/superposition.VID7cQlX_ZVDnix.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/superposition.VID7cQlX_Z2gCF5F.jpg&quot; srcset=&quot;https://jama.me/_astro/superposition.VID7cQlX_wAw9j.jpg 1.5x, https://jama.me/_astro/superposition.VID7cQlX_kXLNS.jpg 2x&quot; alt=&quot;Screenshot of the 3D scene in Unigine Superposition benchmark&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Now we get to the interesting part.
I’ve compiled a few graphs showing the system’s performance in its current state.
I started with Unigine’s Superposition Benchmark on the 1080p Extreme preset and logged data using MSI Afterburner.
Superposition has little variation because it’s essentially a single room, which makes it a good benchmark, but it’s not fully comparable to modern games.
I’ll use &lt;strong&gt;Final Fantasy XV&lt;/strong&gt; later for a more realistic test.&lt;/p&gt;
&lt;h3 id=&quot;baseline&quot;&gt;Baseline&lt;/h3&gt;
&lt;p&gt;First, the out-of-box performance.
Both the CPU and GPU are stock.
Every test below shows a 10-minute load with cooling after the test.&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt; &lt;script type=&quot;module&quot; src=&quot;https://jama.me/Users/janmaslov/Documents/repos/maslov2024/src/components/Chart/index.astro?astro&amp;amp;type=script&amp;amp;index=0&amp;amp;lang.ts&quot;&gt;&lt;/script&gt; 
&lt;p&gt;The test starts at 00:01:25, visible in the framerate and frametime plot.
The GPU temp rises, with voltage and clock speed bouncing around.
The clock fluctuates between 1911MHz and 1923MHz, and the temperature peaks at 74°C, hitting a max of 1.043v.
Power consumption occasionally exceeds the limit (up to 105%) with fans ramping to 2300rpm.&lt;/p&gt;
&lt;p&gt;At this speed, the fans weren’t unbearably loud but still distinct from 1m away, generating a soft vacuum-like drone, albeit much quieter than an actual vacuum cleaner.&lt;/p&gt;
&lt;p&gt;On to frametime analysis.
Frametime is better than framerate for identifying stutters, because what we know as FPS averages all frametimes in a second.
Stutters come from frames taking longer to render than others.
MSI Afterburner can log the highest frametime in a second, so we can still spot stutters.&lt;/p&gt;
&lt;p&gt;For a smooth 60FPS experience, each frame should take exactly 16.667ms to render.
If one takes 20ms (50FPS) for example, you’ll feel a slight stutter.
In this run, the average frametime was &lt;strong&gt;12.825ms (78FPS)&lt;/strong&gt;.
To assess stutters, we can look at the 99th and 99.9th percentiles, meaning the highest 1% and 0.1% of frametimes.
These come out to &lt;strong&gt;13.62ms (73FPS)&lt;/strong&gt; and &lt;strong&gt;15.784ms (63FPS)&lt;/strong&gt;, which means that every now and then performance dipped closer to 60FPS than 80FPS.
Generally, if the slower to render frames are faster than 16.667ms, stutters aren’t noticeable if you’re targeting 60FPS.
Watching the test, I would’ve noticed stutters if the slow frames exceeded that time.&lt;/p&gt;
&lt;h3 id=&quot;with-overclock&quot;&gt;With Overclock&lt;/h3&gt;
&lt;p&gt;Next, I overclocked the GPU by maxing voltage and power limits (+100 and 120%) and boosting the core clock by 66MHz and memory clock by 452MHz.
Here’s the result:&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;p&gt;The test starts at 00:01:29, with the clock now hovering around 2000MHz.
Spikes in voltage hit 1.081v (1.09v is Nvidia’s max for Pascal GPUs).
The GPU temperature sits around 79°C with fans at 2700rpm, generating a louder drone.
Too loud for comfortable gaming in my book, especially with open-backed headphones.&lt;/p&gt;
&lt;p&gt;The frametime analysis shows a slight performance boost:
an average frametime of &lt;strong&gt;12.143ms (82FPS)&lt;/strong&gt;, a 1% low of &lt;strong&gt;12.705ms (79FPS)&lt;/strong&gt;, and a 0.1% low of &lt;strong&gt;14.792ms (68FPS)&lt;/strong&gt;.
While this only translates to a 4FPS increase, the slower frames render quicker, potentially preventing noticeable stutters in other cases.&lt;/p&gt;
&lt;h3 id=&quot;why-do-frametimes-stagnate&quot;&gt;Why Do Frametimes Stagnate?&lt;/h3&gt;
&lt;p&gt;Why the frametime stagnation?
The camera stayed in the exact same position, showing the same scene for both tests.
However, many factors affect frametime, from how dynamic the scene itself is, to GPU clock speed fluctuations, or even background processes.&lt;/p&gt;
&lt;p&gt;For example, steam from the gravity generator consists of particles — two-polygon squares of varying size that spawn and disappear.
Even if the particle system is coded in an optimized way, it’s still a process that can influence frametimes.
The Superposition Benchmark shows the number of rendered polygons in the top-right corner, which constantly changes due to these dynamics.&lt;/p&gt;
&lt;h3 id=&quot;undervolting&quot;&gt;Undervolting&lt;/h3&gt;
&lt;p&gt;You can’t achieve perfectly flat frametimes in a real game, but there’s more to consider.
Both test cases were too loud and hot for my taste.
The overclocked system pulled 415 watts from the wall, compared to 370 watts at stock.
The GTX 1080Ti has a stock power limit of 250 watts, but I set it to 120%, allowing it to pull 300 watts, which it sometimes did.&lt;/p&gt;
&lt;p&gt;Although power consumption matters to me, performance (in terms of gaming, acoustics, and thermals) is my priority, which is why I also tried undervolting.
MSI Afterburner allows GPU voltage control.
By pressing CTRL+F, you can adjust the voltage/frequency curve.
After some trial and error, I found a curve that was stable for my card.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/voltagecurve.cxxWPalA_2nO6RE.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/voltagecurve.cxxWPalA_2usBX2.jxl, https://jama.me/_astro/voltagecurve.cxxWPalA_2rY1nN.jxl 1.5x, https://jama.me/_astro/voltagecurve.cxxWPalA_Z1yEVQP.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/voltagecurve.cxxWPalA_237ErJ.avif, https://jama.me/_astro/voltagecurve.cxxWPalA_20D3Rv.avif 1.5x, https://jama.me/_astro/voltagecurve.cxxWPalA_ZcIfjb.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/voltagecurve.cxxWPalA_qc5yd.webp, https://jama.me/_astro/voltagecurve.cxxWPalA_nHtXY.webp 1.5x, https://jama.me/_astro/voltagecurve.cxxWPalA_mvs9T.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/voltagecurve.cxxWPalA_Z20qdIs.png&quot; srcset=&quot;https://jama.me/_astro/voltagecurve.cxxWPalA_Z22TOiG.png 1.5x, https://jama.me/_astro/voltagecurve.cxxWPalA_171Os5.png 2x&quot; alt=&quot;Screenshot of voltage frequency curve window in MSI Afterburner with modified curve for undervolt&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;416&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Even a slight reduction in voltage can cause crashes under varied loads (e.g., 3D Mark Fire Strike Extreme or any game or other load).
After months without a crash, I’m confident my curve is stable.
My goal wasn’t to squeeze every bit of performance, but to balance high clock speed with low voltage.
Keep in mind, my curve won’t work for every GTX 1080Ti, as manufacturing tolerances vary between GPUs.&lt;/p&gt;
&lt;p&gt;If your card crashes using my settings, increase the voltage at your desired clock speed.
If it runs stable, try lowering the voltage or raising the clock speed.
Or simply leave it as-is.&lt;/p&gt;
&lt;p&gt;The key point is the highest point on the curve at 1974MHz and 0.931v.
Beyond that, I’ve locked all voltage points to the same clock speed.
As long as temperature, power and voltage allow, the GPU Boost algorithm will pick the highest possible clock at the lowest possible voltage, and by not providing the card the opportunity to boost the clock speed even higher, I ensure that’s the voltage/frequency point I’ll get in practice.
I also set the power limit to 120% and raised the memory clock offset by 452MHz.&lt;/p&gt;
&lt;h3 id=&quot;with-undervolt&quot;&gt;With Undervolt&lt;/h3&gt;
&lt;p&gt;Here are the results:&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;p&gt;The test starts at 00:01:06.
The voltage quickly stabilizes at 0.931v, and the clock speed starts out at 1974MHz, then settles at 1949MHz as temperatures rise, which was the goal of this voltage/frequency curve.
The clock speed stays consistent throughout the test with the GPU temperature peaking at 70°C, while the fans spin at around 1870rpm — audible but without the annoying drone.&lt;/p&gt;
&lt;p&gt;Interestingly, the power limit never fully engages, maxing out at 89% (222.5 watts). As temperatures climb, we see reduced power consumption, saving about 70-80 watts under load.
Neat!&lt;/p&gt;
&lt;p&gt;With the clock speed now stable, frametimes improve.
We get an average of &lt;strong&gt;12.506ms (80FPS)&lt;/strong&gt;, a 1% low of &lt;strong&gt;12.954ms (77FPS)&lt;/strong&gt;, and a 0.1% low of &lt;strong&gt;14.553ms (69FPS)&lt;/strong&gt;.
Ignoring the first three seconds, the average is &lt;strong&gt;12.5ms (80FPS)&lt;/strong&gt;, with a 1% low of &lt;strong&gt;12.94ms (77FPS)&lt;/strong&gt; and a 0.1% low of &lt;strong&gt;13.15ms (76FPS)&lt;/strong&gt;.
This notable improvement in the slowest frames can help prevent stutters, thanks to the stable clock speed.&lt;/p&gt;
&lt;p&gt;To better illustrate the real-world impact, here’s a comparison of all three tests (x-axis starting at 50FPS to better illustrate the differences):&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;p&gt;As expected, the undervolt test lands in the middle for average FPS.
In comparison, the clock speed was lower in the baseline and higher in the overclock test.
However, the minimum FPS improved by 12FPS, from &lt;strong&gt;64FPS to 76FPS&lt;/strong&gt;, allowing for more of a buffer for slow frames and reducing perceived micro stutters.&lt;/p&gt;
&lt;h2 id=&quot;performance-in-games&quot;&gt;Performance in Games&lt;/h2&gt;
&lt;a href=&quot;https://jama.me/_astro/ffxv.CNgvYsCX_ZGaOmq.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/ffxv.CNgvYsCX_Z2820Js.jxl, https://jama.me/_astro/ffxv.CNgvYsCX_Z1OnOVh.jxl 1.5x, https://jama.me/_astro/ffxv.CNgvYsCX_rsryR.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/ffxv.CNgvYsCX_Z1Cvxy2.avif, https://jama.me/_astro/ffxv.CNgvYsCX_Z1jRmJQ.avif 1.5x, https://jama.me/_astro/ffxv.CNgvYsCX_GQkUh.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/ffxv.CNgvYsCX_4amUz.webp, https://jama.me/_astro/ffxv.CNgvYsCX_mNxIK.webp 1.5x, https://jama.me/_astro/ffxv.CNgvYsCX_Z2x1x2c.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/ffxv.CNgvYsCX_ZDkIuh.jpg&quot; srcset=&quot;https://jama.me/_astro/ffxv.CNgvYsCX_ZkGxG6.jpg 1.5x, https://jama.me/_astro/ffxv.CNgvYsCX_L9TU3.jpg 2x&quot; alt=&quot;Screenshot of Final Fantasy XV Hammerhead gas station&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Let’s look at in-game performance beyond benchmarks.
I spent 10 minutes standing around Hammerhead in &lt;strong&gt;Final Fantasy XV&lt;/strong&gt;, the game I’m currently playing.
To ensure accurate data, I loaded the game beforehand, then tabbed out to let the system cool before starting the test.
I also verified that tabbing out and back in didn’t affect performance.
The test was conducted at 1920x1080 with the following settings:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/ffxv_settings.oJze34H5_2tH9QK.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/ffxv_settings.oJze34H5_ZcGwhX.jxl, https://jama.me/_astro/ffxv_settings.oJze34H5_2p0O9T.jxl 1.5x, https://jama.me/_astro/ffxv_settings.oJze34H5_Z1j6v5c.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/ffxv_settings.oJze34H5_ZmSjrW.avif, https://jama.me/_astro/ffxv_settings.oJze34H5_2eO1YU.avif 1.5x, https://jama.me/_astro/ffxv_settings.oJze34H5_Z1oSPkK.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/ffxv_settings.oJze34H5_wbQsX.webp, https://jama.me/_astro/ffxv_settings.oJze34H5_Z1VhUS6.webp 1.5x, https://jama.me/_astro/ffxv_settings.oJze34H5_19IYu2.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/ffxv_settings.oJze34H5_xfKsv.png&quot; srcset=&quot;https://jama.me/_astro/ffxv_settings.oJze34H5_Z1Ue1Sy.png 1.5x, https://jama.me/_astro/ffxv_settings.oJze34H5_1GHVfN.png 2x&quot; alt=&quot;Screenshot of Final Fantasy XV settings menu&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;The framerate cap was set to 120FPS for this test (setting it to 60FPS would underutilize the GPU, keeping it cooler and quieter).&lt;/p&gt;
&lt;h3 id=&quot;baseline-1&quot;&gt;Baseline&lt;/h3&gt;
&lt;p&gt;First, the baseline performance with stock factory settings.
CPU temperature and load percentage are included to show how the GPU impacts the CPU.&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;p&gt;The test starts at 00:01:07.
The clock speed starts out at 1987MHz and reaches below 1900MHz after about 90 seconds.
Voltage fluctuates around 1v before stabilizing slightly below that.
The GPU reaches 73°C, with fans spinning at 2100rpm, gradually increasing to 2200rpm by the end.
A noticeable “vacuum drone” begins to appear around that point.&lt;/p&gt;
&lt;p&gt;The power limit is similar to what I observed in the Unigine Superposition test, consistently above 90%, occasionally spiking past 100%.
The CPU starts at 55°C, rising to 71°C due to the open GPU cooler design.&lt;/p&gt;
&lt;p&gt;Interestingly, the GPU load fluctuates, which could indicate a CPU bottleneck (unlikely since the CPU load remained around 50%), a power or voltage limit on the GPU, or a game process momentarily stalling the CPU’s rendering thread.
Game engines are complex, and this variability might be a result of dynamic factors, or it could simply be run-to-run variance.&lt;/p&gt;
&lt;p&gt;For frametime analysis, I excluded the first two seconds to eliminate stutters from tabbing into the game.
While the framerate paints a positive picture with an average of &lt;strong&gt;77FPS&lt;/strong&gt;, the frametime data shows discrepancies.
MSI Afterburner logs the highest frametime in a second, not the average, which explains why the game still stutters despite decent FPS.
Looking at the frametimes, we see an average of &lt;strong&gt;17.706ms (56FPS)&lt;/strong&gt;, a 1% low of &lt;strong&gt;26.66ms (38FPS)&lt;/strong&gt;, and a 0.1% low of &lt;strong&gt;110.07ms (9FPS)&lt;/strong&gt;, reflecting noticeable stuttering.&lt;/p&gt;
&lt;p&gt;At 00:05:14, a framerate increase correlates with the sunset, as disabling the sun’s light source reduces shadow calculations.
When the moonlight source activates shortly after, FPS drops again due to resumed shadow processing.&lt;/p&gt;
&lt;h3 id=&quot;with-overclock-1&quot;&gt;With Overclock&lt;/h3&gt;
&lt;p&gt;Now, the same scene but with overclocked settings.&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;p&gt;This test starts at 00:00:55.
The clock speed hits 2050MHz but drops to just below 2000MHz within the first minute.
Voltage remains above 1v throughout, and the GPU temperature rapidly rises to 83°C.
Fan speeds sharply increase, reaching up to 3100rpm by the end.
The vacuum drone disappears at around 2900rpm, but the wind noise was unbearably loud, making the machine uncomfortable to be around.&lt;/p&gt;
&lt;p&gt;The CPU temperature is slightly raised compared to the baseline, spiking to 75°C.
The GPU’s power limit exceeds 110% throughout the test.
Frametime consistency improves over the baseline, but there are still occasional spikes.
The average frametime is &lt;strong&gt;16.508ms (61FPS)&lt;/strong&gt;, with a 1% low of &lt;strong&gt;26.274ms (38FPS)&lt;/strong&gt; and a 0.1% low of &lt;strong&gt;29.614ms (34FPS)&lt;/strong&gt;.
The average framerate was &lt;strong&gt;83FPS&lt;/strong&gt;, but there’s still a significant discrepancy between FPS and frametime data.&lt;/p&gt;
&lt;h3 id=&quot;with-undervolt-1&quot;&gt;With Undervolt&lt;/h3&gt;
&lt;p&gt;Finally, the test with a custom voltage/frequency curve using MSI Afterburner.&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;p&gt;The test starts at 00:00:48.
Clock speed starts out at 1974MHz and drops to 1949MHz as temperatures rise.
The voltage remains stable at 0.931v.
GPU temperature peaks at 72°C, with the fans running at a more tolerable 2050rpm.
Power consumption fluctuates early on before stabilizing between 90-96%.
Interestingly, the GPU load is more stable here than in the other tests, though this could be due to run-to-run variance or background processes in Windows.
The CPU temperature hovers around 68-70°C.&lt;/p&gt;
&lt;p&gt;The fluctuating power and GPU load at the start are reflected in the frametime plot.
The average frametime is &lt;strong&gt;13.502ms (74FPS)&lt;/strong&gt;, with a 1% low of &lt;strong&gt;21.748ms (46FPS)&lt;/strong&gt; and a 0.1% low of &lt;strong&gt;24.43ms (41FPS)&lt;/strong&gt;.
Cleaning the data of early fluctuations, the average frametime improves to &lt;strong&gt;13.064ms (77FPS)&lt;/strong&gt;, with a 1% low of &lt;strong&gt;15.667ms (64FPS)&lt;/strong&gt; and a 0.1% low of &lt;strong&gt;16.331ms (61FPS)&lt;/strong&gt;.
Once the fluctuations settle, the frametimes become more stable, with slower frames mostly staying above 60FPS.&lt;/p&gt;
&lt;jama-chart style=&quot;--aspect: 1.5;&quot;&gt; &lt;canvas style=&quot;--aspect: 1.5;&quot;&gt;&lt;/canvas&gt; &lt;/jama-chart&gt;  
&lt;p&gt;And with that, this lengthy post comes to an end.
I hope this demonstrates the real-world impact of undervolting your GPU, not just in benchmarks but in games too.
Reflecting on the months-long journey, I wish I had explored the PC case market more thoroughly to avoid the hassle of cooling high-end components in a Mini-ITX case.
Ironically, I now find Lian Li’s PC-Q37 more appealing than I initially did.
But the grass always seems greener.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - Hello World and This Blog&apos;s Rules</title><link>https://jama.me/blog/hello-world/</link><guid isPermaLink="true">https://jama.me/blog/hello-world/</guid><description>In this first post I&apos;ll quickly lay down some rules I&apos;ll follow in this blog, so there&apos;s no misunderstandings in the future.</description><pubDate>Tue, 24 Apr 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;These rules aren’t anything special.
They simply outline what I aim to do with this blog, what I hope to achieve, and what I avoid.
I may add to this list in the future.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I write when I have a topic that I feel adds value, helps someone, or teaches something.
Occasionally, I may post personal stuff.&lt;/p&gt;
&lt;p&gt;I realize that blogging and writing case studies improves my writing skills, which in turn will make me a better writer for games and a better developer.
Being able to structure and communicate thoughts in an understandable way is a very valuable skill to have.
I can’t say I like writing a lot, but as the saying goes, appetite comes with eating.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This blog focuses on &lt;strong&gt;tech, media, programming, and video games&lt;/strong&gt;.
No shitposts, no politics, religion, or other polarizing subjects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;People say I’m pretty level-headed.
I’ll try to approach every topic neutrally and base it strictly on facts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When I say I like something, I mean it.
I don’t get paid for opinions, have no ties to companies, and receive products the same way as you.
My opinion is my own and may differ from yours — please don’t be upset.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;All text on this website is written by me. No AI.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you spot a mistake or have a suggestion, feel free to reach out!&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - Petman.de</title><link>https://jama.me/projects/petman/</link><guid isPermaLink="true">https://jama.me/projects/petman/</guid><description>A content management system built from the ground up, plus the company website made with it.</description><pubDate>Tue, 01 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Visit the website here: &lt;a href=&quot;https://petman.de&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;petman.de&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In 2016, Petman, a German pet food manufacturer specializing in &lt;a href=&quot;https://en.wikipedia.org/wiki/Raw_feeding&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BARF&lt;/a&gt;, did a slight rebranding in order to modernize and approach a younger clientele.
They didn’t update their website as part of the rebranding though, because they were looking to move away from their antiquated WordPress deployment that they had since 2011.&lt;/p&gt;
&lt;h2 id=&quot;the-pains-to-fix&quot;&gt;The pains to fix&lt;/h2&gt;
&lt;p&gt;The team maintaining the website were grappling with technical issues related to their WordPress installation running on 1&amp;amp;1 (now Ionos) services that were long superseded.
The maintainers had no web development experience, consisting of copywriters and packaging designers.
They cited numerous issues they had editing content on the website, breaking it regularly.&lt;/p&gt;
&lt;p&gt;The WordPress theme used was customized by the previous developers from a ready-made theme. And that came with several drawbacks and pains.
Alleviating them was the main reason for requesting a complete makeover from the ground up, in addition to providing a simpler content editing experience.&lt;/p&gt;
&lt;h3 id=&quot;confusing-home-page&quot;&gt;Confusing home page&lt;/h3&gt;
&lt;p&gt;The home page didn’t make it clear what the website is about.&lt;/p&gt;
&lt;p&gt;It was dominated by a huge slider that, for a long time, only acted as a hub to visit the different product categories.
Below that was also a hub for visiting the product categories.
As a result it pushed meaningful content way below the fold.
And it wasn’t better on mobile:&lt;/p&gt;
&lt;div&gt;&lt;a href=&quot;https://jama.me/_astro/old-home-desktop.DnAssVKp_Z2oir4Y.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/old-home-desktop.DnAssVKp_cXxIv.jxl, https://jama.me/_astro/old-home-desktop.DnAssVKp_Z2vnHaC.jxl 1.5x, https://jama.me/_astro/old-home-desktop.DnAssVKp_Z18i05.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/old-home-desktop.DnAssVKp_u83mG.avif, https://jama.me/_astro/old-home-desktop.DnAssVKp_Z2eecwr.avif 1.5x, https://jama.me/_astro/old-home-desktop.DnAssVKp_Z1sH0Im.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/old-home-desktop.DnAssVKp_1OlKrG.webp, https://jama.me/_astro/old-home-desktop.DnAssVKp_ZT0urr.webp 1.5x, https://jama.me/_astro/old-home-desktop.DnAssVKp_ZRkgIP.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/old-home-desktop.DnAssVKp_Z2kJ427.png&quot; srcset=&quot;https://jama.me/_astro/old-home-desktop.DnAssVKp_15NRG.png 1.5x, https://jama.me/_astro/old-home-desktop.DnAssVKp_2cROoy.png 2x&quot; alt=&quot;Website screenshot, old petman.de website in a desktop browser&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;398&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;https://jama.me/_astro/old-home-mobile.BORUNQy__Z178hHW.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/old-home-mobile.BORUNQy__Z13bOGK.jxl, https://jama.me/_astro/old-home-mobile.BORUNQy__g0itW.jxl 1.5x, https://jama.me/_astro/old-home-mobile.BORUNQy__Z2kmdkJ.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/old-home-mobile.BORUNQy__15r1cT.avif, https://jama.me/_astro/old-home-mobile.BORUNQy__2oD9oB.avif 1.5x, https://jama.me/_astro/old-home-mobile.BORUNQy__Z23cHGy.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/old-home-mobile.BORUNQy__ZF0s4E.webp, https://jama.me/_astro/old-home-mobile.BORUNQy__DbF73.webp 1.5x, https://jama.me/_astro/old-home-mobile.BORUNQy__ZHY0By.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/old-home-mobile.BORUNQy__OQseq.png&quot; srcset=&quot;https://jama.me/_astro/old-home-mobile.BORUNQy__293Aq8.png 1.5x, https://jama.me/_astro/old-home-mobile.BORUNQy__c7iHz.png 2x&quot; alt=&quot;Website screenshot, old petman.de website in a mobile browser&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;1067&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;The new homepage had to make the purpose immediately clear and have a responsive layout in a way that wasn’t broken.&lt;/p&gt;
&lt;h3 id=&quot;discoverability&quot;&gt;Discoverability&lt;/h3&gt;
&lt;p&gt;Another of the theme’s major drawbacks was the navigation concept.&lt;/p&gt;
&lt;p&gt;Previously meant for a blog/news website, it split the navigation into a main navigation at the top and a sub-navigation in each of the top categories that vertically lived somewhere around the fold.
That sub-navigation meant terrible discoverability for visitors coming to the site for the first time, as there was no clear indication of the function or intent.
Additionally, that sub-navigation was broken on touch devices:&lt;/p&gt;
&lt;video loop=&quot;&quot; autoplay=&quot;&quot; muted=&quot;true&quot; playsinline=&quot;&quot; controls=&quot;&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/old-nav-av1.Bsr-cy7m.webm&quot; type=&quot;video/webm; codecs=av01.0.05M.08&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/old-nav-hevc.C8P7Veyu.mp4&quot; type=&quot;video/mp4; codecs=hvc1&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/old-nav-avc.Dpjg6rnf.mp4&quot; type=&quot;video/mp4; codecs=avc1.4D401E&quot;&gt;&lt;/video&gt;
&lt;p&gt;The sub-navigation could also have submenus (that wasn’t indicated in any way) that were only accessible by hovering.
When touched, they would open the webpage designated to the subcategory of that submenu, instead of just opening the submenu.&lt;/p&gt;
&lt;h3 id=&quot;performance&quot;&gt;Performance&lt;/h3&gt;
&lt;p&gt;The WordPress installation was weighed down by a myriad of installed plugins and legacy hosting services that weren’t upgraded since the initial deployment in 2011.
Even using a decent 100 Mbit/s cable internet connection in a bigger city yielded page loading times in the seconds.
The Internet Archive links visited to capture the screenshots and videos here are even a little faster.&lt;/p&gt;
&lt;p&gt;Unfortunately, no PageSpeed Insights scores exist from the time.
But Google Analytics was telling the team that the page suffered from bad visitor retention, partly because of sluggish performance.&lt;/p&gt;
&lt;h2 id=&quot;my-involvement&quot;&gt;My involvement&lt;/h2&gt;
&lt;p&gt;A complete transformation was required.
In collaboration with the whole team at Petman I came up with a long list of requirements that included some new ideas to make the website more useful to customers than a simple infodump.
Additionally, we came up with multiple features to make editing content on the website easier, even and especially for non-techies.&lt;/p&gt;
&lt;p&gt;I decided to first see if the project could be done using one of the headless Content Management Systems that were taking off at the time.
The two I was most interested in were &lt;a href=&quot;https://directus.io&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Directus&lt;/a&gt; and &lt;a href=&quot;https://strapi.io&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Strapi&lt;/a&gt;. And, unfortunately, both didn’t offer the tools needed at the time to build custom editing interfaces.
They were great for creating and filling databases in an easy way, but even still it wasn’t easy enough for non-techies.
I believe tools like Airtable pioneered making databases online understandable for everyone without training, and of course Directus and Strapi both came a long way since 2016 when I was working on this project.&lt;/p&gt;
&lt;p&gt;In any case, after confirming that headless CMS at the time wouldn’t be able to provide what was needed, I decided to build a custom CMS from the ground up, tailored to the needs and wants of the Petman team.
And, of course, WordPress was already not easy enough to work with. Using it again would’ve incurred some of the same problems down the line.&lt;/p&gt;
&lt;h2 id=&quot;the-backend&quot;&gt;The backend&lt;/h2&gt;
&lt;p&gt;I needed something that would allow me to develop quickly and still get a performant and stable result.
So I decided on a Node.js &lt;a href=&quot;https://www.npmjs.com/package/express&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Express&lt;/a&gt;-based server with &lt;a href=&quot;https://www.npmjs.com/package/ejs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;EJS&lt;/a&gt; for server-side rendering and a MongoDB database with &lt;a href=&quot;https://www.npmjs.com/package/mongoose&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Mongoose&lt;/a&gt; enforcing schemas.
Today maybe I’d have chosen a different database, but it’s a decent enough choice still.
And it provided a crucial feature for the new website: Geo-queries.&lt;/p&gt;
&lt;h3 id=&quot;admin-ui&quot;&gt;Admin UI&lt;/h3&gt;
&lt;p&gt;I started with building out the admin interface first to have something to populate the website with.&lt;/p&gt;
&lt;p&gt;The requirements stated that all the content had to be editable by non-techies.
That meant building specialized creation and editing UIs for certain types of objects in the database.
First up were pages and files.
Since there would need to be special kinds of pages that would consume special kinds of data, I defined six types of things in the navigation structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Category
&lt;ul&gt;
&lt;li&gt;Acts as a container for other items, can link to something.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Page
&lt;ul&gt;
&lt;li&gt;A “normal” content page that can be edited using a WYSIWYG HTML editor.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Product page
&lt;ul&gt;
&lt;li&gt;A content page specifically to render product categories.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Advanced page
&lt;ul&gt;
&lt;li&gt;A straight-up EJS template to be rendered. Used for pages like the home page, the blog page, and others.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Link
&lt;ul&gt;
&lt;li&gt;A simple link to any URL.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Separator
&lt;ul&gt;
&lt;li&gt;Exactly that.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the page editing interface for editing a normal page:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/edit-page.TrsFmLFq_1CzOrl.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/edit-page.TrsFmLFq_2gYpRC.jxl, https://jama.me/_astro/edit-page.TrsFmLFq_Z1wM4b.jxl 1.5x, https://jama.me/_astro/edit-page.TrsFmLFq_1UwrxW.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/edit-page.TrsFmLFq_ZG0qnB.avif, https://jama.me/_astro/edit-page.TrsFmLFq_25Eutw.avif 1.5x, https://jama.me/_astro/edit-page.TrsFmLFq_Z1MHYHk.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/edit-page.TrsFmLFq_Z14cREo.webp, https://jama.me/_astro/edit-page.TrsFmLFq_1Hs3cJ.webp 1.5x, https://jama.me/_astro/edit-page.TrsFmLFq_Z1dthef.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/edit-page.TrsFmLFq_1j5wCi.png&quot; srcset=&quot;https://jama.me/_astro/edit-page.TrsFmLFq_ZYqFjv.png 1.5x, https://jama.me/_astro/edit-page.TrsFmLFq_ZsWTV4.png 2x&quot; alt=&quot;Website screenshot, editing a normal page in the admin UI&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;504&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Similar to older versions of WordPress, this uses &lt;a href=&quot;https://github.com/tinymce/tinymce&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TinyMCE&lt;/a&gt; as the editor for website contents, with a highly customized configuration.
The output is filtered by the editor and by the server upon saving in a customized way, because it turned out that copywriters really loved to write in Microsoft Word and copy over the text.&lt;/p&gt;
&lt;p&gt;There’s also access to the file manager:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/inline-file-manager.CQqW_SnE_1KeWvQ.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/inline-file-manager.CQqW_SnE_Z3DiRO.jxl, https://jama.me/_astro/inline-file-manager.CQqW_SnE_Zn8LdR.jxl 1.5x, https://jama.me/_astro/inline-file-manager.CQqW_SnE_287PTd.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/inline-file-manager.CQqW_SnE_Z221y9B.avif, https://jama.me/_astro/inline-file-manager.CQqW_SnE_Z2lw1uE.avif 1.5x, https://jama.me/_astro/inline-file-manager.CQqW_SnE_1XGOc2.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/inline-file-manager.CQqW_SnE_1qS3ER.webp, https://jama.me/_astro/inline-file-manager.CQqW_SnE_17nAjO.webp 1.5x, https://jama.me/_astro/inline-file-manager.CQqW_SnE_Z2cp89M.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/inline-file-manager.CQqW_SnE_1TcsEc.png&quot; srcset=&quot;https://jama.me/_astro/inline-file-manager.CQqW_SnE_1zH0j9.png 1.5x, https://jama.me/_astro/inline-file-manager.CQqW_SnE_213gQv.png 2x&quot; alt=&quot;Website screenshot, file manager being used to insert an image&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;294&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Adding files in any TinyMCE content field inserts a shortcode that intelligently decides the rendering based on the media type.
The shortcodes are also sent to the server for rendering and the result is inserted as a non-editable element in the TinyMCE editor.&lt;/p&gt;
&lt;p&gt;Additionally, there is special editing for product categories and products (scrollable because it’s a long page):&lt;/p&gt;
&lt;div&gt;&lt;a href=&quot;https://jama.me/_astro/edit-product.CptCijxo_1w6jAK.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/edit-product.CptCijxo_2vUcbz.jxl, https://jama.me/_astro/edit-product.CptCijxo_3HxOi.jxl 1.5x, https://jama.me/_astro/edit-product.CptCijxo_Z23aoUg.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/edit-product.CptCijxo_2lIp1A.avif, https://jama.me/_astro/edit-product.CptCijxo_Z6tekG.avif 1.5x, https://jama.me/_astro/edit-product.CptCijxo_Z28WJaO.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/edit-product.CptCijxo_Z1OnxQq.webp, https://jama.me/_astro/edit-product.CptCijxo_MAVAe.webp 1.5x, https://jama.me/_astro/edit-product.CptCijxo_pF5DX.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/edit-product.CptCijxo_Z1NjDQS.png&quot; srcset=&quot;https://jama.me/_astro/edit-product.CptCijxo_NEPzL.png 1.5x, https://jama.me/_astro/edit-product.CptCijxo_WE2pJ.png 2x&quot; alt=&quot;Website screenshot, editing a product in the admin UI&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;1326&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;/div&gt;
&lt;h3 id=&quot;the-storelocator&quot;&gt;The storelocator&lt;/h3&gt;
&lt;p&gt;The storelocator was another one of the special requirements to make the website more appealing to customers.
Petman distribute their products mostly through retail stores (or at least they did at the time of the project).
They are represented in lots of points of purchase throughout Germany and the surrounding countries.&lt;/p&gt;
&lt;p&gt;Their retail network is represented in the storelocator as an easy to use way for customers to find retail stores near them:&lt;/p&gt;
&lt;video loop=&quot;&quot; autoplay=&quot;&quot; muted=&quot;true&quot; playsinline=&quot;&quot; controls=&quot;&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/storelocator-av1.f7fbY3xE.webm&quot; type=&quot;video/webm; codecs=av01.0.05M.08&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/storelocator-hevc.BpRmswmy.mp4&quot; type=&quot;video/mp4; codecs=hvc1&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/storelocator-avc.bZzoaO7m.mp4&quot; type=&quot;video/mp4; codecs=avc1.4D401E&quot;&gt;&lt;/video&gt;
&lt;p&gt;This uses MongoDB’s geo queries to quickly find the nearest stores to the provided coordinates, that are found by geocoding.
Geocoding requests are anonymized and sent to Google’s geocoding API by the backend (so no user-identifiable data is shared).
The returned coordinates are then used for the geo query and limited to the three closest results.&lt;/p&gt;
&lt;p&gt;Pretty simple in essence, but the dataset had to be imported and indexed by the database for it to work.
And that had to work for non-techies as well, so the CSV parsing must have been very permissive and/or offer many fallbacks.
The origin dataset from Petman’s CRM was somewhat dirty and incomplete, so rules for fallback values were part of this feature’s requirements.
The backend uses &lt;a href=&quot;https://www.npmjs.com/package/papaparse&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Papa Parse&lt;/a&gt; with a bunch of custom handling.&lt;/p&gt;
&lt;h4 id=&quot;the-map&quot;&gt;The map&lt;/h4&gt;
&lt;p&gt;Previously, the storelocator used Google Maps.
When in 2021 extended GDPR-related laws were implemented in Germany, cease-and-desists related to the usage of Google services became numerous.
We decided to look elsewhere for the map used in the storelocator.
I noticed that map hosting services could be very expensive, and open source tools surprisingly approachable.&lt;/p&gt;
&lt;p&gt;I decided that the best combination of quality and price would be to self-host.
Using &lt;a href=&quot;https://github.com/systemed/tilemaker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tilemaker&lt;/a&gt; to make customized vector tiles from OpenStreetMap data, &lt;a href=&quot;https://github.com/maptiler/tileserver-gl&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TileServer GL&lt;/a&gt; for hosting and &lt;a href=&quot;https://github.com/maplibre/maplibre-gl-js&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MapLibre GL JS&lt;/a&gt; for the visualization allowed for an extemely fast and lightweight solution.
I was floored by how easy the whole process was - once I had enough RAM installed in my PC to make the tiles using tilemaker.&lt;/p&gt;
&lt;p&gt;Of course, the code for making the map interactive in the context of the storelocator had to be rewritten, but MapLibre GL JS is surprisingly fully featured.&lt;/p&gt;
&lt;p&gt;The result is a beautifully detailed map rendering at 60 FPS on most devices:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/map.DKBxXINv_Zz7SVU.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/map.DKBxXINv_W8uaV.jxl, https://jama.me/_astro/map.DKBxXINv_1ADdUe.jxl 1.5x, https://jama.me/_astro/map.DKBxXINv_Z1Jn6Dq.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/map.DKBxXINv_1rDWmm.avif, https://jama.me/_astro/map.DKBxXINv_269G6E.avif 1.5x, https://jama.me/_astro/map.DKBxXINv_Z1tYdi1.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/map.DKBxXINv_Z1UQfWX.webp, https://jama.me/_astro/map.DKBxXINv_Z1hlwdF.webp 1.5x, https://jama.me/_astro/map.DKBxXINv_lk2yr.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/map.DKBxXINv_NTooO.png&quot; srcset=&quot;https://jama.me/_astro/map.DKBxXINv_1sp897.png 1.5x, https://jama.me/_astro/map.DKBxXINv_ZPkSL3.png 2x&quot; alt=&quot;Website screenshot, detailed map view on Petman.de storelocator page&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;398&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;h3 id=&quot;performance-considerations&quot;&gt;Performance considerations&lt;/h3&gt;
&lt;p&gt;To avoid walking into the same trap as with the previous WordPress website, I found that caching would play a big role in making the server-side rendering perform well.
The website initially launched without caching and simply rendered the HTML on every request.
That was fast enough, because the MongoDB queries were decently fast and EJS rendered templates pretty quickly, but I knew that it wouldn’t scale enough.&lt;/p&gt;
&lt;p&gt;Over the next releases I fully decoupled the rendering into a separate process that dealt with caching the data needed for rendering as well.
The data is stored in a Redis cache, while the pre-rendered templates live in RAM for maximum runtime performance.
As soon as an edit happens, the in-memory cache is invalidated and the pages rendered on first request.
The render data that lives in Redis is invalidated on a per-object basis, so the EJS renders are still very fast, because they mostly don’t cause MongoDB queries.&lt;/p&gt;
&lt;p&gt;Static assets are served via Nginx, the reverse proxy server, directly. So no Node.js-overhead is incurred for them.&lt;/p&gt;
&lt;p&gt;Combined with frontend optimizations, this results in amazing performance for a website with a CMS behind it.
Could only be better if all pages were fully pre-rendered, like with a static site generator:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/pagespeed.SWQitsTe_rQ0pp.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/pagespeed.SWQitsTe_Z1utlcC.jxl, https://jama.me/_astro/pagespeed.SWQitsTe_7YvSN.jxl 1.5x, https://jama.me/_astro/pagespeed.SWQitsTe_Z1yThEL.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/pagespeed.SWQitsTe_BHVl5.avif, https://jama.me/_astro/pagespeed.SWQitsTe_2fbNrv.avif 1.5x, https://jama.me/_astro/pagespeed.SWQitsTe_Z21ffb4.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/pagespeed.SWQitsTe_evu4i.webp, https://jama.me/_astro/pagespeed.SWQitsTe_1QYmaI.webp 1.5x, https://jama.me/_astro/pagespeed.SWQitsTe_1r1jJl.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/pagespeed.SWQitsTe_Z2snerW.png&quot; srcset=&quot;https://jama.me/_astro/pagespeed.SWQitsTe_ZOTmlw.png 1.5x, https://jama.me/_astro/pagespeed.SWQitsTe_ZYAYxk.png 2x&quot; alt=&quot;Website screenshot, Petman.de&apos;s PageSpeed Insights overall score of 97&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;398&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;The backend provides a rich featureset in other respects as well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Image optimization via &lt;a href=&quot;https://github.com/mozilla/mozjpeg&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MozJPEG&lt;/a&gt; and &lt;a href=&quot;https://github.com/kornelski/pngquant&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;pngquant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Quality assurance via work-process checkboxes for pages and products&lt;/li&gt;
&lt;li&gt;Extensive SEO features&lt;/li&gt;
&lt;li&gt;User management with RBAC&lt;/li&gt;
&lt;li&gt;Shopify integration (currently disabled)&lt;/li&gt;
&lt;li&gt;Automatic backup management&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-frontend&quot;&gt;The frontend&lt;/h2&gt;
&lt;p&gt;I designed the frontend with an eye for simplicity and accessibility to older customers, who were still the bigger part of Petman’s clientele at the time.
At the same time, I aimed to shift the brand into a friendly and approachable light by opting for &lt;a href=&quot;https://fonts.google.com/specimen/Nunito&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Nunito&lt;/a&gt;, a rounded font, and pastel colors throughout.&lt;/p&gt;
&lt;h3 id=&quot;tackling-the-confusing-home-page&quot;&gt;Tackling the confusing home page&lt;/h3&gt;
&lt;p&gt;I designed the new home page and wrote the copy for the initial version of it (at this point it has seen many further iterations) to be immediately clear about what Petman does, first and foremost.&lt;/p&gt;
&lt;p&gt;Around the fold, many visitors would see the beginning of a large headline prompting to pull them in and get further acquainted with the purpose of the website.&lt;/p&gt;
&lt;p&gt;(As a sign of respect to Ukraine, the hero colors on the homepage are blue-yellow at the time of writing.)&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/new-home-desktop.CvLL-tFt_Z2c1OWJ.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/new-home-desktop.CvLL-tFt_1wIqud.jxl, https://jama.me/_astro/new-home-desktop.CvLL-tFt_17BA25.jxl 1.5x, https://jama.me/_astro/new-home-desktop.CvLL-tFt_1RkW7G.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/new-home-desktop.CvLL-tFt_1NRV8o.avif, https://jama.me/_astro/new-home-desktop.CvLL-tFt_1oL5Fg.avif 1.5x, https://jama.me/_astro/new-home-desktop.CvLL-tFt_pLeop.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/new-home-desktop.CvLL-tFt_Z1V5uAx.webp, https://jama.me/_astro/new-home-desktop.CvLL-tFt_Z2lcl3F.webp 1.5x, https://jama.me/_astro/new-home-desktop.CvLL-tFt_118XnV.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/new-home-desktop.CvLL-tFt_Z10Ybgp.png&quot; srcset=&quot;https://jama.me/_astro/new-home-desktop.CvLL-tFt_Z1q61Ix.png 1.5x, https://jama.me/_astro/new-home-desktop.CvLL-tFt_ZXP4hB.png 2x&quot; alt=&quot;Website screenshot, new petman.de website in a desktop browser&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;391&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;The hub with the four animal cards has survived the transition.
It’s just located towards the bottom of the page and acts as a call to action to lead visitors further once they agreed to scroll down to find out more from the initial copy.&lt;/p&gt;
&lt;h3 id=&quot;tackling-the-discoverability-issues&quot;&gt;Tackling the discoverability issues&lt;/h3&gt;
&lt;p&gt;We decided that the website should have slightly less main content than the old one, which allowed to mostly drop multi-level nested navigation.
Almost all main categories seen in the website’s header don’t have nested categories. The mostly visited categories don’t have them.
This allows for easier discoverability of content for users and makes navigating the site quicker.&lt;/p&gt;
&lt;p&gt;Additionally, product pages, that were previously nested in subcategories, or only available via hub pages, are now clearly and easily available.&lt;/p&gt;
&lt;video loop=&quot;&quot; autoplay=&quot;&quot; muted=&quot;true&quot; playsinline=&quot;&quot; controls=&quot;&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/productpage-av1.Cjkch-op.webm&quot; type=&quot;video/webm; codecs=av01.0.05M.08&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/productpage-hevc.BP6XoxJ9.mp4&quot; type=&quot;video/mp4; codecs=hvc1&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/productpage-avc.BSzRUiy-.mp4&quot; type=&quot;video/mp4; codecs=avc1.4D401E&quot;&gt;&lt;/video&gt;
&lt;p&gt;Products open in dialogs, but can also be visited by URL for SEO reasons.
In general, many SEO-related problems have been fixed with the new website, so it ranks way better in all search engines, which also aids customers in finding what they’re looking for.&lt;/p&gt;
&lt;h3 id=&quot;tackling-the-performance&quot;&gt;Tackling the performance&lt;/h3&gt;
&lt;p&gt;When the website first launched, the frontend was made interactive using jQuery for compatibility reasons, but as soon as support for Internet Explorer went away, that was replaced with lighter weight pure JavaScript plugins and utilities later on.
This dramatically cut down on the amount of JavaScript to be loaded and parsed and was instrumental in getting the PageSpeed Insights score as high as mentioned above.&lt;/p&gt;
&lt;p&gt;Part of the process of porting to pure JavaScript was replacing the jQuery plugin used for making the navigation responsive.
The frontend used &lt;a href=&quot;https://github.com/vinnymoreira/stellarnav&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;StellarNav.js&lt;/a&gt; before. I manually ported it to pure JS and simplified it to make it even lighter weight than it was.&lt;/p&gt;
&lt;h2 id=&quot;verdict&quot;&gt;Verdict&lt;/h2&gt;
&lt;p&gt;While I wouldn’t recommend to create a custom CMS from scratch these days, in 2016 the situation was different though.
The staying power of this solution (going on for over 7 years since first release at the time of writing), and the lasting happiness of the Petman team with it, demonstrates that it was the right decision at the time.&lt;/p&gt;
&lt;p&gt;At release, the new website boasted loading times in the low hundreds of milliseconds, compared to seconds before - even with a fast cabled internet connection.
TTFB was reduced by over 35%, leading to a PageSpeed Insights overall score of 100 back in the day.
With further improvements, the website is still really fast, with a PageSpeed Insights overall score of 97.&lt;/p&gt;
&lt;p&gt;The user experience improvements brought by the new custom content management system dramatically increased visitor retention, in both numbers and in session time, sustainably helping SEO.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - Tea Shop</title><link>https://jama.me/games/teashop/</link><guid isPermaLink="true">https://jama.me/games/teashop/</guid><description>A first person mystery horror adventure about unraveling a ghost town&apos;s past and coping with the happenings.</description><pubDate>Wed, 01 Feb 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Tea Shop is the working title for a first-person horror adventure game I worked on during my Bachelor’s degree studies at Cologne Game Lab.
&lt;a href=&quot;https://taglia.co&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Giovanni Tagliamonte&lt;/a&gt; and I proposed the concept and had a whole semester to refine it and build a game around it.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/teashop1.BCgvZOyY_1k1fYe.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/teashop1.BCgvZOyY_Z1IhUif.jxl, https://jama.me/_astro/teashop1.BCgvZOyY_Z2GpHc.jxl 1.5x, https://jama.me/_astro/teashop1.BCgvZOyY_1qcygI.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/teashop1.BCgvZOyY_Z2t9Drb.avif, https://jama.me/_astro/teashop1.BCgvZOyY_ZMy8Q8.avif 1.5x, https://jama.me/_astro/teashop1.BCgvZOyY_Z1wMhYv.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/teashop1.BCgvZOyY_Z29SvS1.webp, https://jama.me/_astro/teashop1.BCgvZOyY_Zti1hX.webp 1.5x, https://jama.me/_astro/teashop1.BCgvZOyY_Z1TYJgi.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/teashop1.BCgvZOyY_Zr8FYW.png&quot; srcset=&quot;https://jama.me/_astro/teashop1.BCgvZOyY_1erNA6.png 1.5x, https://jama.me/_astro/teashop1.BCgvZOyY_siF1o.png 2x&quot; alt=&quot;Video game screenshot of forest path, thick atmospheric fog&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;A foggy day in the woods.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;how-mundane-can-horror-be&quot;&gt;How Mundane Can Horror Be?&lt;/h2&gt;
&lt;p&gt;One critical piece of the concept is an exploration of ways to transition the player from mundane everyday situations into horror scenes and back in an open-world context.&lt;/p&gt;
&lt;h3 id=&quot;the-design&quot;&gt;The Design&lt;/h3&gt;
&lt;p&gt;We saw the biggest challenge to be having players intuitively control the balance between creepy and comfy scenes themselves in a way that would allow the storyline to progress at a pace that felt right to us.
The game had to provide enough base mechanics, systems and, importantly, things to do outside of progressing the story, which is where we thought most of the creepy scenes would lie.
At the same time, the world had to be designed in a way that allows for rapid transitions between creepy and comfy without being jarring.&lt;/p&gt;
&lt;h3 id=&quot;the-story-sketch&quot;&gt;The Story Sketch&lt;/h3&gt;
&lt;p&gt;It is the 1980s. The player slips into the role of a psychologically disturbed misanthrope, living life as peacefully as possible while recovering from their illness in a secluded hut near the Canadian village of Autumn Well.
One day, waiting for their groceries and medications to be delivered, they find the entire village turned into a ghost town from one moment to the next.
This is where creepy happenings start to haunt the village and its outskirts.
It’s on the player to figure out what happened and how the happenings are connected to the village’s sinister past.&lt;/p&gt;
&lt;p&gt;We based Autumn Well’s general direction loosely on Bright Falls of Alan Wake.
Remedy managed to capture North American forests in a convincing way that resonated with us and we wanted to combine that atmosphere with an even further secluded setting.
Another game that also did it for me, though it came after Tea Shop, is parts of We Create Stuff’s &lt;a href=&quot;https://store.steampowered.com/app/1119980/In_Sound_Mind/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;In Sound Mind&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;tying-it-together&quot;&gt;Tying it Together&lt;/h3&gt;
&lt;p&gt;Given the main character’s disposition, we decided for their flawed mental state to be the driving factor for the gameplay and the horror elements.
They would be the game’s unreliable narrator.&lt;/p&gt;
&lt;p&gt;Throwing a mentally unstable person into creepy situations without acknowledgement would create a huge ludonarrative dissonance.
The game required an opposing force to counteract the horror scenes.
And this is where the other part of the gameplay elements would take over.
The horror setpieces were designed to have little interaction and no combat, meant to be mostly experienced by players simply by walking around and triggering things to happen.
The experience predictably would wear down the main character, and they would subsequently need to calm back down and cope with the happenings.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/teashop2.D7cOkIw-_YULiR.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/teashop2.D7cOkIw-_Z1Qk5px.jxl, https://jama.me/_astro/teashop2.D7cOkIw-_ZaIzOu.jxl 1.5x, https://jama.me/_astro/teashop2.D7cOkIw-_2qiynv.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/teashop2.D7cOkIw-_2t0kfs.avif, https://jama.me/_astro/teashop2.D7cOkIw-_ZUAiXq.avif 1.5x, https://jama.me/_astro/teashop2.D7cOkIw-_ZwGhRI.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/teashop2.D7cOkIw-_Z2hUG0j.webp, https://jama.me/_astro/teashop2.D7cOkIw-_ZBkbpg.webp 1.5x, https://jama.me/_astro/teashop2.D7cOkIw-_ZTSJ9v.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/teashop2.D7cOkIw-_ZzaQ7f.png&quot; srcset=&quot;https://jama.me/_astro/teashop2.D7cOkIw-_16pDsN.png 1.5x, https://jama.me/_astro/teashop2.D7cOkIw-_1soF8b.png 2x&quot; alt=&quot;Video game screenshot of wooden hut in the forest, lit by moonlight, atmospheric&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;The player’s hut at night (brightened for convenience).&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;To create a comfy atmosphere and accelerate this process, we designed a range of repetitive and mindful tasks the player could do while outside the horror setpieces.
One of the more intricate ones implemented is preparing tea, which tied into the interaction system.
We also planned stargazing, walking in the woods at daytime, getting groceries from the supermarket, topping up the generator out back to keep the lights on, and more.
Additionally, the main character’s mental state would get small bonuses for other comfy-feeling situations and interactions, like locking the hut’s front door at night, being indoors while it’s raining, making the bed in the morning, etc.&lt;/p&gt;
&lt;p&gt;For these survival game-esque upkeep tasks, this concept led us to coin the term “Mundane Survival Horror”.&lt;/p&gt;
&lt;h2 id=&quot;where-the-horror-lives&quot;&gt;Where the Horror Lives&lt;/h2&gt;
&lt;p&gt;Of course, all of the mundane elements of the gameplay loop would be useless without some good ol’ horror setpieces!&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/teashop3.Bn7mI2_j_Z1Df6rk.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/teashop3.Bn7mI2_j_2tDkcw.jxl, https://jama.me/_astro/teashop3.Bn7mI2_j_ZTWj1m.jxl 1.5x, https://jama.me/_astro/teashop3.Bn7mI2_j_ZcRjmG.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/teashop3.Bn7mI2_j_1ILB3A.avif, https://jama.me/_astro/teashop3.Bn7mI2_j_Z1EO2ai.avif 1.5x, https://jama.me/_astro/teashop3.Bn7mI2_j_1TjXb1.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/teashop3.Bn7mI2_j_232IBK.webp, https://jama.me/_astro/teashop3.Bn7mI2_j_Z1lxTB8.webp 1.5x, https://jama.me/_astro/teashop3.Bn7mI2_j_1w7vTe.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/teashop3.Bn7mI2_j_Z1jozj7.png&quot; srcset=&quot;https://jama.me/_astro/teashop3.Bn7mI2_j_mbUgV.png 1.5x, https://jama.me/_astro/teashop3.Bn7mI2_j_Z1aLcC1.png 2x&quot; alt=&quot;Video game screenshot of dark creepy mine tunnel with wooden reinforcements, atmospheric&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;A closed-off mine leading to tunnels connected to the MKUltra R&amp;amp;D site (brightened for convenience).&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The village’s backstory is partially tied to operations part of the US’ MKUltra project.
In one of the implemented setpieces, the player would visit a long-abandoned R&amp;amp;D site and would uncover the happenings there when the site was still active, and its ties to the public services of Autumn Well.&lt;/p&gt;
&lt;p&gt;The main loop behind the horror setpieces is leading the player deep inside and have them chased back out after reaching a conclusion or story beat.
In case of the R&amp;amp;D site, the player would explore the way to the site, reading documents and learning about the place they’ve stumbled into.
Along the way, they would find dusty offices, bedrooms, a cafeteria and some slight scares and eerie atmosphere.&lt;/p&gt;
&lt;p&gt;Here, the unreliable narrator-aspect comes into play.
Instead of seeing the place as it is, the main character would substitute a skewed view of reality in an attempt to repress the creepy atmosphere.
One example of this would be that the whole site has electricity and some of the lights are on.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/teashop4.BaJyAS8-_ZFX6dn.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/teashop4.BaJyAS8-_1wtE9d.jxl, https://jama.me/_astro/teashop4.BaJyAS8-_Z1R6Y4F.jxl 1.5x, https://jama.me/_astro/teashop4.BaJyAS8-_JoFQg.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/teashop4.BaJyAS8-_LBV0h.avif, https://jama.me/_astro/teashop4.BaJyAS8-_2sdqAk.avif 1.5x, https://jama.me/_astro/teashop4.BaJyAS8-_Z2dAaoX.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/teashop4.BaJyAS8-_15S3yr.webp, https://jama.me/_astro/teashop4.BaJyAS8-_Z2iHzEr.webp 1.5x, https://jama.me/_astro/teashop4.BaJyAS8-_2tow8b.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/teashop4.BaJyAS8-_Z2gyfmq.png&quot; srcset=&quot;https://jama.me/_astro/teashop4.BaJyAS8-_ZzWJLn.png 1.5x, https://jama.me/_astro/teashop4.BaJyAS8-_Zduco4.png 2x&quot; alt=&quot;Video game screenshot of dark creepy office, old electronics, atmospheric&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;An office with computers, unreliably reconstructed by the main character (brightened for convenience).&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The reality-repressing effect gradually fades as the player eventually finds the site itself, burned down and singed, presumably as part of cleanup operations.
The exploration ends in the pristine-looking director’s office and a document explaining the goings on at the site, and tying it to failures of public services that led to the village’s school closing years ago.
A surreal chase takes place after the player finds the door to the director’s office suddenly opening to another office near the site’s entrance, with lit candles all over all the way to the exit.&lt;/p&gt;
&lt;h2 id=&quot;reflection&quot;&gt;Reflection&lt;/h2&gt;
&lt;p&gt;Tea Shop was an extremely enlightening project to have worked on.
The concept pushed us to innovate on many different fronts. Some of the experiments panned out, and some didn’t.
While I’d call the experiment of the concept itself successful, it was limited by the scope we had to confine ourselves to.
And even then, the scope was a good bit too big for us, because the concept requires a believable open-world environment and tightly designed horror setpieces at the same time.&lt;/p&gt;
&lt;p&gt;In my mind, in terms of systems design, the most difficult aspect to get right was the inventory and two-hand interaction system that allowed us to create intricate multi-step interactions for the aforementioned tea-making process for example.
It was partly bogged down by a hasty implementation, making interactions slightly unreliable, and frustrating as a result.
To me, conceptually, its clunkiness was a valid representation of the main character’s mental state - depressed and unwilling to do the upkeep tasks.
Though we likely would have explored a different mode of interaction if we went further with the project to make it more accessible.&lt;/p&gt;
&lt;p&gt;The other part that was extremely challenging was simply making the player leave their house, and balancing inaction and penalties.
Being an open-world game, technically the player was free to roam around and ignore whatever the game requests from them.
The mundane survival element helped, requiring some upkeep that forced the player to go outside eventually, but the moments where penalties to the main character’s started to kick in, felt contrived and out of left field.
This part definitely still needed refinement.&lt;/p&gt;
&lt;p&gt;All in all, the juxtaposition of creepy and comfy worked really well though.
We were able to implement multiple convincing setpieces that were able to play out their creepy horror vibe to a greater extent precisely because the rest of the game is so focused on calming exercises and routines.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - Petman BARF Calculator</title><link>https://jama.me/projects/barfcalculator/</link><guid isPermaLink="true">https://jama.me/projects/barfcalculator/</guid><description>A web app to calculate the recommended amount of food for pets. The easiest of its kind.</description><pubDate>Tue, 01 Nov 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Visit the website here: &lt;a href=&quot;https://barf-calculator.de&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;barf-calculator.de&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In 2014, German pet food manufacturer specializing in &lt;a href=&quot;https://en.wikipedia.org/wiki/Raw_feeding&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BARF&lt;/a&gt; Petman contacted me for some marketing materials.
From that, a long-term relationship formed.
Looking to promote their products by solving the complexity of mixing the various components that make up a BARF meal, the BARF-Calculator was one of the first bigger projects I did for them.&lt;/p&gt;
&lt;p&gt;According to the practice, a balanced diet for a normal healthy dog consists of multiple ingredients, all of which are traditionally sold separately - what Petman among others are known for.
They came out with a new product line called &lt;em&gt;BARF-In-One&lt;/em&gt;, which sought to provide a ready-made complete solution that was still providing a balanced diet without having to manually mix every meal.
And to promote this new product line, and to provide an easier start for beginners trying out BARF raw feeding for the first time, the idea of an easy to use calculator was conceived.&lt;/p&gt;
&lt;h2 id=&quot;the-status-quo&quot;&gt;The Status Quo&lt;/h2&gt;
&lt;p&gt;Existing calculator websites were way too complicated for anyone but the most dedicated pet owners.
Some solutions were paid (and quite expensive at that), while others required tedious to acquire data, like the body fat percentage of your dog.
Data points that maybe vets would be able to provide, but definitely not the sort users would know or could easily measure at home.
Others had easy to determine inputs, but output so much data that the result became hard to take action upon.
Of course there were also multiple Excel-based solutions floating about, with similar issues.&lt;/p&gt;
&lt;p&gt;So this is an example of what we were up against:&lt;/p&gt;
&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://jama.me/_astro/other-calculator.CUMddWf0_15aldm.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/other-calculator.CUMddWf0_ZOM3AB.jxl, https://jama.me/_astro/other-calculator.CUMddWf0_ZertGn.jxl 1.5x, https://jama.me/_astro/other-calculator.CUMddWf0_22psA3.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/other-calculator.CUMddWf0_ZxCxWq.avif, https://jama.me/_astro/other-calculator.CUMddWf0_Z1G1cpE.avif 1.5x, https://jama.me/_astro/other-calculator.CUMddWf0_Z2hCAm7.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/other-calculator.CUMddWf0_LA97z.webp, https://jama.me/_astro/other-calculator.CUMddWf0_Z15Dsq8.webp 1.5x, https://jama.me/_astro/other-calculator.CUMddWf0_VjOVl.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/other-calculator.CUMddWf0_1GGsrH.png&quot; srcset=&quot;https://jama.me/_astro/other-calculator.CUMddWf0_1YyCHg.png 1.5x, https://jama.me/_astro/other-calculator.CUMddWf0_Z1MMld.png 2x&quot; alt=&quot;Website screenshot, BARF calculator website with lots of output data&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;926&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;&lt;p&gt;A community-made calculator with many, many outputs (scrollable because it’s long). Note the plethora of results at the bottom. 
&lt;a href=&quot;https://www.das-boxerforum.de/barf-rechner.php&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;our-response&quot;&gt;Our Response&lt;/h2&gt;
&lt;p&gt;We decided that, while it’s likely very useful to experts in BARF raw feeding, it wasn’t clear-cut enough for beginners, and made BARF raw feeding feel like an endless rabbit hole.&lt;/p&gt;
&lt;p&gt;Compounded by long articles and books and the aforementioned existing calculators aimed at slightly different target audiences, it was time to offer an easier and more polished alternative.
So this was the first version that went online in 2014:&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/bc-old.Bxi5Y_pZ_ZoTpVl.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/bc-old.Bxi5Y_pZ_Zc66Ub.jxl, https://jama.me/_astro/bc-old.Bxi5Y_pZ_Z10rFML.jxl 1.5x, https://jama.me/_astro/bc-old.Bxi5Y_pZ_1SX6rM.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/bc-old.Bxi5Y_pZ_Z1NgMUb.avif, https://jama.me/_astro/bc-old.Bxi5Y_pZ_2syL1a.avif 1.5x, https://jama.me/_astro/bc-old.Bxi5Y_pZ_Z2dEVaN.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/bc-old.Bxi5Y_pZ_Z228flf.webp, https://jama.me/_astro/bc-old.Bxi5Y_pZ_2eHjA6.webp 1.5x, https://jama.me/_astro/bc-old.Bxi5Y_pZ_T4pd2.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/bc-old.Bxi5Y_pZ_2lS8Oj.png&quot; srcset=&quot;https://jama.me/_astro/bc-old.Bxi5Y_pZ_1xwyVI.png 1.5x, https://jama.me/_astro/bc-old.Bxi5Y_pZ_ZbAcvU.png 2x&quot; alt=&quot;Website screenshot, Petman BARF-Calculator, wooden background&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;359&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;Petman worked closely with experts and vets in the field to nail down a minimum set of criteria to input and we worked together to create formulas that would cover most beginner’s needs.
What was condensed down into three simple steps was the result of months of back and forth between everyone involved before the first version went online.&lt;/p&gt;
&lt;figure&gt;&lt;video loop=&quot;&quot; autoplay=&quot;&quot; muted=&quot;true&quot; playsinline=&quot;&quot; controls=&quot;&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/process-old-av1.DUIXzzQW.webm&quot; type=&quot;video/webm; codecs=av01.0.05M.08&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/process-old-vp9.Bjy31Ymi.webm&quot; type=&quot;video/webm; codecs=vp9&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/process-old-hevc.BTCeqDUo.mp4&quot; type=&quot;video/mp4; codecs=hvc1&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/process-old-avc.CvNg3E5c.mp4&quot; type=&quot;video/mp4; codecs=avc1.4D401E&quot;&gt;&lt;/video&gt;&lt;figcaption&gt;&lt;p&gt;The site launched the same year Germany won a FIFA World Cup, so we featured our mascot with a soccer ball in the background on the
results page.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The task force provided me with matrices of expected amounts of ingredients which I turned into formulas used for the calculations.
The output was designed in a way that was more actionable by providing users with a list of ingredients or product categories to shop for, instead of a long list of nutrients.
That’s essential for owners wanting to go deeper on their pet’s diet, but not so much for someone just starting out and making their first steps.&lt;/p&gt;
&lt;p&gt;Immediately after launch, the Petman BARF-Calculator garnered attention in the community.
It generated quite a bit of controversy, because it didn’t cover many edge cases - something especially dedicated community members with chronically ill or allergic dogs weren’t happy about.
On the other hand, we got tons of positive feedback from people starting out with BARF raw feeding, and even new pet owners, because the calculator provided them with more insight into their dog’s diet in a simple way.
After the launch we worked on refining the outputs to cover more edge cases.&lt;/p&gt;
&lt;p&gt;One requirement the team at Petman insisted on was enabling purchasing products in stores based on the calculator’s output.
Because of Petman’s target audience at the time being older, and because mobile internet coverage in Germany wasn’t as wide in 2014, we suspected people wanting to print out the calculator’s output and take it with them to stores as a sort of shopping list.
Or, of course, to stick it to their fridge as a guide.
In addition, clerks in pet stores could use the calculator to quickly advise customers.&lt;/p&gt;
&lt;p&gt;The feature turned out to be a success with over 30% of users who got a result calculated also generating a PDF from it.&lt;/p&gt;
&lt;figure&gt;&lt;div&gt;&lt;a href=&quot;https://jama.me/_astro/pdf.B8C8toY2_Z2c3QTl.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/pdf.B8C8toY2_4A2M1.jxl, https://jama.me/_astro/pdf.B8C8toY2_1OvC5o.jxl 1.5x, https://jama.me/_astro/pdf.B8C8toY2_Z1RaCmf.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/pdf.B8C8toY2_z6uXr.avif, https://jama.me/_astro/pdf.B8C8toY2_24TvqN.avif 1.5x, https://jama.me/_astro/pdf.B8C8toY2_QKPGw.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/pdf.B8C8toY2_2gMqs3.webp, https://jama.me/_astro/pdf.B8C8toY2_Z19XmvF.webp 1.5x, https://jama.me/_astro/pdf.B8C8toY2_20mjlt.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/pdf.B8C8toY2_Z3D2Y6.png&quot; srcset=&quot;https://jama.me/_astro/pdf.B8C8toY2_Z2lDiQa.png 1.5x, https://jama.me/_astro/pdf.B8C8toY2_EWbEL.png 2x&quot; alt=&quot;Screenshot of PDF document, Petman BARF-Calculator&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;849&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;/div&gt;&lt;figcaption&gt;The BARF-Calculator’s PDF output (scrollable because it’s long).&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;One technical differentiating feature was that the BARF-Calculator ran fully client-side, without necessitating round-trips to a server for the calculation or generating PDFs (solved via &lt;a href=&quot;https://github.com/parallax/jsPDF&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;jsPDF&lt;/a&gt;).
Once the page was loaded, it didn’t require an active internet connection and ran as quick as devices allowed.
User feedback highlighted that they were really thankful for that.
For Petman, it meant that hosting was going to be very easy and portable, with no backend server required.&lt;/p&gt;
&lt;h2 id=&quot;the-redesign&quot;&gt;The Redesign&lt;/h2&gt;
&lt;p&gt;Based on gathered user feedback, at the end of 2015, Petman commissioned a new look for the BARF-Calculator focused on bringing it to mobile devices and incorporating their newly done rebranding.
The calculator should provide the same functionality as before in a new, prettier, package and further refine the formulas.
Additionally, they wanted to add support for new products that came out in the meantime, enabling calculating food for cats as well.&lt;/p&gt;
&lt;p&gt;Given these requirements, we decided that it was best to redevelop the website from the ground up, only keeping the code for the calculations themselves intact.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/bc-new.CtlLRVl__2dKGwm.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/bc-new.CtlLRVl__vbrRe.jxl, https://jama.me/_astro/bc-new.CtlLRVl__Z1K2GMa.jxl 1.5x, https://jama.me/_astro/bc-new.CtlLRVl__ZjXGSa.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/bc-new.CtlLRVl__Z15Ye7L.avif, https://jama.me/_astro/bc-new.CtlLRVl__1HXK1L.avif 1.5x, https://jama.me/_astro/bc-new.CtlLRVl__Czoib.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/bc-new.CtlLRVl__Z1jPFxP.webp, https://jama.me/_astro/bc-new.CtlLRVl__1u7iAH.webp 1.5x, https://jama.me/_astro/bc-new.CtlLRVl__Z1jRo7U.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/bc-new.CtlLRVl__Z211qcd.png&quot; srcset=&quot;https://jama.me/_astro/bc-new.CtlLRVl__MVxWk.png 1.5x, https://jama.me/_astro/bc-new.CtlLRVl__Z2px0QR.png 2x&quot; alt=&quot;Website screenshot, Petman BARF-Calculator, recent&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;381&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;The redesigned website is based on &lt;a href=&quot;https://github.com/alvarotrigo/fullPage.js/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;fullPage.js&lt;/a&gt; and effectively brings the previous experience to mobile devices and small screens.
I revamped the design and moved it away from the skeuomorphic-ish previous style, keeping the dog mascot and adding a cat!&lt;/p&gt;
&lt;p&gt;Using scroll jacking to cleanly separate the steps from each other meant getting to the results was going to take more time.
So I decided to at least make the journey more visually appealing, by having reactive SVGs front and center when inputting the parameters, instead of the PNG graphics with simple swaps based on input in the old calculator.&lt;/p&gt;
&lt;h3 id=&quot;svgs-galore&quot;&gt;SVGs Galore!&lt;/h3&gt;
&lt;p&gt;This was my first foray into getting complex SVGs with multiple moving parts to work cross-browser.&lt;/p&gt;
&lt;video loop=&quot;&quot; autoplay=&quot;&quot; muted=&quot;true&quot; playsinline=&quot;&quot; controls=&quot;&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/weight-slider-av1.-U0dnDXN.webm&quot; type=&quot;video/webm; codecs=av01.0.05M.08&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/weight-slider-vp9.C4F-Re5D.webm&quot; type=&quot;video/webm; codecs=vp9&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/weight-slider-hevc.DQe_BRrP.mp4&quot; type=&quot;video/mp4; codecs=hvc1&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/weight-slider-avc.DynQ2_mP.mp4&quot; type=&quot;video/mp4; codecs=avc1.4D401E&quot;&gt;&lt;/video&gt;
&lt;p&gt;One of the more complex setups was the weight visualization.
Increasing the weight input turns a dial on the scale and scales up the pet (we decided against scaling up singular features like their belly), pushing down the little platform they’re sitting on at the same time.
All animations seen on the site are achieved with pure CSS transitions and properties applied to groups in the SVG graphics.&lt;/p&gt;
&lt;video loop=&quot;&quot; autoplay=&quot;&quot; muted=&quot;true&quot; playsinline=&quot;&quot; controls=&quot;&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/process-new-av1.CnUYIDEP.webm&quot; type=&quot;video/webm; codecs=av01.0.05M.08&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/process-new-vp9.4kxEJSpu.webm&quot; type=&quot;video/webm; codecs=vp9&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/process-new-hevc.BPy78Lgx.mp4&quot; type=&quot;video/mp4; codecs=hvc1&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/process-new-avc.V260qE3N.mp4&quot; type=&quot;video/mp4; codecs=avc1.4D401E&quot;&gt;&lt;/video&gt;
&lt;p&gt;Additionally, the codebase was fully reworked, which allowed for full translation support - including loading and swapping in translations without triggering a page reload.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;When it first launched, the Petman BARF-Calculator was the first of its kind that was as easy to use.
Without knowing the ins and outs of dog diets, users could easily get a starting point on their BARF journey.
This brought in a wholly new clientele for Petman to sell their products to.&lt;/p&gt;
&lt;p&gt;After a while, the BARF-Calculator became the defacto standard for quickly assessing the dietary needs of dogs and cats when considering BARF raw feeding.
This made it one of the most popular BARF raw feeding websites on the web.&lt;/p&gt;
&lt;p&gt;Nowadays, there are lots of other calculators on the web following the same blueprint set forth by this one (meaning the three steps of activity, weight and age).
But even still the BARF-Calculator stably garners more than half a million hits per year - a stat that reliably ebbs and flows with rising and falling interest in the topic.
Petman uses it as one of the metrics for determining plans for their marketing campaigns and product launches.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - A Breathtaking Flight</title><link>https://jama.me/games/abf/</link><guid isPermaLink="true">https://jama.me/games/abf/</guid><description>A first person mystery detective game set on a plane in the 1960s Soviet Union.</description><pubDate>Fri, 01 May 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A Breathtaking Flight is a first person mystery detective game I worked on as part of a semester project during my Bachelor studies at Cologne Game Lab.
It was done in a group of five in a span of one and a half months. The project’s kickstart topic was &lt;em&gt;airplanes&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-rundown&quot;&gt;The Rundown&lt;/h2&gt;
&lt;p&gt;Stalin is dead, Khrushchev is gone.
What happened on one of the last flights to Moscow in 1965? Slip into the role of new KGB agent Elizaveta Syomina and test your investigative skills and solve a meticulously crafted mystery.&lt;/p&gt;
&lt;p&gt;Explore a 3D replication of the Tupolev TU-114, a Soviet passenger plane full of historical artifacts and characters, just waiting to be discovered.
Gather clues, talk with the passengers and use the logic system to uncover the hidden truth.&lt;/p&gt;
&lt;p&gt;Can you find out what really happened on this breathtaking flight?&lt;/p&gt;
&lt;h2 id=&quot;my-involvement&quot;&gt;My Involvement&lt;/h2&gt;
&lt;p&gt;To bring this project over the finish line, I filled in multiple roles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Systems design&lt;/li&gt;
&lt;li&gt;Level design&lt;/li&gt;
&lt;li&gt;Art direction, creation of all 3D models, textures, animations and effects&lt;/li&gt;
&lt;li&gt;Sound design&lt;/li&gt;
&lt;li&gt;UI design&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also helped out in several technical parts of the production:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gameplay systems programming&lt;/li&gt;
&lt;li&gt;Scripting&lt;/li&gt;
&lt;li&gt;Putting it all together in Unity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of the 2D assets (specifically the beautiful character portraits) were done by &lt;a href=&quot;https://alfax.artstation.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Alvaro Quinteros&lt;/a&gt; and I got help with programming and scripting from &lt;a href=&quot;https://de.linkedin.com/in/eduard-eddy-gotwig-145787181&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Eddy Gotwig&lt;/a&gt;, while &lt;a href=&quot;https://taglia.co&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Giovanni Tagliamonte&lt;/a&gt; and &lt;a href=&quot;https://de.linkedin.com/in/tim-gleibs-775570249&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Tim Gleibs&lt;/a&gt; handled the design, writing and some of the scripting.&lt;/p&gt;
&lt;h2 id=&quot;designing-for-the-era&quot;&gt;Designing For the Era&lt;/h2&gt;
&lt;p&gt;Aside from it being a locked-room murder mystery, one of the more interesting aspects of this project was the setting.
Being set aboard an airplane during one of the high points of Soviet Russia meant some interesting challenges in terms of the level design, controlling player progression and keeping the environment interesting.&lt;/p&gt;
&lt;p&gt;Being ordered to the KGB for her exceptional performance, we decided that Elizaveta, on her way to her first day at the KGB’s Moscow offices, would get a super luxurious flight alongside some other eccentric characters.&lt;/p&gt;
&lt;p&gt;I scoured the web for any information I could find about the plane.
While the TU-114 originated as a bomber plane, it was later primarily used for economy class-style passenger transport.
But I also found sources indicating it was used by government officials, which must have meant that more luxurious versions of the TU-114 were made.&lt;/p&gt;
&lt;p&gt;This turned out to be the case:&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/tu114-interior-1.X6ubf0Df_EczB8.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/tu114-interior-1.X6ubf0Df_Z14J2Ko.jxl, https://jama.me/_astro/tu114-interior-1.X6ubf0Df_ZrgGtL.jxl 1.5x, https://jama.me/_astro/tu114-interior-1.X6ubf0Df_lzaHS.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tu114-interior-1.X6ubf0Df_Z2aFA2l.avif, https://jama.me/_astro/tu114-interior-1.X6ubf0Df_Z1xdeKI.avif 1.5x, https://jama.me/_astro/tu114-interior-1.X6ubf0Df_1UIgOW.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tu114-interior-1.X6ubf0Df_ZrAKd4.webp, https://jama.me/_astro/tu114-interior-1.X6ubf0Df_aQA3y.webp 1.5x, https://jama.me/_astro/tu114-interior-1.X6ubf0Df_ZB0tBG.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/tu114-interior-1.X6ubf0Df_Kj69F.jpg&quot; srcset=&quot;https://jama.me/_astro/tu114-interior-1.X6ubf0Df_1nLrqi.jpg 1.5x, https://jama.me/_astro/tu114-interior-1.X6ubf0Df_1lY7cD.jpg 2x&quot; alt=&quot;TU-114 interior with a sofa&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;400&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;A decommissioned TU-114 with a sofa. (&lt;a href=&quot;https://russianplanes.net/id185817&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Source&lt;/a&gt;)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/tu114-interior-2.tQGYRBkP_1Hxukr.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/tu114-interior-2.tQGYRBkP_ZNabWM.jxl, https://jama.me/_astro/tu114-interior-2.tQGYRBkP_tFRGl.jxl 1.5x, https://jama.me/_astro/tu114-interior-2.tQGYRBkP_Z2paLii.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tu114-interior-2.tQGYRBkP_1ksDVR.avif, https://jama.me/_astro/tu114-interior-2.tQGYRBkP_Z2rRpcV.avif 1.5x, https://jama.me/_astro/tu114-interior-2.tQGYRBkP_Z281gE7.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tu114-interior-2.tQGYRBkP_ZpXOkG.webp, https://jama.me/_astro/tu114-interior-2.tQGYRBkP_QRfjr.webp 1.5x, https://jama.me/_astro/tu114-interior-2.tQGYRBkP_ZMMyz7.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/tu114-interior-2.tQGYRBkP_14S5Xo.png&quot; srcset=&quot;https://jama.me/_astro/tu114-interior-2.tQGYRBkP_2mJaCw.png 1.5x, https://jama.me/_astro/tu114-interior-2.tQGYRBkP_7iJK1.png 2x&quot; alt=&quot;TU-114 interior - sleeping coupe cabin&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;379&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;A more barebones sleeping coupe cabin. Apparently there were also fancier ones. (&lt;a href=&quot;https://russianplanes.net/id102650&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Source&lt;/a&gt;)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_Z1I7Xtp.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_14rQpl.jxl, https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_1GUcFX.jxl 1.5x, https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_U2kwB.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_Z1tFQB.avif, https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_AXEp1.avif 1.5x, https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_2ubqDF.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_1GA8WF.webp, https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_2k3uei.webp 1.5x, https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_Z2xjMX.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_Z2aG8tw.jpg&quot; srcset=&quot;https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_Z1xdMcT.jpg 1.5x, https://jama.me/_astro/tu114-interior-3.DfiVH-EQ_1Urh1m.jpg 2x&quot; alt=&quot;TU-114 interior - in-flight restaurant&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;400&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;An in-flight restaurant section where passengers were served freshly cooked food on trays.
A dedicated cook was part of the crew on Aeroflot’s upper class flights. (&lt;a href=&quot;https://itravel.livejournal.com/68201.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Source&lt;/a&gt;)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/tu114-interior-4.7z-rgmf4_ZhPcw2.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/tu114-interior-4.7z-rgmf4_1vqi63.jxl, https://jama.me/_astro/tu114-interior-4.7z-rgmf4_28SDmF.jxl 1.5x, https://jama.me/_astro/tu114-interior-4.7z-rgmf4_Z1oT8Ni.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tu114-interior-4.7z-rgmf4_ptJO6.avif, https://jama.me/_astro/tu114-interior-4.7z-rgmf4_12W65I.avif 1.5x, https://jama.me/_astro/tu114-interior-4.7z-rgmf4_aeWiL.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tu114-interior-4.7z-rgmf4_28yzDn.webp, https://jama.me/_astro/tu114-interior-4.7z-rgmf4_Z2jacSV.webp 1.5x, https://jama.me/_astro/tu114-interior-4.7z-rgmf4_Z2mtN8R.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/tu114-interior-4.7z-rgmf4_Z1IHGMO.jpg&quot; srcset=&quot;https://jama.me/_astro/tu114-interior-4.7z-rgmf4_Z16flwc.jpg 1.5x, https://jama.me/_astro/tu114-interior-4.7z-rgmf4_Zoucjx.jpg 2x&quot; alt=&quot;TU-114 interior - first-class seating&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;399&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;Premium-class group seating. (&lt;a href=&quot;https://itravel.livejournal.com/68201.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Source&lt;/a&gt;)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;We imagined the flight taking a while, so I decided to go with an extended sleeping quarters setup in place of coupe cabins, and of course give the aircraft a premium vibe.
I envisioned the plane to have an extended restaurant section as well to support our narrative and provide more variety in the environment.
We’re on a plane after all - something we came to see as purely utilitarian.
So we decided for the whole interior to be designed around our passengers.&lt;/p&gt;
&lt;p&gt;This meant shifting around sections from the original plane’s usual configurations:&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/tu114-plan.B07svVRG_hiTaT.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/tu114-plan.B07svVRG_Z1InRAS.jxl, https://jama.me/_astro/tu114-plan.B07svVRG_Z2vGyGI.jxl 1.5x, https://jama.me/_astro/tu114-plan.B07svVRG_NBnI4.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tu114-plan.B07svVRG_nNoVO.avif, https://jama.me/_astro/tu114-plan.B07svVRG_Zouh91.avif 1.5x, https://jama.me/_astro/tu114-plan.B07svVRG_mgqcL.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/tu114-plan.B07svVRG_AWF2.webp, https://jama.me/_astro/tu114-plan.B07svVRG_ZLGIpN.webp 1.5x, https://jama.me/_astro/tu114-plan.B07svVRG_Z1fE8FK.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/tu114-plan.B07svVRG_1UWvjK.gif&quot; srcset=&quot;https://jama.me/_astro/tu114-plan.B07svVRG_18DOdU.gif 1.5x, https://jama.me/_astro/tu114-plan.B07svVRG_2r6muk.gif 2x&quot; alt=&quot;TU-114 floor plan&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;329&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;A TU-114’s floor plan, with 8 sleeping cabins and 112 passenger seats. (&lt;a href=&quot;https://airwar.ru/enc/aliner/tu114.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Source&lt;/a&gt;)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Our story progression would have players progress from the plane’s back to the front.
So in our case the sleeping quarters shifted to the back of the plane, with the restaurant section at the front and another section with seats in between.
The environment was made to scale of the real thing.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/layout.CMOs4Fvq_Z1yf6rJ.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/layout.CMOs4Fvq_Z6pKyV.jxl, https://jama.me/_astro/layout.CMOs4Fvq_1eX1x3.jxl 1.5x, https://jama.me/_astro/layout.CMOs4Fvq_1S63JN.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/layout.CMOs4Fvq_8X7Lt.avif, https://jama.me/_astro/layout.CMOs4Fvq_1ulTSs.avif 1.5x, https://jama.me/_astro/layout.CMOs4Fvq_Zs9B0m.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/layout.CMOs4Fvq_1YhnCV.webp, https://jama.me/_astro/layout.CMOs4Fvq_Z1JvX41.webp 1.5x, https://jama.me/_astro/layout.CMOs4Fvq_FqQDA.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/layout.CMOs4Fvq_MBrir.png&quot; srcset=&quot;https://jama.me/_astro/layout.CMOs4Fvq_290epq.png 1.5x, https://jama.me/_astro/layout.CMOs4Fvq_ZDXg27.png 2x&quot; alt=&quot;Floor plan of virtual TU-114 plane in 3D software&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;Our final floor plan.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The main differentiator would probably be the cargo area in the back, which would usually be located in the bottom of the fuselage.
This was also done to provide more visual variety.&lt;/p&gt;
&lt;h2 id=&quot;fusing-reality-with-the-gamey&quot;&gt;Fusing Reality with the Gamey&lt;/h2&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/abf2.Dxgpfv2D_ZMA5pH.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/abf2.Dxgpfv2D_2bHhMe.jxl, https://jama.me/_astro/abf2.Dxgpfv2D_Z1x63TI.jxl 1.5x, https://jama.me/_astro/abf2.Dxgpfv2D_1ToJxN.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/abf2.Dxgpfv2D_2r6b8D.avif, https://jama.me/_astro/abf2.Dxgpfv2D_Z1hHayj.avif 1.5x, https://jama.me/_astro/abf2.Dxgpfv2D_ZqPUcm.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/abf2.Dxgpfv2D_ZMLGNP.webp, https://jama.me/_astro/abf2.Dxgpfv2D_xB5i9.webp 1.5x, https://jama.me/_astro/abf2.Dxgpfv2D_GJxrA.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/abf2.Dxgpfv2D_Z1YrD9k.png&quot; srcset=&quot;https://jama.me/_astro/abf2.Dxgpfv2D_ZD3Q2l.png 1.5x, https://jama.me/_astro/abf2.Dxgpfv2D_ZCEze7.png 2x&quot; alt=&quot;Video game screenshot of luxurious airplane interior&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;A pre-polish pass prototype of the starting section at the back of the plane, with the restrooms off-picture to the right.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;We made the conscious decision to opt for a flat and simple art-style.
We didn’t want the violence happening on the flight to be perceived as very gruesome as to not detract too much from the puzzles to be solved, so we figured this would be a good way to achieve that and glue our characters into the game’s visuals.&lt;/p&gt;
&lt;p&gt;Supporting the environmental art-style, the characters were executed as full-body flat player-facing planes.
Portraying their emotions would happen through text and changing character portraits seen throughout dialogues.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/abf3.C2h3G_H1_Rl6r1.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/abf3.C2h3G_H1_qJw8y.jxl, https://jama.me/_astro/abf3.C2h3G_H1_1M8jfx.jxl 1.5x, https://jama.me/_astro/abf3.C2h3G_H1_Z1AsvpG.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/abf3.C2h3G_H1_G8ptX.avif, https://jama.me/_astro/abf3.C2h3G_H1_22wcAW.avif 1.5x, https://jama.me/_astro/abf3.C2h3G_H1_18sWD5.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/abf3.C2h3G_H1_2wrFlq.webp, https://jama.me/_astro/abf3.C2h3G_H1_Z1clFlw.webp 1.5x, https://jama.me/_astro/abf3.C2h3G_H1_2h4qi2.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/abf3.C2h3G_H1_1kLJ0V.png&quot; srcset=&quot;https://jama.me/_astro/abf3.C2h3G_H1_Z2o1BG1.png 1.5x, https://jama.me/_astro/abf3.C2h3G_H1_VEiBk.png 2x&quot; alt=&quot;Video game screenshot of luxurious airplane interior&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;Players are able to talk to the other passengers and crew members.
Their emotions are conveyed via character portraits.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Combined with a flexible interaction system we only lacked one ingredient for the gameplay loop to be complete:
Something where players could combine clues and hints they found by conversing and interacting into ideas they could challenge and verify in further dialogue.&lt;/p&gt;
&lt;p&gt;This is where we came up with our deduction screen:&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/logicsystem.DslTnPQT_Z9R0m8.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/logicsystem.DslTnPQT_c9Vxj.jxl, https://jama.me/_astro/logicsystem.DslTnPQT_Z1xAvnb.jxl 1.5x, https://jama.me/_astro/logicsystem.DslTnPQT_1GO6WS.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/logicsystem.DslTnPQT_2jme61.avif, https://jama.me/_astro/logicsystem.DslTnPQT_yALaw.avif 1.5x, https://jama.me/_astro/logicsystem.DslTnPQT_1ft9rA.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/logicsystem.DslTnPQT_1V9LOe.webp, https://jama.me/_astro/logicsystem.DslTnPQT_bojSJ.webp 1.5x, https://jama.me/_astro/logicsystem.DslTnPQT_ZmrpqV.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/logicsystem.DslTnPQT_ZKIVH1.png&quot; srcset=&quot;https://jama.me/_astro/logicsystem.DslTnPQT_Z2vuoCv.png 1.5x, https://jama.me/_astro/logicsystem.DslTnPQT_2h7p5k.png 2x&quot; alt=&quot;Video game menu screenshot&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;337&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;The logic system is the primary tool for combining clues into ideas to be challenged.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Later we found that it turned out to be surprisingly similar to the one found in Sherlock Holmes: Crimes and Punishments:&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_1iAILb.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_ZRYWpk.jxl, https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_2n52Yz.jxl 1.5x, https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_Z2foNsQ.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_Z1UgEVw.avif, https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_1kNksn.avif 1.5x, https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_1bu4SA.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_1oBiNT.webp, https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_ZpuOA8.webp 1.5x, https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_Z2ufzGO.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_ZsFR1c.png&quot; srcset=&quot;https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_Z2hN0qe.png 1.5x, https://jama.me/_astro/sherlockholmes-logic.D_9lgG22_qSD9l.png 2x&quot; alt=&quot;Video game menu screenshot&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;The deduction screen from Sherlock Holmes: Crimes and Punishments (&lt;a href=&quot;https://www.truetrophies.com/t89591/sharpshooter-trophy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Source&lt;/a&gt;)&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;It functions a little differently though.
In the pre-polish stage we were in at the end of the project, players would combine clues into ideas to be used in dialogue.
But some of the ideas deduced could be combined with other clues to further deepen the insight into the case, and for even more dialogue options.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://jama.me/_astro/abf1.BM7jzfi1_1eQ3Gv.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/abf1.BM7jzfi1_Z2krMTF.jxl, https://jama.me/_astro/abf1.BM7jzfi1_ZY40MG.jxl 1.5x, https://jama.me/_astro/abf1.BM7jzfi1_Z18lf8U.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/abf1.BM7jzfi1_Z253Tyg.avif, https://jama.me/_astro/abf1.BM7jzfi1_ZIF7rh.avif 1.5x, https://jama.me/_astro/abf1.BM7jzfi1_1AAdTQ.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/abf1.BM7jzfi1_ZeJDGN.webp, https://jama.me/_astro/abf1.BM7jzfi1_16D8pb.webp 1.5x, https://jama.me/_astro/abf1.BM7jzfi1_Z2l0rf8.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/abf1.BM7jzfi1_Z1qpA2i.png&quot; srcset=&quot;https://jama.me/_astro/abf1.BM7jzfi1_Z51MUj.png 1.5x, https://jama.me/_astro/abf1.BM7jzfi1_1oLyS6.png 2x&quot; alt=&quot;Video game screenshot of luxurious airplane interior&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;338&quot;&gt; &lt;/picture&gt;&lt;/a&gt;&lt;figcaption&gt;&lt;p&gt;A pre-polish pass prototype of the restaurant section at the front of the plane.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;reflection&quot;&gt;Reflection&lt;/h2&gt;
&lt;p&gt;For the short amount of time we had, this project turned out to be really expansive.
Our design documents clocked in at over 80 pages, with the whole mystery and resolution fully planned out.&lt;/p&gt;
&lt;p&gt;For me, this project was the first game project where I had to quickly adapt in terms of art-style.
Previously, I mostly worked on realistic-looking gritty and dirty environments with lots of texture and detail to them.
I had a particular style and way of assembling environments worked out for myself.
But because of limitations with character modeling (and our decision to have our characters be flat planes), here, for the first time, I was confronted with making a high-poly flat-textured environment look interesting - all while trying to stay true to the real thing.&lt;/p&gt;
&lt;video loop=&quot;&quot; autoplay=&quot;&quot; muted=&quot;true&quot; playsinline=&quot;&quot; controls=&quot;&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/abf-av1.slRFFK9Z.webm&quot; type=&quot;video/webm; codecs=av01.0.05M.08&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/abf-vp9.BGwBYU7F.webm&quot; type=&quot;video/webm; codecs=vp9&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/abf-hevc.CKIJnfZm.mp4&quot; type=&quot;video/mp4; codecs=hvc1&quot;&gt;&lt;source src=&quot;https://jama.me/_astro/abf-avc.TQ8z4u0N.mp4&quot; type=&quot;video/mp4; codecs=avc1.4D401E&quot;&gt;&lt;/video&gt;
&lt;p&gt;I think I managed it well, but the time wasn’t enough to work around all technological limitations.
Unity’s Enlighten integration for realtime Global Illumination had just shipped at the time, and it was too low resolution for serious use in an environment that showed all lighting imperfections as well as this.&lt;/p&gt;
&lt;p&gt;Bogged down by the ready-made dialogue management and scripting system we used, which turned out to be exceptionally unfit for the task, we unfortunately only got a little past the first of the planned setpieces.
The scripting for the implemented parts turned out to be so ridiculously complex already, that we’d have had to switch to a different system in any case.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item><item><title>jama - Flowers for Her</title><link>https://jama.me/projects/music/</link><guid isPermaLink="true">https://jama.me/projects/music/</guid><description>A Lo-Fi-ish Hip-Hop music album.</description><pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In my free time (of which I don’t have much lately), I make music. I also make music for the games I make.
My latest album is called &lt;strong&gt;Flowers for Her&lt;/strong&gt; and came out self-published on digital streaming services.&lt;/p&gt;
&lt;a href=&quot;https://jama.me/_astro/flowersforher.DMjw1qu9_Z2kJVrC.webp&quot;&gt;&lt;picture&gt; &lt;source srcset=&quot;https://jama.me/_astro/flowersforher.DMjw1qu9_ZrDcdw.jxl, https://jama.me/_astro/flowersforher.DMjw1qu9_Z1KkHBW.jxl 1.5x, https://jama.me/_astro/flowersforher.DMjw1qu9_FlNMo.jxl 2x&quot; type=&quot;image/jxl&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/flowersforher.DMjw1qu9_ZBOYnv.avif, https://jama.me/_astro/flowersforher.DMjw1qu9_Z1UwuLV.avif 1.5x, https://jama.me/_astro/flowersforher.DMjw1qu9_Z2gct6S.avif 2x&quot; type=&quot;image/avif&quot;&gt;&lt;source srcset=&quot;https://jama.me/_astro/flowersforher.DMjw1qu9_hfbxp.webp, https://jama.me/_astro/flowersforher.DMjw1qu9_Z11rjQ1.webp 1.5x, https://jama.me/_astro/flowersforher.DMjw1qu9_13xbpu.webp 2x&quot; type=&quot;image/webp&quot;&gt;  &lt;img src=&quot;https://jama.me/_astro/flowersforher.DMjw1qu9_ij5wW.png&quot; srcset=&quot;https://jama.me/_astro/flowersforher.DMjw1qu9_Z10npQt.png 1.5x, https://jama.me/_astro/flowersforher.DMjw1qu9_Z2vM25m.png 2x&quot; alt=&quot;Vinyl. - Flowers for Her album cover&quot; fetchpriority=&quot;auto&quot; width=&quot;600&quot; height=&quot;600&quot;&gt; &lt;/picture&gt;&lt;/a&gt;
&lt;p&gt;You can find it here (among other lesser known services):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://vinylbeats.bandcamp.com/album/flowers-for-her&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Bandcamp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://open.spotify.com/album/5QSRkuqUgWZTWUpE6xFmUX&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Spotify&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://itunes.apple.com/us/album/flowers-for-her/1436907938&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Apple Music&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;http://www.tidal.com/album/95428898&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Tidal&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.deezer.com/album/73707942&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Deezer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.qobuz.com/album/flowers-for-her-vinyl/z5f4sk40241tc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Qobuz&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://soundcloud.com/vinylbeat/flowers-for-her-album&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SoundCloud&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;🌷 Ever wondered what flowers would sound like in music form?
Flowers for Her is a collection of simplistic but wonderfully peaceful beats that attempt to capture an atmosphere of tranquility, carefreeness and innocence.&lt;/p&gt;
&lt;p&gt;Each track represents a different flower in a diverse bouquet somebody would give their beloved.&lt;/p&gt;</content:encoded><author>hi@jama.me (Jan Maslov)</author></item></channel></rss>