diff --git a/package.json b/package.json index 9978e272..4afff5b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "feishin", - "version": "0.22.0", + "version": "0.23.0", "description": "A modern self-hosted music player.", "keywords": [ "subsonic", diff --git a/src/renderer/components/card/poster-card.module.css b/src/renderer/components/card/poster-card.module.css index 224e72cf..e2570403 100644 --- a/src/renderer/components/card/poster-card.module.css +++ b/src/renderer/components/card/poster-card.module.css @@ -22,7 +22,10 @@ aspect-ratio: 1/1; overflow: hidden; background: var(--theme-card-default-bg); - border-radius: var(--theme-card-poster-radius); + border-radius: var(--theme-radius-md); + border: 2px solid var(--theme-orange-transparent-40); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: var(--theme-shadow-orange-glow-soft); &::before { position: absolute; @@ -39,6 +42,14 @@ } &:hover { + border-color: var(--theme-orange-transparent-70); + box-shadow: + var(--theme-shadow-orange-glow-medium), + 0 0 5px var(--theme-orange-transparent-30), + 0 0 5px var(--theme-orange-transparent-20); + transform: scale(1.03); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + &::before { opacity: 0.5; } diff --git a/src/renderer/components/feature-carousel/feature-carousel.module.css b/src/renderer/components/feature-carousel/feature-carousel.module.css index 75c67339..a4bc8c7b 100644 --- a/src/renderer/components/feature-carousel/feature-carousel.module.css +++ b/src/renderer/components/feature-carousel/feature-carousel.module.css @@ -23,6 +23,10 @@ display: flex; grid-area: image; align-items: flex-end; + + img { + border-radius: var(--theme-radius-md); + } } .info-column { diff --git a/src/renderer/components/page-header/page-header.module.css b/src/renderer/components/page-header/page-header.module.css index 04603d4f..ad63a40e 100644 --- a/src/renderer/components/page-header/page-header.module.css +++ b/src/renderer/components/page-header/page-header.module.css @@ -3,6 +3,8 @@ z-index: 190; width: 100%; height: 65px; + border-bottom: 2px solid var(--theme-orange-transparent-30); + box-shadow: 0 4px 12px var(--theme-orange-transparent-10); } .header { diff --git a/src/renderer/components/virtual-grid/grid-card/poster-card.module.css b/src/renderer/components/virtual-grid/grid-card/poster-card.module.css index 1755bbe1..28e56a7c 100644 --- a/src/renderer/components/virtual-grid/grid-card/poster-card.module.css +++ b/src/renderer/components/virtual-grid/grid-card/poster-card.module.css @@ -25,6 +25,11 @@ align-items: center; aspect-ratio: 1/1; overflow: hidden; + border-radius: var(--theme-radius-md); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + background: var(--theme-card-default-bg); + border: 2px solid var(--theme-orange-transparent-40); + box-shadow: var(--theme-shadow-orange-glow-soft); &::before { position: absolute; @@ -41,6 +46,13 @@ } &:hover { + border-color: var(--theme-orange-transparent-70); + box-shadow: + var(--theme-shadow-orange-glow-medium), + 0 0 5px var(--theme-orange-transparent-30), + 0 0 5px var(--theme-orange-transparent-20); + transform: scale(1.03); + &::before { opacity: 0.5; } @@ -72,7 +84,6 @@ height: 100% !important; max-height: 100%; border: 0; - border-radius: var(--theme-radius-md); img { height: 100%; diff --git a/src/renderer/features/player/components/full-screen-player.module.css b/src/renderer/features/player/components/full-screen-player.module.css index 79d888c4..00f0ff2c 100644 --- a/src/renderer/features/player/components/full-screen-player.module.css +++ b/src/renderer/features/player/components/full-screen-player.module.css @@ -38,3 +38,30 @@ background: var(--theme-overlay-header); backdrop-filter: blur(var(--image-blur)); } + +.scanline-overlay { + position: absolute; + top: 0; + left: 0; + z-index: 1; + width: 100%; + height: 100%; + background: linear-gradient( + 0deg, + transparent 0%, + var(--album-color, rgba(0, 255, 255, 0.15)) 50%, + transparent 100% + ); + background-size: 100% 200%; + animation: scanline 6s linear infinite; + pointer-events: none; +} + +@keyframes scanline { + 0% { + background-position: 0 -100vh; + } + 100% { + background-position: 0 100vh; + } +} diff --git a/src/renderer/features/player/components/full-screen-player.tsx b/src/renderer/features/player/components/full-screen-player.tsx index 1092c860..75d0ab8c 100644 --- a/src/renderer/features/player/components/full-screen-player.tsx +++ b/src/renderer/features/player/components/full-screen-player.tsx @@ -428,6 +428,9 @@ export const FullScreenPlayer = () => { srcLoaded: true, }); + // Convert RGB to RGB with opacity for scanline effect + const scanlineColor = background ? background.replace('rgb', 'rgba').replace(')', ', 0.15)') : 'rgba(0, 255, 255, 0.15)'; + const imageUrl = currentSong?.imageUrl && currentSong.imageUrl.replace(/size=\d+/g, 'size=500'); const backgroundImage = imageUrl && dynamicIsImage @@ -457,6 +460,14 @@ export const FullScreenPlayer = () => { } /> )} +
diff --git a/src/renderer/features/player/components/player-button.module.css b/src/renderer/features/player/components/player-button.module.css index d74dabc2..c98d1ac2 100644 --- a/src/renderer/features/player/components/player-button.module.css +++ b/src/renderer/features/player/components/player-button.module.css @@ -12,13 +12,20 @@ width: 100%; padding: 0.5rem; overflow: visible; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); button { display: flex; } &:focus-visible { - outline: 1px var(--theme-colors-primary-filled) solid; + outline: 2px var(--theme-orange-base) solid; + box-shadow: var(--theme-shadow-orange-glow-soft); + } + + &:hover { + transform: scale(1.1); + filter: drop-shadow(0 0 8px var(--theme-orange-transparent-40)); } &:disabled { @@ -32,13 +39,21 @@ .player-button.active { svg { - fill: var(--theme-colors-primary-filled); + fill: var(--theme-orange-base); + filter: drop-shadow(0 0 6px var(--theme-orange-transparent-50)); } } .main { - background: var(--theme-colors-foreground) !important; + background: linear-gradient(135deg, var(--theme-orange-base), var(--theme-orange-medium)) !important; border-radius: 50%; + box-shadow: var(--theme-shadow-orange-glow-soft); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + &:hover { + box-shadow: var(--theme-shadow-orange-glow-medium); + transform: scale(1.15); + } svg { display: flex; diff --git a/src/renderer/features/player/components/playerbar.module.css b/src/renderer/features/player/components/playerbar.module.css index e1566402..b21ce191 100644 --- a/src/renderer/features/player/components/playerbar.module.css +++ b/src/renderer/features/player/components/playerbar.module.css @@ -1,7 +1,13 @@ .container { width: 100vw; height: 100%; - border-top: var(--theme-colors-border); + border-top: 2px solid var(--theme-orange-transparent-40); + background: linear-gradient( + 180deg, + var(--theme-colors-background) 0%, + rgba(2, 26, 26, 0.95) 100% + ); + box-shadow: 0 -4px 12px var(--theme-orange-transparent-15); } .controls-grid { diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 3974d9eb..464e32cd 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -530,51 +530,51 @@ const initialState: SettingsState = { font: { builtIn: 'Poppins', custom: null, - system: null, - type: FontType.BUILT_IN, + system: 'Noto Sans Regular', + type: FontType.SYSTEM, }, general: { - accent: 'rgb(53, 116, 252)', + accent: 'rgb(214, 46, 83)', albumArtRes: undefined, - albumBackground: false, - albumBackgroundBlur: 6, + albumBackground: true, + albumBackgroundBlur: 50, artistBackground: false, artistBackgroundBlur: 6, artistItems, - buttonSize: 15, + buttonSize: 25, disabledContextMenu: {}, - doubleClickQueueAll: true, + doubleClickQueueAll: false, externalLinks: true, followSystemTheme: false, - genreTarget: GenreTarget.TRACK, + genreTarget: GenreTarget.ALBUM, homeFeature: true, homeItems, language: 'en', lastFM: true, lastfmApiKey: '', musicBrainz: true, - nativeAspectRatio: false, + nativeAspectRatio: true, passwordStore: undefined, playButtonBehavior: Play.NOW, playerbarOpenDrawer: false, resume: true, - showQueueDrawerButton: false, + showQueueDrawerButton: true, sidebarCollapsedNavigation: true, sidebarCollapseShared: false, sidebarItems, sidebarPlaylistList: true, - sideQueueType: 'sideQueue', + sideQueueType: 'sideDrawerQueue', skipButtons: { - enabled: false, + enabled: true, skipBackwardSeconds: 5, - skipForwardSeconds: 10, + skipForwardSeconds: 5, }, theme: AppTheme.DEFAULT_DARK, themeDark: AppTheme.DEFAULT_DARK, themeLight: AppTheme.DEFAULT_LIGHT, volumeWheelStep: 5, - volumeWidth: 70, - zoomFactor: 100, + volumeWidth: 200, + zoomFactor: 145, }, hotkeys: { bindings: { @@ -620,7 +620,7 @@ const initialState: SettingsState = { delayMs: 0, enableAutoTranslation: false, enableNeteaseTranslation: false, - fetch: false, + fetch: true, follow: true, fontSize: 24, fontSizeUnsync: 24, @@ -636,8 +636,8 @@ const initialState: SettingsState = { }, playback: { audioDeviceId: undefined, - crossfadeDuration: 5, - crossfadeStyle: CrossfadeStyle.EQUALPOWER, + crossfadeDuration: 75, + crossfadeStyle: CrossfadeStyle.DIPPED, mediaSession: false, mpvExtraParameters: [], mpvProperties: { @@ -666,7 +666,7 @@ const initialState: SettingsState = { webAudio: true, }, remote: { - enabled: false, + enabled: true, password: randomString(8), port: 4333, username: 'feishin', diff --git a/src/shared/styles/global.css b/src/shared/styles/global.css index c9de9195..0a5bba30 100644 --- a/src/shared/styles/global.css +++ b/src/shared/styles/global.css @@ -33,6 +33,48 @@ html { background: var(--theme-colors-background); } +body::before, +html::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + 105deg, + transparent 40%, + rgba(0, 183, 255, 0.1) 45%, + rgba(0, 183, 255, 0.2) 50%, + rgba(0, 183, 255, 0.1) 55%, + transparent 60% + ); + background-size: 200% 100%; + animation: shimmer 8s infinite; + pointer-events: none; + z-index: 1; +} + +body::after, +html::after { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + 0deg, + transparent 0%, + rgba(0, 255, 255, 0.03) 50%, + transparent 100% + ); + background-size: 100% 200%; + animation: scanline 6s linear infinite; + pointer-events: none; + z-index: 2; +} + input, button, textarea, @@ -59,6 +101,8 @@ img { #app { height: inherit; + position: relative; + z-index: 10; } ::-webkit-scrollbar { @@ -120,6 +164,50 @@ button { } } +@keyframes hologramGlow { + 0%, + 100% { + box-shadow: + 0 0 10px rgba(0, 183, 255, 0.2), + 0 0 20px rgba(0, 183, 255, 0.1), + 0 0 40px rgba(0, 255, 255, 0.05); + } + 50% { + box-shadow: + 0 0 20px rgba(0, 183, 255, 0.4), + 0 0 40px rgba(0, 183, 255, 0.2), + 0 0 80px rgba(0, 255, 255, 0.1); + } +} + +@keyframes float { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-10px); + } +} + +@keyframes shimmer { + 0% { + background-position: -100% 0; + } + 100% { + background-position: 200% 0; + } +} + +@keyframes scanline { + 0% { + background-position: 0 -100vh; + } + 100% { + background-position: 0 100vh; + } +} + @font-face { font-family: Archivo; font-weight: 100 1000; @@ -224,6 +312,51 @@ button { :root { --theme-background-noise: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMDAiIGhlaWdodD0iMzAwIj48ZmlsdGVyIGlkPSJhIiB4PSIwIiB5PSIwIj48ZmVUdXJidWxlbmNlIHR5cGU9ImZyYWN0YWxOb2lzZSIgYmFzZUZyZXF1ZW5jeT0iLjc1IiBzdGl0Y2hUaWxlcz0ic3RpdGNoIi8+PGZlQ29sb3JNYXRyaXggdHlwZT0ic2F0dXJhdGUiIHZhbHVlcz0iMCIvPjwvZmlsdGVyPjxwYXRoIGZpbHRlcj0idXJsKCNhKSIgb3BhY2l0eT0iLjA1IiBkPSJNMCAwaDMwMHYzMDBIMHoiLz48L3N2Zz4='); --theme-fullscreen-player-text-shadow: black 0px 0px 10px; + + /* Intro-inspired color palette */ + --theme-orange-base: rgb(255, 142, 83); + --theme-orange-medium: rgb(255, 123, 52); + --theme-orange-dark: rgb(255, 89, 0); + --theme-orange-transparent-70: rgba(255, 142, 83, 0.7); + --theme-orange-transparent-40: rgba(255, 142, 83, 0.4); + --theme-orange-transparent-30: rgba(255, 142, 83, 0.3); + --theme-orange-transparent-20: rgba(255, 142, 83, 0.2); + --theme-orange-transparent-15: rgba(255, 142, 83, 0.15); + --theme-orange-transparent-10: rgba(255, 142, 83, 0.1); + + --theme-cyan-primary: rgb(0, 183, 255); + --theme-cyan-secondary: rgb(0, 255, 255); + --theme-cyan-transparent-80: rgba(0, 183, 255, 0.8); + --theme-cyan-transparent-70: rgba(0, 183, 255, 0.7); + --theme-cyan-transparent-60: rgba(0, 183, 255, 0.6); + --theme-cyan-transparent-50: rgba(0, 183, 255, 0.5); + --theme-cyan-transparent-40: rgba(0, 183, 255, 0.4); + --theme-cyan-transparent-30: rgba(0, 183, 255, 0.3); + --theme-cyan-transparent-20: rgba(0, 183, 255, 0.2); + --theme-cyan-transparent-10: rgba(0, 183, 255, 0.1); + --theme-cyan-transparent-05: rgba(0, 183, 255, 0.05); + --theme-cyan-transparent-03: rgba(0, 183, 255, 0.03); + + /* Gradients */ + --theme-primary-gradient: linear-gradient( + 45deg, + var(--theme-orange-dark), + var(--theme-orange-medium), + var(--theme-orange-base) + ); + --theme-cyan-gradient: linear-gradient( + 90deg, + var(--theme-cyan-primary), + var(--theme-cyan-secondary) + ); + + /* Shadows */ + --theme-shadow-cyan-glow: 0 0 10px var(--theme-cyan-transparent-30); + --theme-shadow-cyan-glow-medium: 0 0 15px var(--theme-cyan-transparent-20); + --theme-shadow-orange-glow-soft: 0 4px 12px var(--theme-orange-transparent-15); + --theme-shadow-orange-glow-medium: + 0 6px 20px var(--theme-orange-transparent-30), 0 0 20px var(--theme-orange-transparent-20); + --theme-text-shadow-cyan: 0 0 20px var(--theme-cyan-transparent-30); --theme-font-size-xs: var(--mantine-font-size-xs); --theme-font-size-sm: var(--mantine-font-size-sm); --theme-font-size-md: var(--mantine-font-size-md); diff --git a/src/shared/themes/default.ts b/src/shared/themes/default.ts index c698cc96..be3c42db 100644 --- a/src/shared/themes/default.ts +++ b/src/shared/themes/default.ts @@ -7,10 +7,10 @@ export const defaultTheme: AppThemeConfiguration = { 'overlay-subheader': 'linear-gradient(180deg, rgb(0 0 0 / 5%) 0%, var(--theme-colors-background) 100%), var(--theme-background-noise)', 'root-font-size': '16px', - 'scrollbar-handle-active-background': 'rgba(160, 160, 160, 60%)', - 'scrollbar-handle-background': 'rgba(160, 160, 160, 30%)', - 'scrollbar-handle-border-radius': '0px', - 'scrollbar-handle-hover-background': 'rgba(160, 160, 160, 60%)', + 'scrollbar-handle-active-background': 'rgba(255, 142, 83, 0.7)', + 'scrollbar-handle-background': 'rgba(255, 142, 83, 0.4)', + 'scrollbar-handle-border-radius': '0.3rem', + 'scrollbar-handle-hover-background': 'rgba(255, 142, 83, 0.9)', 'scrollbar-size': '12px', 'scrollbar-track-active-background': 'transparent', 'scrollbar-track-background': 'transparent', @@ -18,17 +18,18 @@ export const defaultTheme: AppThemeConfiguration = { 'scrollbar-track-hover-background': 'transparent', }, colors: { - background: 'rgb(16, 16, 16)', - 'background-alternate': 'rgb(0, 0, 0)', + background: 'rgb(2, 26, 26)', + 'background-alternate': 'rgb(19, 16, 16)', black: 'rgb(0, 0, 0)', - foreground: 'rgb(225, 225, 225)', - 'foreground-muted': 'rgb(150, 150, 150)', - 'state-error': 'rgb(204, 50, 50)', - 'state-info': 'rgb(53, 116, 252)', - 'state-success': 'rgb(50, 204, 50)', - 'state-warning': 'rgb(255, 120, 120)', - surface: 'rgb(24, 24, 24)', - 'surface-foreground': 'rgb(215, 215, 215)', + foreground: 'rgb(240, 240, 240)', + 'foreground-muted': 'rgb(187, 187, 187)', + primary: 'rgb(255, 142, 83)', + 'state-error': 'rgb(255, 0, 0)', + 'state-info': 'rgb(0, 183, 255)', + 'state-success': 'rgb(0, 255, 255)', + 'state-warning': 'rgb(255, 142, 83)', + surface: 'rgba(2, 26, 26, 0.8)', + 'surface-foreground': 'rgb(240, 240, 240)', white: 'rgb(255, 255, 255)', }, mode: 'dark',