feat(client): improve shell responsive gutters and cover hover transforms

Add responsive shell content gutters to tokens CSS and fix cover stack hover transform calculations for scaled square covers in SeriesDetailView.
This commit is contained in:
neon
2026-07-31 01:53:37 +00:00
parent 4baad41ed1
commit 68f38411b3
6 changed files with 35 additions and 4 deletions
+14 -1
View File
@@ -70,9 +70,10 @@
/* Shape */
--radius: 0.625rem;
/* App shell (sidebar + header) — one gutter, one material.
/* App shell (sidebar + header): one card gap, one material.
--shell-surface-opacity is user-controlled and overwritten on <html>. */
--shell-gap: 0.75rem;
--shell-content-gutter: 0.75rem;
--shell-surface-opacity: 92%;
--shell-surface: color-mix(in oklch, var(--sidebar) var(--shell-surface-opacity), transparent);
/* Accent ladder: four steps, used for every tinted fill and border in the shell. */
@@ -108,6 +109,18 @@
--audit-settings: oklch(0.55 0.15 25);
}
@media (min-width: 40rem) {
:root {
--shell-content-gutter: 1rem;
}
}
@media (min-width: 64rem) {
:root {
--shell-content-gutter: 1.5rem;
}
}
.dark {
color-scheme: dark;
--tint-c: 0.0175;
+3 -1
View File
@@ -46,7 +46,9 @@ const viewKey = computed(() => {
<AppHeader />
<!-- 2. Independent View Area: Everything below the header scrolls here -->
<div class="app-shell-scroll px-(--shell-gap) pt-(--shell-gap) flex-1 overflow-y-auto overflow-x-hidden relative scroll-smooth bg-transparent">
<div
class="app-shell-scroll px-(--shell-content-gutter) pt-(--shell-gap) flex-1 overflow-y-auto overflow-x-hidden relative scroll-smooth bg-transparent"
>
<router-view v-slot="{ Component }">
<Transition name="page" mode="out-in">
<KeepAlive :include="GRID_VIEW_NAMES" :max="10">
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'
import {
centeredBottomScaleTransform,
centeredScaleShiftPercent,
isSquareCoverRatio,
resolveCoverStackAspectRatio,
resolveCoverStackDisplayMode,
@@ -40,6 +41,9 @@ describe('cover-scale utilities', () => {
})
it('returns centered bottom transform only when scale exceeds 1', () => {
expect(centeredScaleShiftPercent(1)).toBe(0)
expect(centeredScaleShiftPercent(Number.NaN)).toBe(0)
expect(centeredScaleShiftPercent(1.25)).toBe(12.5)
expect(centeredBottomScaleTransform(1)).toBeNull()
expect(centeredBottomScaleTransform(Number.NaN)).toBeNull()
expect(centeredBottomScaleTransform(1.25)).toEqual({
@@ -40,9 +40,14 @@ export function resolveCoverStackDisplayMode(ratio: number | null | undefined):
return isSquareCoverRatio(ratio) ? 'natural-bottom' : undefined
}
export function centeredScaleShiftPercent(scale: number): number {
if (!Number.isFinite(scale) || scale <= 1) return 0
return (scale - 1) * 50
}
export function centeredBottomScaleTransform(scale: number): { transformOrigin: string; transform: string } | null {
if (!Number.isFinite(scale) || scale <= 1) return null
const centerShiftPercent = (scale - 1) * 50
const centerShiftPercent = centeredScaleShiftPercent(scale)
return {
transformOrigin: 'center bottom',
transform: `translateY(-${centerShiftPercent}%) scale(${scale})`,
@@ -447,6 +447,8 @@ describe('SeriesDetailView', () => {
expect(style).toContain('transform-origin: center bottom')
expect(style).toContain('translateY(-12.5%)')
expect(style).toContain('aspect-ratio: 1 / 1')
expect(style).toContain('--lead-cover-hover-translate-y: calc(-12px - 12.5%)')
expect(style).toContain('--lead-cover-hover-scale: 1.2875')
expect(wrapper.get('[data-testid="lead-cover-artwork"]').attributes('data-mode')).toBe('natural-bottom')
expect(wrapper.get('[data-testid="lead-cover-artwork"]').attributes('data-frame-aspect-ratio')).toBe('1/1')
@@ -34,6 +34,7 @@ import { useCoverStack, MAX_VISIBLE as MAX_STACK_VISIBLE } from '../composables/
import {
PORTRAIT_STACK_FRAME_ASPECT_RATIO,
centeredBottomScaleTransform,
centeredScaleShiftPercent,
resolveCoverStackAspectRatio,
resolveCoverStackDisplayMode,
resolveCoverStackFrameAspectRatio,
@@ -251,9 +252,12 @@ const scaledLeadCoverStyles = computed(() =>
const ratio = leadCoverRatioAt(index)
const squareScale = resolveSquareCoverScale(ratio, SERIES_SQUARE_COVER_SCALE)
const squareTransform = centeredBottomScaleTransform(squareScale)
const centerShiftPercent = centeredScaleShiftPercent(squareScale)
const baseForRatio = {
...base,
aspectRatio: resolveCoverStackAspectRatio(ratio),
'--lead-cover-hover-translate-y': `calc(-12px - ${centerShiftPercent}%)`,
'--lead-cover-hover-scale': String(1.03 * squareScale),
}
if (!squareTransform) return baseForRatio
return {
@@ -747,7 +751,8 @@ defineOptions({ name: 'SeriesDetailView' })
}
.series-cover-stack-item:hover {
transform: perspective(1000px) rotateY(0deg) translateY(-12px) translateZ(40px) scale(1.03) !important;
transform: perspective(1000px) rotateY(0deg) translateY(var(--lead-cover-hover-translate-y, -12px)) translateZ(40px)
scale(var(--lead-cover-hover-scale, 1.03)) !important;
z-index: 50 !important;
box-shadow:
0 20px 25px -5px rgba(0, 0, 0, 0.4),