${coverImg
? `
`
diff --git a/client/src/components/Planner/DayPlanSidebar.jsx b/client/src/components/Planner/DayPlanSidebar.jsx
index e36a15c..7283c49 100644
--- a/client/src/components/Planner/DayPlanSidebar.jsx
+++ b/client/src/components/Planner/DayPlanSidebar.jsx
@@ -462,7 +462,7 @@ export default function DayPlanSidebar({
outlineOffset: -2,
borderRadius: isDragTarget ? 8 : 0,
}}
- onMouseEnter={e => { if (!isSelected && !isDragTarget) e.currentTarget.style.background = 'var(--bg-hover)' }}
+ onMouseEnter={e => { if (!isSelected && !isDragTarget) e.currentTarget.style.background = 'var(--bg-tertiary)' }}
onMouseLeave={e => { if (!isSelected) e.currentTarget.style.background = isDragTarget ? 'rgba(17,24,39,0.07)' : 'transparent' }}
>
{/* Tages-Badge */}
@@ -536,7 +536,7 @@ export default function DayPlanSidebar({
{/* Aufgeklappte Orte + Notizen */}
{isExpanded && (
{ e.preventDefault(); if (draggingId) setDropTargetKey(`end-${day.id}`) }}
onDrop={e => {
e.preventDefault()
@@ -614,7 +614,7 @@ export default function DayPlanSidebar({
dragDataRef.current = { assignmentId: String(assignment.id), fromDayId: String(day.id) }
setDraggingId(assignment.id)
}}
- onDragOver={e => { e.preventDefault(); e.stopPropagation(); setDragOverDayId(null); setDropTargetKey(`place-${assignment.id}`) }}
+ onDragOver={e => { e.preventDefault(); e.stopPropagation(); setDragOverDayId(null); if (dropTargetKey !== `place-${assignment.id}`) setDropTargetKey(`place-${assignment.id}`) }}
onDrop={e => {
e.preventDefault(); e.stopPropagation()
const { placeId, assignmentId: fromAssignmentId, noteId, fromDayId } = getDragData(e)
@@ -754,7 +754,7 @@ export default function DayPlanSidebar({
draggable
onDragStart={e => { e.dataTransfer.setData('noteId', String(note.id)); e.dataTransfer.setData('fromDayId', String(day.id)); e.dataTransfer.effectAllowed = 'move'; dragDataRef.current = { noteId: String(note.id), fromDayId: String(day.id) }; setDraggingId(`note-${note.id}`) }}
onDragEnd={() => { setDraggingId(null); setDropTargetKey(null); dragDataRef.current = null }}
- onDragOver={e => { e.preventDefault(); e.stopPropagation(); setDropTargetKey(`note-${note.id}`) }}
+ onDragOver={e => { e.preventDefault(); e.stopPropagation(); if (dropTargetKey !== `note-${note.id}`) setDropTargetKey(`note-${note.id}`) }}
onDrop={e => {
e.preventDefault(); e.stopPropagation()
const { noteId: fromNoteId, assignmentId: fromAssignmentId, fromDayId } = getDragData(e)
@@ -819,9 +819,8 @@ export default function DayPlanSidebar({
)}
{/* Drop-Zone am Listenende — immer vorhanden als Drop-Target */}
{ e.preventDefault(); e.stopPropagation(); setDropTargetKey(`end-${day.id}`) }}
- onDragLeave={() => { if (dropTargetKey === `end-${day.id}`) setDropTargetKey(null) }}
+ style={{ minHeight: 12, padding: '2px 8px' }}
+ onDragOver={e => { e.preventDefault(); e.stopPropagation(); if (dropTargetKey !== `end-${day.id}`) setDropTargetKey(`end-${day.id}`) }}
onDrop={e => {
e.preventDefault(); e.stopPropagation()
const { placeId, assignmentId, noteId, fromDayId } = getDragData(e)
diff --git a/client/src/i18n/translations/de.js b/client/src/i18n/translations/de.js
index da07029..551ec7e 100644
--- a/client/src/i18n/translations/de.js
+++ b/client/src/i18n/translations/de.js
@@ -252,6 +252,8 @@ const de = {
'admin.tabs.addons': 'Addons',
'admin.addons.title': 'Addons',
'admin.addons.subtitle': 'Aktiviere oder deaktiviere Funktionen, um NOMAD nach deinen Wünschen anzupassen.',
+ 'admin.addons.subtitleBefore': 'Aktiviere oder deaktiviere Funktionen, um ',
+ 'admin.addons.subtitleAfter': ' nach deinen Wünschen anzupassen.',
'admin.addons.enabled': 'Aktiviert',
'admin.addons.disabled': 'Deaktiviert',
'admin.addons.type.trip': 'Trip',
diff --git a/client/src/i18n/translations/en.js b/client/src/i18n/translations/en.js
index aa24d49..4d88d37 100644
--- a/client/src/i18n/translations/en.js
+++ b/client/src/i18n/translations/en.js
@@ -252,6 +252,8 @@ const en = {
'admin.tabs.addons': 'Addons',
'admin.addons.title': 'Addons',
'admin.addons.subtitle': 'Enable or disable features to customize your NOMAD experience.',
+ 'admin.addons.subtitleBefore': 'Enable or disable features to customize your ',
+ 'admin.addons.subtitleAfter': ' experience.',
'admin.addons.enabled': 'Enabled',
'admin.addons.disabled': 'Disabled',
'admin.addons.type.trip': 'Trip',
diff --git a/client/src/index.css b/client/src/index.css
index 7d05340..e01393f 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -326,6 +326,15 @@ body {
color: var(--text-faint);
}
+/* Brand images: no save/copy/drag */
+img[alt="NOMAD"] {
+ pointer-events: none;
+ user-select: none;
+ -webkit-user-select: none;
+ -webkit-user-drag: none;
+ -webkit-touch-callout: none;
+}
+
/* Weiche Übergänge */
.transition-smooth {
transition: all 0.2s ease;
diff --git a/client/src/pages/DashboardPage.jsx b/client/src/pages/DashboardPage.jsx
index 6c613cd..899266e 100644
--- a/client/src/pages/DashboardPage.jsx
+++ b/client/src/pages/DashboardPage.jsx
@@ -74,8 +74,42 @@ const GRADIENTS = [
]
function tripGradient(id) { return GRADIENTS[id % GRADIENTS.length] }
+// ── Liquid Glass hover effect ────────────────────────────────────────────────
+function LiquidGlass({ children, dark, style, className = '', onClick }) {
+ const ref = useRef(null)
+ const glareRef = useRef(null)
+ const borderRef = useRef(null)
+
+ const onMove = (e) => {
+ if (!ref.current || !glareRef.current || !borderRef.current) return
+ const rect = ref.current.getBoundingClientRect()
+ const x = e.clientX - rect.left
+ const y = e.clientY - rect.top
+ glareRef.current.style.background = `radial-gradient(circle 250px at ${x}px ${y}px, ${dark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)'} 0%, transparent 70%)`
+ glareRef.current.style.opacity = '1'
+ borderRef.current.style.opacity = '1'
+ borderRef.current.style.maskImage = `radial-gradient(circle 120px at ${x}px ${y}px, black 0%, transparent 100%)`
+ borderRef.current.style.WebkitMaskImage = `radial-gradient(circle 120px at ${x}px ${y}px, black 0%, transparent 100%)`
+ }
+ const onLeave = () => {
+ if (glareRef.current) glareRef.current.style.opacity = '0'
+ if (borderRef.current) borderRef.current.style.opacity = '0'
+ }
+
+ return (
+
+ )
+}
+
// ── Spotlight Card (next upcoming trip) ─────────────────────────────────────
-function SpotlightCard({ trip, onEdit, onDelete, onArchive, onClick, t, locale }) {
+function SpotlightCard({ trip, onEdit, onDelete, onArchive, onClick, t, locale, dark }) {
const status = getTripStatus(trip)
const coverBg = trip.cover_image
@@ -83,7 +117,7 @@ function SpotlightCard({ trip, onEdit, onDelete, onArchive, onClick, t, locale }
: tripGradient(trip.id)
return (
-
onClick(trip)}>
{/* Cover / Background */}
@@ -151,7 +185,7 @@ function SpotlightCard({ trip, onEdit, onDelete, onArchive, onClick, t, locale }
-
+
)
}
@@ -170,9 +204,9 @@ function TripCard({ trip, onEdit, onDelete, onArchive, onClick, t, locale }) {
onMouseLeave={() => setHovered(false)}
onClick={() => onClick(trip)}
style={{
- background: 'var(--bg-card)', borderRadius: 16, overflow: 'hidden', cursor: 'pointer',
- border: '1px solid var(--border-primary)', transition: 'all 0.18s',
- boxShadow: hovered ? '0 8px 28px rgba(0,0,0,0.10)' : '0 1px 4px rgba(0,0,0,0.04)',
+ background: hovered ? 'var(--bg-tertiary)' : 'var(--bg-card)', borderRadius: 16, overflow: 'hidden', cursor: 'pointer',
+ border: `1px solid ${hovered ? 'var(--text-faint)' : 'var(--border-primary)'}`, transition: 'all 0.18s',
+ boxShadow: hovered ? '0 8px 28px rgba(0,0,0,0.15)' : '0 1px 4px rgba(0,0,0,0.04)',
transform: hovered ? 'translateY(-2px)' : 'none',
}}
>
@@ -354,6 +388,7 @@ export default function DashboardPage() {
const { t, locale } = useTranslation()
const { demoMode } = useAuthStore()
const { settings, updateSetting } = useSettingsStore()
+ const dark = settings.dark_mode
const showCurrency = settings.dashboard_currency !== 'off'
const showTimezone = settings.dashboard_timezone !== 'off'
const showSidebar = showCurrency || showTimezone
@@ -575,7 +610,7 @@ export default function DashboardPage() {
{!isLoading && spotlight && (
{ setEditingTrip(tr); setShowForm(true) }}
onDelete={handleDelete}
onArchive={handleArchive}
@@ -635,8 +670,8 @@ export default function DashboardPage() {
{/* Widgets sidebar */}
{showSidebar && (
- {showCurrency && }
- {showTimezone && }
+ {showCurrency && }
+ {showTimezone && }
)}
diff --git a/client/src/pages/LoginPage.jsx b/client/src/pages/LoginPage.jsx
index 50a8b5f..de36efd 100644
--- a/client/src/pages/LoginPage.jsx
+++ b/client/src/pages/LoginPage.jsx
@@ -67,6 +67,8 @@ export default function LoginPage() {
}
}
+ const [showTakeoff, setShowTakeoff] = useState(false)
+
const handleSubmit = async (e) => {
e.preventDefault()
setError('')
@@ -79,10 +81,10 @@ export default function LoginPage() {
} else {
await login(email, password)
}
- navigate('/dashboard')
+ setShowTakeoff(true)
+ setTimeout(() => navigate('/dashboard'), 2600)
} catch (err) {
setError(err.message || t('login.error'))
- } finally {
setIsLoading(false)
}
}
@@ -95,6 +97,157 @@ export default function LoginPage() {
color: '#111827', background: 'white', boxSizing: 'border-box', transition: 'border-color 0.15s',
}
+ if (showTakeoff) {
+ return (
+
+ {/* Sky gradient */}
+
+
+ {/* Stars */}
+ {Array.from({ length: 60 }, (_, i) => (
+
0.7 ? 3 : 1.5,
+ height: Math.random() > 0.7 ? 3 : 1.5,
+ borderRadius: '50%',
+ background: 'white',
+ top: `${Math.random() * 100}%`,
+ left: `${Math.random() * 100}%`,
+ animationDelay: `${0.3 + Math.random() * 0.5}s, ${Math.random() * 1}s`,
+ }} />
+ ))}
+
+ {/* Clouds rushing past */}
+ {[0, 1, 2, 3, 4].map(i => (
+
+ ))}
+
+ {/* Speed lines */}
+ {Array.from({ length: 12 }, (_, i) => (
+
+ ))}
+
+ {/* Plane */}
+
+
+ {/* Contrail */}
+
+
+ {/* Logo fade in + burst */}
+
+

+
{t('login.tagline')}
+
+
+
+
+
+ )
+ }
+
return (
@@ -215,14 +368,11 @@ export default function LoginPage() {
{/* Logo */}
-
-
-
NOMAD
+
+
-
+
{t('login.tagline')}
@@ -261,13 +411,11 @@ export default function LoginPage() {
{/* Mobile logo */}
-
-
-
NOMAD
+

+
{t('login.tagline')}
@@ -346,7 +494,7 @@ export default function LoginPage() {
>
{isLoading
? <>
{mode === 'register' ? t('login.creating') : t('login.signingIn')}>
- : mode === 'register' ? t('login.createAccount') : t('login.signIn')
+ : <>
{mode === 'register' ? t('login.createAccount') : t('login.signIn')}>
}
diff --git a/server/package.json b/server/package.json
index 98d323a..24efe61 100644
--- a/server/package.json
+++ b/server/package.json
@@ -1,6 +1,6 @@
{
"name": "nomad-server",
- "version": "2.5.1",
+ "version": "2.5.2",
"main": "src/index.js",
"scripts": {
"start": "node --experimental-sqlite src/index.js",