feature/comparison-slider #19

Merged
ilia merged 129 commits from feature/comparison-slider into main 2026-02-02 09:23:46 +00:00
5 changed files with 113 additions and 0 deletions
Showing only changes of commit a9c63f2544 - Show all commits

View File

@@ -0,0 +1,2 @@
export { scrollBreadcrumbsStore } from './model';
export { BreadcrumbHeader } from './ui';

View File

@@ -0,0 +1 @@
export * from './store/scrollBreadcrumbsStore.svelte';

View File

@@ -0,0 +1,29 @@
import type { Snippet } from 'svelte';
export interface BreadcrumbItem {
index: number;
title: Snippet<[{ className?: string }]>;
}
class ScrollBreadcrumbsStore {
#items = $state<BreadcrumbItem[]>([]);
get items() {
// Keep them sorted by index for Swiss orderliness
return this.#items.sort((a, b) => a.index - b.index);
}
add(item: BreadcrumbItem) {
if (!this.#items.find(i => i.index === item.index)) {
this.#items.push(item);
}
}
remove(index: number) {
this.#items = this.#items.filter(i => i.index !== index);
}
}
export function createScrollBreadcrumbsStore() {
return new ScrollBreadcrumbsStore();
}
export const scrollBreadcrumbsStore = createScrollBreadcrumbsStore();

View File

@@ -0,0 +1,78 @@
<!--
Component: BreadcrumbHeader
Fixed header for breadcrumbs navigation for sections in the page
-->
<script lang="ts">
import Icon from '@lucide/svelte/icons/align-vertical-justify-center';
import { flip } from 'svelte/animate';
import { slide } from 'svelte/transition';
import { scrollBreadcrumbsStore } from '../../model';
</script>
{#if scrollBreadcrumbsStore.items.length > 0}
<div
transition:slide={{ duration: 200 }}
class="
fixed top-0 left-0 right-0 z-100
backdrop-blur-lg bg-white/20
border-b border-gray-300/50
shadow-[0_1px_3px_rgba(0,0,0,0.04)]
h-12
"
>
<div class="max-w-8xl mx-auto px-6 h-full flex items-center gap-4">
<div class="flex items-center gap-2.5 opacity-70">
<Icon class="size-4 stroke-gray-900 stroke-1" />
<div class="w-px h-2.5 bg-gray-400/50"></div>
<span class="font-mono text-[9px] uppercase tracking-[0.25em] text-gray-500 font-medium">
nav_trace
</span>
</div>
<div class="h-4 w-px bg-gray-300/60"></div>
<nav class="flex items-center gap-3 overflow-x-auto scrollbar-hide flex-1">
{#each scrollBreadcrumbsStore.items as item, idx (item.index)}
<div
animate:flip={{ duration: 200 }}
class="flex items-center gap-3 whitespace-nowrap shrink-0"
>
<span class="font-mono text-[9px] text-gray-400 tracking-wider">
{String(item.index).padStart(2, '0')}
</span>
{@render item.title({
className: 'font-mono text-[10px] font-bold uppercase tracking-tight leading-[0.95] text-gray-900',
})}
{#if idx < scrollBreadcrumbsStore.items.length - 1}
<div class="flex items-center gap-0.5 opacity-40">
<div class="w-1 h-px bg-gray-400"></div>
<div class="w-1 h-px bg-gray-400"></div>
<div class="w-1 h-px bg-gray-400"></div>
</div>
{/if}
</div>
{/each}
</nav>
<div class="flex items-center gap-2 opacity-50 ml-auto">
<div class="w-px h-2.5 bg-gray-300/60"></div>
<span class="font-mono text-[8px] text-gray-400 tracking-wider">
[{scrollBreadcrumbsStore.items.length}]
</span>
</div>
</div>
</div>
{/if}
<style>
/* Hide scrollbar but keep functionality */
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
</style>

View File

@@ -0,0 +1,3 @@
import BreadcrumbHeader from './BreadcrumbHeader/BreadcrumbHeader.svelte';
export { BreadcrumbHeader };