Skip to main content
NB
Two soccer jerseys: Halifax Wanderers FC (blue, 2019) on the left and Valour FC (red, 2020)
  on the right.

The burning question

Which team in the Canadian Premiere League has the best kit ?

A question asked each year when the new season’s kits are released, and the catalyst for many online debates. Before the 2024 season I thought it would be a neat project to create a site that would allow users to vote on which kits they thought were the best.

After very little research I decided on a head-to-head voting style for ranking because it’s much easier (usually) to pick which of 2 options you prefer but much more difficult, and time consuming, to rank a large number of items. I also find with a large number of items, after the top few best and bottom few worst the rest are just kinda - blah.

First up, collection

First things first, collecting the images. If I wanted to find out which kit ever released was the best I needed to get an image of every kit dating back to the year the league started in 2019, which was around 90 kits. With the help of some friends I was able to gather all the images I needed. I decided to store all the images in Cloudinary because they have a wonderful image component that made it so I didn’t have to worry about image sizes being sent to the client, it’s all handled for me by Cloudinary.

Once I had the images it was time to build out the database schema. I wanted to keep the database for this simple and store as little information as possible. I didn’t want to deal with accounts or user data (So no highest voted per user or voting history, thanks GDPR) which meant I was able to keep the database to 3 tables:

  • kit
  • kit_matchup
  • kit_vote.

Creating the data schema

The kit table is the most complex of the table with 3 data columns. The kit table stores the team, year, and version information for a kit that is used in combination to pinpoint a specific kit. Here’s a few example rows:

idteamyearversion
69wanderers2023away
84ottawa2024home
24edmonton2019home

The kit_matchup table saves the combinations of kit matches that appear to users. This is a junction table with 2 foreign key columns to the kit table. It’s used to check how many total matches a kit appeared in to when calculating the overall win rate. Because it’s used in calculating the win rate rows are only created in this table when a vote has been cast. This prevents artificially tanking a kit’s rating by refreshing the page whenever it appears.

idkit_akit_b
46984

The kit_vote table is the simplest of the trio of tables, only needing to store a single id pointing back to the kit table to record the winning kit in a match. I add a new row to the table for every vote cast.

idkit_id
469

NOTE: All tables also include timestamps for when they were created, and one for when they are soft-deleted. I left them out of the tables for brevity.

Once I had the tables created I needed to fill the kit table with all the names, years, and versions for each kit. I would like to say I did this with a fancy seeding script so I could easily re-seed the table if necessary, but I manually entered the data for each kit myself.

Artisanally crafted database seeding✨.

The final step of getting the database set up is creating the queries that will give me the data I need for the lists of “Top Voted” kits. That should be pretty simple, right?

Stored procedures in Supabase

In order to query the database from a frontend application I need to create stored procedures in Supabase that contain the queries. All of the stored procedures I created were variations on the example below:

create
or replace function top_10_kits () returns table (
  kit_id bigint,
  team text,
  version text,
  year text,
  vote_count bigint,
  matchup_count bigint,
  vote_matchup_ratio float
) as $$
  ...long_af_query...
$$ language plpgsql;

This is the signature for the stored procedure with the sql query edited out to keep it short. The procedure returns rows from the kit table with a couple new fields added on. The vote_count, matchup_count, and vote_matchup_ratio are all calculated values I’m using to sort the kits.

The vote and matchup count are straightforward, they are just the sums of votes and match ups for a kit respectively. The match up ratio is where the main sorting ✨magic✨ happens. Well, not magic, just some simple maths really.

kv.vote_count::float / km.matchup_count as vote_matchup_ratio

It’s as simple as the number of times someone voted for a kit divided by the total number of match ups that kit has appeared in. This gives me a nice long number between 0 and 1 that I can manipulate to display as a percent. Speaking of displaying…

Now for the part the people see

I used Sveltekit because I just can’t get enough of Sveltekit right now. I find it really easy to work with for how I like to build my web apps. I like the filesystem-based routing because I like being able to see the path structure in my IDE. With Sveltekit there is also a separate +page.server.ts file that will only run on the server. This allows for very clear separation of what is happening on the server versus on the client.

I’m not a great designer (I’m working on it) so I tried to stick with just 2 colours. I used an eyedropper tool to find the two main colours of the Canadian Premiere League logo and went with those.

The logo for the Canadian Premiere League. It is a stylized maple leaf encircled by coloured
  lines. The logo is divided into three colours; a dark navy blue, a light aqua blue, and a light green.

I like using a darker colour for the header/footer background so that naturally led me to choose the navy blue for that purpose and that left me with the obvious choice of green as the second colour. It was an obvious choice to me because I didn’t want to design with 2 shades of blue, that’s even too much blue for Eiffle 65. I decided to use the green for the page background, and not leave it relegated to accents, because it was reminiscent of a football pitch. It’s also less harsh on the eyes than a plain white background.

Voting/Landing page

For the main page I wanted to keep it simple, and didn’t get any last-minute brain waves, so I went with 2 cards that the user must choose between. Each card has a kit randomly chosen from the entire list of all kits, with a quick check to make sure the same kit isn’t used for both cards. Once the user makes their choice a toast message appears to confirm which kit was voted for and the kits are refreshed with 2 new choices.

The main page for the application.Two soccer jerseys: Halifax Wanderers FC (blue, 2019) on the
  left and Valour FC (red, 2020) on the right.

Top Voted Kits

The “Top Voted Kits” page contains 4 versions of the “Top 3 X Kits” lists:

  • overall
  • home
  • away
  • third

The stored procedures query for the top 10 but I only end up displaying the top 3 because showing more would take up too much space on mobile (even on desktop in imo).

I added the numbered place to the top left corner of each card and styled it to look like a podium medal; gold, silver, or bronze. Each card also includes all the information about the kit and its match stats; how many votes it received, how many rounds it appeared in, and of course the vote ratio displayed as a percent.

A webpage displaying the top voted sports kits. The winning kit is Pacific FC 2023 Home with
  260 votes, shown with two runner-up kits.

Teams pages

Finally there is the Teams page and each individual page to see each team’s kits listed out. This way a user could check the votes, rounds, or ratio for any kit from any team regardless of its position in the overall standings.

I added these shortly after the site originally launched because people were asking me about how specific kits were doing. At first I would manually query the database to tell them but quickly thought that there had to be a better way.

I quickly built out a simple page of links listing the teams and linking to a page that would display the kits from that team. I did a “good enough” developer design for the content and never adjusted it, “good enough” never really had a need to get any better.

Plans For The Future

I will be adding the newly released kits for the 2025 season to my database but don’t know what else the future has in store for this site. I would like to update the design but don’t have any ideas to make it look better.

Sometime in the near future I would like to upgrade the site to Svelte5. I don’t think that should be a difficult update, it was pretty straight-forward when I did it for the site you’re reading this on right now.

The only other feature ideas that I had but never ended up getting around to were adding a “Lowest Voted” page and adding pages or filters to see the highest voted in a chosen time frame. Neither spoke to me enough to add them when I first created the app but maybe soon™️?