diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b8a7ef..f8544b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,29 @@ die Versionierung [Semantic Versioning](https://semver.org/lang/de/).
## [Unreleased]
+Ein neues Gesicht: Bricktrack sieht jetzt aus wie das, was es verwaltet.
+
+### Hinzugefügt
+
+- **Designauswahl** unter Konto → Design: Klemmbaustein (neuer Standard),
+ Warm, Nachtblau (dunkel), Bonbon, Bauklotz und Schlicht (der bisherige
+ Look). Die Wahl gilt pro Gerät und wirkt sofort.
+- **Platzhalter für Sets ohne Bild**: Farbe des Herstellers, gezeichneter
+ Stein und Setnummer statt eines grauen „Bild fehlt".
+- **Startseite als Überblick**: Steinreihe für den Fortschritt (bei mehreren
+ Sammlern in deren Farben), Teile, Wert, nächster Wunschlisten-Termin mit
+ Countdown und Abkürzungen.
+- Neues Logo als Baustein, Schriften lokal gebündelt (kein CDN).
+
+### Geändert
+
+- Karten, Buttons und Badges tragen im Standard-Design Konturen, eine feste
+ Unterkante und Noppen. In der Sammlung und auf der öffentlichen
+ Wunschliste zeigen die Noppen den Sammler bzw. ob ein Wunsch reserviert ist.
+- Die öffentliche Wunschliste hat einen farbigen Kopf mit Anlass und Datum.
+- Screenshots der Anleitung zeigen die feste Navigation nicht mehr mitten
+ im Bild.
+
## [0.3.0] - 2026-09-07
Phase 3: mehrere Kinder, Wertentwicklung, Erinnerungen und ein Schutz vor
diff --git a/CLAUDE.md b/CLAUDE.md
index 9071836..e23f8d7 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -63,6 +63,16 @@ Bild-URLs, die der Nutzer selbst hinterlegt hat.
| `src/lib/reminder-job.ts`, `src/lib/mailer.ts` | Versand und Zeitplan |
| `src/lib/export.ts` | Export und Import des gesamten Bestands |
| `src/lib/money.ts`, `src/lib/format.ts` | Cent-Umrechnung, dd.MM.yyyy |
+| `src/lib/design.ts`, `src/app/designs.css` | Designs: Namen und Cookie bzw. Tokens je `data-design` |
+| `src/components/brick/` | Noppen, Logo, Platzhalter fuer Sets ohne Bild |
+
+### Designs
+
+Das Design steht als `data-design` am `` (Cookie `bricktrack-design`,
+pro Geraet, Standard `klemmbaustein`). Komponenten benutzen nur Tokens
+(`bg-primary`, `border-edge`, `--edge-w`, `--lift`, `bg-tone-1` ...), nie feste
+Farben - sonst stimmt ein Design nicht mehr. Karten mit Noppen: ``
+neben die Karte in einen `relative`-Wrapper, die Karte selbst mit `brick`.
## Befehle
diff --git a/docs/ANLEITUNG.md b/docs/ANLEITUNG.md
index b5304a8..915b257 100644
--- a/docs/ANLEITUNG.md
+++ b/docs/ANLEITUNG.md
@@ -32,6 +32,7 @@ Kinderzimmer, nicht für den Schreibtisch.
18. [Erinnerung vor dem Termin](#18-erinnerungen)
19. [Schutz vor doppelten Einträgen](#19-dubletten)
20. [Antworten auf die zwei wichtigen Fragen](#20-die-zwei-fragen)
+21. [Ein anderes Design](#21-design)
---
@@ -52,17 +53,20 @@ gleich danach unter „Konto" (siehe [Abschnitt 10](#10-passkeys-einrichten)).

-Die Startseite ist die Abzweigung zu den vier Bereichen:
+Die Startseite zeigt auf einen Blick, wo die Sammlung steht:
| | |
|---|---|
-| **Katalog** | Alle bekannten Sets – egal ob vorhanden oder nicht |
-| **Fehlende Sets** | Der direkte Weg zu allem, was noch fehlt |
-| **Sammlung** | Was tatsächlich da ist |
-| **Wunschlisten** | Zum Teilen mit Familie und Freunden |
+| **Steinreihe** | Zehn Plätze für den ganzen Katalog. Jeder volle Stein ist ein Zehntel, das schon da ist – bei mehreren Kindern in deren Farbe |
+| **Teile und Wert** | Alle verbauten Teile und was die Sammlung heute wert ist |
+| **Nächster Termin** | Die nächste Wunschliste mit Datum, wie viele Tage noch bleiben und wie viel offen ist |
+| **Abkürzungen** | Direkt zu den fehlenden Sets, zu den Wunschlisten oder zu einem neuen Set |
Unten am Bildschirmrand liegt die Navigation – auf dem Handy mit dem Daumen
-erreichbar.
+erreichbar. Der aktive Bereich sitzt auf einer kleinen Platte.
+
+Sets ohne Bild bekommen einen farbigen Platzhalter mit der Setnummer. Jeder
+Hersteller hat seine eigene Farbe – Lego gelb, Playmobil blau, Cobi grün.
---
@@ -548,6 +552,27 @@ Haken dran, habt ihr es schon.
---
+## 21. Design
+
+
+
+Unter **Konto → Design** gibt es sechs Looks zur Auswahl:
+
+| Design | |
+|---|---|
+| **Klemmbaustein** | Der Standard: Karten mit Noppen, dunkle Konturen, kräftige Steinfarben |
+| **Warm** | Ruhiger Sandton mit Ziegelrot |
+| **Nachtblau** | Dunkel mit gelbem Akzent – angenehm am Abend |
+| **Bonbon** | Weiches Lila mit Pastellfarben |
+| **Bauklotz** | Primärfarben und dicke schwarze Konturen |
+| **Schlicht** | Neutrales Grau, so wie Bricktrack bis Version 0.3 aussah |
+
+Die Auswahl wirkt sofort und gilt **für das Gerät**, nicht für das Konto: Das
+Tablet im Kinderzimmer kann bunt sein, während das Handy dunkel bleibt.
+Öffentliche Wunschlisten sehen Gäste immer im Standard-Design.
+
+---
+
## Kleinigkeiten, die den Alltag leichter machen
- **Filter stecken in der URL.** Ein Lesezeichen auf
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index fad9430..e448c99 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -59,6 +59,14 @@ Laeuft vollstaendig im Docker-Stack.
rueckt und Eintraege unreserviert sind
- [x] Dublettenpruefung beim Anlegen von Sets
+## Design (unveröffentlicht)
+
+- [x] Designauswahl pro Gerät, sechs Designs, Standard „Klemmbaustein"
+- [x] Platzhalter für Sets ohne Bild, neues Logo, Schriften lokal
+- [x] Startseite als Überblick mit Steinreihe und nächstem Termin
+- [ ] Filter im Katalog als Reiter statt Auswahlfelder
+- [ ] Scan-Button mittig in der unteren Navigation
+
## Danach
Phase 1 bis 3 sind umgesetzt. Was noch offen ist, steht hier - ohne Termin
diff --git a/docs/bilder/01-registrierung.png b/docs/bilder/01-registrierung.png
index 7788f17..4a5e11c 100644
Binary files a/docs/bilder/01-registrierung.png and b/docs/bilder/01-registrierung.png differ
diff --git a/docs/bilder/02-start.png b/docs/bilder/02-start.png
index 441cab0..7f43ae3 100644
Binary files a/docs/bilder/02-start.png and b/docs/bilder/02-start.png differ
diff --git a/docs/bilder/03-verwaltung-hersteller.png b/docs/bilder/03-verwaltung-hersteller.png
index 6ada860..183ecc2 100644
Binary files a/docs/bilder/03-verwaltung-hersteller.png and b/docs/bilder/03-verwaltung-hersteller.png differ
diff --git a/docs/bilder/04-katalog.png b/docs/bilder/04-katalog.png
index 980c770..881d0c9 100644
Binary files a/docs/bilder/04-katalog.png and b/docs/bilder/04-katalog.png differ
diff --git a/docs/bilder/05-fehlende-sets.png b/docs/bilder/05-fehlende-sets.png
index 155fa39..cba66cc 100644
Binary files a/docs/bilder/05-fehlende-sets.png and b/docs/bilder/05-fehlende-sets.png differ
diff --git a/docs/bilder/06-mehrfachauswahl.png b/docs/bilder/06-mehrfachauswahl.png
index c3ef473..4ee6f3d 100644
Binary files a/docs/bilder/06-mehrfachauswahl.png and b/docs/bilder/06-mehrfachauswahl.png differ
diff --git a/docs/bilder/07-wunschliste-dialog.png b/docs/bilder/07-wunschliste-dialog.png
index 974026b..27d6ec0 100644
Binary files a/docs/bilder/07-wunschliste-dialog.png and b/docs/bilder/07-wunschliste-dialog.png differ
diff --git a/docs/bilder/08-set-detail.png b/docs/bilder/08-set-detail.png
index ad0f8c3..883e022 100644
Binary files a/docs/bilder/08-set-detail.png and b/docs/bilder/08-set-detail.png differ
diff --git a/docs/bilder/09-sammlung.png b/docs/bilder/09-sammlung.png
index 87a8485..4ebca63 100644
Binary files a/docs/bilder/09-sammlung.png and b/docs/bilder/09-sammlung.png differ
diff --git a/docs/bilder/10-wunschlisten.png b/docs/bilder/10-wunschlisten.png
index cb6e1c1..cd9e6aa 100644
Binary files a/docs/bilder/10-wunschlisten.png and b/docs/bilder/10-wunschlisten.png differ
diff --git a/docs/bilder/11-wunschliste-bearbeiten.png b/docs/bilder/11-wunschliste-bearbeiten.png
index 779a267..289ed5c 100644
Binary files a/docs/bilder/11-wunschliste-bearbeiten.png and b/docs/bilder/11-wunschliste-bearbeiten.png differ
diff --git a/docs/bilder/12-oeffentliche-wunschliste.png b/docs/bilder/12-oeffentliche-wunschliste.png
index 8a61b0a..de1db97 100644
Binary files a/docs/bilder/12-oeffentliche-wunschliste.png and b/docs/bilder/12-oeffentliche-wunschliste.png differ
diff --git a/docs/bilder/13-reservieren.png b/docs/bilder/13-reservieren.png
index 080d9b0..832bdb9 100644
Binary files a/docs/bilder/13-reservieren.png and b/docs/bilder/13-reservieren.png differ
diff --git a/docs/bilder/14-reserviert.png b/docs/bilder/14-reserviert.png
index 8141b64..f25ea6c 100644
Binary files a/docs/bilder/14-reserviert.png and b/docs/bilder/14-reserviert.png differ
diff --git a/docs/bilder/15-wunschliste-reserviert.png b/docs/bilder/15-wunschliste-reserviert.png
index e19f81e..5bc421d 100644
Binary files a/docs/bilder/15-wunschliste-reserviert.png and b/docs/bilder/15-wunschliste-reserviert.png differ
diff --git a/docs/bilder/16-konto-passkeys.png b/docs/bilder/16-konto-passkeys.png
index 18ae769..c9c58bd 100644
Binary files a/docs/bilder/16-konto-passkeys.png and b/docs/bilder/16-konto-passkeys.png differ
diff --git a/docs/bilder/17-lego-import.png b/docs/bilder/17-lego-import.png
index 380e195..69db989 100644
Binary files a/docs/bilder/17-lego-import.png and b/docs/bilder/17-lego-import.png differ
diff --git a/docs/bilder/18-fehlteile.png b/docs/bilder/18-fehlteile.png
index 8a0597e..63c926a 100644
Binary files a/docs/bilder/18-fehlteile.png and b/docs/bilder/18-fehlteile.png differ
diff --git a/docs/bilder/19-einladungen.png b/docs/bilder/19-einladungen.png
index 5413fca..fc6aa12 100644
Binary files a/docs/bilder/19-einladungen.png and b/docs/bilder/19-einladungen.png differ
diff --git a/docs/bilder/20-daten.png b/docs/bilder/20-daten.png
index 494b872..4735899 100644
Binary files a/docs/bilder/20-daten.png and b/docs/bilder/20-daten.png differ
diff --git a/docs/bilder/21-sammler.png b/docs/bilder/21-sammler.png
index 55db779..7b86943 100644
Binary files a/docs/bilder/21-sammler.png and b/docs/bilder/21-sammler.png differ
diff --git a/docs/bilder/22-sammler-filter.png b/docs/bilder/22-sammler-filter.png
index 3d906b6..e75cf38 100644
Binary files a/docs/bilder/22-sammler-filter.png and b/docs/bilder/22-sammler-filter.png differ
diff --git a/docs/bilder/23-preise.png b/docs/bilder/23-preise.png
index b221209..18cac30 100644
Binary files a/docs/bilder/23-preise.png and b/docs/bilder/23-preise.png differ
diff --git a/docs/bilder/24-erinnerungen.png b/docs/bilder/24-erinnerungen.png
index 5145391..06a4476 100644
Binary files a/docs/bilder/24-erinnerungen.png and b/docs/bilder/24-erinnerungen.png differ
diff --git a/docs/bilder/25-design-nachtblau.png b/docs/bilder/25-design-nachtblau.png
new file mode 100644
index 0000000..334ddd2
Binary files /dev/null and b/docs/bilder/25-design-nachtblau.png differ
diff --git a/e2e/screenshots.spec.ts b/e2e/screenshots.spec.ts
index c0b784e..93f99be 100644
--- a/e2e/screenshots.spec.ts
+++ b/e2e/screenshots.spec.ts
@@ -19,6 +19,9 @@ function shotPath(name: string) {
async function shoot(page: Page, name: string) {
// Let fonts and images settle so the picture is not half-rendered.
await page.waitForLoadState('networkidle');
+ // A fixed bottom bar would be painted where the first viewport ends, in the
+ // middle of a full-page picture. In flow it lands at the end, where it belongs.
+ await page.addStyleTag({ content: '.fixed.bottom-0 { position: static !important; }' });
await page.screenshot({ path: shotPath(name), fullPage: true });
}
@@ -117,6 +120,8 @@ test('Screenshots fuer die Anleitung', async ({ page, browser }) => {
await visitor.getByRole('button', { name: 'Reservieren' }).click();
await expect(visitor.getByText('Reservierung gespeichert')).toBeVisible();
await visitor.getByRole('button', { name: 'Alles klar' }).click();
+ // Otherwise the picture catches the dialog halfway through fading out.
+ await expect(visitor.getByRole('dialog')).toBeHidden();
await shoot(visitor, '14-reserviert');
await visitorContext.close();
@@ -129,6 +134,18 @@ test('Screenshots fuer die Anleitung', async ({ page, browser }) => {
await expect(page.getByText('Passkeys', { exact: true })).toBeVisible();
await shoot(page, '16-konto-passkeys');
+ // The same start page in the dark design, then back to the default.
+ await page.getByText('Nachtblau').click();
+ await expect(page.locator('html')).toHaveAttribute('data-design', 'nacht');
+ // The look switches at once; the cookie follows with the server action.
+ await page.waitForLoadState('networkidle');
+ await visit(page, '/');
+ await expect(page.locator('html')).toHaveAttribute('data-design', 'nacht');
+ await shoot(page, '25-design-nachtblau');
+ await visit(page, '/konto');
+ await page.getByText('Klemmbaustein').click();
+ await expect(page.locator('html')).toHaveAttribute('data-design', 'klemmbaustein');
+
// --- 12. Lego import ------------------------------------------------------
// The screenshot container runs with a placeholder API key so the manual
// shows the form, not the "not configured" hint. Nothing is submitted.
diff --git a/package.json b/package.json
index e593f1a..076b208 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,12 @@
},
"dependencies": {
"@better-auth/passkey": "^1.7.3",
+ "@fontsource-variable/archivo": "^5.3.0",
+ "@fontsource-variable/bricolage-grotesque": "^5.3.0",
+ "@fontsource-variable/fredoka": "^5.3.0",
+ "@fontsource-variable/inter": "^5.3.0",
+ "@fontsource-variable/nunito": "^5.3.0",
+ "@fontsource-variable/outfit": "^5.3.0",
"@zxing/browser": "^0.2.1",
"@zxing/library": "^0.23.0",
"better-auth": "^1.7.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8784d08..222e5fb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11,6 +11,24 @@ importers:
'@better-auth/passkey':
specifier: ^1.7.3
version: 1.7.3(@better-auth/core@1.7.3(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.5.4))(jose@6.2.12)(kysely@0.29.5)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.7.3(better-sqlite3@13.0.3)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/better-sqlite3@9.6.0)(better-sqlite3@13.0.3)(kysely@0.29.5))(next@15.5.25(@babel/core@7.29.7)(@playwright/test@1.63.0)(@types/node@20.19.43)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.0(@types/node@20.19.43)(vite@8.2.2(@types/node@20.19.43)(esbuild@0.25.12)(jiti@2.7.0)(tsx@4.23.13))))(better-call@1.4.0(zod@4.5.4))(nanostores@1.5.3)
+ '@fontsource-variable/archivo':
+ specifier: ^5.3.0
+ version: 5.3.0
+ '@fontsource-variable/bricolage-grotesque':
+ specifier: ^5.3.0
+ version: 5.3.0
+ '@fontsource-variable/fredoka':
+ specifier: ^5.3.0
+ version: 5.3.0
+ '@fontsource-variable/inter':
+ specifier: ^5.3.0
+ version: 5.3.0
+ '@fontsource-variable/nunito':
+ specifier: ^5.3.0
+ version: 5.3.0
+ '@fontsource-variable/outfit':
+ specifier: ^5.3.0
+ version: 5.3.0
'@zxing/browser':
specifier: ^0.2.1
version: 0.2.1(@zxing/library@0.23.0)
@@ -887,6 +905,24 @@ packages:
'@floating-ui/utils@0.2.12':
resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
+ '@fontsource-variable/archivo@5.3.0':
+ resolution: {integrity: sha512-HogK8FJelrD1o7TlZlkIVtHgc20bO5PZRWE7mUeUTdMN055alznQV6/00J00IBeu8FQAH4s3zW9UJNvKExXf+g==}
+
+ '@fontsource-variable/bricolage-grotesque@5.3.0':
+ resolution: {integrity: sha512-TLi9Q4hJjS2UvoTMRSS2nHu6c4R56lAw60NR9QYtVRCHn0XtsFpiEhNffZ8Glsoxu6wEEwLKBP8lb94J52PNBA==}
+
+ '@fontsource-variable/fredoka@5.3.0':
+ resolution: {integrity: sha512-13w98iw2RcmYfZQRZPLSu+0YJK3HHm5A2XrpU26moFIAePmfiKfvkmEJUgkc8r5GNR+pbDijOueS1KdGAMAWmQ==}
+
+ '@fontsource-variable/inter@5.3.0':
+ resolution: {integrity: sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==}
+
+ '@fontsource-variable/nunito@5.3.0':
+ resolution: {integrity: sha512-tw0ch7evIDSwoeUy+0XzocYHfbRWLeCRPJy22KgbrLjDLAbNw/fErBiil10Ssrs3Sjr55zaZrr36a975j8cBkQ==}
+
+ '@fontsource-variable/outfit@5.3.0':
+ resolution: {integrity: sha512-fPb5XnYK0k7SbR++HEmm4+ZqDszmLKQ+sPRUr8oQ/KNC0vy6r6hazJFe8iYozv4cqlHbvJ/JECkJWrfDodUMVQ==}
+
'@hexagon/base64@1.1.28':
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
@@ -5659,6 +5695,18 @@ snapshots:
'@floating-ui/utils@0.2.12': {}
+ '@fontsource-variable/archivo@5.3.0': {}
+
+ '@fontsource-variable/bricolage-grotesque@5.3.0': {}
+
+ '@fontsource-variable/fredoka@5.3.0': {}
+
+ '@fontsource-variable/inter@5.3.0': {}
+
+ '@fontsource-variable/nunito@5.3.0': {}
+
+ '@fontsource-variable/outfit@5.3.0': {}
+
'@hexagon/base64@1.1.28': {}
'@hono/node-server@2.1.1(hono@4.13.7)':
diff --git a/src/app/(app)/katalog/[id]/page.tsx b/src/app/(app)/katalog/[id]/page.tsx
index 8650504..527ec1d 100644
--- a/src/app/(app)/katalog/[id]/page.tsx
+++ b/src/app/(app)/katalog/[id]/page.tsx
@@ -1,7 +1,9 @@
import type { Metadata } from 'next';
import Link from 'next/link';
import { notFound } from 'next/navigation';
-import { Check, ExternalLink, Gift, ImageOff, Pencil } from 'lucide-react';
+import { Check, ExternalLink, Gift, Pencil } from 'lucide-react';
+import { SetPlaceholder } from '@/components/brick/set-placeholder';
+import { Studs } from '@/components/brick/studs';
import { InstructionsSection } from '@/components/katalog/instructions-section';
import { PhotosSection } from '@/components/katalog/photos-section';
import { PriceSection } from '@/components/katalog/price-section';
@@ -114,15 +116,21 @@ export default async function SetDetailPage({ params, searchParams }: Props) {
-
+ {/* Green like "Vorhanden" in the catalog; red stays for actions. */}
+
{statusLabel(item.status)}
{item.completeness !== 'unknown' ? (
diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx
index 09aa574..e5684a1 100644
--- a/src/app/(auth)/layout.tsx
+++ b/src/app/(auth)/layout.tsx
@@ -1,11 +1,14 @@
import Link from 'next/link';
-import { Blocks } from 'lucide-react';
+import { BrickLogo } from '@/components/brick/logo';
export default function AuthLayout({ children }: { children: React.ReactNode }) {
return (
+ );
+}
diff --git a/src/components/brick/studs.tsx b/src/components/brick/studs.tsx
new file mode 100644
index 0000000..06f5c1f
--- /dev/null
+++ b/src/components/brick/studs.tsx
@@ -0,0 +1,25 @@
+import { cn } from 'cn';
+
+/**
+ * The row of studs on top of a card. Purely decorative and only visible in
+ * designs that set `--studs: flex`. The parent must be `relative` and must not
+ * clip its overflow - place it next to the card, not inside.
+ */
+export function Studs({
+ count = 2,
+ className,
+ studClassName,
+}: {
+ count?: number;
+ className?: string;
+ /** Colour of the studs, e.g. the collector's dot class. */
+ studClassName?: string;
+}) {
+ return (
+
+ {Array.from({ length: count }, (_, index) => (
+
+ ))}
+
+ );
+}
diff --git a/src/components/katalog/catalog-grid.tsx b/src/components/katalog/catalog-grid.tsx
index c24b85c..3890ff2 100644
--- a/src/components/katalog/catalog-grid.tsx
+++ b/src/components/katalog/catalog-grid.tsx
@@ -217,7 +217,8 @@ export function CatalogGrid({
'relative rounded-xl text-left outline-none transition-opacity',
'focus-visible:ring-3 focus-visible:ring-ring/50',
owned && 'cursor-not-allowed opacity-50',
- isSelected && '[&>div]:border-primary [&>div]:ring-2 [&>div]:ring-primary/40',
+ isSelected &&
+ '[&_.brick]:outline-3 [&_.brick]:outline-offset-2 [&_.brick]:outline-primary',
)}
>
diff --git a/src/components/katalog/set-card.tsx b/src/components/katalog/set-card.tsx
index 7b83260..5371a3d 100644
--- a/src/components/katalog/set-card.tsx
+++ b/src/components/katalog/set-card.tsx
@@ -1,6 +1,8 @@
import Link from 'next/link';
-import { Check, Gift, ImageOff } from 'lucide-react';
+import { Check, Gift } from 'lucide-react';
import { cn } from 'cn';
+import { SetPlaceholder } from '@/components/brick/set-placeholder';
+import { Studs } from '@/components/brick/studs';
import type { CatalogRow } from '@/lib/queries/catalog';
import { formatEuro } from '@/lib/money';
import { setImageSrc } from '@/lib/images';
@@ -17,56 +19,59 @@ export function SetCardContent({ set }: { set: CatalogRow }) {
const owned = set.ownedCopies > 0;
return (
-
-
- {image ? (
- // Plain img: images are either local files or a URL the user entered,
- // and the optimizer must never fetch arbitrary remote hosts.
- // eslint-disable-next-line @next/next/no-img-element
-
- ) : (
-
-
+
+
+
+
+ {image ? (
+ // Plain img: images are either local files or a URL the user entered,
+ // and the optimizer must never fetch arbitrary remote hosts.
+ // eslint-disable-next-line @next/next/no-img-element
+
+ ) : (
+
+ )}
+
+