Steam

An implementation of Juxtopposed's Steam redesign.

Steam

Summary

🌊 🐸
Year 2024
Status ✅ Completed
Tech
Typography Inter
Collaborators N/A
Hosting Cloudflare Pages
Website Link Steam

This took a minute, but I'm glad I got to the writing a post about this part.

A month or two ago, I stumbled upon Juxtopposed's Steam redesign video. I was in awe with the attention to detail and wanted to try my hand at implementing it. In the past, I had to rely on implementing designs based purely on images, which, I must say, is not the most fun thing to do. So, believe my joy when I found a Figma link to the design. This was a dream come true; an actual, complete - well, almost - design to implement. I say almost because here comes the biggest disclaimer/critique I got after showing my implementation to a colleague:

  • The design is desktop-only, and I had no plans to improvise on that in terms of making it responsive, as such, I stuck to what I found.

Now that that's out of the way, allow me to share with you a story-of-sorts about the implementation.

On a serious note, though, Juxtopposed is awesome.

Tech Stack

I don't know if this project warrants such a section, since this implementation is UI only, nonetheless, I love Svelte and its community too much to not talk about them whenever I get the chance.

  • Everything is set in SvelteKit, and although I'm like Harris the younger who sometimes does things for shits and giggles, I'll always reach out for SvelteKit as my go-to web framework for anything web-related.
  • Then comes Hunter's Kit; the man who never sleeps, but always ships. It has always bothered me that we seem to only use shadcn-svelte for monochromatic sites, mine included, but given how the intent was to always offer one the flexibility to build anything through sensible defaults, I was very curious to see if I could surprise myself. Which I did, but I regret some decisions I made early on(more on that later.)
    For those not acquainted, shadcn-svelte is an amalgam of awesome tools/libs/plugins that you can copy and paste into your apps, whose components are marked by being Accessible, Customizable, and Open Source.

For this project, this is my package.json highlighting the libs I used to get this live, minus miscellaneous ones.

	{
	"@fontsource-variable/inter": "^5.0.17",
	"@neodrag/svelte": "^2.0.3",
	"@poppanator/sveltekit-svg": "^4.2.1",
	"@sveltejs/adapter-cloudflare": "^4.2.1",
	"@sveltejs/enhanced-img": "^0.2.0",
	"bits-ui": "^0.21.2",
	"clsx": "^2.1.0",
	"embla-carousel-autoplay": "8.0.2",
	"embla-carousel-svelte": "8.0.2",
	"embla-carousel-wheel-gestures": "8.0.0-rc05",
	"lucide-svelte": "^0.368.0",
	"svelte-legos": "^0.2.2",
	"sveltekit-search-params": "^2.1.2",
	"tailwindcss": "^3.4.3"
}	

Details

Let's do a quick check of the different kinds of files I ended up.

	find . -type f ( -not ( -path "./node_modules/*" -o -path "./.git/*" -o -path "./.vscode/*" -o -name ".gitignore" ) -a -not -name ".DS_Store" ) | sed -n 's/..*.//p' | sort | uniq -c | sort -nr	
	 229 jpg
 226 svelte
 116 ts
  44 js
  24 png
   8 svg
   6 json
   2 gif
   2 cjs
   1 yml
   1 yaml
   1 webmanifest
   1 prettierrc
   1 prettierignore
   1 pcss
   1 npmrc
   1 md
   1 ico
   1 html	

Yikes! What bothered me here wasn't the 226 Svelte components, but the 255 pictures. Were it an ideal world, I would have known sooner how many images they'd be, and I would have used Steam's APIs, but here we are. For performant images, I was really lucky there was @sveltejs/enhanced-img

@sveltejs/enhanced-img is a plugin offered on top of Vite's built-in asset handling. It provides plug and play image processing that serves smaller file formats like avif or webp, automatically sets the intrinsic width and height of the image to avoid layout shift, creates images of multiple sizes for various devices, and strips EXIF data for privacy. It will work in any Vite-based project including, but not limited to, SvelteKit projects.

My only complaint with the package was that it doesn't handle gif files, but I'm very happy with how it is now, more than ever now that it supports caching.

Pages

Another quick search for +page.svelte files tells me that I have about 27 pages, which sounds about right, and in line with the original design.

	find . -type f -not ( -path "./node_modules/*" -o -path "./.git/*" -o -path "./.vscode/*" -o -name ".gitignore" ) -name "*+page.svelte" | wc -l	
	      27	

Good luck finding them, if you have the time to peruse through. 🙂

Carousels

In my opinion, the crown jewel of this project is David Jerleke's Embla Carousel

Embla Carousel is a lightweight carousel library with fluid motion and great swipe precision

It was so easy setting up carousels, (Hunter did most of the work for us), and extending to include new features like Carousel Dots was fairly easy.

That said, I must confess about something that I failed to achieve - Thumbnails.
I don't want to get into this because I'm ashamed of my shortcoming on this, but I opened a discussion asking for help here, if you'd like some more context.

Lessons

  1. Co-locate your files. And I mean this as advice to my future-self. The Svelte team was right, as always. The opinions that they came up with in terms of file/folder structure, among other things, stem from years of experience, and I was very fortunate to finally understand/share in that experience. Why? Well, I was always against the idea of co-locating files, but after running with dozens of component files, I quickly realized how everything was becoming a mess. This truly shined when I took a break from the project and returned to it, only to realize that co-location is a huge time saver.

Now, all my +page.svelte files look like this:

src/routes/store/+page.svelte
	<script lang="ts">
	import Categories from '$lib/components/shared/categories.svelte';
	import Featured from './featured.svelte';
	import FreeToPlayGames from './free-to-play-games.svelte';
	import Hero from './hero.svelte';
	import NewAndTrending from './new-and-trending.svelte';
	import RecentlyUpdated from './recently-updated.svelte';
	import SpecialOffers from './special-offers.svelte';
	import StreamingNow from './streaming-now.svelte';
	import TopSellers from './top-sellers.svelte';
	import Under5Dollars from './under-5-dollars.svelte';
</script>
 
<section
	id="client-store"
	class="bg-gradient-to-b from-[#040011] to-[#202B3C] px-2 sm:px-4 2xl:px-0"
>
	<Hero />
	<Featured />
	<SpecialOffers />
	<Categories />
	<FreeToPlayGames />
	<RecentlyUpdated />
	<NewAndTrending />
	<TopSellers />
	<Under5Dollars />
	<StreamingNow />
</section>	

and at their worst, I want them to look like this:

src/routes/store/game/+page.svelte
	<script lang="ts">
	import About from './about.svelte';
	import Aside from './aside.svelte';
	import DealsAndOffersDeluxeEdition from './deals-and-offers-deluxe-edition.svelte';
	import DealsAndOffers from './deals-and-offers.svelte';
	import Dlcs from './dlcs.svelte';
	import Header from './header.svelte';
	import Main from './main.svelte';
	import MostHelpfulReviews from './most-helpful-reviews.svelte';
	import OverallReviews from './overall-reviews.svelte';
	import Overlay from './overlay.svelte';
	import RecentReviews from './recent-reviews.svelte';
	import SimilarTitles from './similar-titles.svelte';
	import SortAndFilter from './sort-and-filter.svelte';
	import Summary from './summary.svelte';
	import SystemRequirements from './system-requirements.svelte';
</script>
 
<section
	id="game"
	class="relative z-0 overflow-clip bg-gradient-to-b from-[#0E141B] to-[#202B3B] px-2 sm:px-4 2xl:px-0"
>
	<Overlay />
	<div class="mx-auto max-w-[88rem] space-y-4 py-16">
		<Header />
		<main class="bg-background-main/50 flex gap-4 rounded-md p-4">
			<Main />
			<Aside />
		</main>
		<article class="flex gap-4">
			<div class="max-w-[61.5rem] flex-1 space-y-4">
				<DealsAndOffers />
				<DealsAndOffersDeluxeEdition />
				<Dlcs />
				<About />
				<SystemRequirements />
				<SimilarTitles />
			</div>
			<Summary />
		</article>
		<h2 class="scroll-m-20 text-lg font-semibold tracking-tight">User Reviews</h2>
		<footer id="user-reviews" class="flex gap-4">
			<SortAndFilter />
			<div class="grid h-fit flex-1 grid-cols-[1fr_416px] place-items-stretch gap-4">
				<OverallReviews />
				<MostHelpfulReviews />
				<RecentReviews />
			</div>
		</footer>
	</div>
</section>	

  1. Don't override the shadcn-svelte variables. I only did this to align with the naming conventions of the original design, such that my app.pcss files had these variable names:
src/app.pcss
	@import '@fontsource-variable/inter';
 
@tailwind base;
@tailwind components;
@tailwind utilities;
 
@layer base {
	:root {
		/* Text */
		--text-main: 0 0% 95%;
		--text-dim: 213 9% 51%;
 
		/* Background */
		--bg-main: 212 32% 8%;
		--bg-highlight: 213 15% 14%;
		--bg-hover: 217 16% 23%;
		--bg-secondary: 205 58% 19%;
		--bg-tertiary: 223 35% 20%;
 
		/* Color */
		--color-primary: 202 87% 68%;
		--color-secondary: 224 35% 45%;
 
		/* Accent */
		--accent-green: 79 58% 54%;
		--accent-red: 7 58% 54%;
		--accent-yellow: 50 44% 56%;
	}
}
 
@layer base {
	* {
		@apply border-background-highlight;
	}
 
	body {
		@apply bg-background-main text-text-main font-sans antialiased;
		font-feature-settings:
			'rlig' 1,
			'calt' 1;
	}
}	

But doing this turned out to a huge maintenance problem, because I found myself digging through component files to update the Tailwind classes. Never again. From now on, I'm sticking to the sensible defaults, thank you very much.


Next Steps

Not sure. IMDB redesign next?


P.S.

I really love the challenge of re-creating UI's, so if you manage to get your hands on an MIT Figma file (preferably with the same quality as Juxtopposed's), please don't hesitate to send me a link via electronic mail or something similar.

Cheers.