diff --git a/js/settings.js b/js/settings.js
index 8f37ad2..53a3270 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -22,6 +22,7 @@ import {
libreFmSettings,
homePageSettings,
sidebarSectionSettings,
+ fontSettings,
} from './storage.js';
import { audioContextManager, EQ_PRESETS } from './audio-context.js';
import { db } from './db.js';
@@ -1280,10 +1281,158 @@ export function initializeSettings(scrobbler, player, api, ui) {
});
}
+ // Font Settings
+ initializeFontSettings();
+
// Settings Search functionality
setupSettingsSearch();
}
+function initializeFontSettings() {
+ const fontTypeSelect = document.getElementById('font-type-select');
+ const fontPresetSection = document.getElementById('font-preset-section');
+ const fontGoogleSection = document.getElementById('font-google-section');
+ const fontUrlSection = document.getElementById('font-url-section');
+ const fontUploadSection = document.getElementById('font-upload-section');
+ const fontPresetSelect = document.getElementById('font-preset-select');
+ const fontGoogleInput = document.getElementById('font-google-input');
+ const fontGoogleApply = document.getElementById('font-google-apply');
+ const fontUrlInput = document.getElementById('font-url-input');
+ const fontUrlName = document.getElementById('font-url-name');
+ const fontUrlApply = document.getElementById('font-url-apply');
+ const fontUploadInput = document.getElementById('font-upload-input');
+ const uploadedFontsList = document.getElementById('uploaded-fonts-list');
+
+ if (!fontTypeSelect) return;
+
+ // Load current font config
+ const config = fontSettings.getConfig();
+
+ // Show correct section based on type
+ function showFontSection(type) {
+ fontPresetSection.style.display = type === 'preset' ? 'block' : 'none';
+ fontGoogleSection.style.display = type === 'google' ? 'flex' : 'none';
+ fontUrlSection.style.display = type === 'url' ? 'flex' : 'none';
+ fontUploadSection.style.display = type === 'upload' ? 'block' : 'none';
+ }
+
+ // Initialize UI state
+ fontTypeSelect.value = config.type;
+ showFontSection(config.type);
+
+ if (config.type === 'preset') {
+ fontPresetSelect.value = config.family;
+ } else if (config.type === 'google') {
+ fontGoogleInput.value = config.family || '';
+ } else if (config.type === 'url') {
+ fontUrlInput.value = config.url || '';
+ fontUrlName.value = config.family || '';
+ }
+
+ // Type selector change
+ fontTypeSelect.addEventListener('change', (e) => {
+ showFontSection(e.target.value);
+ });
+
+ // Preset font change
+ fontPresetSelect.addEventListener('change', (e) => {
+ const value = e.target.value;
+ if (value === 'System UI') {
+ fontSettings.loadPresetFont(
+ "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue'",
+ 'sans-serif'
+ );
+ } else if (value === 'monospace') {
+ fontSettings.loadPresetFont('monospace', 'monospace');
+ } else {
+ fontSettings.loadPresetFont(value, 'sans-serif');
+ }
+ });
+
+ // Google Fonts apply
+ fontGoogleApply.addEventListener('click', () => {
+ const input = fontGoogleInput.value.trim();
+ if (!input) return;
+
+ let fontName = input;
+
+ // Check if it's a Google Fonts URL
+ if (input.includes('fonts.google.com')) {
+ const parsed = fontSettings.parseGoogleFontsUrl(input);
+ if (parsed) {
+ fontName = parsed;
+ }
+ }
+
+ fontSettings.loadGoogleFont(fontName);
+ });
+
+ // URL font apply
+ fontUrlApply.addEventListener('click', () => {
+ const url = fontUrlInput.value.trim();
+ const name = fontUrlName.value.trim();
+ if (!url) return;
+
+ fontSettings.loadFontFromUrl(url, name || 'CustomFont');
+ });
+
+ // File upload
+ fontUploadInput.addEventListener('change', async (e) => {
+ const file = e.target.files[0];
+ if (!file) return;
+
+ try {
+ const font = await fontSettings.saveUploadedFont(file);
+ await fontSettings.loadUploadedFont(font.id);
+ renderUploadedFontsList();
+ fontUploadInput.value = '';
+ } catch (err) {
+ console.error('Failed to upload font:', err);
+ alert('Failed to upload font');
+ }
+ });
+
+ // Render uploaded fonts list
+ function renderUploadedFontsList() {
+ const fonts = fontSettings.getUploadedFontList();
+ uploadedFontsList.innerHTML = '';
+
+ fonts.forEach((font) => {
+ const item = document.createElement('div');
+ item.className = 'uploaded-font-item';
+ item.innerHTML = `
+
${font.name}
+
+
+
+
+ `;
+ uploadedFontsList.appendChild(item);
+ });
+
+ // Add event listeners for buttons
+ uploadedFontsList.querySelectorAll('.btn-icon').forEach((btn) => {
+ btn.addEventListener('click', async (e) => {
+ const fontId = e.target.dataset.id;
+ const action = e.target.dataset.action;
+
+ if (action === 'use') {
+ await fontSettings.loadUploadedFont(fontId);
+ fontTypeSelect.value = 'upload';
+ showFontSection('upload');
+ } else if (action === 'delete') {
+ if (confirm('Delete this font?')) {
+ fontSettings.deleteUploadedFont(fontId);
+ renderUploadedFontsList();
+ }
+ }
+ });
+ });
+ }
+
+ renderUploadedFontsList();
+}
+
function setupSettingsSearch() {
const searchInput = document.getElementById('settings-search-input');
if (!searchInput) return;
diff --git a/js/storage.js b/js/storage.js
index 6c85fd8..f76cbbc 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -1159,3 +1159,255 @@ if (typeof window !== 'undefined' && window.matchMedia) {
}
});
}
+
+export const fontSettings = {
+ STORAGE_KEY: 'monochrome-font-config-v2',
+ CUSTOM_FONTS_KEY: 'monochrome-custom-fonts',
+ FONT_LINK_ID: 'monochrome-dynamic-font',
+ FONT_FACE_ID: 'monochrome-dynamic-fontface',
+
+ getDefaultConfig() {
+ return {
+ type: 'preset',
+ family: 'Inter',
+ fallback: 'sans-serif',
+ weights: [400, 500, 600, 700, 800],
+ };
+ },
+
+ getConfig() {
+ try {
+ const stored = localStorage.getItem(this.STORAGE_KEY);
+ if (stored) {
+ return JSON.parse(stored);
+ }
+ } catch {
+ // ignore
+ }
+ return this.getDefaultConfig();
+ },
+
+ setConfig(config) {
+ localStorage.setItem(this.STORAGE_KEY, JSON.stringify(config));
+ },
+
+ parseGoogleFontsUrl(url) {
+ try {
+ if (url.includes('fonts.google.com/specimen/')) {
+ const match = url.match(/specimen\/([^/?]+)/);
+ if (match) {
+ return decodeURIComponent(match[1]).replace(/\+/g, ' ');
+ }
+ }
+ if (url.includes('fonts.googleapis.com/css')) {
+ const match = url.match(/family=([^&:]+)/);
+ if (match) {
+ return decodeURIComponent(match[1]).replace(/\+/g, ' ').split(':')[0];
+ }
+ }
+ } catch {
+ // ignore
+ }
+ return null;
+ },
+
+ async loadGoogleFont(familyName) {
+ const encodedFamily = familyName.replace(/\s+/g, '+');
+ const url = `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@100;200;300;400;500;600;700;800;900&display=swap`;
+
+ let link = document.getElementById(this.FONT_LINK_ID);
+ if (!link) {
+ link = document.createElement('link');
+ link.id = this.FONT_LINK_ID;
+ link.rel = 'stylesheet';
+ document.head.appendChild(link);
+ }
+
+ link.href = url;
+
+ this.setConfig({
+ type: 'google',
+ family: familyName,
+ fallback: 'sans-serif',
+ weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],
+ });
+
+ document.documentElement.style.setProperty('--font-family', `'${familyName}', sans-serif`);
+ },
+
+ async loadFontFromUrl(url, familyName) {
+ const weights = [100, 200, 300, 400, 500, 600, 700, 800, 900];
+ const fontFaceId = this.FONT_FACE_ID;
+
+ let style = document.getElementById(fontFaceId);
+ if (!style) {
+ style = document.createElement('style');
+ style.id = fontFaceId;
+ document.head.appendChild(style);
+ }
+
+ const format = this.getFontFormat(url);
+ const fontFamily = familyName || 'CustomFont';
+
+ style.textContent = `
+ @font-face {
+ font-family: '${fontFamily}';
+ src: url('${url}') format('${format}');
+ font-weight: 100 900;
+ font-style: normal;
+ font-display: swap;
+ }
+ `;
+
+ this.setConfig({
+ type: 'url',
+ family: fontFamily,
+ url: url,
+ fallback: 'sans-serif',
+ weights: weights,
+ });
+
+ document.documentElement.style.setProperty('--font-family', `'${fontFamily}', sans-serif`);
+ },
+
+ getFontFormat(url) {
+ const ext = url.split('.').pop().toLowerCase();
+ const formats = {
+ woff2: 'woff2',
+ woff: 'woff',
+ ttf: 'truetype',
+ otf: 'opentype',
+ };
+ return formats[ext] || 'woff2';
+ },
+
+ async saveUploadedFont(file) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const base64 = e.target.result;
+ const fontId = 'uploaded-' + Date.now();
+ const customFonts = this.getCustomFonts();
+
+ customFonts[fontId] = {
+ name: file.name.replace(/\.[^/.]+$/, ''),
+ base64: base64,
+ format: this.getFontFormat(file.name),
+ size: file.size,
+ uploadedAt: Date.now(),
+ };
+
+ localStorage.setItem(this.CUSTOM_FONTS_KEY, JSON.stringify(customFonts));
+ resolve({ id: fontId, ...customFonts[fontId] });
+ };
+ reader.onerror = reject;
+ reader.readAsDataURL(file);
+ });
+ },
+
+ getCustomFonts() {
+ try {
+ const stored = localStorage.getItem(this.CUSTOM_FONTS_KEY);
+ return stored ? JSON.parse(stored) : {};
+ } catch {
+ return {};
+ }
+ },
+
+ async loadUploadedFont(fontId) {
+ const customFonts = this.getCustomFonts();
+ const font = customFonts[fontId];
+
+ if (!font) {
+ throw new Error('Font not found');
+ }
+
+ const fontFamily = font.name || 'UploadedFont';
+ const fontFaceId = this.FONT_FACE_ID;
+
+ let style = document.getElementById(fontFaceId);
+ if (!style) {
+ style = document.createElement('style');
+ style.id = fontFaceId;
+ document.head.appendChild(style);
+ }
+
+ style.textContent = `
+ @font-face {
+ font-family: '${fontFamily}';
+ src: url('${font.base64}') format('${font.format}');
+ font-weight: 100 900;
+ font-style: normal;
+ font-display: swap;
+ }
+ `;
+
+ this.setConfig({
+ type: 'uploaded',
+ family: fontFamily,
+ fontId: fontId,
+ fallback: 'sans-serif',
+ weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],
+ });
+
+ document.documentElement.style.setProperty('--font-family', `'${fontFamily}', sans-serif`);
+ },
+
+ deleteUploadedFont(fontId) {
+ const customFonts = this.getCustomFonts();
+ delete customFonts[fontId];
+ localStorage.setItem(this.CUSTOM_FONTS_KEY, JSON.stringify(customFonts));
+ },
+
+ loadPresetFont(family, fallback = 'sans-serif') {
+ let link = document.getElementById(this.FONT_LINK_ID);
+ if (link) {
+ link.remove();
+ }
+
+ let style = document.getElementById(this.FONT_FACE_ID);
+ if (style) {
+ style.remove();
+ }
+
+ this.setConfig({
+ type: 'preset',
+ family: family,
+ fallback: fallback,
+ weights: [400, 500, 600, 700, 800],
+ });
+
+ const fontValue = family === 'monospace' ? 'monospace' : `'${family}', ${fallback}`;
+ document.documentElement.style.setProperty('--font-family', fontValue);
+ },
+
+ applyFont() {
+ const config = this.getConfig();
+
+ switch (config.type) {
+ case 'google':
+ this.loadGoogleFont(config.family);
+ break;
+ case 'url':
+ this.loadFontFromUrl(config.url, config.family);
+ break;
+ case 'uploaded':
+ this.loadUploadedFont(config.fontId);
+ break;
+ case 'preset':
+ default:
+ this.loadPresetFont(config.family, config.fallback);
+ break;
+ }
+ },
+
+ getUploadedFontList() {
+ const fonts = this.getCustomFonts();
+ return Object.entries(fonts).map(([id, font]) => ({
+ id,
+ name: font.name,
+ size: font.size,
+ uploadedAt: font.uploadedAt,
+ }));
+ },
+};
diff --git a/js/ui.js b/js/ui.js
index ef5722b..b4a5426 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -42,8 +42,9 @@ import {
createProjectCardHTML,
createTrackFromSong,
} from './tracker.js';
-const savedFont = localStorage.getItem('monochrome-font');
-if (savedFont) document.documentElement.style.setProperty('--font-family', savedFont);
+import { fontSettings } from './storage.js';
+
+fontSettings.applyFont();
function sortTracks(tracks, sortType) {
if (sortType === 'custom') return [...tracks];
@@ -1382,12 +1383,34 @@ export class UIRenderer {
const welcomeEl = document.getElementById('home-welcome');
const contentEl = document.getElementById('home-content');
+ const editorsPicksSectionEmpty = document.getElementById('home-editors-picks-section-empty');
+ const editorsPicksSection = document.getElementById('home-editors-picks-section');
const history = await db.getHistory();
const favorites = await db.getFavorites('track');
const playlists = await db.getPlaylists(true);
- if (history.length === 0 && favorites.length === 0 && playlists.length === 0) {
+ const hasActivity = history.length > 0 || favorites.length > 0 || playlists.length > 0;
+
+ // Handle Editor's Picks visibility based on settings
+ if (!homePageSettings.shouldShowEditorsPicks()) {
+ if (editorsPicksSectionEmpty) editorsPicksSectionEmpty.style.display = 'none';
+ if (editorsPicksSection) editorsPicksSection.style.display = 'none';
+ } else {
+ // Show empty-state section at top when no activity, hide the bottom one
+ if (editorsPicksSectionEmpty) editorsPicksSectionEmpty.style.display = hasActivity ? 'none' : '';
+ // Show bottom section when has activity, render it
+ if (editorsPicksSection) editorsPicksSection.style.display = hasActivity ? '' : 'none';
+ }
+
+ // Render editor's picks in the visible container
+ if (hasActivity) {
+ this.renderHomeEditorsPicks(false, 'home-editors-picks');
+ } else {
+ this.renderHomeEditorsPicks(false, 'home-editors-picks-empty');
+ }
+
+ if (!hasActivity) {
if (welcomeEl) welcomeEl.style.display = 'block';
if (contentEl) contentEl.style.display = 'none';
return;
@@ -1414,7 +1437,6 @@ export class UIRenderer {
this.renderHomeSongs();
this.renderHomeAlbums();
- this.renderHomeEditorsPicks();
this.renderHomeArtists();
this.renderHomeRecent();
}
@@ -1540,16 +1562,8 @@ export class UIRenderer {
});
}
- async renderHomeEditorsPicks(forceRefresh = false) {
- const picksContainer = document.getElementById('home-editors-picks');
- const section = document.getElementById('home-editors-picks-section');
-
- if (!homePageSettings.shouldShowEditorsPicks()) {
- if (section) section.style.display = 'none';
- return;
- }
-
- if (section) section.style.display = '';
+ async renderHomeEditorsPicks(forceRefresh = false, containerId = 'home-editors-picks') {
+ const picksContainer = document.getElementById(containerId);
if (picksContainer) {
if (forceRefresh) picksContainer.innerHTML = this.createSkeletonCards(6);
@@ -1598,7 +1612,7 @@ export class UIRenderer {
if (cardsHTML.length > 0) {
picksContainer.innerHTML = cardsHTML.join('');
- itemsToStore.forEach((item, index) => {
+ itemsToStore.forEach((item, _index) => {
const type = item.type;
const id = item.data.id;
const el = picksContainer.querySelector(`[data-${type}-id="${id}"]`);
diff --git a/package-lock.json b/package-lock.json
index da7d589..2c81c15 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -74,7 +74,6 @@
"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",
@@ -1604,7 +1603,6 @@
"integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@keyv/serialize": "^1.1.1"
}
@@ -1646,7 +1644,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
},
@@ -1690,7 +1687,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -3142,7 +3138,6 @@
"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"
},
@@ -3191,7 +3186,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3215,7 +3209,6 @@
"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",
@@ -3503,7 +3496,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -4288,7 +4280,6 @@
"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",
@@ -6645,7 +6636,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -6729,7 +6719,6 @@
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -7679,7 +7668,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-syntax-patches-for-csstree": "^1.0.19",
@@ -8094,7 +8082,6 @@
"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",
@@ -8419,7 +8406,6 @@
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -8807,7 +8793,6 @@
"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 72a988c..5d51d83 100644
--- a/styles.css
+++ b/styles.css
@@ -268,6 +268,7 @@
margin: 0;
padding: 0;
-webkit-tap-highlight-color: transparent;
+ font-family: var(--font-family, 'Inter', sans-serif) !important;
}
html {
@@ -280,7 +281,7 @@ html {
body {
background-color: var(--background);
color: var(--foreground);
- font-family: var(--font-family, 'Inter', sans-serif);
+ font-family: var(--font-family, 'Inter', sans-serif) !important;
overflow: hidden;
transition:
background-color 0.3s ease,
@@ -318,7 +319,7 @@ kbd {
border-radius: 4px;
padding: 0.25rem 0.5rem;
font-size: 0.85rem;
- font-family: 'Courier New', monospace;
+ font-family: inherit;
box-shadow: 0 2px 4px rgb(0, 0, 0, 0.1);
}
@@ -1774,7 +1775,6 @@ input[type='search']::-webkit-search-cancel-button {
border-radius: var(--radius);
color: var(--foreground);
font-size: 0.9rem;
- font-family: 'Courier New', monospace;
}
.template-input:focus {
@@ -1782,6 +1782,142 @@ input[type='search']::-webkit-search-cancel-button {
border-color: var(--ring);
}
+.font-settings-container {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 1rem;
+}
+
+.font-settings-container .info {
+ width: 100%;
+}
+
+.font-input-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ width: 100%;
+ max-width: 500px;
+}
+
+.font-type-select {
+ width: 100%;
+ padding: 0.5rem;
+ background-color: var(--input);
+ color: var(--foreground);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ font-size: 0.9rem;
+}
+
+.font-section {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.font-section select {
+ width: 100%;
+ padding: 0.5rem;
+ background-color: var(--input);
+ color: var(--foreground);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ font-size: 0.9rem;
+}
+
+.font-input {
+ width: 100%;
+ padding: 0.5rem;
+ background-color: var(--input);
+ color: var(--foreground);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ font-size: 0.9rem;
+}
+
+.font-name-input {
+ margin-top: 0.25rem;
+}
+
+.font-file-input {
+ padding: 0.5rem;
+ background-color: var(--input);
+ color: var(--foreground);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ font-size: 0.9rem;
+ cursor: pointer;
+}
+
+.font-file-input::file-selector-button {
+ background-color: var(--secondary);
+ color: var(--secondary-foreground);
+ border: none;
+ border-radius: var(--radius-sm);
+ padding: 0.4rem 0.8rem;
+ margin-right: 0.75rem;
+ cursor: pointer;
+ font-size: 0.85rem;
+ transition: background-color var(--transition-fast);
+}
+
+.font-file-input::file-selector-button:hover {
+ background-color: var(--secondary-hover);
+}
+
+.uploaded-fonts-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ margin-top: 0.5rem;
+}
+
+.uploaded-font-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0.5rem 0.75rem;
+ background-color: var(--card);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ font-size: 0.9rem;
+}
+
+.uploaded-font-item .font-name {
+ flex: 1;
+ color: var(--foreground);
+}
+
+.uploaded-font-item .font-actions {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.uploaded-font-item .btn-icon {
+ padding: 0.25rem 0.5rem;
+ background-color: var(--secondary);
+ color: var(--secondary-foreground);
+ border: none;
+ border-radius: var(--radius-sm);
+ font-size: 0.75rem;
+ cursor: pointer;
+ transition: background-color var(--transition-fast);
+}
+
+.uploaded-font-item .btn-icon:hover {
+ background-color: var(--secondary-hover);
+}
+
+.uploaded-font-item .btn-delete {
+ background-color: var(--destructive);
+ color: var(--destructive-foreground);
+}
+
+.uploaded-font-item .btn-delete:hover {
+ background-color: var(--destructive-hover);
+}
+
.toggle-switch {
position: relative;
display: inline-block;
@@ -3041,7 +3177,6 @@ input:checked + .slider::before {
.about-tech p {
color: var(--muted-foreground);
- font-family: 'Courier New', monospace;
font-size: 0.9rem;
}
@@ -3205,7 +3340,6 @@ input:checked + .slider::before {
background-color: var(--secondary);
padding: 0.2rem 0.4rem;
border-radius: 4px;
- font-family: 'Courier New', monospace;
font-size: 0.85em;
}
@@ -3376,7 +3510,6 @@ input:checked + .slider::before {
}
.lyrics-timing-display {
- font-family: monospace;
font-size: 0.875rem;
font-weight: 600;
color: var(--foreground);
@@ -4002,7 +4135,6 @@ img[src=''] {
padding: 0.75rem 1.25rem;
border-bottom: 1px solid var(--border);
color: var(--foreground);
- font-family: Inter, sans-serif;
font-size: 0.95rem;
font-weight: 400;
line-height: 1.4;
@@ -4926,7 +5058,6 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b
font-size: 0.8rem;
color: var(--muted-foreground);
margin-bottom: 0.5rem;
- font-family: monospace;
}
.annotation-text {