From a774d9f6deb8217fc44121e01d2143eb34ce299e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 22:49:59 +0000 Subject: [PATCH 1/4] fix(sidebar): key nav icon colours off data-section, not DOM order The per-section icon colours used :nth-child(1..6), but the nav grew to 8 items (files, shared, shared-with-me, recent, favorites, photos, music, trash). The colours had drifted out of sync with their labels, and music and trash got no colour at all. Map each item's colour off a stable `data-section` key instead, covering all eight sections with distinct curated calendar-dot hues (sibling blues for the two share directions, purple for music, red for trash). Reordering or adding nav items can no longer desync the colours. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_013J5koaCrHDMvS7uwpawLBN --- frontend/src/lib/components/AppShell.svelte | 25 ++++++--- frontend/src/lib/styles/ported/sidebar.css | 58 +++++++++++++-------- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/frontend/src/lib/components/AppShell.svelte b/frontend/src/lib/components/AppShell.svelte index f291aa17..62865fbd 100644 --- a/frontend/src/lib/components/AppShell.svelte +++ b/frontend/src/lib/components/AppShell.svelte @@ -23,22 +23,30 @@ href: string; label: string; icon: string; + /** Stable key driving the per-section icon colour (see sidebar.css). */ + section: string; admin?: boolean; } const LINKS: NavLink[] = [ - { href: '/files', label: t('nav.files', 'Files'), icon: 'folder' }, - { href: '/shared', label: t('nav.shared', 'Shared'), icon: 'oxiexport' }, + { href: '/files', label: t('nav.files', 'Files'), icon: 'folder', section: 'files' }, + { href: '/shared', label: t('nav.shared', 'Shared'), icon: 'oxiexport', section: 'shared' }, { href: '/shared-with-me', label: t('nav.shared_with_me', 'Shared with me'), - icon: 'oxiimport' + icon: 'oxiimport', + section: 'shared-with-me' }, - { href: '/recent', label: t('nav.recent', 'Recent'), icon: 'clock' }, - { href: '/favorites', label: t('nav.favorites', 'Favorites'), icon: 'star' }, - { href: '/photos', label: t('nav.photos', 'Photos'), icon: 'images' }, - { href: '/music', label: t('nav.music', 'Music'), icon: 'music' }, - { href: '/trash', label: t('nav.trash', 'Trash'), icon: 'trash' } + { href: '/recent', label: t('nav.recent', 'Recent'), icon: 'clock', section: 'recent' }, + { + href: '/favorites', + label: t('nav.favorites', 'Favorites'), + icon: 'star', + section: 'favorites' + }, + { href: '/photos', label: t('nav.photos', 'Photos'), icon: 'images', section: 'photos' }, + { href: '/music', label: t('nav.music', 'Music'), icon: 'music', section: 'music' }, + { href: '/trash', label: t('nav.trash', 'Trash'), icon: 'trash', section: 'trash' } ]; const isAdmin = $derived(session.user?.role === 'admin'); @@ -256,6 +264,7 @@ class="nav-item" class:active={active(link.href)} href={link.href} + data-section={link.section} onclick={() => (sidebarOpen = false)} > diff --git a/frontend/src/lib/styles/ported/sidebar.css b/frontend/src/lib/styles/ported/sidebar.css index 445c54fc..17a41a9e 100644 --- a/frontend/src/lib/styles/ported/sidebar.css +++ b/frontend/src/lib/styles/ported/sidebar.css @@ -175,30 +175,44 @@ } } -/* Colored icons per section */ -.nav-item:nth-child(1) i, -.nav-item:nth-child(1) .oxi-icon { - color: var(--color-cal-1); -} /* Files - orange */ -.nav-item:nth-child(2) i, -.nav-item:nth-child(2) .oxi-icon { +/* Colored icons per section. + Keyed off the stable `data-section` attribute (set in AppShell's LINKS), NOT + DOM order — adding/reordering nav items can no longer desync the colours from + their labels. Each section maps to one curated calendar-dot hue (the same + --color-cal-* family used elsewhere), spread around the wheel so they read as + a set: the two share directions are sibling blues, music is purple to match + its gradient, and trash is red as a soft "destructive" affordance. */ +.nav-item[data-section="files"] i, +.nav-item[data-section="files"] .oxi-icon { color: var(--color-cal-2); -} /* Shared - blue */ -.nav-item:nth-child(3) i, -.nav-item:nth-child(3) .oxi-icon { - color: var(--color-cal-3); -} /* Recent - teal */ -.nav-item:nth-child(4) i, -.nav-item:nth-child(4) .oxi-icon { - color: var(--color-cal-4); -} /* Favorites - gold */ -.nav-item:nth-child(5) i, -.nav-item:nth-child(5) .oxi-icon { - color: var(--color-cal-5); -} /* Photos - pink */ -.nav-item:nth-child(6) i, -.nav-item:nth-child(6) .oxi-icon { +} /* Files - amber */ +.nav-item[data-section="shared"] i, +.nav-item[data-section="shared"] .oxi-icon { + color: var(--color-cal-7); +} /* Shared (outbound) - sky */ +.nav-item[data-section="shared-with-me"] i, +.nav-item[data-section="shared-with-me"] .oxi-icon { + color: var(--color-cal-8); +} /* Shared with me (inbound) - blue */ +.nav-item[data-section="recent"] i, +.nav-item[data-section="recent"] .oxi-icon { color: var(--color-cal-6); +} /* Recent - teal */ +.nav-item[data-section="favorites"] i, +.nav-item[data-section="favorites"] .oxi-icon { + color: var(--color-cal-3); +} /* Favorites - gold */ +.nav-item[data-section="photos"] i, +.nav-item[data-section="photos"] .oxi-icon { + color: var(--color-cal-10); +} /* Photos - magenta */ +.nav-item[data-section="music"] i, +.nav-item[data-section="music"] .oxi-icon { + color: var(--color-cal-9); +} /* Music - purple */ +.nav-item[data-section="trash"] i, +.nav-item[data-section="trash"] .oxi-icon { + color: var(--color-cal-1); } /* Trash - red */ /* Colourful icons at rest, but the ACTIVE item is always accent — one From 4c2efce3937e7acab4a102ace2a5927a8b23ddc9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 22:52:06 +0000 Subject: [PATCH 2/4] fix(tokens): correct About z-index and accent/dark-mode token reuse - About modal overlay used a raw z-index: 1200, below the semantic --z-modal (3000)/--z-toast (4000) layers, so token-based surfaces would cover it. Consume var(--z-modal) instead. - .btn-primary and .search-button sit on the accent gradient but coloured their text with --color-danger-text (only correct by coincidence, #fff). Use the purpose-built --color-on-accent so retinting danger can't break them. The other --color-danger-text uses are white-on-red and stay. - --color-item-hover-blue/--color-item-hover-sky were light-only literals that would read wrong in dark mode; wrap them in light-dark(). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_013J5koaCrHDMvS7uwpawLBN --- frontend/src/lib/components/AppShell.svelte | 2 +- frontend/src/lib/styles/base/variables.css | 4 ++-- frontend/src/lib/styles/ported/buttons.css | 2 +- frontend/src/lib/styles/ported/topbar.css | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/components/AppShell.svelte b/frontend/src/lib/components/AppShell.svelte index 62865fbd..9fdc887c 100644 --- a/frontend/src/lib/components/AppShell.svelte +++ b/frontend/src/lib/components/AppShell.svelte @@ -1028,7 +1028,7 @@ .about-overlay { position: fixed; inset: 0; - z-index: 1200; + z-index: var(--z-modal); display: flex; align-items: center; justify-content: center; diff --git a/frontend/src/lib/styles/base/variables.css b/frontend/src/lib/styles/base/variables.css index 541f09c6..5bb0760d 100644 --- a/frontend/src/lib/styles/base/variables.css +++ b/frontend/src/lib/styles/base/variables.css @@ -368,8 +368,8 @@ --color-item-active: light-dark(#f8d2ae, #5a5047); --color-item-selected: light-dark(#fff8f6, #39281a); --color-item-hover-accent: light-dark(#fff0ec, #3d342c); - --color-item-hover-blue: #f0f8ff; - --color-item-hover-sky: #e0f2fe; + --color-item-hover-blue: light-dark(#f0f8ff, #18293f); + --color-item-hover-sky: light-dark(#e0f2fe, #103048); /* Sidebar — already dark-leaning in both modes; dark mode goes deeper. */ --color-sidebar-bg-from: light-dark(#2a3042, #0f172a); diff --git a/frontend/src/lib/styles/ported/buttons.css b/frontend/src/lib/styles/ported/buttons.css index 6048b6ab..3f015274 100644 --- a/frontend/src/lib/styles/ported/buttons.css +++ b/frontend/src/lib/styles/ported/buttons.css @@ -18,7 +18,7 @@ .btn-primary { background: var(--color-accent-gradient); - color: var(--color-danger-text); + color: var(--color-on-accent); box-shadow: 0 4px 15px var(--color-accent-shadow); } diff --git a/frontend/src/lib/styles/ported/topbar.css b/frontend/src/lib/styles/ported/topbar.css index 1c2ab828..f05391da 100644 --- a/frontend/src/lib/styles/ported/topbar.css +++ b/frontend/src/lib/styles/ported/topbar.css @@ -165,7 +165,7 @@ top: 50%; transform: translateY(-50%); background: var(--color-accent-gradient); - color: var(--color-danger-text); + color: var(--color-on-accent); border: none; border-radius: var(--radius-xl); width: 36px; From 320c4dc257a542c8fd271929b960bdab22cf18bb Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 22:57:48 +0000 Subject: [PATCH 3/4] refactor(ui): modern-2026 button feel, neutral scrollbar, sticky token Friendly/warm direction, modernised: - Buttons: drop the translateY lift on every variant (it read jittery and dated). They now rest on a soft neutral elevation, bloom their warm brand glow in on hover, and scale-down 0.98 for a tactile press. Motion uses the --motion-base / --ease-standard tokens. - Scrollbar: neutral padded pill at rest that warms to the accent on hover, instead of an always-orange thumb that competed with content. - page-sticky-header: replace the magic top: -20px (coupled to the content gutter) with calc(-1 * var(--space-5)); tokenise its z-index to --z-sticky. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_013J5koaCrHDMvS7uwpawLBN --- frontend/src/lib/styles/ported/buttons.css | 32 ++++++++++++---------- frontend/src/lib/styles/ported/content.css | 27 ++++++++++++------ 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/frontend/src/lib/styles/ported/buttons.css b/frontend/src/lib/styles/ported/buttons.css index 3f015274..3717c2e1 100644 --- a/frontend/src/lib/styles/ported/buttons.css +++ b/frontend/src/lib/styles/ported/buttons.css @@ -9,27 +9,32 @@ font-size: var(--text-base); font-weight: var(--weight-medium); gap: var(--space-2); - transition: all 0.2s ease; + transition: all var(--motion-base) var(--ease-standard); } .btn i { font-size: 15px; } +/* Filled buttons rest on a soft, neutral elevation; their warm brand glow + blooms in on hover (rather than glowing at rest), and a gentle scale-down + gives a tactile press. The old "translateY lift on every button" is gone — + vertical lift is reserved for cards now, so buttons feel calm, not jittery. */ .btn-primary { background: var(--color-accent-gradient); color: var(--color-on-accent); - box-shadow: 0 4px 15px var(--color-accent-shadow); + box-shadow: var(--shadow-sm); } .btn-primary:hover { - transform: translateY(-2px); - box-shadow: 0 6px 20px var(--color-accent-shadow-lg); + box-shadow: 0 4px 14px var(--color-accent-shadow); + filter: brightness(1.05); } .btn-primary:active { - transform: translateY(0); - box-shadow: 0 2px 10px var(--color-accent-shadow); + transform: scale(0.98); + box-shadow: var(--shadow-xs); + filter: brightness(0.97); } .btn-secondary { @@ -41,29 +46,28 @@ .btn-secondary:hover { background-color: var(--color-bg-input-alt); border-color: var(--color-border-medium); - transform: translateY(-2px); - box-shadow: 0 4px 12px var(--color-shadow-sm); } .btn-secondary:active { - transform: translateY(0); + transform: scale(0.98); background-color: var(--color-border); } .btn-danger { background: var(--color-danger-gradient); color: var(--color-danger-text); - box-shadow: 0 4px 15px var(--color-danger-ring); + box-shadow: var(--shadow-sm); } .btn-danger:hover { - transform: translateY(-2px); - box-shadow: 0 6px 20px var(--color-danger-ring-lg); + box-shadow: 0 4px 14px var(--color-danger-ring); + filter: brightness(1.05); } .btn-danger:active { - transform: translateY(0); - box-shadow: 0 2px 10px var(--color-danger-ring); + transform: scale(0.98); + box-shadow: var(--shadow-xs); + filter: brightness(0.97); } /* ── State matrix: focus / disabled / loading ───────────────── */ diff --git a/frontend/src/lib/styles/ported/content.css b/frontend/src/lib/styles/ported/content.css index 7f212f33..52922fbe 100644 --- a/frontend/src/lib/styles/ported/content.css +++ b/frontend/src/lib/styles/ported/content.css @@ -1,18 +1,27 @@ /* ── Scrollbar ── */ ::-webkit-scrollbar { - width: 6px; + width: 10px; + height: 10px; } ::-webkit-scrollbar-track { - background: var(--color-bg-surface); + background: transparent; } +/* Neutral, padded pill that blends in at rest and warms to the brand accent + only on hover — the bar no longer competes with content for attention. */ ::-webkit-scrollbar-thumb { + background: var(--color-border-medium); + border: 2px solid transparent; + border-radius: var(--radius-full); + background-clip: padding-box; +} +::-webkit-scrollbar-thumb:hover { background: var(--color-accent); - border-radius: 3px; + background-clip: padding-box; } * { scrollbar-width: thin; - scrollbar-color: var(--color-accent) var(--color-bg-surface); + scrollbar-color: var(--color-border-medium) transparent; } /* Content area */ @@ -50,11 +59,13 @@ .page-sticky-header { position: sticky; - margin: 0px; - padding: var(--space-2-5) 0px; - top: -20px; /* due to padding top of content-area */ + margin: 0; + padding: var(--space-2-5) 0; + /* Cancel .content-area's --space-5 top padding so the header sticks flush; + stays correct if the gutter token ever changes. */ + top: calc(-1 * var(--space-5)); background-color: var(--color-bg-page); - z-index: 100; /* ensure header is above image preview */ + z-index: var(--z-sticky); /* ensure header is above image preview */ } .actions-bar { From 50070af009e61d8bfe6e1814c5edc29033fd5cd7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 23:02:51 +0000 Subject: [PATCH 4/4] fix(tokens): restore a real text hierarchy for muted/subtle/faint The three tiers had collapsed to one identical value (#5e6a78 / #9fadbe), so hints, metadata, timestamps and placeholders were all indistinguishable. Re-separate them into three perceptibly distinct steps, each verified WCAG AA >=4.5:1 on every page/surface/hover/muted background in both light and dark mode. Emphasis maps to contrast: muted is strongest (general secondary text), faint the weakest (timestamps/placeholders) sitting on the AA floor; secondary stays clearly stronger than all three. The dark floor is binding, so the dark steps separate upward (lighter = more emphasis). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_013J5koaCrHDMvS7uwpawLBN --- frontend/src/lib/styles/base/variables.css | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/styles/base/variables.css b/frontend/src/lib/styles/base/variables.css index 5bb0760d..82efd220 100644 --- a/frontend/src/lib/styles/base/variables.css +++ b/frontend/src/lib/styles/base/variables.css @@ -221,11 +221,14 @@ --color-text: light-dark(#2d3748, #e2e8f0); /* primary body */ --color-text-heading: light-dark(#1e293b, #f1f5f9); /* headings */ --color-text-secondary: light-dark(#475569, #cbd5e1); /* strong secondary */ - /* muted/subtle/faint converge: AA 4.5:1 on the grayish page/muted bgs AND - * on the lighter dark hover bg leaves only a narrow passing window. */ - --color-text-muted: light-dark(#5e6a78, #9fadbe); /* muted */ - --color-text-subtle: light-dark(#5e6a78, #9fadbe); /* subtle */ - --color-text-faint: light-dark(#5e6a78, #9fadbe); /* faintest still-AA */ + /* Three perceptibly distinct tiers, each verified AA ≥4.5:1 on every + * page/surface/hover/muted background in BOTH modes. muted carries the most + * weight (general secondary text — by far the most-used), faint the least + * (timestamps, placeholders) and sits right on the AA floor. Emphasis maps + * to contrast: stronger = darker in light / lighter in dark. */ + --color-text-muted: light-dark(#586472, #b2c0d0); /* muted — strongest (5.4 / 5.6) */ + --color-text-subtle: light-dark(#5e6a78, #a6b4c6); /* subtle — middle (5.0 / 4.9) */ + --color-text-faint: light-dark(#647082, #9fadbe); /* faint — AA floor (4.5 / 4.5) */ /* legacy aliases → one of the tiers above */ --color-text-dark: var(--color-text-secondary); --color-text-dim: var(--color-text-secondary);