Baseline: full tree snapshot after git repository loss

The previous .git directory was found gutted (only info/ remained), so
history is unrecoverable locally. This commit restores version control
with the tree exactly as it stands: the working app, the preview-tracks
launcher on the artist hero, the scrollable-track-previews design bundle
(docs + approved mockups), and the Phase 0 validation artifacts.
This commit is contained in:
KeelrLeek committed 2026-07-22 17:57:00 -05:00
commit a667b4b28e
161 files changed
+53170

No files matched your search

+276
View File
@@ -0,0 +1,276 @@
<script>
import { app, ui, network, playlistStore, refreshPlaylists, toggleSidebar } from "../lib/state.svelte.js";
import * as api from "../lib/api.js";
import Icon from "./Icon.svelte";
function navigate(id) {
ui.view = id;
if (id !== "library") ui.albumId = null;
}
async function newPlaylist() {
const name = `New Playlist ${playlistStore.playlists.length + 1}`;
const id = await api.createPlaylist(name);
await refreshPlaylists();
ui.view = "playlist";
ui.playlistId = id;
}
function openPlaylist(id) {
ui.view = "playlist";
ui.playlistId = id;
}
const items = [
{ id: "home", label: "Home", icon: "home" },
{ id: "search", label: "Search", icon: "search" },
{ id: "library", label: "Your Library", icon: "library" },
{ id: "downloads", label: "Downloads", icon: "download" },
{ id: "settings", label: "Settings", icon: "settings" },
];
</script>
<nav class="sidebar" class:collapsed={ui.sidebarCollapsed}>
<div class="sb-top">
<button
class="collapse-btn"
title={ui.sidebarCollapsed ? "Expand" : "Collapse"}
onclick={toggleSidebar}
>
<Icon name="chevron-left" size={20} />
</button>
</div>
<ul>
{#each items as item (item.id)}
<li>
<button
class="nav-item"
class:active={ui.view === item.id}
title={ui.sidebarCollapsed ? item.label : null}
onclick={() => navigate(item.id)}
>
<span class="icon"><Icon name={item.icon} size={22} /></span>
<span class="label">{item.label}</span>
</button>
</li>
{/each}
</ul>
<div class="playlists">
<div class="pl-head">
<span class="label">Playlists</span>
<button class="pl-new" title="New playlist" onclick={newPlaylist}>
<Icon name="plus" size={16} />
</button>
</div>
<div class="pl-list">
{#each playlistStore.playlists as pl (pl.id)}
<button
class="pl-item"
class:active={ui.view === "playlist" && ui.playlistId === pl.id}
onclick={() => openPlaylist(pl.id)}
>
<span class="pl-name">{pl.name}</span>
<span class="pl-count">{pl.track_count}</span>
</button>
{/each}
</div>
</div>
<div class="footer">
{#if app.connection === "online" && network.shareFiles > 0}
<div class="share-info" title="Files you share with the network">
sharing {network.shareFiles} files
</div>
{/if}
</div>
</nav>
<style>
.sidebar {
grid-area: sidebar;
background: var(--bg-base);
border-radius: var(--radius);
display: flex;
flex-direction: column;
padding: 12px 8px;
overflow: hidden;
}
ul {
list-style: none;
}
.sb-top {
display: flex;
justify-content: flex-end;
padding: 0 4px 6px;
}
.collapse-btn {
width: 30px;
height: 30px;
border-radius: 6px;
display: grid;
place-items: center;
color: var(--text-secondary);
flex: 0 0 auto;
transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), color 0.15s;
}
.collapse-btn:hover {
color: var(--text-primary);
background: var(--bg-highlight);
}
/* Label collapse: fades and zeroes width so icons stay put. */
.label {
white-space: nowrap;
overflow: hidden;
max-width: 160px;
opacity: 1;
transition: opacity 0.15s ease, max-width 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
/* ---- collapsed icon-rail state ---- */
.sidebar.collapsed .sb-top {
justify-content: center;
}
.sidebar.collapsed .collapse-btn {
transform: rotate(180deg);
}
.sidebar.collapsed .label {
opacity: 0;
max-width: 0;
}
.sidebar.collapsed .nav-item {
justify-content: center;
gap: 0;
padding: 10px 0;
}
.sidebar.collapsed .pl-head {
justify-content: center;
padding: 0 0 8px;
}
.sidebar.collapsed .pl-list {
display: none;
}
.sidebar.collapsed .footer {
opacity: 0;
}
.playlists {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--border);
}
.pl-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 12px 8px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-secondary);
}
.pl-new {
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--bg-highlight);
color: var(--text-secondary);
font-size: 16px;
display: grid;
place-items: center;
}
.pl-new:hover {
color: var(--text-primary);
background: var(--bg-press);
}
.pl-list {
overflow-y: auto;
flex: 1;
}
.pl-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
padding: 7px 12px;
border-radius: 5px;
color: var(--text-secondary);
font-size: 13px;
text-align: left;
}
.pl-item:hover {
color: var(--text-primary);
}
.pl-item.active {
background: var(--bg-highlight);
color: var(--text-primary);
}
.pl-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pl-count {
font-size: 11px;
color: var(--text-muted);
flex-shrink: 0;
}
.nav-item {
display: flex;
align-items: center;
gap: 14px;
width: 100%;
padding: 10px 12px;
border-radius: 6px;
color: var(--text-secondary);
font-weight: 600;
transition: color 0.15s;
}
.nav-item:hover {
color: var(--text-primary);
}
.nav-item.active {
color: var(--text-primary);
background: var(--bg-highlight);
}
.icon {
width: 22px;
display: grid;
place-items: center;
}
.footer {
padding: 8px 4px;
}
.share-info {
padding: 2px 8px;
font-size: 11px;
color: var(--text-muted);
}
</style>