feat: Last.fm beta, Similar Artists, Statistics Last.fm stats, TooltipPortal, CustomSelect, Psychowave theme (v1.7.0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-03-20 18:35:26 +01:00
parent 9400a5fb2b
commit 2ba7845c79
31 changed files with 1379 additions and 365 deletions
+92 -217
View File
@@ -1716,57 +1716,6 @@
}
}
/* ── Drifting color orbs ── */
@keyframes orb-a {
0% {
transform: translate(0px, 0px) scale(1);
}
33% {
transform: translate(90px, -50px) scale(1.12);
}
66% {
transform: translate(-40px, 70px) scale(0.94);
}
100% {
transform: translate(0px, 0px) scale(1);
}
}
@keyframes orb-b {
0% {
transform: translate(0px, 0px) scale(1);
}
33% {
transform: translate(-70px, 40px) scale(1.08);
}
66% {
transform: translate(50px, -60px) scale(1.14);
}
100% {
transform: translate(0px, 0px) scale(1);
}
}
@keyframes orb-c {
0% {
transform: translate(0px, 0px) scale(1);
}
50% {
transform: translate(60px, 50px) scale(0.9);
}
100% {
transform: translate(0px, 0px) scale(1);
}
}
/* ── Cover breathing ── */
@keyframes cover-breathe {
@@ -1781,38 +1730,23 @@
}
@keyframes ken-burns {
0% {
transform: scale(1.08) translate(0%, 0%);
}
25% {
transform: scale(1.12) translate(-1.5%, 1%);
}
50% {
transform: scale(1.10) translate(1%, -1.5%);
}
75% {
transform: scale(1.13) translate(1.5%, 0.5%);
}
100% {
transform: scale(1.08) translate(0%, 0%);
}
0% { transform: scale(1.1) translate(-8%, -8%); }
25% { transform: scale(1.15) translate(8%, -8%); }
50% { transform: scale(1.1) translate(8%, 8%); }
75% { transform: scale(1.15) translate(-8%, 8%); }
100% { transform: scale(1.1) translate(-8%, -8%); }
}
/* ── Blurred background ── */
.fs-bg {
position: absolute;
inset: -15%;
inset: -30%;
background-size: cover;
background-position: top center;
background-position: center center;
filter: blur(6px) brightness(0.25) saturate(1.6);
animation: ken-burns 40s ease-in-out infinite;
transform: scale(1.2);
animation: ken-burns 90s ease-in-out infinite;
z-index: 0;
will-change: opacity;
will-change: transform;
pointer-events: none;
transition: opacity 700ms ease;
}
@@ -1825,45 +1759,6 @@
pointer-events: none;
}
/* ── Drifting color orbs ── */
.fs-orb {
position: absolute;
border-radius: 50%;
filter: blur(110px);
opacity: 0.22;
pointer-events: none;
z-index: 0;
}
.fs-orb-1 {
background: var(--ctp-mauve);
width: 700px;
height: 700px;
top: -220px;
left: -180px;
animation: orb-a 20s ease-in-out infinite;
}
.fs-orb-2 {
background: var(--ctp-blue);
width: 600px;
height: 600px;
bottom: -180px;
right: -120px;
animation: orb-b 26s ease-in-out infinite;
animation-delay: -9s;
}
.fs-orb-3 {
background: var(--ctp-lavender);
width: 480px;
height: 480px;
top: 35%;
right: 5%;
animation: orb-c 17s ease-in-out infinite;
animation-delay: -14s;
}
/* ── Close button ── */
.fs-close {
position: absolute;
@@ -2272,71 +2167,11 @@
}
/* ─ CSS Tooltips ─ */
/* Tooltips are handled by TooltipPortal (React portal) — no CSS pseudo-elements needed */
[data-tooltip] {
position: relative;
}
[data-tooltip]::before,
[data-tooltip]::after {
position: absolute;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease, visibility 0.2s ease;
pointer-events: none;
z-index: 9999;
}
[data-tooltip]::before {
content: '';
border: 5px solid transparent;
border-top-color: var(--border-subtle);
bottom: 100%;
left: 50%;
transform: translateX(-50%);
}
/* Inner triangle for the tooltip arrow to match background */
[data-tooltip]::after {
content: attr(data-tooltip);
background: var(--bg-card);
color: var(--text-primary);
padding: var(--space-1) var(--space-2);
border-radius: var(--radius-sm);
font-size: 12px;
bottom: calc(100% + 6px);
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6);
font-weight: 500;
border: 1px solid var(--border-subtle);
}
[data-tooltip]:hover::before,
[data-tooltip]:hover::after {
opacity: 1;
visibility: visible;
}
[data-tooltip-wrap]::after {
white-space: pre-line;
max-width: 220px;
text-align: left;
}
/* Modifiers for position */
[data-tooltip][data-tooltip-pos="bottom"]::before {
top: 100%;
bottom: auto;
border-top-color: transparent;
border-bottom-color: var(--border-subtle);
}
[data-tooltip][data-tooltip-pos="bottom"]::after {
top: calc(100% + 6px);
bottom: auto;
}
/* ─ Playlists Page ─ */
.playlist-page-header {
@@ -2479,7 +2314,7 @@
.stats-overview {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(3, 1fr);
gap: var(--space-4);
}
@@ -2746,47 +2581,6 @@
background: linear-gradient(to bottom, transparent 40%, var(--bg-app) 100%);
}
/* Orbs — will-change promotes each to its own compositor layer so
the blur is composited on the GPU and doesn't repaint on every frame */
.np-orb {
position: absolute;
border-radius: 50%;
filter: blur(70px);
opacity: 0.3;
pointer-events: none;
z-index: 0;
will-change: transform;
}
.np-orb-1 {
background: var(--ctp-mauve);
width: 400px;
height: 400px;
top: -120px;
left: -80px;
animation: orb-a 18s ease-in-out infinite;
}
.np-orb-2 {
background: var(--ctp-blue);
width: 320px;
height: 320px;
bottom: -80px;
right: -60px;
animation: orb-b 24s ease-in-out infinite;
animation-delay: -9s;
}
.np-orb-3 {
background: var(--ctp-lavender);
width: 260px;
height: 260px;
top: 30%;
right: 10%;
animation: orb-c 15s ease-in-out infinite;
animation-delay: -5s;
}
.np-hero-content {
position: relative;
z-index: 1;
@@ -3208,6 +3002,87 @@
min-width: 140px;
}
/* ─ Custom Select ─ */
.custom-select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
width: 100%;
padding: 0.5rem 0.75rem;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text-primary);
font-size: 0.875rem;
cursor: pointer;
text-align: left;
transition: border-color 0.15s, background 0.15s;
}
.custom-select-trigger:hover {
border-color: var(--accent);
background: var(--bg-hover);
}
.custom-select-trigger:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.custom-select-label {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.custom-select-chevron {
flex-shrink: 0;
color: var(--text-muted);
transition: transform 0.2s ease;
}
.custom-select-chevron.open {
transform: rotate(180deg);
}
.custom-select-dropdown {
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
overflow-y: auto;
overscroll-behavior: contain;
}
.custom-select-option {
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
color: var(--text-primary);
cursor: pointer;
transition: background 0.1s;
}
.custom-select-option:hover {
background: var(--bg-hover);
}
.custom-select-option.selected {
color: var(--accent);
font-weight: 600;
background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.custom-select-option.disabled {
color: var(--text-muted);
cursor: default;
}
.custom-select-group-label {
padding: 0.375rem 0.75rem 0.25rem;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-muted);
border-top: 1px solid var(--border-subtle);
margin-top: 0.25rem;
}
.custom-select-group-label:first-child {
border-top: none;
margin-top: 0;
}
.eq-ctrl-btn {
background: var(--bg-hover);
border: 1px solid var(--border);
+55
View File
@@ -422,6 +422,61 @@
--danger: #bf616a;
}
/* ─── Psychowave — Synthwave / Retrowave ─── */
[data-theme='psychowave'] {
color-scheme: dark;
--select-arrow: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23c9a8ff%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpolyline%20points%3D%226%209%2012%2015%2018%209%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E");
/* ── Palette ── */
--ctp-crust: #120720;
--ctp-mantle: #160a28;
--ctp-base: #1c0b35;
--ctp-surface0: #2a1050;
--ctp-surface1: #381860;
--ctp-surface2: #4a2878;
--ctp-overlay0: #5e3d90;
--ctp-overlay1: #7b50b8;
--ctp-overlay2: #9868d8;
/* Soft lavender text */
--ctp-text: #f0e6ff;
--ctp-subtext1: #c9a8ff;
--ctp-subtext0: #a882e0;
/* Neon accents */
--ctp-mauve: #ff2d78; /* hot pink — primary accent & button gradient start */
--ctp-lavender: #bf5fff; /* electric purple — button gradient end */
--ctp-pink: #ff2d78;
--ctp-flamingo: #ff6b9d;
--ctp-rosewater: #ff8fc0;
--ctp-blue: #00d4ff; /* electric cyan */
--ctp-sapphire: #007aff;
--ctp-sky: #00bfff;
--ctp-teal: #00e5cc;
--ctp-green: #39ff14; /* neon green */
--ctp-yellow: #ffd700; /* neon gold */
--ctp-peach: #ff8c42;
--ctp-maroon: #ff4d6d;
--ctp-red: #ff2d55;
/* ── Semantic tokens ── */
--bg-app: #1c0b35;
--bg-sidebar: #160a28;
--bg-card: #2a1050;
--bg-hover: #381860;
--bg-player: #120720;
--bg-glass: rgba(28, 11, 53, 0.82);
--accent: #ff2d78;
--accent-dim: rgba(255, 45, 120, 0.15);
--accent-glow: rgba(255, 45, 120, 0.45);
--text-primary: #f0e6ff;
--text-secondary:#c9a8ff;
--text-muted: #9b72d0;
--border: #4a2878;
--border-subtle: #2a1050;
--positive: #39ff14;
--warning: #ffd700;
--danger: #ff2d55;
}
/* ─── Global Base Settings ─── */
:root {
/* Typography */