feat(ui): double showcase cadence + filter bar matches TopNav frost
CI / lint (push) Successful in 2s
CI / backend-lint-and-test (push) Successful in 12s
CI / frontend-build (push) Successful in 17s
CI / integration (push) Successful in 2m56s

- Showcase reveal cadence 80ms -> 160ms (slower, more deliberate one-at-a-time
  cascade per operator). Bump showcase.spec timer advances to cover 60x160ms.
- Gallery filter bar now uses the EXACT gradiated-obsidian frost + blur as
  TopNav (was a flat rgba(...,0.55) block), so the two read as one continuous
  piece of chrome with images visibly scrolling under both; the nav's
  transparent bottom edge against the bar's opaque top leaves a faint seam that
  separates them at the very top of scroll.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 09:02:21 -04:00
parent 21a73cd1dc
commit 0497394710
3 changed files with 19 additions and 9 deletions
@@ -217,7 +217,7 @@ function pushFilter(mutate) {
<style scoped>
/* The whole chrome (bar row + expandable refine panel) is one sticky,
hazey block pinned directly under the 64px TopNav and continuous with it. */
frosted block pinned directly under the 64px TopNav and continuous with it. */
.fc-filterbar-wrap {
position: sticky;
top: 64px;
@@ -227,9 +227,18 @@ function pushFilter(mutate) {
and a gap shows through when scrolled to the top. */
margin-top: -8px;
margin-bottom: 12px;
/* Same hazey obsidian (#14171A = 20,23,26) + blur as the TopNav so the two
read as one piece of chrome; content scrolls under both. */
background: rgba(20, 23, 26, 0.55);
/* EXACT same gradiated obsidian (#14171A = 20,23,26) frost as the TopNav so
the two read as one continuous piece of chrome — images scroll visibly
under both. The nav's gradient fades to transparent at ITS bottom; this
bar re-darkens at its top, so a faint seam (the page/image showing through
the nav's transparent edge) separates them when scrolled to the very top,
while under-scroll they frost as one. */
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%
);
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
}
+2 -1
View File
@@ -24,7 +24,8 @@ import { preloadImage } from '../utils/preloadImage.js'
// fetch + decode (a cold TABLESAMPLE is a separate, query-side concern);
// everything after it is buffer-smoothed.
const PAGE = 3
const CADENCE_MS = 80 // floor between fully-loaded reveals
const CADENCE_MS = 160 // floor between fully-loaded reveals (doubled
// 2026-06-04 — slower, more deliberate cadence)
const PRIME = 6 // items buffered before the drain starts
const BUFFER_TARGET = 30 // producer tops the queue up to this
const BUFFER_MIN = 12 // ...and refills once the queue dips below this
+4 -4
View File
@@ -46,8 +46,8 @@ describe('showcase store: buffered cascade', () => {
stubShowcase()
const s = useShowcaseStore()
const p = s.loadInitial()
// Past INITIAL_COUNT (60) × CADENCE (80ms) plus fetch microtasks.
await vi.advanceTimersByTimeAsync(6000)
// Past INITIAL_COUNT (60) × CADENCE (160ms = 9.6s) plus fetch microtasks.
await vi.advanceTimersByTimeAsync(12000)
await p
expect(s.images.length).toBe(60)
const ids = s.images.map((i) => i.id)
@@ -65,7 +65,7 @@ describe('showcase store: buffered cascade', () => {
// the cadence — a 1s window must not dump the whole buffer at once.
expect(early).toBeGreaterThan(0)
expect(early).toBeLessThan(60)
await vi.advanceTimersByTimeAsync(6000)
await vi.advanceTimersByTimeAsync(12000)
await p
expect(s.images.length).toBe(60)
})
@@ -80,7 +80,7 @@ describe('showcase store: buffered cascade', () => {
await vi.advanceTimersByTimeAsync(1000)
expect(s.images.length).toBe(0) // gated on the un-decoded first image
release()
await vi.advanceTimersByTimeAsync(6000)
await vi.advanceTimersByTimeAsync(12000)
await p
expect(s.images.length).toBe(60) // cascade proceeds once it decodes
})