Header/Navigation-Styling ueberarbeitet
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+63
-25
@@ -10,12 +10,37 @@
|
|||||||
/* ============================================
|
/* ============================================
|
||||||
Top navigation (views/partials/header.ejs)
|
Top navigation (views/partials/header.ejs)
|
||||||
============================================
|
============================================
|
||||||
The bar keeps its deep navy in both themes: it frames the light content area
|
The bar carries no surface of its own — it takes the page background and is
|
||||||
like the chrome of a tool, and it is the one surface that never changes as you
|
held apart from the content only by a hairline (and, once scrolled, the
|
||||||
move through the app. Everything in it is quiet — slate on navy — except the
|
faintest shadow). Everything in it is quiet; the single accent is the active
|
||||||
active item, which is the single accent: a thin, glowing sky rail. That rail is
|
item, marked by a thin rail on the bottom edge. That rail is the whole point,
|
||||||
the whole point, since the previous header had no active state at all.
|
since the header used to have no active state at all.
|
||||||
The dropdown panels follow the theme (white / slate) so they read as content. */
|
|
||||||
|
--nav-bg mirrors the page background per theme. It is what the focus rings and
|
||||||
|
the badge ring cut themselves out against — without it they would need a
|
||||||
|
hard-coded colour and would break in one of the two themes. */
|
||||||
|
:root { --nav-bg: #f9fafb; } /* gray-50, matches body */
|
||||||
|
.dark { --nav-bg: #111827; } /* gray-900, matches body */
|
||||||
|
|
||||||
|
.nav-brand:focus-visible,
|
||||||
|
.nav-item:focus-visible,
|
||||||
|
.icon-btn:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px var(--nav-bg), 0 0 0 4px rgba(37, 99, 235, 0.6);
|
||||||
|
}
|
||||||
|
.dark .nav-brand:focus-visible,
|
||||||
|
.dark .nav-item:focus-visible,
|
||||||
|
.dark .icon-btn:focus-visible {
|
||||||
|
box-shadow: 0 0 0 2px var(--nav-bg), 0 0 0 4px rgba(56, 189, 248, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hairline separation only once content actually slides underneath. */
|
||||||
|
#siteHeader.is-scrolled {
|
||||||
|
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06), 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||||
|
}
|
||||||
|
.dark #siteHeader.is-scrolled {
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -27,22 +52,23 @@
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
color: rgb(148 163 184); /* slate-400 */
|
color: rgb(71 85 105); /* slate-600 */
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
transition: color 150ms ease, background-color 150ms ease;
|
transition: color 150ms ease, background-color 150ms ease;
|
||||||
}
|
}
|
||||||
button.nav-item, .icon-btn { cursor: pointer; }
|
button.nav-item, .icon-btn { cursor: pointer; }
|
||||||
.nav-item:hover {
|
.nav-item:hover {
|
||||||
|
color: rgb(15 23 42); /* slate-900 */
|
||||||
|
background-color: rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
.nav-item.is-active { color: rgb(15 23 42); }
|
||||||
|
|
||||||
|
.dark .nav-item { color: rgb(148 163 184); } /* slate-400 */
|
||||||
|
.dark .nav-item:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: rgba(255, 255, 255, 0.06);
|
background-color: rgba(255, 255, 255, 0.06);
|
||||||
}
|
}
|
||||||
.nav-item.is-active {
|
.dark .nav-item.is-active { color: #fff; }
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.nav-item:focus-visible {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 0 2px #0b1220, 0 0 0 4px rgba(56, 189, 248, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The rail: sits on the header's bottom edge, lights up for the active section
|
/* The rail: sits on the header's bottom edge, lights up for the active section
|
||||||
and previews faintly on hover. */
|
and previews faintly on hover. */
|
||||||
@@ -58,14 +84,18 @@ button.nav-item, .icon-btn { cursor: pointer; }
|
|||||||
transition: transform 200ms ease, background-color 200ms ease;
|
transition: transform 200ms ease, background-color 200ms ease;
|
||||||
}
|
}
|
||||||
.nav-item:hover .nav-rail {
|
.nav-item:hover .nav-rail {
|
||||||
background-color: rgba(148, 163, 184, 0.45);
|
background-color: rgba(100, 116, 139, 0.35);
|
||||||
transform: scaleX(1);
|
transform: scaleX(1);
|
||||||
}
|
}
|
||||||
.nav-item.is-active .nav-rail {
|
.nav-item.is-active .nav-rail {
|
||||||
background-color: rgb(56 189 248); /* sky-400 */
|
background-color: rgb(37 99 235); /* blue-600, the app's action colour */
|
||||||
box-shadow: 0 0 12px rgba(56, 189, 248, 0.8);
|
|
||||||
transform: scaleX(1);
|
transform: scaleX(1);
|
||||||
}
|
}
|
||||||
|
.dark .nav-item:hover .nav-rail { background-color: rgba(148, 163, 184, 0.4); }
|
||||||
|
.dark .nav-item.is-active .nav-rail {
|
||||||
|
background-color: rgb(56 189 248); /* sky-400 reads better on the dark page */
|
||||||
|
box-shadow: 0 0 10px rgba(56, 189, 248, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
/* Counters shown inline next to a nav label.
|
/* Counters shown inline next to a nav label.
|
||||||
`:not(.hidden)` is deliberate: a bare `display: inline-flex` would fight
|
`:not(.hidden)` is deliberate: a bare `display: inline-flex` would fight
|
||||||
@@ -97,17 +127,18 @@ button.nav-item, .icon-btn { cursor: pointer; }
|
|||||||
width: 2.25rem;
|
width: 2.25rem;
|
||||||
height: 2.25rem;
|
height: 2.25rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
color: rgb(148 163 184);
|
color: rgb(71 85 105); /* slate-600 */
|
||||||
transition: color 150ms ease, background-color 150ms ease;
|
transition: color 150ms ease, background-color 150ms ease;
|
||||||
}
|
}
|
||||||
.icon-btn:hover {
|
.icon-btn:hover {
|
||||||
|
color: rgb(15 23 42);
|
||||||
|
background-color: rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
.dark .icon-btn { color: rgb(148 163 184); }
|
||||||
|
.dark .icon-btn:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: rgba(255, 255, 255, 0.08);
|
background-color: rgba(255, 255, 255, 0.08);
|
||||||
}
|
}
|
||||||
.icon-btn:focus-visible {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 0 2px #0b1220, 0 0 0 4px rgba(56, 189, 248, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Count badge floating on an icon button. Same `:not(.hidden)` reason as above. */
|
/* Count badge floating on an icon button. Same `:not(.hidden)` reason as above. */
|
||||||
.dot-badge:not(.hidden) {
|
.dot-badge:not(.hidden) {
|
||||||
@@ -127,7 +158,7 @@ button.nav-item, .icon-btn { cursor: pointer; }
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
box-shadow: 0 0 0 2px #0b1220;
|
box-shadow: 0 0 0 2px var(--nav-bg); /* cut out against the bar in both themes */
|
||||||
}
|
}
|
||||||
.dot-badge--red { background-color: rgb(244 63 94); }
|
.dot-badge--red { background-color: rgb(244 63 94); }
|
||||||
|
|
||||||
@@ -216,11 +247,18 @@ nav .dd-panel { right: auto; left: 0; }
|
|||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
font-size: 0.9375rem;
|
font-size: 0.9375rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: rgb(148 163 184);
|
color: rgb(71 85 105); /* slate-600 */
|
||||||
transition: background-color 150ms ease, color 150ms ease;
|
transition: background-color 150ms ease, color 150ms ease;
|
||||||
}
|
}
|
||||||
.m-item:hover { background-color: rgba(255, 255, 255, 0.06); color: #fff; }
|
.m-item:hover { background-color: rgba(15, 23, 42, 0.05); color: rgb(15 23 42); }
|
||||||
.m-item.is-active {
|
.m-item.is-active {
|
||||||
|
color: rgb(29 78 216); /* blue-700 */
|
||||||
|
background-color: rgb(239 246 255); /* blue-50 */
|
||||||
|
box-shadow: inset 2px 0 0 rgb(37 99 235);
|
||||||
|
}
|
||||||
|
.dark .m-item { color: rgb(148 163 184); }
|
||||||
|
.dark .m-item:hover { background-color: rgba(255, 255, 255, 0.06); color: #fff; }
|
||||||
|
.dark .m-item.is-active {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: rgba(56, 189, 248, 0.12);
|
background-color: rgba(56, 189, 248, 0.12);
|
||||||
box-shadow: inset 2px 0 0 rgb(56 189 248);
|
box-shadow: inset 2px 0 0 rgb(56 189 248);
|
||||||
|
|||||||
+27
-24
@@ -24,30 +24,31 @@
|
|||||||
var angemeldet = (typeof user !== 'undefined' && user);
|
var angemeldet = (typeof user !== 'undefined' && user);
|
||||||
var initiale = angemeldet ? String(user.username || '?').charAt(0).toUpperCase() : '?';
|
var initiale = angemeldet ? String(user.username || '?').charAt(0).toUpperCase() : '?';
|
||||||
%>
|
%>
|
||||||
|
<!-- The bar carries no surface of its own: it takes the page background, so it
|
||||||
|
reads as part of the page rather than as a lid on top of it. Held apart from
|
||||||
|
the content only by a hairline and, once scrolled, the faintest shadow.
|
||||||
|
Slightly translucent + blurred so content passing underneath stays quiet. -->
|
||||||
<header id="siteHeader"
|
<header id="siteHeader"
|
||||||
class="sticky top-0 z-50 bg-[#0b1220] text-slate-300 border-b border-white/5 transition-shadow duration-300">
|
class="sticky top-0 z-50 border-b border-gray-200/80 bg-gray-50/85 backdrop-blur-md transition-shadow duration-300 dark:border-white/5 dark:bg-gray-900/85">
|
||||||
<!-- A single cool glow anchored at the brand, instead of the old full-width
|
<div class="container mx-auto px-4">
|
||||||
left-to-right gradient sweep. Gives the bar depth without shouting. -->
|
|
||||||
<div class="pointer-events-none absolute inset-0 bg-[radial-gradient(900px_circle_at_0%_-40%,rgba(56,189,248,0.16),transparent_60%)]"></div>
|
|
||||||
|
|
||||||
<div class="relative container mx-auto px-4">
|
|
||||||
<div class="flex h-16 items-center gap-3">
|
<div class="flex h-16 items-center gap-3">
|
||||||
|
|
||||||
<!-- Brand -->
|
<!-- Brand -->
|
||||||
<a href="/" class="group flex shrink-0 items-center gap-2.5 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-400/70 focus-visible:ring-offset-2 focus-visible:ring-offset-[#0b1220]">
|
<a href="/" class="nav-brand group flex shrink-0 items-center gap-2.5 rounded-lg">
|
||||||
<span class="grid h-9 w-9 place-items-center rounded-xl bg-gradient-to-br from-sky-400 to-blue-600 shadow-lg shadow-blue-900/40 ring-1 ring-white/10">
|
<span class="grid h-9 w-9 place-items-center rounded-xl bg-gradient-to-br from-sky-400 to-blue-600 shadow-sm shadow-blue-600/20 ring-1 ring-black/5">
|
||||||
<svg class="h-5 w-5 text-white" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
<svg class="h-5 w-5 text-white" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="truncate text-[15px] font-semibold tracking-tight text-white">
|
<span class="truncate text-[15px] font-semibold tracking-tight text-gray-900 dark:text-white">
|
||||||
Bewerbungs-Tracker
|
Bewerbungs-Tracker
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Primary navigation (desktop). Order follows the actual workflow:
|
<!-- Primary navigation (desktop), right-aligned next to the account
|
||||||
finden -> bewerben -> antworten. -->
|
cluster. Order follows the actual workflow: finden -> bewerben ->
|
||||||
<nav class="ml-4 hidden items-center gap-1 lg:flex" aria-label="Hauptnavigation">
|
antworten. -->
|
||||||
|
<nav class="ml-auto hidden items-center gap-1 lg:flex" aria-label="Hauptnavigation">
|
||||||
|
|
||||||
<a href="/" class="nav-item <%= istBewerbungen ? 'is-active' : '' %>">
|
<a href="/" class="nav-item <%= istBewerbungen ? 'is-active' : '' %>">
|
||||||
<span>Bewerbungen</span>
|
<span>Bewerbungen</span>
|
||||||
@@ -114,8 +115,10 @@
|
|||||||
</a>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Right cluster: notifications + account -->
|
<!-- Right cluster: notifications + account. Below lg the nav is hidden,
|
||||||
<div class="ml-auto flex items-center gap-1.5">
|
so `ml-auto` keeps this pinned right; from lg the nav already claims
|
||||||
|
the free space and the cluster just follows it. -->
|
||||||
|
<div class="ml-auto flex items-center gap-1.5 lg:ml-3">
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -143,8 +146,8 @@
|
|||||||
<!-- Account -->
|
<!-- Account -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<button type="button" data-dd="ddUser" aria-expanded="false" aria-haspopup="true"
|
<button type="button" data-dd="ddUser" aria-expanded="false" aria-haspopup="true"
|
||||||
class="flex items-center gap-2 rounded-lg py-1.5 pl-1.5 pr-2 text-sm text-slate-300 transition-colors hover:bg-white/10 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-400/70 focus-visible:ring-offset-2 focus-visible:ring-offset-[#0b1220]">
|
class="nav-item !gap-2 !py-1.5 !pl-1.5 !pr-2">
|
||||||
<span class="grid h-7 w-7 place-items-center rounded-full bg-white/10 text-[11px] font-semibold text-white ring-1 ring-white/15">
|
<span class="grid h-7 w-7 place-items-center rounded-full bg-blue-600/10 text-[11px] font-semibold text-blue-700 ring-1 ring-blue-600/20 dark:bg-white/10 dark:text-white dark:ring-white/15">
|
||||||
<%= initiale %>
|
<%= initiale %>
|
||||||
</span>
|
</span>
|
||||||
<span class="hidden max-w-[10rem] truncate font-medium md:inline"><%= user.username %></span>
|
<span class="hidden max-w-[10rem] truncate font-medium md:inline"><%= user.username %></span>
|
||||||
@@ -205,7 +208,7 @@
|
|||||||
|
|
||||||
<!-- Mobile navigation: the same structure, spelled out. The old header just
|
<!-- Mobile navigation: the same structure, spelled out. The old header just
|
||||||
dropped the labels and left seven unlabelled icons. -->
|
dropped the labels and left seven unlabelled icons. -->
|
||||||
<div id="mobileNav" class="hidden border-t border-white/5 bg-[#0b1220] lg:hidden">
|
<div id="mobileNav" class="hidden border-t border-gray-200/80 bg-gray-50 lg:hidden dark:border-white/5 dark:bg-gray-900">
|
||||||
<nav class="container mx-auto space-y-4 px-4 py-4" aria-label="Hauptnavigation (mobil)">
|
<nav class="container mx-auto space-y-4 px-4 py-4" aria-label="Hauptnavigation (mobil)">
|
||||||
<div class="space-y-0.5">
|
<div class="space-y-0.5">
|
||||||
<a href="/" class="m-item <%= istBewerbungen ? 'is-active' : '' %>">Bewerbungen</a>
|
<a href="/" class="m-item <%= istBewerbungen ? 'is-active' : '' %>">Bewerbungen</a>
|
||||||
@@ -230,15 +233,15 @@
|
|||||||
<% if (angemeldet) { %>
|
<% if (angemeldet) { %>
|
||||||
<div class="space-y-0.5">
|
<div class="space-y-0.5">
|
||||||
<p class="m-label">Konto</p>
|
<p class="m-label">Konto</p>
|
||||||
<p class="px-3 pb-1 text-sm text-white">
|
<p class="px-3 pb-1 text-sm text-gray-900 dark:text-white">
|
||||||
<%= user.username %>
|
<%= user.username %>
|
||||||
<span class="text-slate-500">· <%= user.is_admin ? 'Administrator' : 'Benutzer' %></span>
|
<span class="text-gray-500 dark:text-slate-500">· <%= user.is_admin ? 'Administrator' : 'Benutzer' %></span>
|
||||||
</p>
|
</p>
|
||||||
<a href="/einstellungen" class="m-item <%= istEinstellungen ? 'is-active' : '' %>">Einstellungen</a>
|
<a href="/einstellungen" class="m-item <%= istEinstellungen ? 'is-active' : '' %>">Einstellungen</a>
|
||||||
<% if (user.is_admin) { %>
|
<% if (user.is_admin) { %>
|
||||||
<a href="/admin" class="m-item <%= istAdmin ? 'is-active' : '' %>">Benutzer verwalten</a>
|
<a href="/admin" class="m-item <%= istAdmin ? 'is-active' : '' %>">Benutzer verwalten</a>
|
||||||
<% } %>
|
<% } %>
|
||||||
<a href="/logout" class="m-item text-rose-300 hover:bg-rose-500/10 hover:text-rose-200">Abmelden</a>
|
<a href="/logout" class="m-item !text-rose-600 hover:!bg-rose-50 dark:!text-rose-300 dark:hover:!bg-rose-500/10">Abmelden</a>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -308,13 +311,13 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subtle lift once the page is scrolled — the bar reads as floating
|
// The bar has no surface of its own, so once content starts sliding
|
||||||
// above the content rather than welded to it.
|
// underneath it needs the faintest separation — a hairline shadow, not
|
||||||
|
// a drop shadow.
|
||||||
var head = document.getElementById('siteHeader');
|
var head = document.getElementById('siteHeader');
|
||||||
function onScroll() {
|
function onScroll() {
|
||||||
if (!head) return;
|
if (!head) return;
|
||||||
head.classList.toggle('shadow-xl', window.scrollY > 4);
|
head.classList.toggle('is-scrolled', window.scrollY > 4);
|
||||||
head.classList.toggle('shadow-black/30', window.scrollY > 4);
|
|
||||||
}
|
}
|
||||||
window.addEventListener('scroll', onScroll, { passive: true });
|
window.addEventListener('scroll', onScroll, { passive: true });
|
||||||
onScroll();
|
onScroll();
|
||||||
|
|||||||
Reference in New Issue
Block a user