all repos — weimar @ master

Unnamed repository; edit this file 'description' to name the repository.

web/src/routes/+layout.svelte (view raw)

  1<script lang="ts">
  2	import { onMount } from 'svelte';
  3	import { page } from '$app/state';
  4	import { logout, getMe, getConfig, type CurrentUser, type SiteConfig } from '$lib/api';
  5	import Avatar from '$lib/Avatar.svelte';
  6
  7	let { children } = $props();
  8
  9	let user = $state<CurrentUser | null>(null);
 10	let checking = $state(true);
 11	let siteConfig = $state<SiteConfig | null>(null);
 12	let baseTitle = $state('weimar');
 13
 14	onMount(() => {
 15		getConfig()
 16			.then((c) => {
 17				siteConfig = c;
 18				baseTitle = c.site_title;
 19			})
 20			.catch(() => {});
 21		getMe()
 22			.then((u) => { user = u; })
 23			.catch(() => {})
 24			.finally(() => { checking = false; });
 25	});
 26
 27	$effect(() => {
 28		const path = page.url.pathname;
 29		const base = baseTitle;
 30		switch (path) {
 31			case '/browse':
 32				document.title = base + ' - Browse';
 33				break;
 34			case '/upload':
 35				document.title = base + ' - Upload';
 36				break;
 37			case '/settings':
 38				document.title = base + ' - Settings';
 39				break;
 40			case '/login':
 41				document.title = base + ' - Login';
 42				break;
 43			default:
 44				if (path.startsWith('/image/')) {
 45					document.title = base + ' - Image #' + path.split('/')[2];
 46				} else {
 47					document.title = base;
 48				}
 49		}
 50	});
 51
 52	async function handleLogout() {
 53		await logout();
 54		user = null;
 55		window.location.href = '/login';
 56	}
 57</script>
 58
 59<nav>
 60	<div class="nav-brand">
 61		<a href="/" class="nav-logo">
 62			{#if siteConfig?.logo_url}
 63				<img src={siteConfig.logo_url} alt={siteConfig.site_title} class="nav-logo-img" />
 64			{:else}
 65				{siteConfig?.site_title ?? 'weimar'}
 66			{/if}
 67		</a>
 68		<span class="nav-divider"></span>
 69	</div>
 70	<div class="nav-links">
 71		<a href="/browse">BROWSE</a>
 72		<a href="/upload">UPLOAD</a>
 73		{#if !checking}
 74			{#if user}
 75				<a href="/settings" class="nav-avatar-link">
 76					<Avatar src={user.avatar_url} username={user.username} size={28} />
 77				</a>
 78				<button class="link logout" onclick={handleLogout}>LOGOUT</button>
 79			{:else}
 80				<a href="/login">LOGIN</a>
 81			{/if}
 82		{/if}
 83	</div>
 84</nav>
 85
 86<div class="main">
 87	{@render children()}
 88</div>
 89
 90<style>
 91	:global(*) {
 92		margin: 0;
 93		padding: 0;
 94		box-sizing: border-box;
 95	}
 96
 97	:global(body) {
 98		font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
 99		background: #f5f5f5;
100		color: #1a1a1a;
101		-webkit-font-smoothing: antialiased;
102	}
103
104	:global(h1) {
105		font-family: 'Oswald', sans-serif;
106		font-weight: 700;
107		font-size: 2.5rem;
108		text-transform: uppercase;
109		letter-spacing: 0.03em;
110		line-height: 1.1;
111		color: #1a1a1a;
112	}
113
114	:global(h2) {
115		font-family: 'Oswald', sans-serif;
116		font-weight: 600;
117		font-size: 1.5rem;
118		text-transform: uppercase;
119		letter-spacing: 0.05em;
120	}
121
122	:global(a) {
123		color: #c62828;
124		text-decoration: none;
125		font-weight: 600;
126	}
127
128	:global(a:hover) {
129		text-decoration: underline;
130	}
131
132	nav {
133		display: flex;
134		align-items: center;
135		justify-content: space-between;
136		padding: 0 1.5rem;
137		height: 3.5rem;
138		background: #1a1a1a;
139		border-bottom: 3px solid #c62828;
140	}
141
142	.nav-brand {
143		display: flex;
144		align-items: center;
145		gap: 1rem;
146	}
147
148	.nav-logo {
149		font-family: 'Oswald', sans-serif;
150		font-weight: 700;
151		font-size: 1.4rem;
152		letter-spacing: 0.15em;
153		color: #fff;
154		text-decoration: none;
155	}
156	.nav-logo:hover {
157		text-decoration: none;
158		color: #ef5350;
159	}
160	.nav-logo-img {
161		display: block;
162		height: 1.8rem;
163		width: auto;
164	}
165
166	.nav-divider {
167		display: block;
168		width: 1px;
169		height: 1.6rem;
170		background: rgba(255,255,255,0.2);
171	}
172
173	.nav-links {
174		display: flex;
175		align-items: center;
176		gap: 0;
177		height: 100%;
178	}
179
180	.nav-links a, .logout {
181		display: flex;
182		align-items: center;
183		height: 100%;
184		padding: 0 1.25rem;
185		font-family: 'Oswald', sans-serif;
186		font-weight: 500;
187		font-size: 0.85rem;
188		letter-spacing: 0.12em;
189		color: rgba(255,255,255,0.8);
190		text-decoration: none;
191		text-transform: uppercase;
192		transition: background 0.15s, color 0.15s;
193		position: relative;
194	}
195
196	.nav-links a:hover, .logout:hover {
197		background: #c62828;
198		color: #fff;
199		text-decoration: none;
200	}
201
202	.nav-links a::after {
203		content: '';
204		position: absolute;
205		bottom: 0;
206		left: 0;
207		right: 0;
208		height: 0;
209		background: #c62828;
210		transition: height 0.15s;
211	}
212
213	.nav-links a:hover::after {
214		height: 3px;
215	}
216
217	.nav-avatar-link {
218		padding: 0 0.75rem !important;
219	}
220	.nav-avatar-link::after {
221		display: none !important;
222	}
223	.nav-avatar-link:hover {
224		background: none !important;
225	}
226
227	.logout {
228		background: none;
229		border: none;
230		cursor: pointer;
231		font-size: 0.85rem;
232	}
233
234	.main {
235		max-width: 1400px;
236		margin: 0 auto;
237		padding: 1.5rem;
238	}
239</style>