add more fonts functionality
This commit is contained in:
parent
599b11cfc4
commit
003ddc0ab3
6 changed files with 657 additions and 67 deletions
113
index.html
113
index.html
|
|
@ -1096,6 +1096,20 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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">
|
<div id="home-content" style="display: none">
|
||||||
<section class="content-section">
|
<section class="content-section">
|
||||||
<div
|
<div
|
||||||
|
|
@ -1165,19 +1179,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-grid" id="home-recommended-albums"></div>
|
<div class="card-grid" id="home-recommended-albums"></div>
|
||||||
</section>
|
</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">
|
<section class="content-section">
|
||||||
<div
|
<div
|
||||||
style="
|
style="
|
||||||
|
|
@ -1247,6 +1248,19 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-grid" id="home-recent-mixed"></div>
|
<div class="card-grid" id="home-recent-mixed"></div>
|
||||||
</section>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -2101,26 +2115,71 @@
|
||||||
<button class="btn-secondary" id="reset-custom-theme">Reset</button>
|
<button class="btn-secondary" id="reset-custom-theme">Reset</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item font-settings-container">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span class="label">Font</span>
|
<span class="label">Font</span>
|
||||||
<span class="description">Choose the application font</span>
|
<span class="description"
|
||||||
</div>
|
>Choose from presets, Google Fonts, URLs, or upload your own</span
|
||||||
<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"
|
|
||||||
>
|
>
|
||||||
System UI
|
</div>
|
||||||
</option>
|
<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>
|
<option value="monospace">Monospace</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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="setting-item">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span class="label">Waveform Seekbar</span>
|
<span class="label">Waveform Seekbar</span>
|
||||||
|
|
|
||||||
149
js/settings.js
149
js/settings.js
|
|
@ -22,6 +22,7 @@ import {
|
||||||
libreFmSettings,
|
libreFmSettings,
|
||||||
homePageSettings,
|
homePageSettings,
|
||||||
sidebarSectionSettings,
|
sidebarSectionSettings,
|
||||||
|
fontSettings,
|
||||||
} from './storage.js';
|
} from './storage.js';
|
||||||
import { audioContextManager, EQ_PRESETS } from './audio-context.js';
|
import { audioContextManager, EQ_PRESETS } from './audio-context.js';
|
||||||
import { db } from './db.js';
|
import { db } from './db.js';
|
||||||
|
|
@ -1280,10 +1281,158 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Font Settings
|
||||||
|
initializeFontSettings();
|
||||||
|
|
||||||
// Settings Search functionality
|
// Settings Search functionality
|
||||||
setupSettingsSearch();
|
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() {
|
function setupSettingsSearch() {
|
||||||
const searchInput = document.getElementById('settings-search-input');
|
const searchInput = document.getElementById('settings-search-input');
|
||||||
if (!searchInput) return;
|
if (!searchInput) return;
|
||||||
|
|
|
||||||
252
js/storage.js
252
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,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
||||||
44
js/ui.js
44
js/ui.js
|
|
@ -42,8 +42,9 @@ import {
|
||||||
createProjectCardHTML,
|
createProjectCardHTML,
|
||||||
createTrackFromSong,
|
createTrackFromSong,
|
||||||
} from './tracker.js';
|
} from './tracker.js';
|
||||||
const savedFont = localStorage.getItem('monochrome-font');
|
import { fontSettings } from './storage.js';
|
||||||
if (savedFont) document.documentElement.style.setProperty('--font-family', savedFont);
|
|
||||||
|
fontSettings.applyFont();
|
||||||
|
|
||||||
function sortTracks(tracks, sortType) {
|
function sortTracks(tracks, sortType) {
|
||||||
if (sortType === 'custom') return [...tracks];
|
if (sortType === 'custom') return [...tracks];
|
||||||
|
|
@ -1382,12 +1383,34 @@ export class UIRenderer {
|
||||||
|
|
||||||
const welcomeEl = document.getElementById('home-welcome');
|
const welcomeEl = document.getElementById('home-welcome');
|
||||||
const contentEl = document.getElementById('home-content');
|
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 history = await db.getHistory();
|
||||||
const favorites = await db.getFavorites('track');
|
const favorites = await db.getFavorites('track');
|
||||||
const playlists = await db.getPlaylists(true);
|
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 (welcomeEl) welcomeEl.style.display = 'block';
|
||||||
if (contentEl) contentEl.style.display = 'none';
|
if (contentEl) contentEl.style.display = 'none';
|
||||||
return;
|
return;
|
||||||
|
|
@ -1414,7 +1437,6 @@ export class UIRenderer {
|
||||||
|
|
||||||
this.renderHomeSongs();
|
this.renderHomeSongs();
|
||||||
this.renderHomeAlbums();
|
this.renderHomeAlbums();
|
||||||
this.renderHomeEditorsPicks();
|
|
||||||
this.renderHomeArtists();
|
this.renderHomeArtists();
|
||||||
this.renderHomeRecent();
|
this.renderHomeRecent();
|
||||||
}
|
}
|
||||||
|
|
@ -1540,16 +1562,8 @@ export class UIRenderer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderHomeEditorsPicks(forceRefresh = false) {
|
async renderHomeEditorsPicks(forceRefresh = false, containerId = 'home-editors-picks') {
|
||||||
const picksContainer = document.getElementById('home-editors-picks');
|
const picksContainer = document.getElementById(containerId);
|
||||||
const section = document.getElementById('home-editors-picks-section');
|
|
||||||
|
|
||||||
if (!homePageSettings.shouldShowEditorsPicks()) {
|
|
||||||
if (section) section.style.display = 'none';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (section) section.style.display = '';
|
|
||||||
|
|
||||||
if (picksContainer) {
|
if (picksContainer) {
|
||||||
if (forceRefresh) picksContainer.innerHTML = this.createSkeletonCards(6);
|
if (forceRefresh) picksContainer.innerHTML = this.createSkeletonCards(6);
|
||||||
|
|
@ -1598,7 +1612,7 @@ export class UIRenderer {
|
||||||
|
|
||||||
if (cardsHTML.length > 0) {
|
if (cardsHTML.length > 0) {
|
||||||
picksContainer.innerHTML = cardsHTML.join('');
|
picksContainer.innerHTML = cardsHTML.join('');
|
||||||
itemsToStore.forEach((item, index) => {
|
itemsToStore.forEach((item, _index) => {
|
||||||
const type = item.type;
|
const type = item.type;
|
||||||
const id = item.data.id;
|
const id = item.data.id;
|
||||||
const el = picksContainer.querySelector(`[data-${type}-id="${id}"]`);
|
const el = picksContainer.querySelector(`[data-${type}-id="${id}"]`);
|
||||||
|
|
|
||||||
15
package-lock.json
generated
15
package-lock.json
generated
|
|
@ -74,7 +74,6 @@
|
||||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.5",
|
"@babel/generator": "^7.28.5",
|
||||||
|
|
@ -1604,7 +1603,6 @@
|
||||||
"integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==",
|
"integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@keyv/serialize": "^1.1.1"
|
"@keyv/serialize": "^1.1.1"
|
||||||
}
|
}
|
||||||
|
|
@ -1646,7 +1644,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
|
|
@ -1690,7 +1687,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
|
|
@ -3142,7 +3138,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@svta/cml-xml/-/cml-xml-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@svta/cml-xml/-/cml-xml-1.0.1.tgz",
|
||||||
"integrity": "sha512-11LkJa5kDEcsRMWkVI1ABH3KLCxGoiSVe4kQ293ItVj8ncTTQ7htmCGiJDjS+Cmy35UgF3e/vc0ysJIiWRTx2g==",
|
"integrity": "sha512-11LkJa5kDEcsRMWkVI1ABH3KLCxGoiSVe4kQ293ItVj8ncTTQ7htmCGiJDjS+Cmy35UgF3e/vc0ysJIiWRTx2g==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
|
|
@ -3191,7 +3186,6 @@
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
|
|
@ -3215,7 +3209,6 @@
|
||||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fast-uri": "^3.0.1",
|
"fast-uri": "^3.0.1",
|
||||||
|
|
@ -3503,7 +3496,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
|
|
@ -4288,7 +4280,6 @@
|
||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
|
|
@ -6645,7 +6636,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
|
|
@ -6729,7 +6719,6 @@
|
||||||
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
|
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssesc": "^3.0.0",
|
"cssesc": "^3.0.0",
|
||||||
"util-deprecate": "^1.0.2"
|
"util-deprecate": "^1.0.2"
|
||||||
|
|
@ -7679,7 +7668,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@csstools/css-parser-algorithms": "^3.0.5",
|
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||||
"@csstools/css-syntax-patches-for-csstree": "^1.0.19",
|
"@csstools/css-syntax-patches-for-csstree": "^1.0.19",
|
||||||
|
|
@ -8094,7 +8082,6 @@
|
||||||
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
|
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/source-map": "^0.3.3",
|
"@jridgewell/source-map": "^0.3.3",
|
||||||
"acorn": "^8.15.0",
|
"acorn": "^8.15.0",
|
||||||
|
|
@ -8419,7 +8406,6 @@
|
||||||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
|
|
@ -8807,7 +8793,6 @@
|
||||||
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
147
styles.css
147
styles.css
|
|
@ -268,6 +268,7 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
font-family: var(--font-family, 'Inter', sans-serif) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
|
@ -280,7 +281,7 @@ html {
|
||||||
body {
|
body {
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
font-family: var(--font-family, 'Inter', sans-serif);
|
font-family: var(--font-family, 'Inter', sans-serif) !important;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition:
|
transition:
|
||||||
background-color 0.3s ease,
|
background-color 0.3s ease,
|
||||||
|
|
@ -318,7 +319,7 @@ kbd {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
font-family: 'Courier New', monospace;
|
font-family: inherit;
|
||||||
box-shadow: 0 2px 4px rgb(0, 0, 0, 0.1);
|
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);
|
border-radius: var(--radius);
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.template-input:focus {
|
.template-input:focus {
|
||||||
|
|
@ -1782,6 +1782,142 @@ input[type='search']::-webkit-search-cancel-button {
|
||||||
border-color: var(--ring);
|
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 {
|
.toggle-switch {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
@ -3041,7 +3177,6 @@ input:checked + .slider::before {
|
||||||
|
|
||||||
.about-tech p {
|
.about-tech p {
|
||||||
color: var(--muted-foreground);
|
color: var(--muted-foreground);
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3205,7 +3340,6 @@ input:checked + .slider::before {
|
||||||
background-color: var(--secondary);
|
background-color: var(--secondary);
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3376,7 +3510,6 @@ input:checked + .slider::before {
|
||||||
}
|
}
|
||||||
|
|
||||||
.lyrics-timing-display {
|
.lyrics-timing-display {
|
||||||
font-family: monospace;
|
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
|
|
@ -4002,7 +4135,6 @@ img[src=''] {
|
||||||
padding: 0.75rem 1.25rem;
|
padding: 0.75rem 1.25rem;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
font-family: Inter, sans-serif;
|
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
|
@ -4926,7 +5058,6 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: var(--muted-foreground);
|
color: var(--muted-foreground);
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
font-family: monospace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.annotation-text {
|
.annotation-text {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue