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',