fix: use black strokes for favicon
Author: Aaron Steven White
Commit
437bc10ad8f929b04699ea0e77d1345dc13db761Parent: e156aac80a
Structural diff unavailable
These commits were pushed via plain git push, so no pre-parsed
schemas are available. Install git-remote-cospan and re-push via panproto:// to
see scope-level changes, breaking change detection, and semantic diffs.
brew install panproto/tap/git-remote-cospan26 files changed +695 -711
@@ -9,7 +9,7 @@
99 href="https://fonts.googleapis.com/css2?family=Inter:wght@300..700&family=JetBrains+Mono:wght@400;500;600&display=swap" 1010 rel="stylesheet" 1111 /> 12- <link rel="icon" type="image/svg+xml" href="/logo-dark.svg" /> 12+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 1313 <title>Cospan</title> 1414 %sveltekit.head% 1515 </head>
@@ -1,5 +1,6 @@
11 <script lang="ts"> 22 import { goto } from '$app/navigation'; 3+ import { page } from '$app/stores'; 34 import { debounce } from '$lib/utils/debounce.js'; 45 import UserMenu from '$lib/components/auth/UserMenu.svelte'; 56 import NotificationBell from '$lib/components/shared/NotificationBell.svelte';
@@ -7,7 +8,15 @@
78 let { user }: { user?: { authenticated: boolean; did: string; handle: string; displayName?: string; avatar?: string } | null } = $props(); 89 910 let searchValue = $state(''); 11+ let mobileSearchValue = $state(''); 1012 let searchInputRef = $state<HTMLInputElement | null>(null); 13+ let mobileMenuOpen = $state(false); 14+ 15+ // Close mobile menu on navigation 16+ $effect(() => { 17+ $page.url.pathname; 18+ mobileMenuOpen = false; 19+ }); 1120 1221 const submitSearch = debounce((q: string) => { 1322 if (q.trim()) {
@@ -21,8 +30,14 @@
2130 } 2231 } 2332 33+ function handleMobileSearchKeydown(event: KeyboardEvent) { 34+ if (event.key === 'Enter' && mobileSearchValue.trim()) { 35+ goto(`/search?q=${encodeURIComponent(mobileSearchValue.trim())}`); 36+ mobileMenuOpen = false; 37+ } 38+ } 39+ 2440 function handleGlobalKeydown(event: KeyboardEvent) { 25- // Do not capture when typing in an input, textarea, or contenteditable 2641 const target = event.target as HTMLElement; 2742 if ( 2843 target.tagName === 'INPUT' ||
@@ -32,11 +47,18 @@
3247 return; 3348 } 3449 35- // "/" to focus search 3650 if (event.key === '/') { 3751 event.preventDefault(); 3852 searchInputRef?.focus(); 3953 } 54+ 55+ if (event.key === 'Escape' && mobileMenuOpen) { 56+ mobileMenuOpen = false; 57+ } 58+ } 59+ 60+ function toggleMobileMenu() { 61+ mobileMenuOpen = !mobileMenuOpen; 4062 } 4163 </script> 4264
@@ -47,9 +69,9 @@
4769 <div class="flex items-center gap-6"> 4870 <a href="/" class="flex items-center gap-2 text-lg font-semibold tracking-tight text-text-primary"> 4971 <img src="/logo-dark.svg" alt="" class="h-6 w-6" /> 50- Cospan 72+ <span class="hidden xs:inline">Cospan</span> 5173 </a> 52- <div class="hidden items-center gap-4 sm:flex"> 74+ <div class="hidden items-center gap-4 md:flex"> 5375 <a href="/" class="text-sm text-text-secondary hover:text-text-primary transition-colors"> 5476 Explore 5577 </a>
@@ -64,8 +86,9 @@
6486 </a> 6587 </div> 6688 </div> 67- <div class="flex items-center gap-4"> 68- <div class="relative hidden sm:block"> 89+ <div class="flex items-center gap-3"> 90+ <!-- Desktop search --> 91+ <div class="relative hidden md:block"> 6992 <svg 7093 class="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-text-secondary" 7194 fill="none"
@@ -93,7 +116,7 @@
93116 {#if user?.authenticated} 94117 <a 95118 href="/new" 96- class="rounded-md border border-border bg-surface-1 p-1.5 text-text-secondary transition-colors hover:border-accent hover:text-text-primary" 119+ class="hidden rounded-md border border-border bg-surface-1 p-1.5 text-text-secondary transition-colors hover:border-accent hover:text-text-primary sm:block" 97120 title="New repository" 98121 > 99122 <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
@@ -101,7 +124,105 @@
101124 </svg> 102125 </a> 103126 {/if} 104- <UserMenu {user} /> 127+ <div class="hidden sm:block"> 128+ <UserMenu {user} /> 129+ </div> 130+ 131+ <!-- Mobile hamburger --> 132+ <button 133+ onclick={toggleMobileMenu} 134+ class="rounded-md p-1.5 text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary md:hidden" 135+ aria-label="Toggle menu" 136+ > 137+ {#if mobileMenuOpen} 138+ <svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 139+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /> 140+ </svg> 141+ {:else} 142+ <svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 143+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /> 144+ </svg> 145+ {/if} 146+ </button> 105147 </div> 106148 </nav> 149+ 150+ <!-- Mobile menu --> 151+ {#if mobileMenuOpen} 152+ <div class="border-t border-border bg-surface-1 px-4 py-3 md:hidden"> 153+ <!-- Mobile search --> 154+ <div class="mb-3"> 155+ <div class="relative"> 156+ <svg 157+ class="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-text-secondary" 158+ fill="none" 159+ viewBox="0 0 24 24" 160+ stroke="currentColor" 161+ stroke-width="2" 162+ > 163+ <path 164+ stroke-linecap="round" 165+ stroke-linejoin="round" 166+ d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" 167+ /> 168+ </svg> 169+ <input 170+ type="text" 171+ bind:value={mobileSearchValue} 172+ onkeydown={handleMobileSearchKeydown} 173+ placeholder="Search repositories..." 174+ class="w-full rounded-md border border-border bg-surface-0 py-2 pl-8 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:border-accent focus:outline-none" 175+ /> 176+ </div> 177+ </div> 178+ 179+ <!-- Mobile nav links --> 180+ <div class="space-y-1"> 181+ <a href="/" class="block rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary"> 182+ Explore 183+ </a> 184+ <a href="/feed" class="block rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary"> 185+ Feed 186+ </a> 187+ <a href="/orgs" class="block rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary"> 188+ Orgs 189+ </a> 190+ <a href="/search" class="block rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary"> 191+ Search 192+ </a> 193+ </div> 194+ 195+ {#if user?.authenticated} 196+ <div class="mt-3 border-t border-border pt-3 space-y-1"> 197+ <a 198+ href="/new" 199+ class="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary" 200+ > 201+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 202+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> 203+ </svg> 204+ New repository 205+ </a> 206+ <a 207+ href="/{user.did}" 208+ class="block rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary" 209+ > 210+ Your profile 211+ </a> 212+ <a 213+ href="/settings" 214+ class="block rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary" 215+ > 216+ Settings 217+ </a> 218+ </div> 219+ {:else} 220+ <div class="mt-3 border-t border-border pt-3"> 221+ <div class="sm:hidden"> 222+ <UserMenu {user} /> 223+ </div> 224+ </div> 225+ {/if} 226+ </div> 227+ {/if} 107228 </header>
@@ -0,0 +1,45 @@
1+<script lang="ts"> 2+ let { 3+ basePath, 4+ activeTab = 'code', 5+ openIssueCount = 0, 6+ openMrCount = 0, 7+ isOwner = false 8+ }: { 9+ basePath: string; 10+ activeTab?: string; 11+ openIssueCount?: number; 12+ openMrCount?: number; 13+ isOwner?: boolean; 14+ } = $props(); 15+ 16+ let tabs = $derived([ 17+ { id: 'code', label: 'Code', href: basePath, badge: 0 }, 18+ { id: 'issues', label: 'Issues', href: `${basePath}/issues`, badge: openIssueCount }, 19+ { id: 'pulls', label: 'Merge Requests', href: `${basePath}/pulls`, badge: openMrCount }, 20+ { id: 'branches', label: 'Branches', href: `${basePath}/branches`, badge: 0 }, 21+ { id: 'tags', label: 'Tags', href: `${basePath}/tags`, badge: 0 }, 22+ { id: 'releases', label: 'Releases', href: `${basePath}/releases`, badge: 0 }, 23+ { id: 'compare', label: 'Compare', href: `${basePath}/compare`, badge: 0 }, 24+ ...(isOwner ? [{ id: 'settings', label: 'Settings', href: `${basePath}/settings`, badge: 0 }] : []), 25+ ]); 26+</script> 27+ 28+<div class="mb-6 flex items-center gap-1 overflow-x-auto border-b border-border scrollbar-none"> 29+ {#each tabs as tab (tab.id)} 30+ <a 31+ href={tab.href} 32+ class="whitespace-nowrap border-b-2 px-4 py-2 text-sm font-medium transition-colors 33+ {activeTab === tab.id 34+ ? 'border-accent text-text-primary' 35+ : 'border-transparent text-text-secondary hover:text-text-primary'}" 36+ > 37+ {tab.label} 38+ {#if tab.badge > 0} 39+ <span class="ml-1 rounded-full bg-surface-2 px-1.5 py-0.5 text-xs"> 40+ {tab.badge} 41+ </span> 42+ {/if} 43+ </a> 44+ {/each} 45+</div>
@@ -0,0 +1,15 @@
1+<script lang="ts"> 2+ let { href, label = 'Back to repository' }: { href: string; label?: string } = $props(); 3+</script> 4+ 5+<div class="mt-6"> 6+ <a 7+ {href} 8+ class="inline-flex items-center gap-1.5 text-sm text-accent transition-colors hover:text-accent-hover" 9+ > 10+ <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 11+ <path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" /> 12+ </svg> 13+ {label} 14+ </a> 15+</div>
@@ -0,0 +1,55 @@
1+<script lang="ts"> 2+ import type { Snippet } from 'svelte'; 3+ 4+ let { 5+ message, 6+ description, 7+ icon = 'inbox', 8+ ctaHref, 9+ ctaLabel, 10+ children 11+ }: { 12+ message: string; 13+ description?: string; 14+ icon?: 'inbox' | 'search' | 'users' | 'star' | 'tag' | 'branch' | 'merge' | 'bell' | 'folder' | 'release'; 15+ ctaHref?: string; 16+ ctaLabel?: string; 17+ children?: Snippet; 18+ } = $props(); 19+ 20+ const icons: Record<string, string> = { 21+ inbox: 'M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4', 22+ search: 'M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z', 23+ users: 'M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z', 24+ star: 'M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z', 25+ tag: 'M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z M6 6h.008v.008H6V6z', 26+ branch: 'M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6z', 27+ merge: 'M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5', 28+ bell: 'M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0', 29+ folder: 'M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z', 30+ release: 'M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z M6 6h.008v.008H6V6z', 31+ }; 32+ 33+ let iconPath = $derived(icons[icon] ?? icons.inbox); 34+</script> 35+ 36+<div class="flex flex-col items-center gap-3 py-12 text-text-secondary"> 37+ <svg class="h-12 w-12" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"> 38+ <path stroke-linecap="round" stroke-linejoin="round" d={iconPath} /> 39+ </svg> 40+ <p class="text-sm">{message}</p> 41+ {#if description} 42+ <p class="text-xs">{description}</p> 43+ {/if} 44+ {#if ctaHref && ctaLabel} 45+ <a 46+ href={ctaHref} 47+ class="mt-1 rounded-md bg-accent px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-hover" 48+ > 49+ {ctaLabel} 50+ </a> 51+ {/if} 52+ {#if children} 53+ {@render children()} 54+ {/if} 55+</div>