This commit is contained in:
2026-09-16 05:30:51 +02:00
parent 20470435ea
commit a7c3a5e472
12 changed files with 1374 additions and 172 deletions
+91 -10
View File
@@ -42,6 +42,8 @@
--x2154: #215483;
--bg-canvas-opacity: 1.0;
--bg-canvas-blur: 30px;
--bg-canvas-color: transparent;
--bg-canvas-color-opacity: 0.0;
--onara-bg-opacity: 0.7;
--onara-backdrop-blur: 5px;
--onara-grid-dim-opacity: 0.38;
@@ -3270,6 +3272,27 @@ body.sidebar-right-hidden #sidebar-drag-zone {
will-change: transform, box-shadow, border-color;
}
/* Motion trail ghost — faint blurred echo of the eye that smears on fast movement */
.eye-trail-ghost {
position: absolute;
top: 50%;
left: 50%;
width: clamp(300px, 72vmin, 700px);
height: clamp(300px, 72vmin, 700px);
max-width: calc(100% - 60px);
max-height: calc(100% - 80px);
border-radius: 50%;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-color: transparent;
pointer-events: none;
z-index: 0;
opacity: 0;
will-change: transform, opacity, filter;
mix-blend-mode: screen;
}
.audio-eye-inner-canvas {
position: absolute;
top: 50%;
@@ -3280,6 +3303,16 @@ body.sidebar-right-hidden #sidebar-drag-zone {
z-index: 2;
}
.audio-void-outer-canvas {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
z-index: 0;
border-radius: 0;
}
.audio-cover-circle i {
font-size: 3.4rem;
color: var(--accent);
@@ -3365,10 +3398,10 @@ body.sidebar-right-hidden #sidebar-drag-zone {
.v0ck.v0ck_fullscreen .audio-cover-circle,
#main:fullscreen .v0ck.v0ck_fullscreen .audio-cover-circle,
#onara-modal:fullscreen .v0ck.v0ck_fullscreen .audio-cover-circle {
width: clamp(260px, 56vmin, 560px);
height: clamp(260px, 56vmin, 560px);
max-width: calc(100% - 80px);
max-height: calc(100% - 100px);
width: clamp(300px, 72vmin, 700px);
height: clamp(300px, 72vmin, 700px);
max-width: calc(100% - 60px);
max-height: calc(100% - 80px);
}
.v0ck:fullscreen .audio-cover-circle i,
@@ -10365,6 +10398,7 @@ div.favs div.posts {
opacity: var(--bg-canvas-opacity, 1.0);
filter: var(--bg-canvas-filter, none);
-webkit-filter: var(--bg-canvas-filter, none);
background-color: var(--bg-canvas-color, transparent);
}
button#togglebg {
@@ -20816,6 +20850,24 @@ body.onara-modal-open .posts > a.thumb.onara-active::after {
opacity: 1 !important;
}
body.onara-modal-open .posts > a.thumb.loaded {
opacity: 1 !important;
filter: none !important;
z-index: 50 !important;
position: relative !important;
}
body.onara-modal-open #bg,
body.onara-modal-open #bg.fader-in,
body.onara-modal-open #bg.fader-out,
body.onara-modal-open #bg.fast-fade {
z-index: 51 !important;
cursor: pointer !important;
transition: none !important;
animation: none !important;
opacity: var(--bg-canvas-opacity, 1.0) !important;
}
/* Navbar and right sidebar stay fully visible, sharp, and interactive */
body.onara-modal-open nav.navbar {
opacity: 1 !important;
@@ -20885,7 +20937,7 @@ body.onara-modal-open .admin-bar.open {
transition: opacity 0.28s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.28s cubic-bezier(0.16, 1, 0.3, 1),
right 0.3s ease-in-out !important;
cursor: default;
cursor: pointer;
}
.onara-modal-wrapper::-webkit-scrollbar {
@@ -20962,7 +21014,7 @@ body.sidebar-right-hidden .onara-modal-wrapper {
height: 100% !important;
min-height: 100% !important;
z-index: 1 !important;
cursor: default;
cursor: pointer;
}
.onara-modal-content {
@@ -20977,7 +21029,7 @@ body.sidebar-right-hidden .onara-modal-wrapper {
flex: 1 0 auto !important;
padding: 20px 0 80px 0 !important;
box-sizing: border-box !important;
cursor: default;
cursor: pointer;
}
#onara-item-mount {
@@ -20986,7 +21038,7 @@ body.sidebar-right-hidden .onara-modal-wrapper {
flex-direction: column !important;
align-items: center !important;
justify-content: flex-start !important;
cursor: default;
cursor: pointer;
}
/* Center the item layout inside modal and make it 100% scrollable */
@@ -21008,13 +21060,13 @@ body.layout-modern #onara-modal .item-layout-container {
box-sizing: border-box !important;
grid-template-columns: none !important;
grid-template-rows: none !important;
cursor: default;
cursor: pointer;
}
#onara-modal .item-main-content,
body.layout-legacy #onara-modal .item-layout-container .item-main-content,
body.layout-modern #onara-modal .item-layout-container .item-main-content {
cursor: default !important;
cursor: pointer !important;
display: flex !important;
flex-direction: column !important;
align-items: center !important;
@@ -21035,6 +21087,35 @@ body.layout-modern #onara-modal .item-layout-container .item-main-content {
display: none !important;
}
#onara-modal .item-main-content > *,
#onara-modal .content,
#onara-modal ._204863,
#onara-modal .item_title,
#onara-modal .item-title,
#onara-modal .metadata,
#onara-modal #comments-container,
#onara-modal .item-sidebar-left,
#onara-modal .media-object,
#onara-modal .v0ck {
cursor: default;
}
#onara-modal a,
#onara-modal button,
#onara-modal .btn,
#onara-modal [role="button"],
#onara-modal label,
#onara-modal .rating-tag,
#onara-modal .sidebar-tag {
cursor: pointer;
}
#onara-modal input,
#onara-modal textarea,
#onara-modal [contenteditable="true"] {
cursor: text;
}
#onara-modal .item-main-content > * {
width: 100% !important;
max-width: 800px !important;
+9 -1
View File
@@ -107,9 +107,17 @@
if (canManage) {
span.classList.add('can-cycle');
}
} else if (!window.f0ckSession?.is_anonymized && window.f0ckSession?.logged_in && !window.f0ckSession?.is_anon && (tag.display_name || tag.user)) {
} else {
span.classList.add('tag-badge');
span.setAttribute('data-tag-id', tag.id || '');
span.setAttribute('data-tag', tag.tag);
span.setAttribute('data-tag-normalized', tag.normalized);
if (!window.f0ckSession?.is_anonymized && window.f0ckSession?.logged_in && !window.f0ckSession?.is_anon && (tag.display_name || tag.user)) {
span.setAttribute("tooltip", tag.display_name || tag.user);
}
const isExcl = !!tag.is_excluded;
if (isExcl) span.classList.add('tag-is-excluded');
}
span.appendChild(contentEl);
+1148 -18
View File
File diff suppressed because it is too large Load Diff
+18 -12
View File
@@ -1585,19 +1585,25 @@
});
};
setupAudioTuningSlider('onara_bg_opacity_slider', 'onara_bg_opacity_val', 'onaraBgOpacity', 0.70, v => Math.round(v * 100) + '%');
setupAudioTuningSlider('onara_backdrop_blur_slider', 'onara_backdrop_blur_val', 'onaraBackdropBlur', 5, v => Math.round(v) + 'px');
setupAudioTuningSlider('onara_grid_dim_slider', 'onara_grid_dim_val', 'onaraGridDim', 0.38, v => Math.round(v * 100) + '%');
setupAudioTuningSlider('bg_canvas_opacity_slider', 'bg_canvas_opacity_val', 'bgCanvasOpacity', 1.00, v => Math.round(v * 100) + '%');
setupAudioTuningSlider('bg_canvas_blur_slider', 'bg_canvas_blur_val', 'bgCanvasBlur', 30, v => Math.round(v) + 'px');
setupAudioTuningSlider('bg_canvas_brightness_slider', 'bg_canvas_brightness_val', 'bgCanvasBrightness', 0.45, v => Number(v).toFixed(2) + 'x');
setupAudioTuningSlider('bg_canvas_saturate_slider', 'bg_canvas_saturate_val', 'bgCanvasSaturate', 1.8, v => Number(v).toFixed(2) + 'x');
setupAudioTuningSlider('bg_canvas_contrast_slider', 'bg_canvas_contrast_val', 'bgCanvasContrast', 1.1, v => Number(v).toFixed(2) + 'x');
setupAudioTuningSlider('bg_canvas_zoom_slider', 'bg_canvas_zoom_val', 'bgCanvasZoom', 1.12, v => Number(v).toFixed(2) + 'x');
// Blur Engine slider — shows friendly label text instead of raw number
const blurMethodNames = ['Canvas 2D', 'GPU CSS Filter', 'Hybrid'];
setupAudioTuningSlider('bg_blur_method_slider', 'bg_blur_method_val', 'bgBlurMethod', 0, v => blurMethodNames[Math.round(v)] || String(Math.round(v)), v => Math.round(Number(v)));
// Onara enable/disable toggle (localStorage only — no server round-trip needed)
const onaraEnabledToggle = document.getElementById('onara_enabled_toggle');
if (onaraEnabledToggle) {
const LS_KEY = 'f0ck_onara_enabled';
// Seed checkbox from localStorage; fall back to the server-set session value
const stored = localStorage.getItem(LS_KEY);
const sessionOnara = !!(window.f0ckSession?.onara);
onaraEnabledToggle.checked = stored !== null ? stored === 'true' : sessionOnara;
// Reflect current state into window.onara so isOnaraActive() sees it
window.onara = onaraEnabledToggle.checked;
onaraEnabledToggle.addEventListener('change', () => {
const enabled = onaraEnabledToggle.checked;
localStorage.setItem(LS_KEY, String(enabled));
window.onara = enabled;
showStatus('Onara viewer ' + (enabled ? 'enabled' : 'disabled') + '.', 'success');
});
}
// Quote Emojis Toggle
const quoteEmojisToggle = document.getElementById('quote_emojis_toggle');
+9 -3
View File
@@ -198,7 +198,9 @@ window.escapeHtmlUpload = window.escapeHtmlUpload || ((unsafe) => {
tag_id: item.rating_tag_id || item.tag_id || 0,
is_oc: !!item.is_oc,
slug: item.slug,
visibility: item.visibility || 0
visibility: item.visibility || 0,
is_album: !!item.is_album,
album_count: item.album_count || 0
});
}
})
@@ -3068,7 +3070,9 @@ window.initUploadForm = (selector) => {
tag_id: res.tag_id ?? 0,
is_oc: !!res.is_oc,
slug: res.slug,
visibility: res.visibility || 0
visibility: res.visibility || 0,
is_album: !!res.is_album,
album_count: res.album_count || 0
});
}
@@ -3320,7 +3324,9 @@ window.initUploadForm = (selector) => {
tag_id: up.tag_id ?? 0,
is_oc: !!up.is_oc,
slug: up.slug,
visibility: up.visibility || 0
visibility: up.visibility || 0,
is_album: !!up.is_album,
album_count: up.album_count || 0
});
}
}
+19 -1
View File
@@ -103,12 +103,30 @@
if (canManage) {
span.classList.add('can-cycle');
}
} else if (!window.f0ckSession?.is_anonymized && window.f0ckSession?.logged_in && !window.f0ckSession?.is_anon && (tag.display_name || tag.user)) {
} else {
span.classList.add('tag-badge');
span.setAttribute('data-tag-id', tag.id || '');
span.setAttribute('data-tag', tag.tag);
span.setAttribute('data-tag-normalized', tag.normalized);
if (!window.f0ckSession?.is_anonymized && window.f0ckSession?.logged_in && !window.f0ckSession?.is_anon && (tag.display_name || tag.user)) {
span.setAttribute('tooltip', tag.display_name || tag.user);
}
const isExcl = !!tag.is_excluded;
if (isExcl) span.classList.add('tag-is-excluded');
}
span.insertAdjacentElement("beforeend", contentEl);
if (!isRating && tag.can_exclude) {
const excludeBtn = document.createElement('button');
excludeBtn.type = 'button';
excludeBtn.className = 'tag-exclude-btn';
excludeBtn.setAttribute('title', tag.is_excluded ? 'Excluded (click to unexclude)' : 'Exclude tag');
excludeBtn.setAttribute('aria-label', 'Exclude tag');
excludeBtn.innerHTML = `<i class="fa-solid ${tag.is_excluded ? 'fa-circle-check' : 'fa-ban'}"></i>`;
span.insertAdjacentElement('beforeend', excludeBtn);
}
if (window.f0ckSession && (window.f0ckSession.is_admin || window.f0ckSession.is_moderator) && !isRating) {
const space = document.createTextNode('\u00A0'); // &nbsp;
span.appendChild(space);
+20
View File
@@ -503,6 +503,26 @@ export default router => {
VALUES (${postid}, ${nextTagId}, ${+req.session.id})
`;
}
// Propagate rating to all album sub-items if this is an album
const albumCheck = await sql`SELECT id FROM items WHERE id = ${postid} AND is_album = true LIMIT 1`;
if (albumCheck.length > 0) {
const subItems = await sql`SELECT id FROM album_items WHERE item_id = ${postid}`;
for (const sub of subItems) {
await sql`
DELETE FROM album_items_tags_assign
WHERE album_item_id = ${sub.id}
AND tag_id IN (1, 2, ${nsflId})
`;
if (nextTagId > 0) {
await sql`
INSERT INTO album_items_tags_assign (album_item_id, tag_id, user_id)
VALUES (${sub.id}, ${nextTagId}, ${+req.session.id})
ON CONFLICT DO NOTHING
`;
}
}
}
}
// Automatically generate/verify blurred thumbnail on cycle
+6 -2
View File
@@ -534,7 +534,9 @@ export default router => {
tag_id: effectiveRating ? (effectiveRating === 'sfw' ? 1 : (effectiveRating === 'nsfw' ? 2 : (cfg.nsfl_tag_id || 3))) : 0,
is_oc: !!is_oc,
slug: itemSlug,
visibility: targetVisibility
visibility: targetVisibility,
is_album: false,
album_count: 0
})})`;
} catch (err) {
console.error('[UPLOAD-URL] YouTube new_item notify failed:', err);
@@ -869,7 +871,9 @@ export default router => {
tag_id: effectiveRating ? (effectiveRating === 'sfw' ? 1 : (effectiveRating === 'nsfw' ? 2 : (cfg.nsfl_tag_id || 3))) : 0,
is_oc: !!is_oc,
slug: itemSlug,
visibility: targetVisibility
visibility: targetVisibility,
is_album: false,
album_count: 0
})})`;
} catch (err) {
console.error('[UPLOAD-URL] new_item notify failed:', err);
+3 -1
View File
@@ -493,7 +493,9 @@ export default (router) => {
username: session.user,
display_name: session.display_name || null,
tag_id: rating === 'sfw' ? 1 : (rating === 'nsfw' ? 2 : (cfg.nsfl_tag_id || 3)),
is_oc: false
is_oc: false,
is_album: false,
album_count: 0
})})`;
} catch (err) {
console.error('[REHOST] new_item notify failed:', err);
+8 -3
View File
@@ -114,9 +114,12 @@ export default (router, tpl) => {
}
const f0ck = await db`
select i.dest, i.mime, i.username, i.id, i.visibility, ta.tag_id
select i.dest, i.mime, i.username, i.id, i.visibility, i.is_album, i.album_count,
(select ta2.tag_id from tags_assign ta2
join tags t2 on t2.id = ta2.tag_id
where ta2.item_id = i.id and t2.tag in ('sfw','nsfw','nsfl')
limit 1) as tag_id
from "items" i
left join tags_assign ta on ta.item_id = i.id and ta.tag_id in (1, 2)
where i.id = ${id} and i.active = false
limit 1
`;
@@ -217,7 +220,9 @@ export default (router, tpl) => {
mime: f0ck[0].mime,
username: f0ck[0].username,
tag_id: f0ck[0].tag_id,
is_oc: !!f0ck[0].is_oc
is_oc: !!f0ck[0].is_oc,
is_album: !!f0ck[0].is_album,
album_count: f0ck[0].album_count || 0
})})`;
} catch (err) {
console.error('[MOD APPROVE] new_item notify failed:', err);
+6 -2
View File
@@ -463,7 +463,9 @@ export const handleUpload = async (req, res, self) => {
tag_id: effectiveRating ? (effectiveRating === 'sfw' ? 1 : (effectiveRating === 'nsfw' ? 2 : (cfg.nsfl_tag_id || 3))) : 0,
is_oc: !!is_oc,
slug: itemSlug,
visibility: targetVisibility
visibility: targetVisibility,
is_album: false,
album_count: 0
})})`;
} catch (err) {
console.error('[UPLOAD] YouTube new_item notify failed:', err);
@@ -1227,7 +1229,9 @@ export const handleUpload = async (req, res, self) => {
tag_id: effectiveRating ? (effectiveRating === 'sfw' ? 1 : (effectiveRating === 'nsfw' ? 2 : (cfg.nsfl_tag_id || 3))) : 0,
is_oc: !!is_oc,
slug: itemSlug,
visibility: targetVisibility
visibility: targetVisibility,
is_album: !!is_album,
album_count: is_album ? rawFiles.length : 0
})})`;
} catch (err) {
console.error('[UPLOAD] new_item notify failed:', err);
+6 -88
View File
@@ -252,94 +252,12 @@
<small class="text-muted" style="margin-left: 25px;">{{ t('settings.enable_bg_blur_hint') }}</small>
</div>
<div class="setting-item" style="margin-top: 15px; padding-left: 25px;">
<div style="display: flex; align-items: center; justify-content: space-between; max-width: 380px;">
<label for="onara_bg_opacity_slider" style="font-size: 0.9em; margin-bottom: 0; font-weight: bold;">Onara Backdrop Darkness</label>
<span id="onara_bg_opacity_val" style="font-size: 0.85em; opacity: 0.8;">70%</span>
</div>
<input type="range" id="onara_bg_opacity_slider" min="0" max="1" step="0.01" value="0.70" style="max-width: 380px; width: 100%; margin-top: 4px;">
<small class="text-muted" style="display: block;">Overlay darkness in Onara mode (lower values allow background canvas to shine through).</small>
</div>
<div class="setting-item" style="margin-top: 15px; padding-left: 25px;">
<div style="display: flex; align-items: center; justify-content: space-between; max-width: 380px;">
<label for="onara_backdrop_blur_slider" style="font-size: 0.9em; margin-bottom: 0; font-weight: bold;">Onara Backdrop Blur</label>
<span id="onara_backdrop_blur_val" style="font-size: 0.85em; opacity: 0.8;">5px</span>
</div>
<input type="range" id="onara_backdrop_blur_slider" min="0" max="50" step="1" value="5" style="max-width: 380px; width: 100%; margin-top: 4px;">
<small class="text-muted" style="display: block;">Backdrop blur applied behind the Onara media modal.</small>
</div>
<div class="setting-item" style="margin-top: 15px; padding-left: 25px;">
<div style="display: flex; align-items: center; justify-content: space-between; max-width: 380px;">
<label for="onara_grid_dim_slider" style="font-size: 0.9em; margin-bottom: 0; font-weight: bold;">Onara Background Grid Opacity</label>
<span id="onara_grid_dim_val" style="font-size: 0.85em; opacity: 0.8;">38%</span>
</div>
<input type="range" id="onara_grid_dim_slider" min="0" max="1" step="0.01" value="0.38" style="max-width: 380px; width: 100%; margin-top: 4px;">
<small class="text-muted" style="display: block;">Control how clearly the background thumbnail grid shines through over the ambient canvas in Onara mode.</small>
</div>
<div class="setting-item" style="margin-top: 15px; padding-left: 25px;">
<div style="display: flex; align-items: center; justify-content: space-between; max-width: 380px;">
<label for="bg_canvas_opacity_slider" style="font-size: 0.9em; margin-bottom: 0; font-weight: bold;">Background Canvas Opacity</label>
<span id="bg_canvas_opacity_val" style="font-size: 0.85em; opacity: 0.8;">100%</span>
</div>
<input type="range" id="bg_canvas_opacity_slider" min="0" max="1" step="0.01" value="1.00" style="max-width: 380px; width: 100%; margin-top: 4px;">
<small class="text-muted" style="display: block;">Control the visibility and brightness of the ambient background canvas & audio visualizer.</small>
</div>
<div class="setting-item" style="margin-top: 15px; padding-left: 25px;">
<div style="display: flex; align-items: center; justify-content: space-between; max-width: 380px;">
<label for="bg_canvas_blur_slider" style="font-size: 0.9em; margin-bottom: 0; font-weight: bold;">Background Blur Radius</label>
<span id="bg_canvas_blur_val" style="font-size: 0.85em; opacity: 0.8;">30px</span>
</div>
<input type="range" id="bg_canvas_blur_slider" min="0" max="300" step="1" value="30" style="max-width: 380px; width: 100%; margin-top: 4px;">
<small class="text-muted" style="display: block;">Adjust the ambient blur radius. Higher values = softer, dreamier glow. In GPU CSS mode this goes up to 300px at full resolution.</small>
</div>
<div class="setting-item" style="margin-top: 15px; padding-left: 25px;">
<div style="display: flex; align-items: center; justify-content: space-between; max-width: 380px;">
<label for="bg_canvas_brightness_slider" style="font-size: 0.9em; margin-bottom: 0; font-weight: bold;">Background Brightness</label>
<span id="bg_canvas_brightness_val" style="font-size: 0.85em; opacity: 0.8;">0.45x</span>
</div>
<input type="range" id="bg_canvas_brightness_slider" min="0.1" max="3.0" step="0.05" value="0.45" style="max-width: 380px; width: 100%; margin-top: 4px;">
<small class="text-muted" style="display: block;">Boost brightness so colors pop. Values &lt; 1 darken, &gt; 1 amplify.</small>
</div>
<div class="setting-item" style="margin-top: 15px; padding-left: 25px;">
<div style="display: flex; align-items: center; justify-content: space-between; max-width: 380px;">
<label for="bg_blur_method_slider" style="font-size: 0.9em; margin-bottom: 0; font-weight: bold;">Blur Engine</label>
<span id="bg_blur_method_val" style="font-size: 0.85em; opacity: 0.8;">Canvas 2D</span>
</div>
<input type="range" id="bg_blur_method_slider" min="0" max="2" step="1" value="0" style="max-width: 380px; width: 100%; margin-top: 4px;">
<small class="text-muted" style="display: block;"><strong>0 = Canvas 2D</strong> (downsampled, fast) &nbsp;|&nbsp; <strong>1 = GPU CSS Filter</strong> (classic look, full-res compositor blur like older versions) &nbsp;|&nbsp; <strong>2 = Hybrid</strong> (CSS blur + canvas color grading)</small>
</div>
<div class="setting-item" style="margin-top: 15px; padding-left: 25px;">
<div style="display: flex; align-items: center; justify-content: space-between; max-width: 380px;">
<label for="bg_canvas_saturate_slider" style="font-size: 0.9em; margin-bottom: 0; font-weight: bold;">Color Saturation</label>
<span id="bg_canvas_saturate_val" style="font-size: 0.85em; opacity: 0.8;">1.80x</span>
</div>
<input type="range" id="bg_canvas_saturate_slider" min="0.5" max="4.0" step="0.05" value="1.8" style="max-width: 380px; width: 100%; margin-top: 4px;">
<small class="text-muted" style="display: block;">Boost color saturation of the background. 1.0 = original, higher = more vivid / neon.</small>
</div>
<div class="setting-item" style="margin-top: 15px; padding-left: 25px;">
<div style="display: flex; align-items: center; justify-content: space-between; max-width: 380px;">
<label for="bg_canvas_contrast_slider" style="font-size: 0.9em; margin-bottom: 0; font-weight: bold;">Contrast Depth</label>
<span id="bg_canvas_contrast_val" style="font-size: 0.85em; opacity: 0.8;">1.10x</span>
</div>
<input type="range" id="bg_canvas_contrast_slider" min="0.5" max="3.0" step="0.05" value="1.1" style="max-width: 380px; width: 100%; margin-top: 4px;">
<small class="text-muted" style="display: block;">Contrast punch. Values &gt; 1.0 darken blacks and pop highlights.</small>
</div>
<div class="setting-item" style="margin-top: 15px; padding-left: 25px;">
<div style="display: flex; align-items: center; justify-content: space-between; max-width: 380px;">
<label for="bg_canvas_zoom_slider" style="font-size: 0.9em; margin-bottom: 0; font-weight: bold;">Edge Overscan Zoom</label>
<span id="bg_canvas_zoom_val" style="font-size: 0.85em; opacity: 0.8;">1.12x</span>
</div>
<input type="range" id="bg_canvas_zoom_slider" min="1.0" max="1.8" step="0.01" value="1.12" style="max-width: 380px; width: 100%; margin-top: 4px;">
<small class="text-muted" style="display: block;">Zoom the background canvas slightly to hide blurry edge bleeding. 1.12 eliminates edge artifacts at typical blur values.</small>
<div class="setting-item" style="margin-top: 15px;">
<label for="onara_enabled_toggle" style="cursor: pointer; display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="onara_enabled_toggle">
<span>Enable Onara viewer</span>
</label>
<small class="text-muted" style="margin-left: 25px;">Open items in the fullscreen Onara modal instead of navigating to the item page. Fine-tune appearance in the <strong>Tuner → Background</strong> tab.</small>
</div>
<div class="setting-item" style="margin-top: 15px;">