forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
9.4 KiB
1// Shell: the sticky top bar, the signed-in sidebar, the page column and the footer. From 900px
2// the sidebar is always there, and a repo's tabs take it over. Below 900px the sidebar moves
3// into a menu in the top bar, and the repo tabs become a horizontal strip.
4
5// Icons: strokes use currentColor, so they follow the text colour in both themes.
6.ico {
7 flex: none;
8 width: 18px;
9 height: 18px;
10 fill: none;
11 stroke: currentColor;
12 stroke-width: 1.8;
13 stroke-linecap: round;
14 stroke-linejoin: round;
15}
16
17// Top bar
18
19.topbar {
20 position: sticky;
21 top: 0;
22 z-index: 20;
23 display: flex;
24 align-items: center;
25 gap: 12px;
26 box-sizing: border-box;
27 height: var(--topbar-h);
28 min-width: 0;
29 padding: 0 16px;
30 background: var(--bg);
31 border-bottom: 1px solid var(--border);
32}
33
34.wordmark {
35 display: inline-flex;
36 align-items: center;
37 gap: 8px;
38 flex: none;
39 font-size: 17px;
40 font-weight: 700;
41 letter-spacing: -0.02em;
42 color: var(--text);
43 text-decoration: none;
44
45 &:hover {
46 text-decoration: none;
47 }
48}
49
50.wordmark-mark {
51 width: 20px;
52 height: 20px;
53 color: var(--accent);
54}
55
56.topbar .crumbs {
57 display: flex;
58 align-items: center;
59 flex: 0 1 auto;
60 min-width: 0;
61 overflow: hidden;
62 font-size: 14px;
63 color: var(--muted);
64 white-space: nowrap;
65
66 &::before {
67 content: "/";
68 margin-right: 10px;
69 color: var(--border-strong);
70 }
71
72 ol {
73 flex-wrap: nowrap;
74 }
75}
76
77.top-search {
78 position: relative;
79 flex: 0 1 340px;
80 min-width: 0;
81 margin: 0 0 0 auto;
82
83 input {
84 box-sizing: border-box;
85 width: 100%;
86 height: 36px;
87 min-width: 0;
88 margin: 0;
89 padding: 0 12px 0 36px;
90 border: 1px solid var(--border);
91 border-radius: var(--radius);
92 background: var(--surface);
93 color: var(--text);
94 font: inherit;
95 font-size: 14px;
96
97 &::placeholder {
98 color: var(--muted);
99 }
100
101 &:focus {
102 outline: none;
103 border-color: var(--accent);
104 box-shadow: 0 0 0 3px var(--accent-soft);
105 }
106 }
107}
108
109.top-search-icon {
110 position: absolute;
111 top: 10px;
112 left: 11px;
113 width: 16px;
114 height: 16px;
115 color: var(--muted);
116 pointer-events: none;
117}
118
119.topbar .icon-btn {
120 position: relative;
121 display: grid;
122 place-items: center;
123 flex: none;
124 box-sizing: border-box;
125 width: 36px;
126 height: 36px;
127 margin: 0;
128 padding: 0;
129 border: 1px solid var(--border);
130 border-radius: var(--radius);
131 background: var(--surface);
132 color: var(--text-2);
133 font: inherit;
134 list-style: none;
135 cursor: pointer;
136
137 &:hover {
138 background: var(--surface-2);
139 text-decoration: none;
140 }
141
142 &::-webkit-details-marker {
143 display: none;
144 }
145}
146
147.theme-form {
148 display: flex;
149 flex: none;
150 margin: 0;
151}
152
153.topbar .bell {
154 color: var(--text-2);
155}
156
157.topbar .bell-count {
158 position: absolute;
159 top: -6px;
160 right: -6px;
161 min-width: 1.2rem;
162 padding: 0 0.3rem;
163 box-shadow: 0 0 0 2px var(--bg);
164 font-size: 0.6875rem;
165 line-height: 1.2rem;
166}
167
168// The shared button keeps its natural width in columns; in the top bar it sits on the centre line.
169.topbar .button {
170 align-self: center;
171 flex: none;
172}
173
174.topbar-link {
175 flex: none;
176 color: var(--text-2);
177 font-size: 14.5px;
178 font-weight: 500;
179 text-decoration: none;
180
181 &:hover {
182 color: var(--text);
183 text-decoration: none;
184 }
185}
186
187// The avatar menu: the avatar is the summary; the menu list keeps its links from main.scss.
188.topbar .menu {
189 flex: none;
190
191 summary {
192 display: grid;
193 place-items: center;
194 width: 36px;
195 height: 36px;
196 padding: 0;
197 border: 0;
198 border-radius: 50%;
199 }
200
201 summary img {
202 display: block;
203 width: 28px;
204 height: 28px;
205 }
206
207 &[open] summary {
208 box-shadow: 0 0 0 2px var(--accent-soft);
209 }
210}
211
212.topbar .menu-list {
213 top: calc(100% + 8px);
214}
215
216// Phone menu: the sidebar in a panel under the top bar. Hidden from 900px.
217
218.nav-toggle {
219 flex: none;
220}
221
222.nav-panel {
223 position: absolute;
224 top: 100%;
225 left: 12px;
226 z-index: 30;
227 box-sizing: border-box;
228 width: 280px;
229 max-width: calc(100vw - 24px);
230 max-height: calc(100vh - var(--topbar-h));
231 padding: 12px;
232 overflow-y: auto;
233 background: var(--surface);
234 border: 1px solid var(--border);
235 border-radius: var(--radius);
236 box-shadow: var(--shadow);
237}
238
239// Sidebar
240
241.shell {
242 display: grid;
243 grid-template-columns: minmax(0, 1fr);
244 min-height: calc(100vh - var(--topbar-h));
245}
246
247.shell-sidebar {
248 display: none;
249}
250
251.side-links {
252 display: flex;
253 flex-direction: column;
254 gap: 2px;
255}
256
257.side-link {
258 display: flex;
259 align-items: center;
260 gap: 10px;
261 min-height: 36px;
262 padding: 0 12px;
263 border-radius: var(--radius);
264 color: var(--text-2);
265 font-size: 14.5px;
266 font-weight: 500;
267 white-space: nowrap;
268 text-decoration: none;
269
270 &:hover {
271 background: var(--surface-2);
272 text-decoration: none;
273 }
274
275 .ico {
276 color: var(--muted);
277 }
278
279 .count {
280 margin-left: auto;
281 font-size: 12.5px;
282 color: var(--muted);
283 font-variant-numeric: tabular-nums;
284 }
285
286 &.on {
287 background: var(--accent-soft);
288 color: var(--accent-ink);
289 font-weight: 600;
290
291 .ico,
292 .count {
293 color: var(--accent-ink);
294 }
295 }
296}
297
298.side-group {
299 margin-top: 24px;
300}
301
302.side-h {
303 margin: 0 12px 8px;
304 font-size: 12.5px;
305 font-weight: 600;
306 line-height: 1.4;
307 color: var(--muted);
308}
309
310.side-empty {
311 margin: 0 12px;
312 font-size: 13px;
313 color: var(--muted);
314}
315
316.side-repos {
317 display: flex;
318 flex-direction: column;
319 gap: 2px;
320
321 a {
322 display: flex;
323 align-items: center;
324 min-height: 32px;
325 padding: 0 12px;
326 overflow: hidden;
327 border-radius: var(--radius);
328 font-family: var(--mono);
329 font-size: 13.5px;
330 color: var(--text-2);
331 white-space: nowrap;
332 text-decoration: none;
333
334 &:hover {
335 background: var(--surface-2);
336 text-decoration: none;
337 }
338 }
339}
340
341// Page column and footer
342
343.shell > main {
344 box-sizing: border-box;
345 width: 100%;
346 min-width: 0;
347 max-width: 1100px;
348 margin: 0 auto;
349 padding: 24px 16px 48px;
350}
351
352.site-footer {
353 display: flex;
354 flex-wrap: wrap;
355 align-items: center;
356 gap: 12px;
357 box-sizing: border-box;
358 padding: 14px 16px 20px;
359 font-size: 12.5px;
360 color: var(--muted);
361 border-top: 1px solid var(--border);
362}
363
364// Wide screens: the sidebar sits beside the page, and a repo's tabs replace its links.
365
366@media (max-width: 699px) {
367 .topbar .crumbs {
368 display: none;
369 }
370
371 .top-search {
372 flex: 1 1 0;
373 }
374}
375
376// Narrow phones: the search field no longer fits beside the other top bar items, so it becomes an
377// icon that opens the public repository search on the Explore page (the same search, no JS).
378.topbar .top-search-link {
379 display: none;
380}
381
382@media (max-width: 639px) {
383 .top-search {
384 display: none;
385 }
386
387 .topbar .top-search-link {
388 display: grid;
389 margin-left: auto;
390 }
391}
392
393@media (min-width: 700px) {
394 .shell > main {
395 padding: 28px 28px 48px;
396 }
397
398 .site-footer {
399 padding: 14px 28px 20px;
400 }
401}
402
403@media (min-width: 900px) {
404 .nav-toggle {
405 display: none;
406 }
407
408 .shell:has(> .shell-sidebar) {
409 grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
410 }
411
412 .shell-sidebar {
413 position: sticky;
414 top: var(--topbar-h);
415 align-self: start;
416 display: block;
417 box-sizing: border-box;
418 height: calc(100vh - var(--topbar-h));
419 min-width: 0;
420 padding: 16px 12px 24px;
421 overflow-y: auto;
422 background: var(--surface);
423 border-right: 1px solid var(--border);
424 }
425
426 // A repo page: its tabs become the sidebar, fixed under the top bar, and the global links go.
427 .shell:has(.repo-tabs) .shell-sidebar .side-links,
428 .shell:has(.repo-tabs) .shell-sidebar .side-group {
429 display: none;
430 }
431
432 .shell:has(> .shell-sidebar) .repo-tabs {
433 position: fixed;
434 top: var(--topbar-h);
435 bottom: 0;
436 left: 0;
437 z-index: 5;
438 box-sizing: border-box;
439 width: var(--sidebar-w);
440 flex-direction: column;
441 align-items: stretch;
442 gap: 2px;
443 margin: 0;
444 padding: 16px 12px 24px;
445 overflow-x: hidden;
446 overflow-y: auto;
447 background: var(--surface);
448 border-right: 1px solid var(--border);
449 border-bottom: 0;
450 }
451
452 .shell:has(> .shell-sidebar) .repo-tabs .tab {
453 display: flex;
454 align-items: center;
455 gap: 8px;
456 min-height: 36px;
457 padding: 0 12px;
458 border-radius: var(--radius);
459 box-shadow: none;
460 color: var(--text-2);
461 font-size: 14.5px;
462 font-weight: 500;
463
464 &:hover {
465 background: var(--surface-2);
466 color: var(--text);
467 }
468
469 &.active {
470 background: var(--accent-soft);
471 color: var(--accent-ink);
472 font-weight: 600;
473 box-shadow: none;
474 }
475 }
476
477 .shell:has(> .shell-sidebar) .repo-tabs .tab-count {
478 margin-left: auto;
479 padding: 0;
480 background: none;
481 font-size: 12.5px;
482 color: var(--muted);
483 font-variant-numeric: tabular-nums;
484 }
485}
486
487// Soft navigation: a thin progress line along the top while a page is being fetched in place
488// (nav.js sets aria-busy on <html>). Decorative; the page stays usable underneath.
489html[aria-busy="true"] body::before {
490 content: "";
491 position: fixed;
492 top: 0;
493 left: 0;
494 right: 0;
495 height: 2px;
496 z-index: 1000;
497 background: var(--accent);
498 transform-origin: left center;
499 pointer-events: none;
500 animation: soft-nav-progress 1.1s ease-in-out infinite;
501}
502
503@keyframes soft-nav-progress {
504 0% { transform: scaleX(0); }
505 50% { transform: scaleX(0.7); }
506 100% { transform: scaleX(1); opacity: 0; }
507}
508
509@media (prefers-reduced-motion: reduce) {
510 html[aria-busy="true"] body::before {
511 animation: none;
512 transform: scaleX(1);
513 opacity: 0.45;
514 }
515}