cars: brand logos everywhere (search cards, brand grid, model headers), stricter logo matching + curated fixes, logo refresh in cars-photos
All checks were successful
Deploy production / deploy (push) Successful in 2m24s

This commit is contained in:
2026-10-24 17:37:43 +00:00
parent fac49b57ba
commit 8442d59b68
14 changed files with 664 additions and 42 deletions

View File

@@ -15,6 +15,7 @@ const staticPaths = [
'/lists',
'/trackers/books',
'/trackers/cars',
'/trackers/cars/brands',
'/trackers/music',
'/projects/game-tracker',
'/projects/game-tracker/pokemon',

View File

@@ -9,7 +9,10 @@ export const load: PageServerLoad = async ({ url }) => {
cars,
total,
pageSize: PAGE_SIZE,
brands: overview.brands,
brands: overview.brands.map(({ slug, name, models }) => ({ slug, name, models })),
topBrands: overview.byNl
.slice(0, 16)
.map(({ slug, name, models, nlCount, logo }) => ({ slug, name, models, nlCount, logo })),
stats: overview.stats,
currentYear: new Date().getFullYear(),
seo: {

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { page } from '$app/state';
import BrandTile from '#lib/cars/BrandTile.svelte';
import CarCard from '#lib/cars/CarCard.svelte';
import {
BODIES,
@@ -10,6 +11,7 @@
fuelLabel,
fmtCompact,
fmtNumber,
LOGO_NOTE,
SORTS
} from '#lib/cars/shared.js';
import { Input } from '#lib/components/ui/input/index.js';
@@ -92,6 +94,22 @@
</div>
</dl>
{#if !filtering && f.page === 1}
<section class="mb-8" aria-labelledby="brands-heading">
<div class="mb-3 flex items-baseline justify-between gap-3">
<h2 id="brands-heading" class="text-lg font-semibold">brands</h2>
<a href="/trackers/cars/brands" class="text-sm text-[var(--site-muted)] hover:underline"
>all {fmtNumber(data.stats.brands)} brands →</a
>
</div>
<ul class="brands">
{#each data.topBrands as brand (brand.slug)}
<li><BrandTile {brand} compact /></li>
{/each}
</ul>
</section>
{/if}
<form
bind:this={form}
method="GET"
@@ -213,11 +231,17 @@
register, CC0) and
<a href="https://www.fueleconomy.gov/feg/download.shtml" class="underline">fueleconomy.gov</a>
(US EPA/DOE). Models are grouped from registered trade names, so a few odd splits remain. Prices are
average catalogue prices of new cars when first registered in NL. Blank means unknown.
average catalogue prices of new cars when first registered in NL. Blank means unknown. Photos from
Wikimedia Commons, credited on each car's page. {LOGO_NOTE}
</footer>
</div>
<style>
.brands {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
gap: 0.5rem;
}
.stats {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));

View File

@@ -1,7 +1,15 @@
<script lang="ts">
import BrandBadge from '#lib/cars/BrandBadge.svelte';
import CarCard from '#lib/cars/CarCard.svelte';
import { commonsPage, flag, fmtNumber, thumbAt, thumbSrcset, years } from '#lib/cars/shared.js';
import {
commonsPage,
flag,
fmtNumber,
LOGO_NOTE,
thumbAt,
thumbSrcset,
years
} from '#lib/cars/shared.js';
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
import type { PageProps } from './$types';
@@ -74,7 +82,9 @@
>{/if}{#if b.logoFile}
· Logo: <a href={commonsPage(b.logoFile)} rel="noopener"
>{b.logoAuthor ?? 'Wikimedia Commons'}</a
>{#if b.logoLicense}, {b.logoLicense}{/if}{/if}. Photos from Wikimedia Commons.
>{#if b.logoLicense}, {b.logoLicense}{/if}{/if}. Photos from Wikimedia Commons.{#if b.logo}
{LOGO_NOTE}{/if}
<a href="/trackers/cars/brands" class="underline">All brands</a>.
</p>
</div>

View File

@@ -34,7 +34,7 @@ export const load: PageServerLoad = async ({ params }) => {
const { model, brand, variants, photos, prev, next } = found;
return {
car: publicCar(model),
brand: { slug: brand.slug, name: brand.name, country: brand.country },
brand: { slug: brand.slug, name: brand.name, country: brand.country, logo: brand.logo },
variants: variants.map(publicVariant),
photos,
prev,

View File

@@ -152,7 +152,7 @@
<header class="hero">
<div class="flex items-center gap-4">
<a href="/trackers/cars/{data.brand.slug}" aria-label="{data.brand.name} models"
><BrandBadge name={data.brand.name} size="lg" /></a
><BrandBadge name={data.brand.name} logo={data.brand.logo} size="lg" /></a
>
<div class="min-w-0">
<p class="dex">{dexLabel(car.dex)}</p>

View File

@@ -0,0 +1,20 @@
import { carOverview } from '#lib/server/cars.js';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async () => {
const { byNl, stats } = await carOverview();
return {
brands: byNl.map(({ slug, name, models, nlCount, logo }) => ({
slug,
name,
models,
nlCount,
logo
})),
stats,
seo: {
title: 'Car brands',
description: `All ${stats.brands} car brands in the pokedex with their logos, most common on Dutch roads first.`
}
};
};

View File

@@ -0,0 +1,49 @@
<script lang="ts">
import BrandTile from '#lib/cars/BrandTile.svelte';
import { LOGO_NOTE, fmtNumber } from '#lib/cars/shared.js';
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
const withLogo = $derived(data.brands.filter((b) => b.logo).length);
</script>
<div class="mx-auto max-w-6xl px-4 py-12 sm:px-6 sm:py-16">
<a href="/trackers/cars" class="back"><ArrowLeft size={15} /> all cars</a>
<header class="mt-6 mb-8">
<h1 class="text-4xl font-semibold tracking-tight sm:text-5xl">brands</h1>
<p class="mt-3 max-w-2xl text-[var(--site-muted)]">
All {fmtNumber(data.brands.length)} brands in the pokedex, the ones you see most on Dutch roads
first. Tap one for its models.
</p>
</header>
<ul class="grid">
{#each data.brands as brand (brand.slug)}
<li><BrandTile {brand} /></li>
{/each}
</ul>
<p class="mt-10 text-xs leading-relaxed text-[var(--site-muted)]">
{withLogo} of {data.brands.length} brands have a logo; the rest get a coloured monogram.
{LOGO_NOTE} Each brand page credits its logo's author and licence.
</p>
</div>
<style>
.back {
display: inline-flex;
align-items: center;
gap: 0.35rem;
font-size: 0.85rem;
color: var(--site-muted);
}
.back:hover {
color: var(--site-text);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
gap: 0.75rem;
}
</style>