/* ============================================ NextJobs - Custom Styles ============================================ */ /* Base styles */ * { box-sizing: border-box; } /* ============================================ Top navigation (views/partials/header.ejs) ============================================ The bar carries no surface of its own — it takes the page background and is held apart from the content only by a hairline (and, once scrolled, the faintest shadow). Everything in it is quiet; the single accent is the active item, marked by a thin rail on the bottom edge. That rail is the whole point, since the header used to have no active state at all. --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 { position: relative; display: flex; align-items: center; gap: 0.375rem; padding: 0.5rem 0.75rem; border-radius: 0.5rem; font-size: 0.875rem; font-weight: 500; letter-spacing: -0.01em; color: rgb(71 85 105); /* slate-600 */ white-space: nowrap; transition: color 150ms ease, background-color 150ms ease; } button.nav-item, .icon-btn { cursor: pointer; } .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; background-color: rgba(255, 255, 255, 0.06); } .dark .nav-item.is-active { color: #fff; } /* The rail: sits on the header's bottom edge, lights up for the active section and previews faintly on hover. */ .nav-rail { position: absolute; left: 0.75rem; right: 0.75rem; bottom: -1px; height: 2px; border-radius: 9999px; background-color: transparent; transform: scaleX(0); transition: transform 200ms ease, background-color 200ms ease; } .nav-item:hover .nav-rail { background-color: rgba(100, 116, 139, 0.35); transform: scaleX(1); } .nav-item.is-active .nav-rail { background-color: rgb(37 99 235); /* blue-600, the app's action colour */ 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. `:not(.hidden)` is deliberate: a bare `display: inline-flex` would fight Tailwind's `.hidden` (same specificity, load order decides) and the badge would show even at count 0. This way "hidden" always wins. */ .nav-badge:not(.hidden) { display: inline-flex; } .nav-badge { align-items: center; justify-content: center; min-width: 1.125rem; height: 1.125rem; padding: 0 0.3rem; border-radius: 9999px; font-size: 0.625rem; font-weight: 700; line-height: 1; color: #fff; } .nav-badge--red { background-color: rgb(244 63 94); } /* rose-500 */ .nav-badge--green { background-color: rgb(16 185 129); } /* emerald-500 */ /* Icon-only controls in the bar (bell, burger). */ .icon-btn { position: relative; display: grid; place-items: center; width: 2.25rem; height: 2.25rem; border-radius: 0.5rem; color: rgb(71 85 105); /* slate-600 */ transition: color 150ms ease, background-color 150ms ease; } .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; background-color: rgba(255, 255, 255, 0.08); } /* Count badge floating on an icon button. Same `:not(.hidden)` reason as above. */ .dot-badge:not(.hidden) { display: inline-flex; } .dot-badge { position: absolute; top: -0.125rem; right: -0.125rem; align-items: center; justify-content: center; min-width: 1.125rem; height: 1.125rem; padding: 0 0.25rem; border-radius: 9999px; font-size: 0.625rem; font-weight: 700; line-height: 1; color: #fff; 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); } /* Dropdown panels. */ .dd-panel { position: absolute; right: 0; top: calc(100% + 0.5rem); z-index: 50; padding: 0.375rem; border-radius: 0.75rem; background-color: #fff; box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.35); border: 1px solid rgba(15, 23, 42, 0.08); animation: dd-in 140ms ease-out; } .dark .dd-panel { background-color: rgb(30 41 59); /* slate-800 */ border-color: rgba(255, 255, 255, 0.08); box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.6); } /* Menus that hang off a left-aligned nav item open to the left edge. */ nav .dd-panel { right: auto; left: 0; } /* Auf schmalen Schirmen taugt das Ausrichten am Knopf nicht: das Panel hängt an der Glocke, rechts von ihr stehen aber noch Konto und Burger — ein 20rem breites Panel wächst dann nach links aus dem Bild heraus (gemessen: -51px bei 375px). Deshalb spannt es sich hier über die Breite des Headers. Der Trick ist `position: static` auf dem Anker: damit wird der Header (sticky = positioniert) zum Bezugsrahmen des absolut gesetzten Panels. Das hält auch die vertikale Lage richtig — top: 100% ist dann die Unterkante des Headers, inklusive des Impersonation-Banners, wenn eines da ist. */ @media (max-width: 767px) { #siteHeader .dd-anchor { position: static; } #siteHeader .dd-panel { left: 0.75rem; right: 0.75rem; width: auto; /* schlägt Tailwinds w-80 / w-60 (ID erhöht die Spezifität) */ max-width: none; } } @keyframes dd-in { from { opacity: 0; transform: translateY(-4px) scale(0.98); } to { opacity: 1; transform: none; } } @media (prefers-reduced-motion: reduce) { .dd-panel { animation: none; } .nav-rail { transition: none; } } .dd-item { display: flex; align-items: flex-start; gap: 0.625rem; padding: 0.5rem 0.625rem; border-radius: 0.5rem; color: rgb(51 65 85); /* slate-700 */ transition: background-color 150ms ease, color 150ms ease; } .dd-item:hover { background-color: rgb(241 245 249); } /* slate-100 */ .dark .dd-item { color: rgb(203 213 225); } .dark .dd-item:hover { background-color: rgba(255, 255, 255, 0.06); } .dd-item:focus-visible { outline: none; box-shadow: 0 0 0 2px rgba(56, 189, 248, 0.8); } .dd-item.is-active { background-color: rgb(239 246 255); color: rgb(29 78 216); } /* blue-50/700 */ .dark .dd-item.is-active { background-color: rgba(56, 189, 248, 0.12); color: rgb(125 211 252); } .dd-item--danger { color: rgb(190 18 60); } /* rose-700 */ .dd-item--danger:hover { background-color: rgb(255 241 242); } .dark .dd-item--danger { color: rgb(253 164 175); } .dark .dd-item--danger:hover { background-color: rgba(244, 63, 94, 0.12); } .dd-icon { width: 1.125rem; height: 1.125rem; flex-shrink: 0; margin-top: 0.075rem; opacity: 0.7; } .dd-title { display: block; font-size: 0.875rem; font-weight: 500; line-height: 1.35; } .dd-desc { display: block; margin-top: 0.075rem; font-size: 0.75rem; line-height: 1.35; color: rgb(100 116 139); /* slate-500 */ } .dark .dd-desc { color: rgb(148 163 184); } .dd-item.is-active .dd-desc { color: inherit; opacity: 0.75; } /* Mobile menu. */ .m-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.625rem 0.75rem; border-radius: 0.5rem; font-size: 0.9375rem; font-weight: 500; color: rgb(71 85 105); /* slate-600 */ transition: background-color 150ms ease, color 150ms ease; } .m-item:hover { background-color: rgba(15, 23, 42, 0.05); color: rgb(15 23 42); } .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; background-color: rgba(56, 189, 248, 0.12); box-shadow: inset 2px 0 0 rgb(56 189 248); } .m-label { padding: 0 0.75rem; font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; color: rgb(100 116 139); } /* Smooth scrolling */ html { scroll-behavior: smooth; } /* Custom scrollbar for dark mode */ .dark ::-webkit-scrollbar { width: 8px; height: 8px; } .dark ::-webkit-scrollbar-track { background: #374151; } .dark ::-webkit-scrollbar-thumb { background: #6b7280; border-radius: 4px; } .dark ::-webkit-scrollbar-thumb:hover { background: #8b94a3; } /* Light mode scrollbar */ ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: #f1f1f1; } ::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: #a1a1a1; } /* Modal backdrop animation */ .fixed.inset-0.bg-black.bg-opacity-50 { animation: fadeIn 0.2s ease-out; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } /* Modal content animation */ .fixed.inset-0 > div > div { animation: slideIn 0.3s ease-out; } @keyframes slideIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } /* Table hover effect */ .tbody tr:hover { transition: background-color 0.15s ease; } /* Focus styles for inputs */ input:focus, textarea:focus, select:focus { outline: none; box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5); } /* Dark mode focus */ .dark input:focus, .dark textarea:focus, .dark select:focus { box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3); } /* Button focus */ button:focus { outline: none; box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5); } /* Loading spinner for buttons */ button.loading { position: relative; pointer-events: none; } button.loading::after { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 16px; height: 16px; border: 2px solid transparent; border-top-color: currentColor; border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: translate(-50%, -50%) rotate(360deg); } } /* Status badges */ .status-badge { display: inline-block; padding: 4px 8px; border-radius: 9999px; font-size: 12px; font-weight: 500; } /* Table cell wrapping */ .table-cell-wrap { white-space: normal; word-wrap: break-word; } /* Responsive adjustments */ @media (max-width: 768px) { /* Make table scroll horizontally on mobile */ .overflow-hidden { overflow-x: auto; } table { min-width: 768px; } } /* ============================================ Smartphone support (< 640px) The layout already collapses most grids via Tailwind's sm:/md: prefixes and the header ships a burger menu. The rules below cover the few surfaces that still assume desktop: tall modals, the 7-column dashboard grid, and a couple of non-wrapping toolbars. ============================================ */ /* Modals: the overlay locks body scroll (overflow:hidden on ), so a panel taller than the viewport would have its bottom cut off and the Save button unreachable. Let the OVERLAY itself scroll like a page — the panel grows to its natural height, and the overlay scrolls to reveal it. The panel uses `margin:auto`, which centers a short panel but — unlike `align-items:center` — does NOT clip the top when the panel is taller than the viewport (the classic flexbox top-clip bug), so both the close button (top) and the Save button (bottom) stay reachable. Applies on every screen size: the Bewerbung-hinzufügen form has enough fields (Datum bis Interne Notizen) to overflow even on a laptop, not just a phone. */ .fixed.inset-0 { overflow-y: auto !important; } .fixed.inset-0 > .min-h-screen { min-height: 100vh; align-items: flex-start !important; } .fixed.inset-0 > .min-h-screen > .max-w-lg, .fixed.inset-0 > .min-h-screen > .max-w-md, .fixed.inset-0 > .min-h-screen > .max-w-2xl, .fixed.inset-0 > .min-h-screen > .max-w-3xl, .fixed.inset-0 > .min-h-screen > .max-w-4xl { margin: auto !important; } @media (max-width: 640px) { .fixed.inset-0 > .min-h-screen > .max-w-lg, .fixed.inset-0 > .min-h-screen > .max-w-md { max-width: calc(100vw - 2rem) !important; } } /* Dashboard application list: the desktop layout is a 7-column grid forced to 760px and scrolled horizontally — usable on a phone only with two-thumb scrolling. Below md, restyle each row into a stacked card so the primary content fits the viewport. The column header row is hidden; the cells are re-placed via grid-template-areas (their DOM order is unrelated to their visual position). Die Grenze ist bewusst 767px und nicht 640px wie beim Rest: das Raster braucht allein durch seine minmax()-Spalten rund 764px, und die Karte bekommt ihre min-w-[760px] erst ab md (768px). Zwischen 641px und 767px — Handy quer, große Phones — galt vorher also das Desktop-Raster ohne Not, und Stelle, Art und Status lagen außerhalb des Sichtbaren (gemessen: 132px Überhang bei 667px). */ @media (max-width: 767px) { .app-head { display: none !important; } .app-line.app-grid { grid-template-columns: 1.25rem 1fr auto !important; grid-template-areas: "chevron firma actions" "chevron stelle actions" "chevron datum actions" "chevron art actions" "chevron status actions" !important; row-gap: 0.2rem !important; column-gap: 0.625rem !important; padding: 0.75rem 1rem !important; align-items: start !important; } .app-line.app-grid > *:nth-child(1) { grid-area: chevron; align-self: center; } .app-line.app-grid > *:nth-child(2) { grid-area: datum; } .app-line.app-grid > *:nth-child(3) { grid-area: firma; } .app-line.app-grid > *:nth-child(4) { grid-area: stelle; } .app-line.app-grid > *:nth-child(5) { grid-area: art; } .app-line.app-grid > *:nth-child(6) { grid-area: status; } .app-line.app-grid > *:nth-child(7) { grid-area: actions; align-self: center; } /* Let text wrap inside a card instead of truncating to the now-narrow column. */ .app-line.app-grid .truncate { white-space: normal !important; overflow: visible !important; text-overflow: clip !important; } /* Enlarge the edit/delete hit targets for touch. */ .app-line.app-grid .app-actions a, .app-line.app-grid .app-actions button { padding: 0.5rem !important; } } /* Print styles */ @media print { .no-print { display: none !important; } body { background: white; color: black; } .dark body { background: white; color: black; } } /* Header shadow */ header { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); } /* Card shadows */ .bg-white, .bg-gray-800 { box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); } /* Utility classes */ .truncate-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .truncate-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }