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