From 20a18d175d6c0f47195c8d9e03fb9c8e0e08db6c Mon Sep 17 00:00:00 2001 From: fullstackjam Date: Sun, 28 Jun 2026 01:15:48 +0800 Subject: [PATCH] feat(ui): package fingerprint visualization across config surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow the updated OpenBoot Redesign design file (v2): replace the flat "DNA segment bar" with an animated "package fingerprint" — tall vertical bars colored by package type (cli = green, cask = amber, npm = blue), with heights seeded deterministically per config so a given config renders the same shape everywhere, rising on mount via a shared `ob-rise` keyframe. New shared pieces: - src/lib/fingerprint.ts — countPackageTypes + buildFingerprint (seeded LCG) - PackageFingerprint.svelte — renders the bars + defines the global keyframe Applied to the four surfaces the design touches: - explore: header restyled to a "community registry / N public" pill badge with a larger title and new copy; each card gains a fingerprint - profile: each card gains a fingerprint (matches the same config on explore) - dashboard (ConfigRow): flat DNA bar -> fingerprint (hidden for 0-pkg configs) - config detail (ConfigDetail): hero reworked into a bordered card with the fingerprint as a faded background, meta count-swatches, and an install-rail glow Verified explore/detail/dashboard/profile in light and dark via Playwright. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/components/ConfigDetail.svelte | 195 +++++++++++-------- src/lib/components/ConfigRow.svelte | 39 +--- src/lib/components/PackageFingerprint.svelte | 26 +++ src/lib/fingerprint.ts | 65 +++++++ src/routes/[username]/+page.svelte | 12 ++ src/routes/explore/+page.svelte | 71 +++++-- 6 files changed, 290 insertions(+), 118 deletions(-) create mode 100644 src/lib/components/PackageFingerprint.svelte create mode 100644 src/lib/fingerprint.ts diff --git a/src/lib/components/ConfigDetail.svelte b/src/lib/components/ConfigDetail.svelte index 86a9c86..cf0d826 100644 --- a/src/lib/components/ConfigDetail.svelte +++ b/src/lib/components/ConfigDetail.svelte @@ -3,6 +3,7 @@ import { getCatalogItem } from '$lib/macos-prefs-catalog'; import ShareModal from '$lib/components/ShareModal.svelte'; import MacOSPreferencesDisplay from '$lib/components/MacOSPreferencesDisplay.svelte'; + import PackageFingerprint from '$lib/components/PackageFingerprint.svelte'; let { configUser, @@ -134,22 +135,13 @@ const configNpm = $derived(configPkgs.filter((p) => p.type === 'npm')); const configTaps = $derived(configPkgs.filter((p) => p.type === 'tap')); - // Package "DNA" — one segment per package, opacity ramping within each group. - type Seg = { color: string; opacity: number }; - const dna = $derived.by((): Seg[] => { - const groups: [number, string][] = [ - [configCli.length, 'var(--accent)'], - [configApps.length, 'var(--amber)'], - [configNpm.length, '#7aa2e3'] - ]; - const segs: Seg[] = []; - for (const [n, color] of groups) { - for (let i = 0; i < n; i++) { - segs.push({ color, opacity: 0.55 + (i / Math.max(n - 1, 1)) * 0.45 }); - } - } - return segs; + // Package "fingerprint" — tall bars colored by type, behind the hero. + const fpCounts = $derived({ + cli: configCli.length, + cask: configApps.length, + npm: configNpm.length }); + const fpSeed = $derived(String(config.alias ?? config.id ?? config.name ?? '')); const dnaLegend = $derived( [ @@ -199,51 +191,51 @@
- - -

{config.name}

- {#if config.description} -

{config.description}

- {/if} - -
- - - {config.install_count || 0} installs - - {#if formatDate(config.updated_at)} - updated {formatDate(config.updated_at)} - {/if} - {#if config.base_preset} - base preset {config.base_preset} - {/if} -
- - {#if dna.length > 0} -
- {#each dna as seg} - - {/each} +
+ -
- {#each dnaLegend as l} - {l.n} {l.label} - {/each} +
+
+
+ + +

{config.name}

+ {#if config.description} +

{config.description}

+ {/if} + +
+ + + {config.install_count || 0} installs + + {#if formatDate(config.updated_at)} + updated {formatDate(config.updated_at)} + {/if} + {#if dnaLegend.length > 0} + + {#each dnaLegend as l} + {l.n} + {/each} + + {/if} +
- {/if} +
-
+
Install this setup