Skip to content

Commit 449d2c1

Browse files
Add 10 polish improvements for a more professional feel
1. Scroll to top on tab change — no more stranded mid-scroll 2. Sticky table header — stays visible when scrolling 2600+ models 3. Custom ::selection color — brand-consistent indigo highlight 4. Escape key blurs search — complements ⌘K shortcut 5. Logo is a clickable home link — returns to Models tab 6. Indigo left-border accent on row hover — visual affordance 7. Global focus-visible ring — keyboard accessibility polish 8. Fade transition on tab content — smooth tab switching 9. Title tooltip on truncated model names — full name on hover 10. Animated stat card numbers — count-up from 0 on load Co-authored-by: Ishaan Jaff <ishaan-jaff@users.noreply.github.com>
1 parent 58fdc13 commit 449d2c1

2 files changed

Lines changed: 72 additions & 17 deletions

File tree

src/App.svelte

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
e.preventDefault();
4545
searchInput?.focus();
4646
}
47+
if (e.key === "Escape" && document.activeElement === searchInput) {
48+
searchInput?.blur();
49+
}
4750
}
4851
4952
// Quick start tab state per model
@@ -521,7 +524,7 @@ We also need to update [${RESOURCE_BACKUP_NAME}](https://github.com/${REPO_FULL_
521524
{/if}
522525
</div>
523526
<div class="model-name-group">
524-
<span class="model-title">{getDisplayModelName(name, litellm_provider)}</span>
527+
<span class="model-title" title={getDisplayModelName(name, litellm_provider)}>{getDisplayModelName(name, litellm_provider)}</span>
525528
{#if mode}
526529
<span class="mode-badge">{getModeLabel(mode)}</span>
527530
{/if}
@@ -1092,6 +1095,9 @@ curl http://0.0.0.0:4000/v1/chat/completions \
10921095
background-color: var(--bg-secondary);
10931096
white-space: nowrap;
10941097
user-select: none;
1098+
position: sticky;
1099+
top: 63px;
1100+
z-index: 10;
10951101
}
10961102
10971103
.th-model { padding-left: 1rem; }
@@ -1121,7 +1127,10 @@ curl http://0.0.0.0:4000/v1/chat/completions \
11211127
cursor: pointer;
11221128
}
11231129
1124-
tbody tr.model-row:hover { background-color: var(--hover-bg); }
1130+
tbody tr.model-row:hover {
1131+
background-color: var(--hover-bg);
1132+
box-shadow: inset 3px 0 0 var(--litellm-primary);
1133+
}
11251134
tbody tr.model-row.expanded { background-color: var(--bg-secondary); }
11261135
tbody tr.model-row:last-child { border-bottom: none; }
11271136

src/Main.svelte

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { onMount } from "svelte";
3+
import { fade } from "svelte/transition";
34
import App from "./App.svelte";
45
import Providers from "./Providers.svelte";
56
import Cookbook from "./Cookbook.svelte";
@@ -44,6 +45,7 @@
4445
activeTab = tab;
4546
closeMobileMenu();
4647
trackTabChange(tab);
48+
window.scrollTo({ top: 0, behavior: "smooth" });
4749
4850
if (updateUrl) {
4951
const path = getPathFromTab(tab);
@@ -64,6 +66,23 @@
6466
let modelCount = 0;
6567
let statsLoading = true;
6668
69+
let displayModelCount = 0;
70+
let displayProviderCount = 0;
71+
let displayEndpointCount = 0;
72+
let displayComboCount = 0;
73+
74+
function animateValue(start: number, end: number, duration: number, setter: (v: number) => void) {
75+
const startTime = performance.now();
76+
function step(now: number) {
77+
const elapsed = now - startTime;
78+
const progress = Math.min(elapsed / duration, 1);
79+
const eased = 1 - Math.pow(1 - progress, 3);
80+
setter(Math.round(start + (end - start) * eased));
81+
if (progress < 1) requestAnimationFrame(step);
82+
}
83+
requestAnimationFrame(step);
84+
}
85+
6786
onMount(async () => {
6887
initAnalytics();
6988
trackPageView('Home');
@@ -109,6 +128,11 @@
109128
modelCount = Object.keys(modelsData).length;
110129
111130
statsLoading = false;
131+
132+
animateValue(0, modelCount, 800, (v) => displayModelCount = v);
133+
animateValue(0, providerCount, 600, (v) => displayProviderCount = v);
134+
animateValue(0, endpointCount, 600, (v) => displayEndpointCount = v);
135+
animateValue(0, providerEndpointCount, 700, (v) => displayComboCount = v);
112136
} catch (error) {
113137
console.error("Failed to load statistics:", error);
114138
statsLoading = false;
@@ -127,10 +151,10 @@
127151
<!-- Header -->
128152
<header class="header">
129153
<div class="header-content">
130-
<div class="logo-section-header">
154+
<a href="/" class="logo-section-header" on:click|preventDefault={() => selectTab("models")}>
131155
<span class="logo-emoji">🚅</span>
132156
<span class="logo-text-header">LiteLLM</span>
133-
</div>
157+
</a>
134158

135159
<!-- Desktop Navigation -->
136160
<div class="desktop-nav">
@@ -256,35 +280,39 @@
256280
<div class="stats-section">
257281
<div class="stats-container">
258282
<button class="stat-card" on:click={() => selectTab("models")}>
259-
<div class="stat-value">{modelCount.toLocaleString()}</div>
283+
<div class="stat-value">{displayModelCount.toLocaleString()}</div>
260284
<div class="stat-label">Models Supported</div>
261285
</button>
262286
<button class="stat-card" on:click={() => selectTab("providers")}>
263-
<div class="stat-value">{providerCount}</div>
287+
<div class="stat-value">{displayProviderCount.toLocaleString()}</div>
264288
<div class="stat-label">Providers</div>
265289
</button>
266290
<button class="stat-card" on:click={() => selectTab("providers")}>
267-
<div class="stat-value">{endpointCount}</div>
291+
<div class="stat-value">{displayEndpointCount.toLocaleString()}</div>
268292
<div class="stat-label">Unique Endpoints</div>
269293
</button>
270294
<button class="stat-card" on:click={() => selectTab("providers")}>
271-
<div class="stat-value">{providerEndpointCount}</div>
295+
<div class="stat-value">{displayComboCount.toLocaleString()}</div>
272296
<div class="stat-label">Provider + Endpoint Combos</div>
273297
</button>
274298
</div>
275299
</div>
276300
{/if}
277301

278302
<!-- Content -->
279-
{#if activeTab === "models"}
280-
<App />
281-
{:else if activeTab === "providers"}
282-
<Providers />
283-
{:else if activeTab === "cookbook"}
284-
<Cookbook />
285-
{:else if activeTab === "guardrails"}
286-
<Guardrails />
287-
{/if}
303+
{#key activeTab}
304+
<div in:fade={{ duration: 150, delay: 50 }}>
305+
{#if activeTab === "models"}
306+
<App />
307+
{:else if activeTab === "providers"}
308+
<Providers />
309+
{:else if activeTab === "cookbook"}
310+
<Cookbook />
311+
{:else if activeTab === "guardrails"}
312+
<Guardrails />
313+
{/if}
314+
</div>
315+
{/key}
288316

289317
<!-- Request Form Modal -->
290318
<RequestForm bind:this={requestForm} />
@@ -301,6 +329,22 @@
301329
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
302330
}
303331
332+
:global(::selection) {
333+
background: rgba(99, 102, 241, 0.2);
334+
color: inherit;
335+
}
336+
337+
:global(*:focus-visible) {
338+
outline: 2px solid var(--litellm-primary);
339+
outline-offset: 2px;
340+
}
341+
342+
:global(input:focus-visible),
343+
:global(textarea:focus-visible),
344+
:global(select:focus-visible) {
345+
outline: none;
346+
}
347+
304348
:global(*::-webkit-scrollbar) {
305349
width: 8px;
306350
height: 8px;
@@ -420,6 +464,8 @@
420464
display: flex;
421465
align-items: center;
422466
gap: 0.5rem;
467+
text-decoration: none;
468+
color: inherit;
423469
}
424470
425471
.logo-emoji {

0 commit comments

Comments
 (0)