- add tokens/ColorTokens.h (Slate/Blue/Red/Green scales, semantic roles,
shared SearchTint ramp) and reference it from painted widgets
- dark: media-action-menu and modern-tray-menu moved from Gray/Zinc to Slate
- light: full Gray->Slate sweep, one-off tinted blues collapsed to
Blue-50/Blue-100/Slate-50, Windows-blue links mapped to Blue-600/700
- radii off-scale values (10px/16px) converged to the 8/14 scale,
font sizes 9/15/22/26px converged to the documented scale
- dark white overlays collapsed from 40+ alpha stops to a 6-step scale
(player OSD keeps a separate high-alpha band, now decimal notation)
- librarycard/collectioncard palettes now read from ColorTokens (no visual change)
- searchhistorychip/searchcompleterpopup private blue palettes replaced by
the shared SearchTint ramp so both render identical heat levels
- removed dead #playerControlBar/#playPauseBtn and webdav-visibility-toggle
rules; close-button red unified on #DC2626/#EF4444
- palette header comments in both QSS files updated as the reference
QGraphicsDropShadowEffect forces the host widget through an off-screen
software Gaussian blur on every repaint. On the floating sidebar (HomeView,
blur 25) and the player right sidebar (PlayerView, blur 35), the blur was
re-rasterized on every frame of the 350ms slide animation, the main source
of UI jank.
Bake the blur once into a 9-patch PNG (cached to CacheLocation/shadows via
QGraphicsBlurEffect) and render it at runtime as a cheap 9-slice stretch with
zero per-frame blur cost:
- utils/shadowpixmap: off-screen bake + on-disk cache, dpr-aware.
- components/shadowoverlay: mouse-transparent sibling widget, lazy-baked,
paints the 9-patch; optional body cutout for semi-transparent targets so
the shadow does not bleed through the body.
- homeview/playerview: drop the effect; the overlay tracks the sidebar
geometry via the existing eventFilter (Move/Resize/Show/Hide), so every
setGeometry/move site and the slide animation drive it for free.