diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..11b0400 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,284 @@ +# Monochrome Design System + +A comprehensive design language for consistent UI/UX across the Monochrome music streaming application. + +## Design Tokens + +### Typography Scale + +| Token | Value | Usage | +| ------------- | --------------- | ---------------------------- | +| `--text-xs` | 0.75rem (12px) | Captions, badges, timestamps | +| `--text-sm` | 0.875rem (14px) | Secondary text, labels | +| `--text-base` | 1rem (16px) | Body text (default) | +| `--text-md` | 1.125rem (18px) | Lead paragraphs | +| `--text-lg` | 1.25rem (20px) | Small headings | +| `--text-xl` | 1.5rem (24px) | H4, card titles | +| `--text-2xl` | 1.875rem (30px) | H3 | +| `--text-3xl` | 2.25rem (36px) | H2 | +| `--text-4xl` | 3rem (48px) | H1 | +| `--text-5xl` | 3.75rem (60px) | Display text | + +### Font Weights + +| Token | Value | +| ----------------- | ----- | +| `--font-normal` | 400 | +| `--font-medium` | 500 | +| `--font-semibold` | 600 | +| `--font-bold` | 700 | + +### Spacing Scale + +| Token | Value | Pixels | +| ------------ | ------- | ------ | +| `--space-1` | 0.25rem | 4px | +| `--space-2` | 0.5rem | 8px | +| `--space-3` | 0.75rem | 12px | +| `--space-4` | 1rem | 16px | +| `--space-5` | 1.25rem | 20px | +| `--space-6` | 1.5rem | 24px | +| `--space-8` | 2rem | 32px | +| `--space-10` | 2.5rem | 40px | +| `--space-12` | 3rem | 48px | +| `--space-16` | 4rem | 64px | + +### Border Radius Scale + +| Token | Value | Usage | +| --------------- | ------ | ----------------------- | +| `--radius-none` | 0 | Sharp corners | +| `--radius-xs` | 2px | Small badges, tags | +| `--radius-sm` | 4px | Inputs, small buttons | +| `--radius-md` | 8px | Cards, panels (default) | +| `--radius-lg` | 12px | Large cards, modals | +| `--radius-xl` | 16px | Hero elements | +| `--radius-2xl` | 24px | Extra large elements | +| `--radius-full` | 9999px | Circles, pills | + +### Transition Timing + +| Token | Value | +| -------------------- | ----- | +| `--duration-instant` | 0ms | +| `--duration-fast` | 150ms | +| `--duration-normal` | 300ms | +| `--duration-slow` | 500ms | + +### Easing Functions + +| Token | Value | Usage | +| ----------------- | --------------------------------------- | --------------------- | +| `--ease-linear` | linear | Continuous animations | +| `--ease-in` | cubic-bezier(0.4, 0, 1, 1) | Entering elements | +| `--ease-out` | cubic-bezier(0, 0, 0.2, 1) | Exiting elements | +| `--ease-in-out` | cubic-bezier(0.4, 0, 0.2, 1) | Standard transitions | +| `--ease-out-back` | cubic-bezier(0.34, 1.56, 0.64, 1) | Bouncy effects | +| `--ease-elastic` | cubic-bezier(0.68, -0.55, 0.265, 1.55) | Playful animations | +| `--ease-spring` | cubic-bezier(0.175, 0.885, 0.32, 1.275) | Snappy interactions | + +### Shadows + +| Token | Value | +| ---------------- | ------------------------------------------------------------------- | +| `--shadow-none` | none | +| `--shadow-xs` | 0 1px 2px 0 rgb(0 0 0 / 0.05) | +| `--shadow-sm` | 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1) | +| `--shadow-md` | 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1) | +| `--shadow-lg` | 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1) | +| `--shadow-xl` | 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1) | +| `--shadow-2xl` | 0 25px 50px -12px rgb(0 0 0 / 0.25) | +| `--shadow-inner` | inset 0 2px 4px 0 rgb(0 0 0 / 0.05) | +| `--shadow-glow` | 0 0 20px rgb(var(--highlight-rgb) / 0.5) | + +### Z-Index Scale + +| Token | Value | Usage | +| -------------- | ----- | --------------- | +| `--z-hide` | -1 | Hidden elements | +| `--z-base` | 0 | Default | +| `--z-docked` | 10 | Docked elements | +| `--z-dropdown` | 1000 | Dropdowns | +| `--z-sticky` | 1100 | Sticky headers | +| `--z-banner` | 1200 | Banners | +| `--z-overlay` | 1300 | Overlays | +| `--z-modal` | 1400 | Modals | +| `--z-popover` | 1500 | Popovers | +| `--z-tooltip` | 1600 | Tooltips | +| `--z-toast` | 1700 | Toasts | + +## Component Tokens + +### Buttons + +| Token | Value | +| ------------------ | ----------------------------- | +| `--btn-height-sm` | 32px | +| `--btn-height-md` | 40px | +| `--btn-height-lg` | 48px | +| `--btn-padding-sm` | var(--space-2) var(--space-3) | +| `--btn-padding-md` | var(--space-3) var(--space-4) | +| `--btn-padding-lg` | var(--space-4) var(--space-6) | + +### Inputs + +| Token | Value | +| ----------------- | ----------------------------- | +| `--input-height` | 40px | +| `--input-padding` | var(--space-3) var(--space-4) | + +### Cards + +| Token | Value | +| ---------------- | ---------------- | +| `--card-padding` | var(--space-4) | +| `--card-gap` | var(--space-4) | +| `--card-radius` | var(--radius-lg) | + +### Modals + +| Token | Value | +| ---------------------- | ---------------- | +| `--modal-padding` | var(--space-6) | +| `--modal-radius` | var(--radius-xl) | +| `--modal-max-width-sm` | 400px | +| `--modal-max-width-md` | 500px | +| `--modal-max-width-lg` | 600px | +| `--modal-max-width-xl` | 800px | + +## Utility Classes + +### Typography + +```css +.text-xs, .text-sm, .text-base, .text-md, .text-lg, .text-xl, .text-2xl, .text-3xl, .text-4xl +.font-normal, .font-medium, .font-semibold, .font-bold +.leading-none, .leading-tight, .leading-snug, .leading-normal, .leading-relaxed +``` + +### Spacing + +```css +.m-0, .m-1, .m-2, .m-3, .m-4, .m-6, .m-8 +.mt-0, .mt-1, .mt-2, .mt-3, .mt-4, .mt-6 +.mb-0, .mb-1, .mb-2, .mb-3, .mb-4, .mb-6 +.ml-0, .ml-2, .ml-4 +.mr-0, .mr-2, .mr-4 +.mx-0, .mx-2, .mx-4 +.my-0, .my-2, .my-4 +.p-0, .p-1, .p-2, .p-3, .p-4, .p-6 +.px-0, .px-2, .px-3, .px-4 +.py-0, .py-1, .py-2, .py-3 +.gap-0, .gap-1, .gap-2, .gap-3, .gap-4, .gap-6 +``` + +### Border Radius + +```css +.rounded-none, .rounded-xs, .rounded-sm, .rounded-md, .rounded-lg, .rounded-xl, .rounded-full +``` + +### Shadows + +```css +.shadow-none, .shadow-xs, .shadow-sm, .shadow-md, .shadow-lg, .shadow-xl +``` + +### Display & Flex + +```css +.block, .inline-block, .inline, .flex, .inline-flex, .grid, .hidden +.flex-row, .flex-col, .flex-wrap, .flex-nowrap +.items-start, .items-center, .items-end +.justify-start, .justify-center, .justify-end, .justify-between +.flex-1, .flex-auto, .flex-none +``` + +### Text + +```css +.text-left, .text-center, .text-right +.truncate +.line-clamp-2, .line-clamp-3 +.text-muted, .text-highlight +``` + +### Other + +```css +.cursor-pointer, .cursor-default +.transition-fast, .transition-normal, .transition-slow +``` + +## Best Practices + +### DO: + +- Use design tokens for all values +- Use utility classes for common patterns +- Keep component styles in CSS, not inline JS +- Use semantic HTML elements +- Maintain consistent spacing using the spacing scale + +### DON'T: + +- Use hardcoded pixel values +- Use inline styles in JavaScript +- Mix different border-radius values arbitrarily +- Skip the spacing scale with custom values +- Use arbitrary font sizes outside the type scale + +## Migration Guide + +### From hardcoded values: + +```css +/* Before */ +.element { + padding: 16px; + font-size: 14px; + border-radius: 4px; + margin-bottom: 24px; +} + +/* After */ +.element { + padding: var(--space-4); + font-size: var(--text-sm); + border-radius: var(--radius-sm); + margin-bottom: var(--space-6); +} +``` + +### From inline styles: + +```javascript +// Before +element.style.cssText = 'display: flex; gap: 8px; padding: 16px;'; + +// After +element.classList.add('flex', 'gap-2', 'p-4'); +``` + +## Themes + +The design system supports multiple themes. Each theme defines color variables while maintaining consistent spacing, typography, and other design tokens. + +Available themes: + +- `monochrome` (default) +- `dark` +- `ocean` +- `purple` +- `forest` +- `mocha` +- `machiatto` +- `frappe` +- `latte` +- `white` + +## Notes + +- The `--highlight-rgb` variable must be in comma-separated RGB format (e.g., `245, 245, 245`) for use with `rgb()` function +- All spacing values are in rem units for accessibility +- The design system is mobile-first and responsive diff --git a/js/lyrics.js b/js/lyrics.js index c3b0125..922b2eb 100644 --- a/js/lyrics.js +++ b/js/lyrics.js @@ -926,6 +926,29 @@ export function openLyricsPanel(track, audioPlayer, lyricsManager, forceOpen = f sidePanelManager.open('lyrics', 'Lyrics', renderControls, renderContent, forceOpen); } +function getLyricsHighlightColor() { + // Check if the current theme is light + const isLight = getComputedStyle(document.documentElement).colorScheme === 'light'; + return isLight ? '#000' : '#fff'; +} + +function updateLyricsTheme() { + const highlightColor = getLyricsHighlightColor(); + document.querySelectorAll('am-lyrics').forEach((el) => { + el.setAttribute('highlight-color', highlightColor); + }); +} + +// watch for theme changes +const themeObserver = new MutationObserver(() => { + updateLyricsTheme(); +}); + +themeObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-theme', 'style'], +}); + async function renderLyricsComponent(container, track, audioPlayer, lyricsManager) { container.innerHTML = '
Loading lyrics...
'; @@ -951,7 +974,7 @@ async function renderLyricsComponent(container, track, audioPlayer, lyricsManage amLyrics.setAttribute('query', `${title} ${artist}`.trim()); if (isrc) amLyrics.setAttribute('isrc', isrc); - amLyrics.setAttribute('highlight-color', '#93c5fd'); + amLyrics.setAttribute('highlight-color', getLyricsHighlightColor()); amLyrics.setAttribute('hover-background-color', 'rgba(59, 130, 246, 0.14)'); amLyrics.setAttribute('autoscroll', ''); amLyrics.setAttribute('interpolate', ''); @@ -960,8 +983,6 @@ async function renderLyricsComponent(container, track, audioPlayer, lyricsManage container.appendChild(amLyrics); - // Setup observer IMMEDIATELY to catch lyrics as they load (not after waiting) - // This is critical - observer must be running before lyrics arrive from LRCLIB lyricsManager.setupLyricsObserver(amLyrics); // If Romaji mode is enabled and track has Asian text, ensure Kuroshiro is ready diff --git a/package-lock.json b/package-lock.json index 2152da2..088afe7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,6 +81,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1609,6 +1610,7 @@ "integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -1650,6 +1652,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -1693,6 +1696,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -3270,6 +3274,7 @@ "resolved": "https://registry.npmjs.org/@svta/cml-xml/-/cml-xml-1.0.1.tgz", "integrity": "sha512-11LkJa5kDEcsRMWkVI1ABH3KLCxGoiSVe4kQ293ItVj8ncTTQ7htmCGiJDjS+Cmy35UgF3e/vc0ysJIiWRTx2g==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=20" }, @@ -3318,6 +3323,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3341,6 +3347,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3638,6 +3645,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4675,6 +4683,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7296,6 +7305,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -7379,6 +7389,7 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -8484,6 +8495,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-syntax-patches-for-csstree": "^1.0.19", @@ -8934,6 +8946,7 @@ "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -9308,6 +9321,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -9744,6 +9758,7 @@ "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, diff --git a/styles.css b/styles.css index 64b677e..86d160e 100644 --- a/styles.css +++ b/styles.css @@ -1,47 +1,189 @@ /* stylelint-disable no-descending-specificity */ + +/* ========================================================================== + DESIGN SYSTEM - Monochrome + A comprehensive design language for consistent UI/UX + ========================================================================== */ + :root { color-scheme: light dark; - /* Font - default to Inter for instant render, JS will override if needed */ - --font-family: 'Inter', sans-serif; - --font-size-scale: 100%; /* Dynamic font size scale (50% - 200%) */ + /* ------------------------------------------------------------------------- + TYPOGRAPHY SCALE + Use these variables for ALL font sizes + ------------------------------------------------------------------------- */ + --font-family: 'Inter', -apple-system, blinkmacsystemfont, 'Segoe UI', roboto, sans-serif; + --font-size-scale: 100%; - /* Spacing */ - --spacing-xs: 0.25rem; - --spacing-sm: 0.5rem; - --spacing-md: 1rem; - --spacing-lg: 1.5rem; - --spacing-xl: 2rem; - --spacing-2xl: 3rem; - --spacing-3xl: 4rem; + /* Typography Scale - Base 16px (1rem) */ + --text-xs: 0.75rem; /* 12px - Captions, badges, timestamps */ + --text-sm: 0.875rem; /* 14px - Secondary text, labels */ + --text-base: 1rem; /* 16px - Body text (default) */ + --text-md: 1.125rem; /* 18px - Lead paragraphs */ + --text-lg: 1.25rem; /* 20px - Small headings */ + --text-xl: 1.5rem; /* 24px - H4, card titles */ + --text-2xl: 1.875rem; /* 30px - H3 */ + --text-3xl: 2.25rem; /* 36px - H2 */ + --text-4xl: 3rem; /* 48px - H1 */ + --text-5xl: 3.75rem; /* 60px - Display text */ - /* Layout */ - --radius: 0.75rem; + /* Font Weights */ + --font-normal: 400; + --font-medium: 500; + --font-semibold: 600; + --font-bold: 700; - /* Slightly more rounded for modern feel */ - --radius-sm: 0.5rem; - --radius-full: 9999px; + /* Line Heights */ + --leading-none: 1; + --leading-tight: 1.25; + --leading-snug: 1.375; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + + /* ------------------------------------------------------------------------- + SPACING SCALE + Use these variables for ALL spacing (margin, padding, gap) + ------------------------------------------------------------------------- */ + --space-0: 0; + --space-1: 0.25rem; /* 4px */ + --space-2: 0.5rem; /* 8px */ + --space-3: 0.75rem; /* 12px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-8: 2rem; /* 32px */ + --space-10: 2.5rem; /* 40px */ + --space-12: 3rem; /* 48px */ + --space-16: 4rem; /* 64px */ + --space-20: 5rem; /* 80px */ + --space-24: 6rem; /* 96px */ + + /* Legacy spacing aliases (deprecated, use --space-* instead) */ + --spacing-xs: var(--space-1); + --spacing-sm: var(--space-2); + --spacing-md: var(--space-4); + --spacing-lg: var(--space-6); + --spacing-xl: var(--space-8); + --spacing-2xl: var(--space-12); + --spacing-3xl: var(--space-16); + + /* ------------------------------------------------------------------------- + BORDER RADIUS SCALE + Use these variables for ALL border-radius values + ------------------------------------------------------------------------- */ + --radius-none: 0; + --radius-xs: 2px; /* Small badges, tags */ + --radius-sm: 4px; /* Inputs, small buttons */ + --radius-md: 8px; /* Cards, panels */ + --radius-lg: 12px; /* Large cards, modals */ + --radius-xl: 16px; /* Hero elements */ + --radius-2xl: 24px; /* Extra large elements */ + --radius-full: 9999px; /* Circles, pills, avatars */ + + /* Legacy radius aliases (deprecated, use --radius-md for default) */ + --radius: var(--radius-md); + + /* ------------------------------------------------------------------------- + LAYOUT DIMENSIONS + ------------------------------------------------------------------------- */ --player-bar-height-desktop: 90px; --player-bar-height-mobile: 130px; + --sidebar-width: 240px; + --sidebar-collapsed-width: 70px; - /* Animations & Transitions */ - --transition-fast: 0.15s cubic-bezier(0.4, 0, 0.2, 1); - --transition-normal: 0.3s cubic-bezier(0.4, 0, 0.2, 1); - --transition-slow: 0.5s cubic-bezier(0.4, 0, 0.2, 1); + /* ------------------------------------------------------------------------- + TRANSITIONS & ANIMATIONS + ------------------------------------------------------------------------- */ + --duration-instant: 0ms; + --duration-fast: 150ms; + --duration-normal: 300ms; + --duration-slow: 500ms; + --ease-linear: linear; + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); --ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1); --ease-elastic: cubic-bezier(0.68, -0.55, 0.265, 1.55); + --ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275); + + /* Legacy transition aliases */ + --transition-fast: var(--duration-fast) var(--ease-in-out); + --transition-normal: var(--duration-normal) var(--ease-in-out); + --transition-slow: var(--duration-slow) var(--ease-in-out); --transition: var(--transition-normal); - /* Fallback */ - - /* Shadows (layered for depth) */ - --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + /* ------------------------------------------------------------------------- + SHADOWS + ------------------------------------------------------------------------- */ + --shadow-none: none; + --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); - --shadow-glow: 0 0 15px var(--highlight-rgb); + --shadow-inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.05); + --shadow-glow: 0 0 20px rgb(var(--highlight-rgb) / 0.5); + + /* ------------------------------------------------------------------------- + Z-INDEX SCALE + ------------------------------------------------------------------------- */ + --z-hide: -1; + --z-base: 0; + --z-docked: 10; + --z-dropdown: 1000; + --z-sticky: 1100; + --z-banner: 1200; + --z-overlay: 1300; + --z-modal: 1400; + --z-popover: 1500; + --z-tooltip: 1600; + --z-toast: 1700; + + /* ------------------------------------------------------------------------- + COMPONENT TOKENS + ------------------------------------------------------------------------- */ + + /* Buttons */ + --btn-height-sm: 32px; + --btn-height-md: 40px; + --btn-height-lg: 48px; + --btn-padding-sm: var(--space-2) var(--space-3); + --btn-padding-md: var(--space-3) var(--space-4); + --btn-padding-lg: var(--space-4) var(--space-6); + + /* Inputs */ + --input-height: 40px; + --input-padding: var(--space-3) var(--space-4); + + /* Cards */ + --card-padding: var(--space-4); + --card-gap: var(--space-4); + --card-radius: var(--radius-lg); + + /* Modals */ + --modal-padding: var(--space-6); + --modal-radius: var(--radius-xl); + --modal-max-width-sm: 400px; + --modal-max-width-md: 500px; + --modal-max-width-lg: 600px; + --modal-max-width-xl: 800px; + + /* Covers */ --cover-filter: blur(50px) brightness(0.4); + --cover-radius: var(--radius-md); + + /* ------------------------------------------------------------------------- + SEMANTIC COLORS (will be overridden by themes) + ------------------------------------------------------------------------- */ + --color-danger: #ef4444; + --color-danger-hover: #dc2626; + --color-success: #10b981; + --color-success-hover: #059669; + --color-warning: #f59e0b; + --color-warning-hover: #d97706; + --color-info: #3b82f6; + --color-info-hover: #2563eb; } :root[data-theme='monochrome'] { @@ -171,7 +313,7 @@ --input: #45475a; --ring: #89b4fa; --highlight: #89b4fa; - --highlight-rgb: #b4befe; + --highlight-rgb: 180, 190, 254; --active-highlight: #b4befe; --explicit-badge: #f9e2af; } @@ -193,7 +335,7 @@ --input: #494d64; --ring: #8aadf4; --highlight: #8aadf4; - --highlight-rgb: #b7bdf8; + --highlight-rgb: 183, 189, 248; --active-highlight: #b7bdf8; --explicit-badge: #eed49f; } @@ -215,7 +357,7 @@ --input: #45475a; --ring: #8caaee; --highlight: #8caaee; - --highlight-rgb: #babbf1; + --highlight-rgb: 186, 187, 241; --active-highlight: #babbf1; --explicit-badge: #e5c890; } @@ -237,7 +379,7 @@ --input: #bcc0cc; --ring: #fdfdfd; --highlight: #1e66f5; - --highlight-rgb: #7287fd; + --highlight-rgb: 114, 135, 253; --active-highlight: #7287fd; --explicit-badge: #df8e1d; } @@ -321,13 +463,518 @@ a { kbd { background-color: var(--secondary); border: 1px solid var(--border); - border-radius: 4px; - padding: 0.25rem 0.5rem; - font-size: 0.85rem; + border-radius: var(--radius-sm); + padding: var(--space-1) var(--space-2); + font-size: var(--text-sm); font-family: inherit; - box-shadow: 0 2px 4px rgb(0, 0, 0, 0.1); + box-shadow: var(--shadow-sm); } +/* ========================================================================== + UTILITY CLASSES + Consistent utility classes for common patterns + ========================================================================== */ + +/* Typography utilities */ +.text-xs { + font-size: var(--text-xs); +} + +.text-sm { + font-size: var(--text-sm); +} + +.text-base { + font-size: var(--text-base); +} + +.text-md { + font-size: var(--text-md); +} + +.text-lg { + font-size: var(--text-lg); +} + +.text-xl { + font-size: var(--text-xl); +} + +.text-2xl { + font-size: var(--text-2xl); +} + +.text-3xl { + font-size: var(--text-3xl); +} + +.text-4xl { + font-size: var(--text-4xl); +} + +.font-normal { + font-weight: var(--font-normal); +} + +.font-medium { + font-weight: var(--font-medium); +} + +.font-semibold { + font-weight: var(--font-semibold); +} + +.font-bold { + font-weight: var(--font-bold); +} + +.leading-none { + line-height: var(--leading-none); +} + +.leading-tight { + line-height: var(--leading-tight); +} + +.leading-snug { + line-height: var(--leading-snug); +} + +.leading-normal { + line-height: var(--leading-normal); +} + +.leading-relaxed { + line-height: var(--leading-relaxed); +} + +/* Spacing utilities */ +.m-0 { + margin: var(--space-0); +} + +.m-1 { + margin: var(--space-1); +} + +.m-2 { + margin: var(--space-2); +} + +.m-3 { + margin: var(--space-3); +} + +.m-4 { + margin: var(--space-4); +} + +.m-6 { + margin: var(--space-6); +} + +.m-8 { + margin: var(--space-8); +} + +.mt-0 { + margin-top: var(--space-0); +} + +.mt-1 { + margin-top: var(--space-1); +} + +.mt-2 { + margin-top: var(--space-2); +} + +.mt-3 { + margin-top: var(--space-3); +} + +.mt-4 { + margin-top: var(--space-4); +} + +.mt-6 { + margin-top: var(--space-6); +} + +.mb-0 { + margin-bottom: var(--space-0); +} + +.mb-1 { + margin-bottom: var(--space-1); +} + +.mb-2 { + margin-bottom: var(--space-2); +} + +.mb-3 { + margin-bottom: var(--space-3); +} + +.mb-4 { + margin-bottom: var(--space-4); +} + +.mb-6 { + margin-bottom: var(--space-6); +} + +.ml-0 { + margin-left: var(--space-0); +} + +.ml-2 { + margin-left: var(--space-2); +} + +.ml-4 { + margin-left: var(--space-4); +} + +.mr-0 { + margin-right: var(--space-0); +} + +.mr-2 { + margin-right: var(--space-2); +} + +.mr-4 { + margin-right: var(--space-4); +} + +.mx-0 { + margin-left: var(--space-0); + margin-right: var(--space-0); +} + +.mx-2 { + margin-left: var(--space-2); + margin-right: var(--space-2); +} + +.mx-4 { + margin-left: var(--space-4); + margin-right: var(--space-4); +} + +.my-0 { + margin-top: var(--space-0); + margin-bottom: var(--space-0); +} + +.my-2 { + margin-top: var(--space-2); + margin-bottom: var(--space-2); +} + +.my-4 { + margin-top: var(--space-4); + margin-bottom: var(--space-4); +} + +.p-0 { + padding: var(--space-0); +} + +.p-1 { + padding: var(--space-1); +} + +.p-2 { + padding: var(--space-2); +} + +.p-3 { + padding: var(--space-3); +} + +.p-4 { + padding: var(--space-4); +} + +.p-6 { + padding: var(--space-6); +} + +.px-0 { + padding-left: var(--space-0); + padding-right: var(--space-0); +} + +.px-2 { + padding-left: var(--space-2); + padding-right: var(--space-2); +} + +.px-3 { + padding-left: var(--space-3); + padding-right: var(--space-3); +} + +.px-4 { + padding-left: var(--space-4); + padding-right: var(--space-4); +} + +.py-0 { + padding-top: var(--space-0); + padding-bottom: var(--space-0); +} + +.py-1 { + padding-top: var(--space-1); + padding-bottom: var(--space-1); +} + +.py-2 { + padding-top: var(--space-2); + padding-bottom: var(--space-2); +} + +.py-3 { + padding-top: var(--space-3); + padding-bottom: var(--space-3); +} + +.gap-0 { + gap: var(--space-0); +} + +.gap-1 { + gap: var(--space-1); +} + +.gap-2 { + gap: var(--space-2); +} + +.gap-3 { + gap: var(--space-3); +} + +.gap-4 { + gap: var(--space-4); +} + +.gap-6 { + gap: var(--space-6); +} + +/* Radius utilities */ +.rounded-none { + border-radius: var(--radius-none); +} + +.rounded-xs { + border-radius: var(--radius-xs); +} + +.rounded-sm { + border-radius: var(--radius-sm); +} + +.rounded-md { + border-radius: var(--radius-md); +} + +.rounded-lg { + border-radius: var(--radius-lg); +} + +.rounded-xl { + border-radius: var(--radius-xl); +} + +.rounded-full { + border-radius: var(--radius-full); +} + +/* Shadow utilities */ +.shadow-none { + box-shadow: var(--shadow-none); +} + +.shadow-xs { + box-shadow: var(--shadow-xs); +} + +.shadow-sm { + box-shadow: var(--shadow-sm); +} + +.shadow-md { + box-shadow: var(--shadow-md); +} + +.shadow-lg { + box-shadow: var(--shadow-lg); +} + +.shadow-xl { + box-shadow: var(--shadow-xl); +} + +/* Display utilities */ +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.inline { + display: inline; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.grid { + display: grid; +} + +.hidden { + display: none; +} + +/* Flex utilities */ +.flex-row { + flex-direction: row; +} + +.flex-col { + flex-direction: column; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.flex-nowrap { + flex-wrap: nowrap; +} + +.items-start { + align-items: flex-start; +} + +.items-center { + align-items: center; +} + +.items-end { + align-items: flex-end; +} + +.justify-start { + justify-content: flex-start; +} + +.justify-center { + justify-content: center; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-between { + justify-content: space-between; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-auto { + flex: 1 1 auto; +} + +.flex-none { + flex: none; +} + +/* Text utilities */ +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.line-clamp-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.line-clamp-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* Color utilities */ +.text-muted { + color: var(--muted-foreground); +} + +.text-highlight { + color: var(--highlight); +} + +/* Cursor utilities */ +.cursor-pointer { + cursor: pointer; +} + +.cursor-default { + cursor: default; +} + +/* Transition utilities */ +.transition-fast { + transition: all var(--duration-fast) var(--ease-in-out); +} + +.transition-normal { + transition: all var(--duration-normal) var(--ease-in-out); +} + +.transition-slow { + transition: all var(--duration-slow) var(--ease-in-out); +} + +/* ========================================================================== + APP LAYOUT + ========================================================================== */ + .app-container { display: grid; height: 100vh; @@ -581,13 +1228,13 @@ kbd { #pinned-items-list .nav-item a .pinned-item-cover { width: 24px; height: 24px; - border-radius: 4px; + border-radius: var(--radius-sm); flex-shrink: 0; object-fit: cover; } #pinned-items-list .nav-item a .pinned-item-cover.artist { - border-radius: 50%; + border-radius: var(--radius-full); } #pinned-items-list .nav-item a .pinned-item-name { @@ -600,7 +1247,7 @@ kbd { .pinned-item-collage { width: 24px; height: 24px; - border-radius: 4px; + border-radius: var(--radius-sm); flex-shrink: 0; display: grid; grid-template-columns: 1fr 1fr; @@ -643,7 +1290,7 @@ kbd { .nav-btn { width: 32px; height: 32px; - border-radius: 50%; + border-radius: var(--radius-full); background-color: var(--card); border: 1px solid var(--border); @@ -1250,7 +1897,7 @@ input[type='search']::-webkit-search-cancel-button { } .card.artist .card-image { - border-radius: 50%; + border-radius: var(--radius-full); } .card-image-wrapper .explicit-badge { @@ -1307,7 +1954,7 @@ input[type='search']::-webkit-search-cancel-button { font-size: 0.6rem; font-weight: 700; padding: 0.15rem 0.3rem; - border-radius: 3px; + border-radius: var(--radius-xs); margin-left: 0.5rem; vertical-align: middle; line-height: 1; @@ -1319,7 +1966,7 @@ input[type='search']::-webkit-search-cancel-button { font-size: 0.6rem; font-weight: 700; padding: 0.15rem 0.3rem; - border-radius: 3px; + border-radius: var(--radius-xs); margin-left: 0.5rem; vertical-align: middle; line-height: 1; @@ -1502,7 +2149,7 @@ input[type='search']::-webkit-search-cancel-button { width: 40px; height: 40px; background-color: var(--muted); - border-radius: 4px; + border-radius: var(--radius-sm); object-fit: cover; flex-shrink: 0; } @@ -1650,7 +2297,7 @@ input[type='search']::-webkit-search-cancel-button { } .detail-header-image.artist { - border-radius: 50%; + border-radius: var(--radius-full); } .detail-header-info .type { @@ -1688,7 +2335,7 @@ input[type='search']::-webkit-search-cancel-button { background-color: var(--secondary); color: var(--muted-foreground); padding: 0.15rem 0.6rem; - border-radius: 1rem; + border-radius: var(--radius-full); font-size: 0.75rem; font-weight: 500; text-transform: capitalize; @@ -1726,7 +2373,7 @@ input[type='search']::-webkit-search-cancel-button { width: 40px; height: 40px; padding: 0; - border-radius: 50%; + border-radius: var(--radius-full); display: flex; align-items: center; justify-content: center; @@ -1827,7 +2474,7 @@ input[type='search']::-webkit-search-cancel-button { .modal-actions .btn-secondary { padding: 0.875rem 1.75rem; - border-radius: 2rem; + border-radius: var(--radius-full); font-weight: 600; font-size: 0.95rem; } @@ -1890,7 +2537,7 @@ input[type='search']::-webkit-search-cancel-button { content: ''; width: 14px; height: 16px; - border-radius: 4px; + border-radius: var(--radius-sm); flex-shrink: 0; display: inline-block; background-image: radial-gradient(currentcolor 1.2px, transparent 1.3px); @@ -2091,7 +2738,7 @@ input[type='search']::-webkit-search-cancel-button { width: 18px; height: 18px; background: var(--primary); - border-radius: 50%; + border-radius: var(--radius-full); cursor: pointer; transition: background-color var(--transition-fast); } @@ -2104,7 +2751,7 @@ input[type='search']::-webkit-search-cancel-button { width: 18px; height: 18px; background: var(--primary); - border-radius: 50%; + border-radius: var(--radius-full); cursor: pointer; border: none; transition: background-color var(--transition-fast); @@ -2201,7 +2848,7 @@ input[type='search']::-webkit-search-cancel-button { inset: 0; background-color: var(--secondary); transition: background-color var(--transition-normal); - border-radius: 24px; + border-radius: var(--radius-2xl); box-shadow: inset 0 2px 4px rgb(0, 0, 0, 0.1); /* Inner shadow for depth */ @@ -2218,7 +2865,7 @@ input[type='search']::-webkit-search-cancel-button { transition: transform var(--transition-spring, 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)); /* Spring animation */ - border-radius: 50%; + border-radius: var(--radius-full); box-shadow: 0 2px 4px rgb(0, 0, 0, 0.2); } @@ -2274,7 +2921,7 @@ input:checked + .slider::before { .track-info .cover { width: 56px; height: 56px; - border-radius: 4px; + border-radius: var(--radius-sm); background-color: var(--muted); object-fit: cover; flex-shrink: 0; @@ -2362,7 +3009,7 @@ input:checked + .slider::before { justify-content: center; width: 32px; height: 32px; - border-radius: 50%; + border-radius: var(--radius-full); position: relative; -webkit-tap-highlight-color: transparent; } @@ -2451,7 +3098,7 @@ input:checked + .slider::before { flex-grow: 1; height: 6px; background-color: var(--secondary); - border-radius: 3px; + border-radius: var(--radius-xs); } .progress-bar:hover { @@ -2462,7 +3109,7 @@ input:checked + .slider::before { width: 0; height: 100%; background-color: var(--muted-foreground); - border-radius: 3px; + border-radius: var(--radius-xs); transition: background-color 0.2s ease; position: relative; pointer-events: none; @@ -2482,7 +3129,7 @@ input:checked + .slider::before { width: 12px; height: 12px; background-color: var(--highlight); - border-radius: 50%; + border-radius: var(--radius-full); box-shadow: 0 2px 4px rgb(0, 0, 0, 0.3); } @@ -2542,7 +3189,7 @@ input:checked + .slider::before { width: 100px; height: 4px; background-color: var(--secondary); - border-radius: 2px; + border-radius: var(--radius-xs); } .volume-controls .volume-bar:hover { @@ -2553,7 +3200,7 @@ input:checked + .slider::before { width: var(--volume-level, 70%); height: 100%; background-color: var(--muted-foreground); - border-radius: 2px; + border-radius: var(--radius-xs); transition: background-color 0.2s ease; position: relative; pointer-events: none; @@ -2573,7 +3220,7 @@ input:checked + .slider::before { width: 12px; height: 12px; background-color: var(--highlight); - border-radius: 50%; + border-radius: var(--radius-full); box-shadow: 0 2px 4px rgb(0, 0, 0, 0.3); } @@ -2632,7 +3279,7 @@ input:checked + .slider::before { padding: 0.5rem 0.75rem; margin-right: 8px; cursor: pointer; - border-radius: 4px; + border-radius: var(--radius-sm); transition: background-color var(--transition-fast), transform var(--transition-fast); @@ -2684,7 +3331,7 @@ input:checked + .slider::before { align-items: center; padding: 0.5rem 0.75rem; background: var(--accent); - border-radius: 4px; + border-radius: var(--radius-sm); margin-bottom: 0.25rem; } @@ -2717,7 +3364,7 @@ input:checked + .slider::before { cursor: pointer; padding: 0.25rem 0.5rem; font-size: 0.8rem; - border-radius: 4px; + border-radius: var(--radius-sm); } .blocked-items-list .unblock-btn:hover { @@ -2838,7 +3485,7 @@ input:checked + .slider::before { font-size: 2rem; width: 48px; height: 48px; - border-radius: 50%; + border-radius: var(--radius-full); cursor: pointer; display: flex; align-items: center; @@ -2953,7 +3600,7 @@ input:checked + .slider::before { flex: 1; height: 6px; background: rgb(255, 255, 255, 0.2); - border-radius: 3px; + border-radius: var(--radius-xs); cursor: pointer; position: relative; transition: height 0.2s ease; @@ -2966,7 +3613,7 @@ input:checked + .slider::before { .fullscreen-progress-container .progress-fill { height: 100%; background: var(--foreground); - border-radius: 3px; + border-radius: var(--radius-xs); width: 0%; transition: width 0.1s ease; position: relative; @@ -2987,7 +3634,7 @@ input:checked + .slider::before { width: 12px; height: 12px; background-color: var(--highlight); - border-radius: 50%; + border-radius: var(--radius-full); box-shadow: 0 2px 4px rgb(0, 0, 0, 0.3); } @@ -3004,7 +3651,7 @@ input:checked + .slider::before { color: var(--foreground); cursor: pointer; padding: 0.5rem; - border-radius: 50%; + border-radius: var(--radius-full); display: flex; align-items: center; justify-content: center; @@ -3029,7 +3676,7 @@ input:checked + .slider::before { height: 64px; background: var(--foreground); color: var(--background); - border-radius: 50%; + border-radius: var(--radius-full); } .fullscreen-buttons #fs-play-pause-btn:hover { @@ -3055,7 +3702,7 @@ input:checked + .slider::before { color: var(--foreground); cursor: pointer; padding: 0.5rem; - border-radius: 50%; + border-radius: var(--radius-full); display: flex; align-items: center; justify-content: center; @@ -3077,7 +3724,7 @@ input:checked + .slider::before { width: 150px; height: 6px; background-color: rgb(255, 255, 255, 0.2); - border-radius: 3px; + border-radius: var(--radius-xs); cursor: pointer; position: relative; transition: height 0.2s ease; @@ -3090,7 +3737,7 @@ input:checked + .slider::before { .fs-volume-fill { height: 100%; background-color: var(--foreground); - border-radius: 3px; + border-radius: var(--radius-xs); width: var(--fs-volume-level, 70%); transition: width 0.1s ease; position: relative; @@ -3111,7 +3758,7 @@ input:checked + .slider::before { width: 12px; height: 12px; background-color: var(--highlight); - border-radius: 50%; + border-radius: var(--radius-full); box-shadow: 0 2px 4px rgb(0, 0, 0, 0.3); } @@ -3324,7 +3971,7 @@ input:checked + .slider::before { width: 40px; height: 40px; flex-shrink: 0; - border-radius: 4px; + border-radius: var(--radius-sm); } .skeleton-track-details { @@ -3372,7 +4019,7 @@ input:checked + .slider::before { } .skeleton-card.artist .skeleton-card-image { - border-radius: 50%; + border-radius: var(--radius-full); } .skeleton-card-title { @@ -3447,7 +4094,7 @@ input:checked + .slider::before { display: flex; align-items: center; justify-content: center; - border-radius: 4px; + border-radius: var(--radius-sm); transition: transform var(--transition-fast), color var(--transition-fast), @@ -3711,7 +4358,7 @@ input:checked + .slider::before { width: 6px; height: 6px; background-color: var(--highlight); - border-radius: 50%; + border-radius: var(--radius-full); animation: pulse 2s infinite; } @@ -3727,7 +4374,7 @@ input:checked + .slider::before { width: 8px; height: 8px; background-color: #10b981; - border-radius: 50%; + border-radius: var(--radius-full); } #download-current-btn:disabled { @@ -3757,7 +4404,7 @@ input:checked + .slider::before { .template-guide code { background-color: var(--secondary); padding: 0.2rem 0.4rem; - border-radius: 4px; + border-radius: var(--radius-sm); font-size: 0.85em; } @@ -4290,7 +4937,7 @@ img[src=''] { border: none; color: white; padding: 0.75rem; - border-radius: 50%; + border-radius: var(--radius-full); cursor: pointer; z-index: 1001; display: flex; @@ -4465,7 +5112,7 @@ img[src=''] { width: 100%; height: 8px; background: var(--secondary); - border-radius: 4px; + border-radius: var(--radius-sm); overflow: hidden; position: relative; } @@ -4473,7 +5120,7 @@ img[src=''] { .csv-import-progress .progress-fill { height: 100%; background: linear-gradient(90deg, var(--primary), var(--highlight)); - border-radius: 4px; + border-radius: var(--radius-sm); width: 0%; transition: width 0.3s ease; position: relative; @@ -5428,7 +6075,7 @@ img[src=''] { width: 16px; height: 16px; background-color: var(--highlight); - border-radius: 50%; + border-radius: var(--radius-full); box-shadow: 0 2px 6px rgb(0, 0, 0, 0.3); } @@ -5574,7 +6221,7 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b .genius-annotated { background-color: rgb(255, 255, 100, 0.1); cursor: pointer; - border-radius: 4px; + border-radius: var(--radius-sm); transition: background-color 0.2s; } @@ -6290,7 +6937,7 @@ textarea:focus { width: 18px; height: 18px; background: linear-gradient(145deg, var(--primary), var(--highlight)); - border-radius: 50%; + border-radius: var(--radius-full); cursor: grab; margin-left: -6px; box-shadow: @@ -6306,7 +6953,7 @@ textarea:focus { width: 18px; height: 18px; background: linear-gradient(145deg, var(--primary), var(--highlight)); - border-radius: 50%; + border-radius: var(--radius-full); cursor: grab; box-shadow: 0 2px 8px rgb(0, 0, 0, 0.3),