feat: add cyan-orange color scheme with enhanced visual effects

- Add new CSS animations (hologramGlow, float, shimmer, scanline)
- Introduce cyan and orange color palette variants
- Update scrollbar styling with orange theme colors
- Modify player components with new gradient backgrounds
- Update global theme colors and surface styling
- Adjust default settings for enhanced visual experience
This commit is contained in:
Ante Budimir 2025-11-15 21:17:54 +02:00
parent 7a12e4657f
commit 228fc8e82b
12 changed files with 261 additions and 40 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "feishin", "name": "feishin",
"version": "0.22.0", "version": "0.23.0",
"description": "A modern self-hosted music player.", "description": "A modern self-hosted music player.",
"keywords": [ "keywords": [
"subsonic", "subsonic",

View file

@ -22,7 +22,10 @@
aspect-ratio: 1/1; aspect-ratio: 1/1;
overflow: hidden; overflow: hidden;
background: var(--theme-card-default-bg); 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 { &::before {
position: absolute; position: absolute;
@ -39,6 +42,14 @@
} }
&:hover { &: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 { &::before {
opacity: 0.5; opacity: 0.5;
} }

View file

@ -23,6 +23,10 @@
display: flex; display: flex;
grid-area: image; grid-area: image;
align-items: flex-end; align-items: flex-end;
img {
border-radius: var(--theme-radius-md);
}
} }
.info-column { .info-column {

View file

@ -3,6 +3,8 @@
z-index: 190; z-index: 190;
width: 100%; width: 100%;
height: 65px; height: 65px;
border-bottom: 2px solid var(--theme-orange-transparent-30);
box-shadow: 0 4px 12px var(--theme-orange-transparent-10);
} }
.header { .header {

View file

@ -25,6 +25,11 @@
align-items: center; align-items: center;
aspect-ratio: 1/1; aspect-ratio: 1/1;
overflow: hidden; 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 { &::before {
position: absolute; position: absolute;
@ -41,6 +46,13 @@
} }
&:hover { &: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 { &::before {
opacity: 0.5; opacity: 0.5;
} }
@ -72,7 +84,6 @@
height: 100% !important; height: 100% !important;
max-height: 100%; max-height: 100%;
border: 0; border: 0;
border-radius: var(--theme-radius-md);
img { img {
height: 100%; height: 100%;

View file

@ -38,3 +38,30 @@
background: var(--theme-overlay-header); background: var(--theme-overlay-header);
backdrop-filter: blur(var(--image-blur)); 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;
}
}

View file

@ -428,6 +428,9 @@ export const FullScreenPlayer = () => {
srcLoaded: true, 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 imageUrl = currentSong?.imageUrl && currentSong.imageUrl.replace(/size=\d+/g, 'size=500');
const backgroundImage = const backgroundImage =
imageUrl && dynamicIsImage imageUrl && dynamicIsImage
@ -457,6 +460,14 @@ export const FullScreenPlayer = () => {
} }
/> />
)} )}
<div
className={styles.scanlineOverlay}
style={
{
'--album-color': scanlineColor,
} as CSSProperties
}
/>
<div className={styles.responsiveContainer}> <div className={styles.responsiveContainer}>
<FullScreenPlayerImage /> <FullScreenPlayerImage />
<FullScreenPlayerQueue /> <FullScreenPlayerQueue />

View file

@ -12,13 +12,20 @@
width: 100%; width: 100%;
padding: 0.5rem; padding: 0.5rem;
overflow: visible; overflow: visible;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
button { button {
display: flex; display: flex;
} }
&:focus-visible { &: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 { &:disabled {
@ -32,13 +39,21 @@
.player-button.active { .player-button.active {
svg { svg {
fill: var(--theme-colors-primary-filled); fill: var(--theme-orange-base);
filter: drop-shadow(0 0 6px var(--theme-orange-transparent-50));
} }
} }
.main { .main {
background: var(--theme-colors-foreground) !important; background: linear-gradient(135deg, var(--theme-orange-base), var(--theme-orange-medium)) !important;
border-radius: 50%; 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 { svg {
display: flex; display: flex;

View file

@ -1,7 +1,13 @@
.container { .container {
width: 100vw; width: 100vw;
height: 100%; 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 { .controls-grid {

View file

@ -530,51 +530,51 @@ const initialState: SettingsState = {
font: { font: {
builtIn: 'Poppins', builtIn: 'Poppins',
custom: null, custom: null,
system: null, system: 'Noto Sans Regular',
type: FontType.BUILT_IN, type: FontType.SYSTEM,
}, },
general: { general: {
accent: 'rgb(53, 116, 252)', accent: 'rgb(214, 46, 83)',
albumArtRes: undefined, albumArtRes: undefined,
albumBackground: false, albumBackground: true,
albumBackgroundBlur: 6, albumBackgroundBlur: 50,
artistBackground: false, artistBackground: false,
artistBackgroundBlur: 6, artistBackgroundBlur: 6,
artistItems, artistItems,
buttonSize: 15, buttonSize: 25,
disabledContextMenu: {}, disabledContextMenu: {},
doubleClickQueueAll: true, doubleClickQueueAll: false,
externalLinks: true, externalLinks: true,
followSystemTheme: false, followSystemTheme: false,
genreTarget: GenreTarget.TRACK, genreTarget: GenreTarget.ALBUM,
homeFeature: true, homeFeature: true,
homeItems, homeItems,
language: 'en', language: 'en',
lastFM: true, lastFM: true,
lastfmApiKey: '', lastfmApiKey: '',
musicBrainz: true, musicBrainz: true,
nativeAspectRatio: false, nativeAspectRatio: true,
passwordStore: undefined, passwordStore: undefined,
playButtonBehavior: Play.NOW, playButtonBehavior: Play.NOW,
playerbarOpenDrawer: false, playerbarOpenDrawer: false,
resume: true, resume: true,
showQueueDrawerButton: false, showQueueDrawerButton: true,
sidebarCollapsedNavigation: true, sidebarCollapsedNavigation: true,
sidebarCollapseShared: false, sidebarCollapseShared: false,
sidebarItems, sidebarItems,
sidebarPlaylistList: true, sidebarPlaylistList: true,
sideQueueType: 'sideQueue', sideQueueType: 'sideDrawerQueue',
skipButtons: { skipButtons: {
enabled: false, enabled: true,
skipBackwardSeconds: 5, skipBackwardSeconds: 5,
skipForwardSeconds: 10, skipForwardSeconds: 5,
}, },
theme: AppTheme.DEFAULT_DARK, theme: AppTheme.DEFAULT_DARK,
themeDark: AppTheme.DEFAULT_DARK, themeDark: AppTheme.DEFAULT_DARK,
themeLight: AppTheme.DEFAULT_LIGHT, themeLight: AppTheme.DEFAULT_LIGHT,
volumeWheelStep: 5, volumeWheelStep: 5,
volumeWidth: 70, volumeWidth: 200,
zoomFactor: 100, zoomFactor: 145,
}, },
hotkeys: { hotkeys: {
bindings: { bindings: {
@ -620,7 +620,7 @@ const initialState: SettingsState = {
delayMs: 0, delayMs: 0,
enableAutoTranslation: false, enableAutoTranslation: false,
enableNeteaseTranslation: false, enableNeteaseTranslation: false,
fetch: false, fetch: true,
follow: true, follow: true,
fontSize: 24, fontSize: 24,
fontSizeUnsync: 24, fontSizeUnsync: 24,
@ -636,8 +636,8 @@ const initialState: SettingsState = {
}, },
playback: { playback: {
audioDeviceId: undefined, audioDeviceId: undefined,
crossfadeDuration: 5, crossfadeDuration: 75,
crossfadeStyle: CrossfadeStyle.EQUALPOWER, crossfadeStyle: CrossfadeStyle.DIPPED,
mediaSession: false, mediaSession: false,
mpvExtraParameters: [], mpvExtraParameters: [],
mpvProperties: { mpvProperties: {
@ -666,7 +666,7 @@ const initialState: SettingsState = {
webAudio: true, webAudio: true,
}, },
remote: { remote: {
enabled: false, enabled: true,
password: randomString(8), password: randomString(8),
port: 4333, port: 4333,
username: 'feishin', username: 'feishin',

View file

@ -33,6 +33,48 @@ html {
background: var(--theme-colors-background); 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, input,
button, button,
textarea, textarea,
@ -59,6 +101,8 @@ img {
#app { #app {
height: inherit; height: inherit;
position: relative;
z-index: 10;
} }
::-webkit-scrollbar { ::-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-face {
font-family: Archivo; font-family: Archivo;
font-weight: 100 1000; font-weight: 100 1000;
@ -224,6 +312,51 @@ button {
:root { :root {
--theme-background-noise: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMDAiIGhlaWdodD0iMzAwIj48ZmlsdGVyIGlkPSJhIiB4PSIwIiB5PSIwIj48ZmVUdXJidWxlbmNlIHR5cGU9ImZyYWN0YWxOb2lzZSIgYmFzZUZyZXF1ZW5jeT0iLjc1IiBzdGl0Y2hUaWxlcz0ic3RpdGNoIi8+PGZlQ29sb3JNYXRyaXggdHlwZT0ic2F0dXJhdGUiIHZhbHVlcz0iMCIvPjwvZmlsdGVyPjxwYXRoIGZpbHRlcj0idXJsKCNhKSIgb3BhY2l0eT0iLjA1IiBkPSJNMCAwaDMwMHYzMDBIMHoiLz48L3N2Zz4='); --theme-background-noise: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMDAiIGhlaWdodD0iMzAwIj48ZmlsdGVyIGlkPSJhIiB4PSIwIiB5PSIwIj48ZmVUdXJidWxlbmNlIHR5cGU9ImZyYWN0YWxOb2lzZSIgYmFzZUZyZXF1ZW5jeT0iLjc1IiBzdGl0Y2hUaWxlcz0ic3RpdGNoIi8+PGZlQ29sb3JNYXRyaXggdHlwZT0ic2F0dXJhdGUiIHZhbHVlcz0iMCIvPjwvZmlsdGVyPjxwYXRoIGZpbHRlcj0idXJsKCNhKSIgb3BhY2l0eT0iLjA1IiBkPSJNMCAwaDMwMHYzMDBIMHoiLz48L3N2Zz4=');
--theme-fullscreen-player-text-shadow: black 0px 0px 10px; --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-xs: var(--mantine-font-size-xs);
--theme-font-size-sm: var(--mantine-font-size-sm); --theme-font-size-sm: var(--mantine-font-size-sm);
--theme-font-size-md: var(--mantine-font-size-md); --theme-font-size-md: var(--mantine-font-size-md);

View file

@ -7,10 +7,10 @@ export const defaultTheme: AppThemeConfiguration = {
'overlay-subheader': 'overlay-subheader':
'linear-gradient(180deg, rgb(0 0 0 / 5%) 0%, var(--theme-colors-background) 100%), var(--theme-background-noise)', 'linear-gradient(180deg, rgb(0 0 0 / 5%) 0%, var(--theme-colors-background) 100%), var(--theme-background-noise)',
'root-font-size': '16px', 'root-font-size': '16px',
'scrollbar-handle-active-background': 'rgba(160, 160, 160, 60%)', 'scrollbar-handle-active-background': 'rgba(255, 142, 83, 0.7)',
'scrollbar-handle-background': 'rgba(160, 160, 160, 30%)', 'scrollbar-handle-background': 'rgba(255, 142, 83, 0.4)',
'scrollbar-handle-border-radius': '0px', 'scrollbar-handle-border-radius': '0.3rem',
'scrollbar-handle-hover-background': 'rgba(160, 160, 160, 60%)', 'scrollbar-handle-hover-background': 'rgba(255, 142, 83, 0.9)',
'scrollbar-size': '12px', 'scrollbar-size': '12px',
'scrollbar-track-active-background': 'transparent', 'scrollbar-track-active-background': 'transparent',
'scrollbar-track-background': 'transparent', 'scrollbar-track-background': 'transparent',
@ -18,17 +18,18 @@ export const defaultTheme: AppThemeConfiguration = {
'scrollbar-track-hover-background': 'transparent', 'scrollbar-track-hover-background': 'transparent',
}, },
colors: { colors: {
background: 'rgb(16, 16, 16)', background: 'rgb(2, 26, 26)',
'background-alternate': 'rgb(0, 0, 0)', 'background-alternate': 'rgb(19, 16, 16)',
black: 'rgb(0, 0, 0)', black: 'rgb(0, 0, 0)',
foreground: 'rgb(225, 225, 225)', foreground: 'rgb(240, 240, 240)',
'foreground-muted': 'rgb(150, 150, 150)', 'foreground-muted': 'rgb(187, 187, 187)',
'state-error': 'rgb(204, 50, 50)', primary: 'rgb(255, 142, 83)',
'state-info': 'rgb(53, 116, 252)', 'state-error': 'rgb(255, 0, 0)',
'state-success': 'rgb(50, 204, 50)', 'state-info': 'rgb(0, 183, 255)',
'state-warning': 'rgb(255, 120, 120)', 'state-success': 'rgb(0, 255, 255)',
surface: 'rgb(24, 24, 24)', 'state-warning': 'rgb(255, 142, 83)',
'surface-foreground': 'rgb(215, 215, 215)', surface: 'rgba(2, 26, 26, 0.8)',
'surface-foreground': 'rgb(240, 240, 240)',
white: 'rgb(255, 255, 255)', white: 'rgb(255, 255, 255)',
}, },
mode: 'dark', mode: 'dark',