From 50d6c422075ee7a23a2419c80a16f71d81e3cb40 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 13 Jul 2026 15:50:33 -0400 Subject: [PATCH] fix(ui): softer chrome-gradient falloff + segmented media toggle (#1478) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two operator-flagged polish items from the Vuetify-4 review: Gradient: reshape the nav + sub-header fade from a near-linear ramp to a hold-then-soft-drop profile — hold high opacity (0.92 → seam 0.68) through the bulk of the chrome, then ease to transparent over a small section at the bottom with an intermediate stop, so it tails off softly instead of running a straight line into a hard edge. Raising the shared --fc-chrome-seam also makes the tab strips more legible over scrolling content. Media toggle: FC's global VBtn { rounded: 'pill' } default made Vuetify 4 pill-round each SEGMENT of the All/Images/Videos v-btn-toggle individually, so the rounded ends collided at the joins. Square the inner segments and clip the group to one 8px outline — a proper segmented control. Both are colour/border-radius only — no control height changes, so the filter bar height and the nav offset (--fc-nav-h) are untouched. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/src/components/TopNav.vue | 15 ++++++++++----- .../src/components/gallery/GalleryFilterBar.vue | 14 ++++++++++++++ frontend/src/styles/app.css | 15 ++++++++++++++- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/TopNav.vue b/frontend/src/components/TopNav.vue index 1b1efbd..467278b 100644 --- a/frontend/src/components/TopNav.vue +++ b/frontend/src/components/TopNav.vue @@ -145,12 +145,17 @@ const health = computed(() => { align-items: center; gap: 1rem; padding: 0.75rem 1rem; - /* Obsidian (#14171A = 20,23,26) gradient fade — content scrolls under it. */ + /* Obsidian (#14171A) fade — content scrolls under it. Holds high (0.92 → + 0.84) through the top half, then eases to transparent over the bottom + quarter so it tails off softly instead of a straight line to a hard edge + (operator 2026-07-13). Shared --fc-chrome-rgb keeps it in sync with the + sub-header continuation. */ background: linear-gradient( to bottom, - rgba(20, 23, 26, 0.92) 0%, - rgba(20, 23, 26, 0.65) 60%, - rgba(20, 23, 26, 0) 100% + rgba(var(--fc-chrome-rgb), 0.92) 0%, + rgba(var(--fc-chrome-rgb), 0.84) 50%, + rgba(var(--fc-chrome-rgb), 0.55) 75%, + rgba(var(--fc-chrome-rgb), 0) 100% ); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); @@ -165,7 +170,7 @@ const health = computed(() => { background: linear-gradient( to bottom, rgba(var(--fc-chrome-rgb), 0.92) 0%, - rgba(var(--fc-chrome-rgb), 0.72) 55%, + rgba(var(--fc-chrome-rgb), 0.84) 60%, rgba(var(--fc-chrome-rgb), var(--fc-chrome-seam)) 100% ); } diff --git a/frontend/src/components/gallery/GalleryFilterBar.vue b/frontend/src/components/gallery/GalleryFilterBar.vue index f8dde94..0c5dc39 100644 --- a/frontend/src/components/gallery/GalleryFilterBar.vue +++ b/frontend/src/components/gallery/GalleryFilterBar.vue @@ -331,6 +331,20 @@ function pushFilter(mutate) { .fc-filterbar-wrap :deep(.v-btn-group) { background-color: rgba(20, 23, 26, 0.72); } +/* Media toggle (All / Images / Videos) as ONE cohesive segmented control. + FC's global VBtn { rounded: 'pill' } default made Vuetify 4 pill-round each + SEGMENT individually, so the rounded ends collided at the joins — the shapes + landed awkwardly on the button edges (operator 2026-07-13). Square the inner + segments (over the pill utility's !important) and clip the group to a single + 8px outline (matches the chips/tiles rounding elsewhere in the app). Radius + only — no height change, so the bar height and nav offset are untouched. */ +.fc-filterbar-wrap :deep(.v-btn-toggle) { + border-radius: 8px; + overflow: hidden; +} +.fc-filterbar-wrap :deep(.v-btn-toggle .v-btn) { + border-radius: 0 !important; +} .fc-filterbar__search { max-width: 320px; min-width: 200px; } .fc-filterbar__chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } /* The tag chips' bodies toggle include/exclude — signal they're clickable. */ diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 7d210a4..e9f9552 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -77,7 +77,13 @@ legible tabs/controls over scrolling content), lower it for a lighter fade. */ :root { --fc-chrome-rgb: 20, 23, 26; /* obsidian #14171A — matches the TopNav */ - --fc-chrome-seam: 0.46; /* alpha where the nav hands off to the sub-header */ + /* Alpha where the nav hands off to the sub-header — also the "hold" level of + the fade. The chrome stays fairly opaque (0.92 → this) through the bulk of + its height, then drops to transparent in a small eased section at the very + bottom (see the multi-stop gradients), so it reads as a slow falloff that + tails off softly rather than a straight line to a hard edge (operator + 2026-07-13). Raise for heavier/​more-legible chrome, lower for a lighter fade. */ + --fc-chrome-seam: 0.68; /* Actual TopNav height, measured live (ResizeObserver in TopNav.vue) and used by full-height workspaces (Explore/Subscriptions: calc(100vh - var)) and by every sticky sub-header pinned beneath the nav (top: var). This was a @@ -92,9 +98,16 @@ controls legible as the fill thins toward transparent — the solid-surface bars it replaces had none, so it must live here. */ .fc-chrome-continues { + /* Continues the nav's fade: HOLDS near the seam alpha through the first ~55% + (subtle), then eases down to transparent over the last ~45% with an + intermediate stop so the tail is soft — no hard line at the bottom edge + (operator 2026-07-13). Percentage stops keep the shape spanning the + element's height (survives the filter bar's expanding refine panel). */ background: linear-gradient( to bottom, rgba(var(--fc-chrome-rgb), var(--fc-chrome-seam)) 0%, + rgba(var(--fc-chrome-rgb), 0.60) 55%, + rgba(var(--fc-chrome-rgb), 0.28) 82%, rgba(var(--fc-chrome-rgb), 0) 100% ); backdrop-filter: blur(2px);