add more fonts functionality

This commit is contained in:
Eduard Prigoana 2026-02-09 00:01:40 +00:00
parent 599b11cfc4
commit 003ddc0ab3
6 changed files with 657 additions and 67 deletions

View file

@ -1096,6 +1096,20 @@
</p>
</div>
<section class="content-section" id="home-editors-picks-section-empty" style="margin-top: 0">
<div
style="
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
"
>
<h2 class="section-title" style="margin-bottom: 0">Editor's Picks</h2>
</div>
<div class="card-grid" id="home-editors-picks-empty"></div>
</section>
<div id="home-content" style="display: none">
<section class="content-section">
<div
@ -1165,19 +1179,6 @@
</div>
<div class="card-grid" id="home-recommended-albums"></div>
</section>
<section class="content-section" id="home-editors-picks-section">
<div
style="
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
"
>
<h2 class="section-title" style="margin-bottom: 0">Editor's Picks</h2>
</div>
<div class="card-grid" id="home-editors-picks"></div>
</section>
<section class="content-section">
<div
style="
@ -1247,6 +1248,19 @@
</div>
<div class="card-grid" id="home-recent-mixed"></div>
</section>
<section class="content-section" id="home-editors-picks-section">
<div
style="
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
"
>
<h2 class="section-title" style="margin-bottom: 0">Editor's Picks</h2>
</div>
<div class="card-grid" id="home-editors-picks"></div>
</section>
</div>
</div>
@ -2101,25 +2115,70 @@
<button class="btn-secondary" id="reset-custom-theme">Reset</button>
</div>
</div>
<div class="setting-item">
<div class="setting-item font-settings-container">
<div class="info">
<span class="label">Font</span>
<span class="description">Choose the application font</span>
</div>
<select id="font-select">
<option value="'Inter', sans-serif">Inter (Default)</option>
<option value="'Roboto', sans-serif">Roboto</option>
<option value="'Open Sans', sans-serif">Open Sans</option>
<option value="'Lato', sans-serif">Lato</option>
<option value="'Montserrat', sans-serif">Montserrat</option>
<option value="'Poppins', sans-serif">Poppins</option>
<option
value="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"
<span class="description"
>Choose from presets, Google Fonts, URLs, or upload your own</span
>
System UI
</option>
<option value="monospace">Monospace</option>
</select>
</div>
<div class="font-input-group">
<select id="font-type-select" class="font-type-select">
<option value="preset">Preset</option>
<option value="google">Google Fonts</option>
<option value="url">URL</option>
<option value="upload">Upload</option>
</select>
<div id="font-preset-section" class="font-section">
<select id="font-preset-select">
<option value="Inter">Inter (Default)</option>
<option value="Roboto">Roboto</option>
<option value="Open Sans">Open Sans</option>
<option value="Lato">Lato</option>
<option value="Montserrat">Montserrat</option>
<option value="Poppins">Poppins</option>
<option value="System UI">System UI</option>
<option value="monospace">Monospace</option>
</select>
</div>
<div id="font-google-section" class="font-section" style="display: none">
<input
type="text"
id="font-google-input"
placeholder="Enter Google Fonts URL or font name (e.g., IBM Plex Mono)"
class="font-input"
/>
<button id="font-google-apply" class="btn-secondary">Apply</button>
</div>
<div id="font-url-section" class="font-section" style="display: none">
<input
type="text"
id="font-url-input"
placeholder="Enter font file URL (.woff, .woff2, .ttf, .otf)"
class="font-input"
/>
<input
type="text"
id="font-url-name"
placeholder="Font name (optional)"
class="font-input font-name-input"
/>
<button id="font-url-apply" class="btn-secondary">Apply</button>
</div>
<div id="font-upload-section" class="font-section" style="display: none">
<input
type="file"
id="font-upload-input"
accept=".woff,.woff2,.ttf,.otf"
class="font-file-input"
/>
<div id="uploaded-fonts-list" class="uploaded-fonts-list"></div>
</div>
</div>
</div>
<div class="setting-item">
<div class="info">

View file

@ -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 = `
<span class="font-name">${font.name}</span>
<div class="font-actions">
<button class="btn-icon" data-id="${font.id}" data-action="use">Use</button>
<button class="btn-icon btn-delete" data-id="${font.id}" data-action="delete">Delete</button>
</div>
`;
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;

View file

@ -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,
}));
},
};

View file

@ -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}"]`);

15
package-lock.json generated
View file

@ -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"
},

View file

@ -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 {