fix: address all CodeRabbit review findings from PR #477
Engine & algorithm: - Use default shelf Q (1/√2) in calculateBiquadResponse for shelf filters - Compute normalization offset on measurement frequency grid to avoid bias - Try stale cache for all fetch errors in autoeq-importer, not just timeouts Audio pipeline: - Pass postProcessingQuality (preserves Dolby Atmos override) in api.js - Persist custom band frequencies in equalizerSettings storage - Restore custom frequencies on _loadSettings instead of regenerating defaults - Export clamped preamp value in applyAutoEQBands text output - Propagate filter type and Q values through equalizer import chain - Update freqRange after importing custom filter frequencies - Remove return in finally block that hid LOSSLESS fallback failures Data consistency: - Normalize artist IDs with String() in blockArtist/unblockArtist Lint & code quality: - Annotate empty catch blocks (Atmos codec probes) - Remove unused catch parameters Accessibility: - Add aria-label and for attributes to all AutoEQ form controls - Add role="status" aria-live="polite" to feedback spans - Update filter type documentation to reflect shelf support - Hide parametric-only sections by default to match active tab UI: - Move AutoEq button directly under graph - Hide shared button in Parametric/Speaker modes - Replace hardcoded white legend dot with theme-adaptive color-mix - Add pointer-events:none and focus-within to profile delete button
This commit is contained in:
parent
77f9e10fdc
commit
782e98061b
10 changed files with 149 additions and 50 deletions
34
index.html
34
index.html
|
|
@ -4046,8 +4046,8 @@
|
|||
<h4>Parametric EQ — Manual Control</h4>
|
||||
<ol>
|
||||
<li>
|
||||
Each band is a <b>peaking filter</b> with frequency, gain, and Q
|
||||
(width).
|
||||
Each band supports <b>peaking, low-shelf, and high-shelf</b> filter
|
||||
types with frequency, gain, and Q (width).
|
||||
</li>
|
||||
<li>
|
||||
<b>Drag nodes</b> on the graph to adjust frequency and gain
|
||||
|
|
@ -4162,6 +4162,7 @@
|
|||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button id="autoeq-run-btn" class="autoeq-run-btn" disabled>AutoEq</button>
|
||||
</div>
|
||||
|
||||
<!-- Database Browser -->
|
||||
|
|
@ -4180,6 +4181,7 @@
|
|||
id="autoeq-headphone-search"
|
||||
placeholder="Search model (e.g. HD 600)..."
|
||||
autocomplete="off"
|
||||
aria-label="Search headphone model"
|
||||
/>
|
||||
</div>
|
||||
<div class="autoeq-database-content">
|
||||
|
|
@ -4194,11 +4196,10 @@
|
|||
|
||||
<!-- AutoEQ Controls -->
|
||||
<div class="autoeq-controls-section">
|
||||
<button id="autoeq-run-btn" class="autoeq-run-btn" disabled>AutoEq</button>
|
||||
<div class="autoeq-control-group">
|
||||
<label class="autoeq-control-label">HEADPHONE MODEL</label>
|
||||
<label class="autoeq-control-label" for="autoeq-headphone-select">HEADPHONE MODEL</label>
|
||||
<div class="autoeq-select-wrapper">
|
||||
<select id="autoeq-headphone-select">
|
||||
<select id="autoeq-headphone-select" aria-label="Headphone model">
|
||||
<option value="">Select a headphone...</option>
|
||||
</select>
|
||||
<button
|
||||
|
|
@ -4257,8 +4258,8 @@
|
|||
|
||||
<div class="autoeq-controls-row">
|
||||
<div class="autoeq-control-mini">
|
||||
<label class="autoeq-control-label">FILTER BANDS</label>
|
||||
<select id="autoeq-band-count">
|
||||
<label class="autoeq-control-label" for="autoeq-band-count">FILTER BANDS</label>
|
||||
<select id="autoeq-band-count" aria-label="Filter bands">
|
||||
<option value="5">5</option>
|
||||
<option value="10" selected>10</option>
|
||||
<option value="15">15</option>
|
||||
|
|
@ -4267,8 +4268,8 @@
|
|||
</select>
|
||||
</div>
|
||||
<div class="autoeq-control-mini">
|
||||
<label class="autoeq-control-label">MAX HZ</label>
|
||||
<select id="autoeq-max-freq">
|
||||
<label class="autoeq-control-label" for="autoeq-max-freq">MAX HZ</label>
|
||||
<select id="autoeq-max-freq" aria-label="Maximum frequency">
|
||||
<option value="6000">6k</option>
|
||||
<option value="8000">8k</option>
|
||||
<option value="10000">10k</option>
|
||||
|
|
@ -4278,8 +4279,8 @@
|
|||
</select>
|
||||
</div>
|
||||
<div class="autoeq-control-mini">
|
||||
<label class="autoeq-control-label">SAMPLE RATE</label>
|
||||
<select id="autoeq-sample-rate">
|
||||
<label class="autoeq-control-label" for="autoeq-sample-rate">SAMPLE RATE</label>
|
||||
<select id="autoeq-sample-rate" aria-label="Sample rate">
|
||||
<option value="44100">44.1k</option>
|
||||
<option value="48000" selected>48k</option>
|
||||
<option value="96000">96k</option>
|
||||
|
|
@ -4297,7 +4298,7 @@
|
|||
<use svg="!lucide/download.svg" size="20" />
|
||||
</button>
|
||||
</div>
|
||||
<span id="autoeq-status" class="autoeq-status"></span>
|
||||
<span id="autoeq-status" class="autoeq-status" role="status" aria-live="polite"></span>
|
||||
</div>
|
||||
|
||||
<!-- Saved Profiles -->
|
||||
|
|
@ -4313,6 +4314,7 @@
|
|||
id="autoeq-profile-name"
|
||||
class="autoeq-profile-name-input"
|
||||
placeholder="Profile name..."
|
||||
aria-label="AutoEQ profile name"
|
||||
maxlength="50"
|
||||
/>
|
||||
<button id="autoeq-save-btn" class="btn-primary autoeq-save-btn">
|
||||
|
|
@ -4524,7 +4526,7 @@
|
|||
>
|
||||
Measure All
|
||||
</button>
|
||||
<span id="speaker-eq-status" class="autoeq-status"></span>
|
||||
<span id="speaker-eq-status" class="autoeq-status" role="status" aria-live="polite"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -4542,6 +4544,7 @@
|
|||
id="speaker-profile-name"
|
||||
class="autoeq-profile-name-input"
|
||||
placeholder="Profile name..."
|
||||
aria-label="Speaker EQ profile name"
|
||||
maxlength="50"
|
||||
/>
|
||||
<button id="speaker-save-btn" class="btn-primary autoeq-save-btn">
|
||||
|
|
@ -4577,7 +4580,7 @@
|
|||
</div>
|
||||
<div class="autoeq-filters-content" id="autoeq-filters-content">
|
||||
<!-- Preset Selector (visible in parametric mode) -->
|
||||
<div class="autoeq-preset-row" id="autoeq-preset-row">
|
||||
<div class="autoeq-preset-row" id="autoeq-preset-row" style="display: none">
|
||||
<div class="autoeq-control-group">
|
||||
<label class="autoeq-control-label">PRESET</label>
|
||||
<select id="parametric-preset-select">
|
||||
|
|
@ -4603,7 +4606,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Saved Profiles for Parametric EQ -->
|
||||
<div class="autoeq-parametric-profiles" id="autoeq-parametric-profiles">
|
||||
<div class="autoeq-parametric-profiles" id="autoeq-parametric-profiles" style="display: none">
|
||||
<div class="autoeq-saved-header">
|
||||
<div class="autoeq-saved-header-left">
|
||||
<span class="autoeq-saved-title">SAVED PROFILES</span>
|
||||
|
|
@ -4617,6 +4620,7 @@
|
|||
id="parametric-profile-name"
|
||||
class="autoeq-profile-name-input"
|
||||
placeholder="Profile name..."
|
||||
aria-label="Parametric EQ profile name"
|
||||
maxlength="50"
|
||||
/>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -1500,7 +1500,9 @@ export class LosslessAPI {
|
|||
a.canPlayType('audio/mp4; codecs="ec-3"') || a.canPlayType('audio/mp4; codecs="eac3"')
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch {
|
||||
// Atmos codec probe — intentionally swallowed; canPlayAtmos stays false
|
||||
}
|
||||
|
||||
const paramsArray = [];
|
||||
|
||||
|
|
@ -1888,7 +1890,7 @@ export class LosslessAPI {
|
|||
quality,
|
||||
onProgress,
|
||||
options.signal,
|
||||
lookup.info?.audioQuality ?? null
|
||||
postProcessingQuality
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -728,7 +728,8 @@ class AudioContextManager {
|
|||
this.isEQEnabled = equalizerSettings.isEnabled();
|
||||
this.bandCount = equalizerSettings.getBandCount();
|
||||
this.freqRange = equalizerSettings.getFreqRange();
|
||||
this.frequencies = generateFrequencies(this.bandCount, this.freqRange.min, this.freqRange.max);
|
||||
const customFreqs = equalizerSettings.getCustomFrequencies(this.bandCount);
|
||||
this.frequencies = customFreqs || generateFrequencies(this.bandCount, this.freqRange.min, this.freqRange.max);
|
||||
this.currentGains = equalizerSettings.getGains(this.bandCount);
|
||||
this.currentTypes = equalizerSettings.getBandTypes(this.bandCount);
|
||||
this.currentQs = equalizerSettings.getBandQs(this.bandCount);
|
||||
|
|
@ -827,12 +828,13 @@ class AudioContextManager {
|
|||
}
|
||||
|
||||
// Persist normalized band descriptors to settings store
|
||||
equalizerSettings.setCustomFrequencies(this.frequencies);
|
||||
equalizerSettings.setGains(this.currentGains);
|
||||
equalizerSettings.setBandTypes(this.currentTypes);
|
||||
equalizerSettings.setBandQs(this.currentQs);
|
||||
|
||||
// Generate export text using clamped gain values
|
||||
const lines = [`Preamp: ${preamp.toFixed(1)} dB`];
|
||||
// Generate export text using the actual applied preamp value
|
||||
const lines = [`Preamp: ${this.preamp.toFixed(1)} dB`];
|
||||
sortedBands.forEach((band, index) => {
|
||||
if (index >= count) return;
|
||||
const filterType = band.type === 'lowshelf' ? 'LS' : band.type === 'highshelf' ? 'HS' : 'PK';
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ function calculateBiquadResponse(f, band, sr = DEFAULT_SR) {
|
|||
if (!band.type || band.type.length === 0) return 0;
|
||||
const w = (2 * PI * band.freq) / sr;
|
||||
const p = (2 * PI * f) / sr;
|
||||
const s = Math.sin(w) / (2 * band.q);
|
||||
const t = band.type[0];
|
||||
// WebAudio ignores Q for shelf filters; use 1/√2 (slope = 1) to match
|
||||
const effectiveQ = t === 'l' || t === 'h' ? Math.SQRT1_2 : band.q;
|
||||
const s = Math.sin(w) / (2 * effectiveQ);
|
||||
const A = Math.pow(DB_BASE, band.gain / DB_DIVISOR);
|
||||
const c = Math.cos(w);
|
||||
let b0 = 0,
|
||||
|
|
@ -33,8 +36,6 @@ function calculateBiquadResponse(f, band, sr = DEFAULT_SR) {
|
|||
a1 = 0,
|
||||
a2 = 0;
|
||||
|
||||
const t = band.type[0];
|
||||
|
||||
if (t === 'p') {
|
||||
b0 = 1 + s * A;
|
||||
b1 = -2 * c;
|
||||
|
|
@ -98,19 +99,37 @@ function interpolate(freq, data) {
|
|||
|
||||
/**
|
||||
* Calculate normalization offset based on midrange average (250-2500 Hz)
|
||||
* @param {Array<{freq: number, gain: number}>} data - Frequency response data
|
||||
* @returns {number} Average gain in midrange
|
||||
* With one argument: returns the midrange average of that curve (for graph centering).
|
||||
* With two arguments: evaluates both curves on the measurement frequency grid
|
||||
* to avoid sampling-density bias, returning (avgTarget - avgMeasurement).
|
||||
* @param {Array<{freq: number, gain: number}>} measurement - Measurement/data curve
|
||||
* @param {Array<{freq: number, gain: number}>} [target] - Optional target curve
|
||||
* @returns {number} Midrange average, or alignment offset when target is provided
|
||||
*/
|
||||
function getNormalizationOffset(data) {
|
||||
let sum = 0,
|
||||
function getNormalizationOffset(measurement, target) {
|
||||
if (!target) {
|
||||
let sum = 0,
|
||||
count = 0;
|
||||
for (const p of measurement) {
|
||||
if (p.freq >= 250 && p.freq <= 2500) {
|
||||
sum += p.gain;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count > 0 ? sum / count : interpolate(1000, measurement);
|
||||
}
|
||||
let sumTarget = 0,
|
||||
sumMeasurement = 0,
|
||||
count = 0;
|
||||
for (const p of data) {
|
||||
for (const p of measurement) {
|
||||
if (p.freq >= 250 && p.freq <= 2500) {
|
||||
sum += p.gain;
|
||||
sumTarget += interpolate(p.freq, target);
|
||||
sumMeasurement += p.gain;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count > 0 ? sum / count : interpolate(1000, data);
|
||||
if (count > 0) return sumTarget / count - sumMeasurement / count;
|
||||
return interpolate(1000, target) - interpolate(1000, measurement);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -135,7 +154,7 @@ function runAutoEqAlgorithm(
|
|||
sampleRate = DEFAULT_SR
|
||||
) {
|
||||
if (minFreq > maxFreq) return [];
|
||||
const off = getNormalizationOffset(target) - getNormalizationOffset(measurement);
|
||||
const off = getNormalizationOffset(measurement, target);
|
||||
let err = measurement.map((p) => ({ freq: p.freq, gain: p.gain + off - interpolate(p.freq, target) }));
|
||||
|
||||
const hasInRangePoints = err.some((p) => p.freq >= minFreq && p.freq <= maxFreq);
|
||||
|
|
|
|||
|
|
@ -211,15 +211,15 @@ async function fetchAutoEqIndex() {
|
|||
} catch (err) {
|
||||
if (err.name === 'AbortError') {
|
||||
console.warn('[AutoEQ] GitHub API request timed out. Falling back to cache or fallback index.');
|
||||
try {
|
||||
const cached = await db.getSetting(CACHE_KEY);
|
||||
if (cached?.data) return cached.data;
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
} else {
|
||||
console.error('[AutoEQ] Failed to fetch index:', err);
|
||||
}
|
||||
try {
|
||||
const cached = await db.getSetting(CACHE_KEY);
|
||||
if (cached?.data) return cached.data;
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return FALLBACK_INDEX;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -696,10 +696,38 @@ export class Equalizer {
|
|||
});
|
||||
}
|
||||
|
||||
// Extract and apply gains
|
||||
// Extract and apply gains, types, and Qs
|
||||
const gains = sliced.map((f) => f.gain);
|
||||
this.setAllGains(gains);
|
||||
|
||||
// Apply filter types (PK/LS/HS -> peaking/lowshelf/highshelf)
|
||||
const typeMap = { PK: 'peaking', LS: 'lowshelf', HS: 'highshelf', LSC: 'lowshelf', HSC: 'highshelf' };
|
||||
const types = sliced.map((f) => typeMap[f.type] || 'peaking');
|
||||
this.currentTypes = types;
|
||||
if (this.filters.length === types.length) {
|
||||
types.forEach((type, i) => {
|
||||
if (this.filters[i]) this.filters[i].type = type;
|
||||
});
|
||||
}
|
||||
equalizerSettings.setBandTypes(types);
|
||||
|
||||
// Apply Q values
|
||||
const qs = sliced.map((f) => f.q);
|
||||
this.currentQs = qs;
|
||||
if (this.filters.length === qs.length) {
|
||||
qs.forEach((q, i) => {
|
||||
if (this.filters[i]) this.filters[i].Q.value = q;
|
||||
});
|
||||
}
|
||||
equalizerSettings.setBandQs(qs);
|
||||
|
||||
// Persist custom frequencies and update freqRange
|
||||
equalizerSettings.setCustomFrequencies(newFreqs);
|
||||
const minFreq = Math.min(...newFreqs);
|
||||
const maxFreq = Math.max(...newFreqs);
|
||||
this.freqRange = { min: minFreq, max: maxFreq };
|
||||
equalizerSettings.setFreqRange(minFreq, maxFreq);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn('[Equalizer] Failed to import settings:', e);
|
||||
|
|
|
|||
14
js/player.js
14
js/player.js
|
|
@ -177,7 +177,9 @@ export class Player {
|
|||
if (this.video.readyState >= 2 && (this.audio.readyState > 0 || this.audio.src)) {
|
||||
this.audio.currentTime = this.video.currentTime;
|
||||
}
|
||||
} catch (err) {}
|
||||
} catch {
|
||||
// Video-to-audio time sync may fail if readyState is stale
|
||||
}
|
||||
}
|
||||
|
||||
const syncedEvent = new Event(eventName, { bubbles: e.bubbles, cancelable: e.cancelable });
|
||||
|
|
@ -1032,12 +1034,12 @@ export class Player {
|
|||
try {
|
||||
await this.playTrackFromQueue(startTime, recursiveCount, true);
|
||||
return;
|
||||
} catch (retryError) {
|
||||
} catch {
|
||||
// LOSSLESS fallback also failed — fall through to error handling below
|
||||
} finally {
|
||||
this.quality = originalQuality;
|
||||
this.isFallbackRetry = false;
|
||||
this.isFallbackInProgress = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1746,7 +1748,9 @@ export class Player {
|
|||
a.canPlayType('audio/mp4; codecs="ec-3"') || a.canPlayType('audio/mp4; codecs="eac3"')
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch {
|
||||
// Atmos codec detection may fail on some browsers
|
||||
}
|
||||
|
||||
let isAtmosPlaying = isTrackAtmos && deviceSupportsAtmos;
|
||||
const q = this.quality || localStorage.getItem('adaptive-playback-quality') || 'auto';
|
||||
|
|
@ -1814,7 +1818,7 @@ export class Player {
|
|||
// Re-enable ABR so it can dynamically downgrade within that new codec family if needed
|
||||
this.shakaPlayer.configure({ abr: { enabled: true } });
|
||||
}
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// fail silently on abr checks
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1571,7 +1571,7 @@ export async function initializeSettings(scrobbler, player, api, ui) {
|
|||
// Draw Original measurement (normalized + shifted)
|
||||
if (graphMeasurement) {
|
||||
const normOff = targetData
|
||||
? getNormalizationOffset(targetData) - getNormalizationOffset(graphMeasurement)
|
||||
? getNormalizationOffset(graphMeasurement, targetData)
|
||||
: 0;
|
||||
const normalized = graphMeasurement.map((p) => ({ freq: p.freq, gain: p.gain + normOff + graphShift }));
|
||||
drawCurve(normalized, originalColor, 1.5);
|
||||
|
|
@ -1746,7 +1746,7 @@ export async function initializeSettings(scrobbler, player, api, ui) {
|
|||
}
|
||||
const targetEntry = tList.find((t) => t.id === tId);
|
||||
const targetData = targetEntry?.data;
|
||||
const normOff = targetData ? getNormalizationOffset(targetData) - getNormalizationOffset(measurement) : 0;
|
||||
const normOff = targetData ? getNormalizationOffset(measurement, targetData) : 0;
|
||||
const sampleRate = autoeqSampleRate ? parseInt(autoeqSampleRate.value, 10) : 48000;
|
||||
|
||||
autoeqCorrectedCurve = measurement.map((p) => {
|
||||
|
|
@ -3113,6 +3113,8 @@ export async function initializeSettings(scrobbler, player, api, ui) {
|
|||
|
||||
// Graph always visible in all modes
|
||||
if (graphSection) graphSection.style.display = '';
|
||||
// Only show shared AutoEq button in AutoEQ mode
|
||||
if (autoeqRunBtn) autoeqRunBtn.style.display = mode === 'autoeq' ? '' : 'none';
|
||||
|
||||
// Hide all mode-specific sections first
|
||||
if (controlsSection) controlsSection.style.display = 'none';
|
||||
|
|
|
|||
|
|
@ -1012,6 +1012,7 @@ export const equalizerSettings = {
|
|||
FREQ_MIN_KEY: 'equalizer-freq-min',
|
||||
FREQ_MAX_KEY: 'equalizer-freq-max',
|
||||
PREAMP_KEY: 'equalizer-preamp',
|
||||
CUSTOM_FREQUENCIES_KEY: 'equalizer-custom-frequencies',
|
||||
DEFAULT_BAND_COUNT: 16,
|
||||
MIN_BANDS: 3,
|
||||
MAX_BANDS: 32,
|
||||
|
|
@ -1280,6 +1281,40 @@ export const equalizerSettings = {
|
|||
}
|
||||
},
|
||||
|
||||
getCustomFrequencies(bandCount) {
|
||||
const count = bandCount || this.getBandCount();
|
||||
try {
|
||||
const stored = localStorage.getItem(this.CUSTOM_FREQUENCIES_KEY);
|
||||
if (stored) {
|
||||
const freqs = JSON.parse(stored);
|
||||
if (Array.isArray(freqs) && freqs.length === count) {
|
||||
return freqs;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
setCustomFrequencies(frequencies) {
|
||||
try {
|
||||
if (Array.isArray(frequencies) && frequencies.length >= this.MIN_BANDS && frequencies.length <= this.MAX_BANDS) {
|
||||
localStorage.setItem(this.CUSTOM_FREQUENCIES_KEY, JSON.stringify(frequencies));
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[EQ] Failed to save custom frequencies:', e);
|
||||
}
|
||||
},
|
||||
|
||||
clearCustomFrequencies() {
|
||||
try {
|
||||
localStorage.removeItem(this.CUSTOM_FREQUENCIES_KEY);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
|
||||
getBandTypes(bandCount) {
|
||||
const count = bandCount || this.getBandCount();
|
||||
try {
|
||||
|
|
@ -2731,7 +2766,7 @@ export const contentBlockingSettings = {
|
|||
blockArtist(artist) {
|
||||
if (!artist || !artist.id) return;
|
||||
const blocked = this.getBlockedArtists();
|
||||
if (!blocked.some((a) => a.id === artist.id)) {
|
||||
if (!blocked.some((a) => String(a.id) === String(artist.id))) {
|
||||
blocked.push({
|
||||
id: artist.id,
|
||||
name: artist.name || 'Unknown Artist',
|
||||
|
|
@ -2742,7 +2777,7 @@ export const contentBlockingSettings = {
|
|||
},
|
||||
|
||||
unblockArtist(artistId) {
|
||||
const blocked = this.getBlockedArtists().filter((a) => a.id !== artistId);
|
||||
const blocked = this.getBlockedArtists().filter((a) => String(a.id) !== String(artistId));
|
||||
this.setBlockedArtists(blocked);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -7907,8 +7907,8 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn {
|
|||
}
|
||||
|
||||
.legend-target .legend-dot {
|
||||
background: rgb(255 255 255 / 0.5);
|
||||
border: 1px dashed rgb(255 255 255 / 0.3);
|
||||
background: color-mix(in srgb, var(--foreground) 50%, transparent);
|
||||
border: 1px dashed color-mix(in srgb, var(--foreground) 30%, transparent);
|
||||
}
|
||||
|
||||
.legend-corrected .legend-dot {
|
||||
|
|
@ -8304,6 +8304,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn {
|
|||
color: var(--muted-foreground);
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: all var(--transition-fast);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
|
@ -8313,8 +8314,10 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn {
|
|||
color: var(--destructive-foreground);
|
||||
}
|
||||
|
||||
.autoeq-profile-card:hover .autoeq-profile-delete {
|
||||
.autoeq-profile-card:hover .autoeq-profile-delete,
|
||||
.autoeq-profile-card:focus-within .autoeq-profile-delete {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* --- Database Browser --- */
|
||||
|
|
|
|||
Loading…
Reference in a new issue