fix: use black strokes for favicon

Author: Aaron Steven White
Commit 437bc10ad8f929b04699ea0e77d1345dc13db761
Parent: 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-cospan
26 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>
cospan · schematic version control on atproto built on AT Protocol