gfds
This commit is contained in:
+95
-6
@@ -21640,7 +21640,8 @@ body.onara-modal-open:not(.onara-modal-closing) .posts > a.thumb:not(.onara-acti
|
|||||||
filter: none !important;
|
filter: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
transition: opacity 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important;
|
transition: opacity 0.25s cubic-bezier(0.16, 1, 0.3, 1),
|
||||||
|
scale 0.4s cubic-bezier(0.22, 1, 0.36, 1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.onara-modal-closing .posts > a.thumb:not(.onara-active) {
|
body.onara-modal-closing .posts > a.thumb:not(.onara-active) {
|
||||||
@@ -21648,7 +21649,9 @@ body.onara-modal-closing .posts > a.thumb:not(.onara-active) {
|
|||||||
filter: none !important;
|
filter: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
transition: opacity 0.2s cubic-bezier(0.25, 0, 0.4, 1) !important;
|
scale: 1;
|
||||||
|
transition: opacity 0.2s cubic-bezier(0.25, 0, 0.4, 1),
|
||||||
|
scale 0.3s cubic-bezier(0.25, 0, 0.4, 1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.onara-modal-open:not(.onara-modal-closing) .posts > a.thumb:not(.onara-active)::after {
|
body.onara-modal-open:not(.onara-modal-closing) .posts > a.thumb:not(.onara-active)::after {
|
||||||
@@ -21665,10 +21668,15 @@ body.onara-modal-closing .posts > a.thumb:not(.onara-active)::after {
|
|||||||
body.onara-modal-open:not(.onara-modal-closing) .posts > a.thumb.onara-active {
|
body.onara-modal-open:not(.onara-modal-closing) .posts > a.thumb.onara-active {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
filter: none !important;
|
filter: none !important;
|
||||||
box-shadow: 0 0 0 3px var(--accent, #9f0), 0 0 20px var(--accent, #9f0), 0 0 35px rgba(255, 255, 255, 0.9) !important;
|
/* Accent ring + glow, plus a depth shadow underneath for the lifted-out 3D look */
|
||||||
|
box-shadow: 0 0 0 3px var(--accent, #9f0), 0 0 20px var(--accent, #9f0), 0 0 35px rgba(255, 255, 255, 0.9),
|
||||||
|
0 18px 40px rgba(0, 0, 0, 0.7) !important;
|
||||||
z-index: 52 !important;
|
z-index: 52 !important;
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
transition: box-shadow 0.28s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.25s ease !important;
|
/* Pops out toward the viewer (scale is separate from the magnetic hover's translate) */
|
||||||
|
scale: 1.1;
|
||||||
|
transition: box-shadow 0.28s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.25s ease,
|
||||||
|
scale 0.45s cubic-bezier(0.22, 1.3, 0.36, 1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.onara-modal-open:not(.onara-modal-closing) .posts > a.thumb.onara-active::after {
|
body.onara-modal-open:not(.onara-modal-closing) .posts > a.thumb.onara-active::after {
|
||||||
@@ -21682,15 +21690,17 @@ body.onara-modal-closing .posts > a.thumb.onara-active,
|
|||||||
body.onara-modal-closing .posts > a.thumb.loaded.onara-active {
|
body.onara-modal-closing .posts > a.thumb.loaded.onara-active {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
filter: none !important;
|
filter: none !important;
|
||||||
box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent, 0 0 0 0 transparent !important;
|
box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent, 0 0 0 0 transparent, 0 0 0 0 transparent !important;
|
||||||
outline-color: transparent !important;
|
outline-color: transparent !important;
|
||||||
border-color: transparent !important;
|
border-color: transparent !important;
|
||||||
z-index: 50 !important;
|
z-index: 50 !important;
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
|
scale: 1;
|
||||||
transition: box-shadow 0.2s cubic-bezier(0.25, 0, 0.4, 1),
|
transition: box-shadow 0.2s cubic-bezier(0.25, 0, 0.4, 1),
|
||||||
outline-color 0.2s cubic-bezier(0.25, 0, 0.4, 1),
|
outline-color 0.2s cubic-bezier(0.25, 0, 0.4, 1),
|
||||||
border-color 0.2s cubic-bezier(0.25, 0, 0.4, 1),
|
border-color 0.2s cubic-bezier(0.25, 0, 0.4, 1),
|
||||||
opacity 0.2s cubic-bezier(0.25, 0, 0.4, 1) !important;
|
opacity 0.2s cubic-bezier(0.25, 0, 0.4, 1),
|
||||||
|
scale 0.3s cubic-bezier(0.25, 0, 0.4, 1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.onara-modal-closing .posts > a.thumb.onara-active::after {
|
body.onara-modal-closing .posts > a.thumb.onara-active::after {
|
||||||
@@ -23511,3 +23521,82 @@ html.album-strip-dragging * {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Magnetic thumbnail hover (f0ckm.js): one ring per grid glides between thumbnails with a slight spring
|
||||||
|
overshoot; the hovered thumb leans toward the cursor (--pull-x/--pull-y). Flat 1px ring, no glow.
|
||||||
|
Transform/translate only, so it stays on the compositor. */
|
||||||
|
div.posts > .thumb-magnet {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
z-index: 3;
|
||||||
|
pointer-events: none;
|
||||||
|
box-shadow: 0 0 0 1px var(--white);
|
||||||
|
opacity: 0;
|
||||||
|
will-change: transform;
|
||||||
|
transition:
|
||||||
|
transform 0.32s cubic-bezier(0.22, 1.25, 0.36, 1),
|
||||||
|
width 0.32s cubic-bezier(0.22, 1.25, 0.36, 1),
|
||||||
|
height 0.32s cubic-bezier(0.22, 1.25, 0.36, 1),
|
||||||
|
opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.posts > .thumb-magnet.is-on {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.posts > .thumb-magnet.no-glide {
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The thumbnail the ring is on (mouse or keyboard) drops its own hover/focus/touch-active shadow, so
|
||||||
|
only the gliding ring shows; a plain touch tap without the ring keeps its own highlight */
|
||||||
|
div.posts.has-thumb-magnet > a.magnet-target:not(.notif-item) {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.posts.has-thumb-magnet > a.thumb:not(.notif-item) {
|
||||||
|
translate: var(--pull-x, 0px) var(--pull-y, 0px);
|
||||||
|
transition: box-shadow 0.25s cubic-bezier(0.16, 1, 0.3, 1),
|
||||||
|
border-color 0.25s cubic-bezier(0.16, 1, 0.3, 1),
|
||||||
|
outline-color 0.25s cubic-bezier(0.16, 1, 0.3, 1),
|
||||||
|
translate 0.3s cubic-bezier(0.22, 1.25, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rubber-band thumbnail drag (f0ckm.js): follows the mouse 1:1 while held (no translate transition),
|
||||||
|
then springs back on release with the regular translate transition, a bit slower and bouncier */
|
||||||
|
div.posts.has-thumb-magnet > a.thumb.thumb-dragging:not(.notif-item),
|
||||||
|
div.posts > a.thumb.thumb-dragging:not(.notif-item) {
|
||||||
|
translate: var(--pull-x, 0px) var(--pull-y, 0px);
|
||||||
|
transition: box-shadow 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.posts > a.thumb:not(.notif-item):not(.thumb-dragging) {
|
||||||
|
translate: var(--pull-x, 0px) var(--pull-y, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.posts > a.thumb.thumb-returning:not(.notif-item) {
|
||||||
|
transition: box-shadow 0.25s cubic-bezier(0.16, 1, 0.3, 1),
|
||||||
|
border-color 0.25s cubic-bezier(0.16, 1, 0.3, 1),
|
||||||
|
outline-color 0.25s cubic-bezier(0.16, 1, 0.3, 1),
|
||||||
|
translate 0.5s cubic-bezier(0.22, 1.45, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.posts > .thumb-magnet.is-dragging {
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.thumb-drag-active,
|
||||||
|
html.thumb-drag-active * {
|
||||||
|
cursor: grabbing !important;
|
||||||
|
user-select: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.posts > .thumb-magnet.is-returning {
|
||||||
|
transition:
|
||||||
|
transform 0.5s cubic-bezier(0.22, 1.45, 0.36, 1),
|
||||||
|
opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|||||||
+20
-1
@@ -838,4 +838,23 @@
|
|||||||
.v0ck:not(.v0ck_hover),
|
.v0ck:not(.v0ck_hover),
|
||||||
.v0ck:not(.v0ck_hover) * {
|
.v0ck:not(.v0ck_hover) * {
|
||||||
cursor: none !important;
|
cursor: none !important;
|
||||||
}
|
}
|
||||||
|
/* Volume: pointer over the whole volume area, not only the 5px slider strip, including the slider's
|
||||||
|
thumb/track parts (Firefox doesn't hand the input's cursor to them). Only while the controls are
|
||||||
|
shown, so the idle cursor auto-hide above still applies. */
|
||||||
|
.v0ck.v0ck_hover .v0ck_volume_group,
|
||||||
|
.v0ck.v0ck_hover .v0ck_volume_group *,
|
||||||
|
.v0ck.v0ck_hover .v0ck_volume_group input[type="range"][name="volume"] {
|
||||||
|
cursor: pointer !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Vendor pseudo-elements in separate rules: one unknown pseudo-element drops a whole selector list */
|
||||||
|
.v0ck.v0ck_hover .v0ck_volume_group input[type="range"][name="volume"]::-webkit-slider-thumb,
|
||||||
|
.v0ck.v0ck_hover .v0ck_volume_group input[type="range"][name="volume"]::-webkit-slider-runnable-track {
|
||||||
|
cursor: pointer !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v0ck.v0ck_hover .v0ck_volume_group input[type="range"][name="volume"]::-moz-range-thumb,
|
||||||
|
.v0ck.v0ck_hover .v0ck_volume_group input[type="range"][name="volume"]::-moz-range-track {
|
||||||
|
cursor: pointer !important;
|
||||||
|
}
|
||||||
|
|||||||
+214
-1
@@ -11459,7 +11459,10 @@ window.cancelAnimFrame = (function () {
|
|||||||
|
|
||||||
window.syncNavbarHeight = () => {
|
window.syncNavbarHeight = () => {
|
||||||
const nav = document.querySelector('.navbar');
|
const nav = document.querySelector('.navbar');
|
||||||
if (nav) document.documentElement.style.setProperty('--navbar-h', nav.offsetHeight + 'px');
|
// Exact (fractional) height, not offsetHeight: Onara makes the navbar position:fixed and holds the
|
||||||
|
// page in place with margin-top: var(--navbar-h). A rounded value (78 vs 78.19px) shifted the whole
|
||||||
|
// page by that fraction on every open/close.
|
||||||
|
if (nav) document.documentElement.style.setProperty('--navbar-h', nav.getBoundingClientRect().height + 'px');
|
||||||
};
|
};
|
||||||
window.syncNavbarHeight();
|
window.syncNavbarHeight();
|
||||||
window.addEventListener('resize', window.syncNavbarHeight);
|
window.addEventListener('resize', window.syncNavbarHeight);
|
||||||
@@ -25193,3 +25196,213 @@ window.BulkSelection = (() => {
|
|||||||
// Keep it unlocked while it's being used
|
// Keep it unlocked while it's being used
|
||||||
document.addEventListener('input', (e) => { if (e.target === active) armTimer(); }, true);
|
document.addEventListener('input', (e) => { if (e.target === active) armTimer(); }, true);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// ── Magnetic thumbnail hover ──────────────────────────────────────────────────
|
||||||
|
// One ring per grid glides from thumbnail to thumbnail (slight spring overshoot) instead of each thumb
|
||||||
|
// switching its own box-shadow on and off, and the hovered thumbnail leans a few px toward the cursor.
|
||||||
|
// Only transform/translate animate (compositor only, no layout). Mouse/trackpad devices only.
|
||||||
|
(() => {
|
||||||
|
if (!window.matchMedia || !window.matchMedia('(hover: hover) and (pointer: fine)').matches) return;
|
||||||
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||||
|
|
||||||
|
const THUMB_SEL = 'div.posts > a.thumb:not(.notif-item)';
|
||||||
|
const PULL = 3; // max px a thumbnail leans toward the cursor
|
||||||
|
let current = null;
|
||||||
|
let rafId = 0;
|
||||||
|
let lastEvent = null;
|
||||||
|
|
||||||
|
const magnetFor = (posts) => {
|
||||||
|
let m = posts.querySelector(':scope > .thumb-magnet');
|
||||||
|
if (!m) {
|
||||||
|
if (getComputedStyle(posts).position === 'static') posts.style.position = 'relative';
|
||||||
|
m = document.createElement('div');
|
||||||
|
m.className = 'thumb-magnet';
|
||||||
|
m.setAttribute('aria-hidden', 'true');
|
||||||
|
posts.appendChild(m);
|
||||||
|
posts.classList.add('has-thumb-magnet');
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
};
|
||||||
|
|
||||||
|
const place = (m, thumb, px = 0, py = 0, instant = false) => {
|
||||||
|
if (instant) m.classList.add('no-glide');
|
||||||
|
m.style.width = thumb.offsetWidth + 'px';
|
||||||
|
m.style.height = thumb.offsetHeight + 'px';
|
||||||
|
m.style.transform = 'translate3d(' + (thumb.offsetLeft + px) + 'px,' + (thumb.offsetTop + py) + 'px,0)';
|
||||||
|
if (instant) { void m.offsetWidth; m.classList.remove('no-glide'); }
|
||||||
|
};
|
||||||
|
|
||||||
|
const release = (thumb) => {
|
||||||
|
if (!thumb) return;
|
||||||
|
thumb.style.removeProperty('--pull-x');
|
||||||
|
thumb.style.removeProperty('--pull-y');
|
||||||
|
thumb.classList.remove('magnet-target');
|
||||||
|
};
|
||||||
|
|
||||||
|
const hoverTo = (thumb) => {
|
||||||
|
if (!thumb || thumb === current) return;
|
||||||
|
const m = magnetFor(thumb.parentElement);
|
||||||
|
const wasHidden = !m.classList.contains('is-on');
|
||||||
|
release(current);
|
||||||
|
current = thumb;
|
||||||
|
thumb.classList.add('magnet-target');
|
||||||
|
// Coming in from outside the grid: appear on the thumb, glide only between thumbnails
|
||||||
|
place(m, thumb, 0, 0, wasHidden);
|
||||||
|
m.classList.add('is-on');
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mouseover', (e) => {
|
||||||
|
if (drag && drag.moved) return; // the ring stays on the thumbnail being dragged
|
||||||
|
hoverTo(e.target.closest && e.target.closest(THUMB_SEL));
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
document.addEventListener('mouseout', (e) => {
|
||||||
|
if (!current || (drag && drag.moved)) return;
|
||||||
|
const to = e.relatedTarget;
|
||||||
|
if (to && to.closest && to.closest(THUMB_SEL)) return; // moving to another thumb: mouseover handles it
|
||||||
|
if (to && current.contains(to)) return;
|
||||||
|
const posts = current.parentElement;
|
||||||
|
const m = posts && posts.querySelector(':scope > .thumb-magnet');
|
||||||
|
// Still inside the grid (a gap between thumbs): keep the ring where it is, just drop the lean
|
||||||
|
if (to && posts && posts.contains(to)) { release(current); current.classList.add('magnet-target'); if (m) place(m, current); return; }
|
||||||
|
release(current);
|
||||||
|
if (m) m.classList.remove('is-on');
|
||||||
|
current = null;
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// Heavy rubber-band drag: a grabbed thumbnail follows the mouse with rising resistance (never more
|
||||||
|
// than DRAG_MAX px) and springs back on release. A real drag doesn't open the item.
|
||||||
|
const DRAG_MAX = 28; // px the thumb can be pulled at most
|
||||||
|
const DRAG_SOFT = 90; // px of mouse travel for most of that pull: bigger = heavier
|
||||||
|
let drag = null;
|
||||||
|
const resist = (d) => DRAG_MAX * Math.tanh(d / DRAG_SOFT);
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', (e) => {
|
||||||
|
if (e.button !== 0 || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;
|
||||||
|
const thumb = e.target.closest && e.target.closest(THUMB_SEL);
|
||||||
|
if (!thumb) return;
|
||||||
|
drag = { thumb, x0: e.clientX, y0: e.clientY, moved: false };
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// Native "drag this link" would take the gesture over
|
||||||
|
document.addEventListener('dragstart', (e) => {
|
||||||
|
if (e.target && e.target.closest && e.target.closest(THUMB_SEL)) e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', (e) => {
|
||||||
|
if (!drag) return;
|
||||||
|
const dx = e.clientX - drag.x0;
|
||||||
|
const dy = e.clientY - drag.y0;
|
||||||
|
if (!drag.moved) {
|
||||||
|
if (Math.hypot(dx, dy) < 4) return;
|
||||||
|
drag.moved = true;
|
||||||
|
drag.thumb.classList.add('thumb-dragging');
|
||||||
|
document.documentElement.classList.add('thumb-drag-active');
|
||||||
|
}
|
||||||
|
const px = +resist(dx).toFixed(2);
|
||||||
|
const py = +resist(dy).toFixed(2);
|
||||||
|
drag.thumb.style.setProperty('--pull-x', px + 'px');
|
||||||
|
drag.thumb.style.setProperty('--pull-y', py + 'px');
|
||||||
|
const m = drag.thumb.parentElement && drag.thumb.parentElement.querySelector(':scope > .thumb-magnet');
|
||||||
|
if (m && drag.thumb === current) { m.classList.add('is-dragging'); place(m, drag.thumb, px, py); }
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
window.addEventListener('mouseup', (e) => {
|
||||||
|
if (!drag) return;
|
||||||
|
const d = drag;
|
||||||
|
drag = null;
|
||||||
|
if (!d.moved) return;
|
||||||
|
d.thumb.classList.remove('thumb-dragging');
|
||||||
|
document.documentElement.classList.remove('thumb-drag-active');
|
||||||
|
const m = d.thumb.parentElement && d.thumb.parentElement.querySelector(':scope > .thumb-magnet');
|
||||||
|
// Spring back (the thumb's translate transition does the glide, slower and bouncier than the lean)
|
||||||
|
d.thumb.classList.add('thumb-returning');
|
||||||
|
clearTimeout(d.thumb._returnTimer);
|
||||||
|
d.thumb._returnTimer = setTimeout(() => d.thumb.classList.remove('thumb-returning'), 550);
|
||||||
|
d.thumb.style.setProperty('--pull-x', '0px');
|
||||||
|
d.thumb.style.setProperty('--pull-y', '0px');
|
||||||
|
if (m) {
|
||||||
|
m.classList.remove('is-dragging');
|
||||||
|
// Ring springs back on exactly the thumb's curve, so they stay together
|
||||||
|
m.classList.add('is-returning');
|
||||||
|
if (d.thumb === current) place(m, d.thumb);
|
||||||
|
clearTimeout(m._returnTimer);
|
||||||
|
m._returnTimer = setTimeout(() => m.classList.remove('is-returning'), 550);
|
||||||
|
}
|
||||||
|
// It was a drag, not a click: swallow the click that follows this mouseup
|
||||||
|
const swallow = (ev) => {
|
||||||
|
if (ev.target && ev.target.closest && ev.target.closest(THUMB_SEL) === d.thumb) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('click', swallow, { capture: true, once: true });
|
||||||
|
setTimeout(() => window.removeEventListener('click', swallow, { capture: true }), 0);
|
||||||
|
// Released over another thumbnail (or outside the grid): hand the ring over now
|
||||||
|
const under = document.elementFromPoint(e.clientX, e.clientY);
|
||||||
|
const underThumb = under && under.closest && under.closest(THUMB_SEL);
|
||||||
|
if (underThumb && underThumb !== d.thumb) {
|
||||||
|
setTimeout(() => hoverTo(underThumb), 0);
|
||||||
|
} else if (!underThumb && d.thumb === current) {
|
||||||
|
if (m) m.classList.remove('is-on');
|
||||||
|
release(d.thumb);
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
if (!current || drag) return;
|
||||||
|
lastEvent = e;
|
||||||
|
if (rafId) return;
|
||||||
|
rafId = requestAnimationFrame(() => {
|
||||||
|
rafId = 0;
|
||||||
|
const t = current;
|
||||||
|
const ev = lastEvent;
|
||||||
|
if (!t || !ev || !t.isConnected) return;
|
||||||
|
const r = t.getBoundingClientRect();
|
||||||
|
const nx = ((ev.clientX - r.left) / r.width - 0.5) * 2; // -1 .. 1
|
||||||
|
const ny = ((ev.clientY - r.top) / r.height - 0.5) * 2;
|
||||||
|
if (Math.abs(nx) > 1.05 || Math.abs(ny) > 1.05) return;
|
||||||
|
const px = +(nx * PULL).toFixed(2);
|
||||||
|
const py = +(ny * PULL).toFixed(2);
|
||||||
|
t.style.setProperty('--pull-x', px + 'px');
|
||||||
|
t.style.setProperty('--pull-y', py + 'px');
|
||||||
|
const m = t.parentElement && t.parentElement.querySelector(':scope > .thumb-magnet');
|
||||||
|
if (m) place(m, t, px, py);
|
||||||
|
});
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// Keyboard (Tab, then arrow keys, which call .focus() on the thumbs): the ring glides to the focused
|
||||||
|
// thumbnail the same way it follows the mouse
|
||||||
|
document.addEventListener('focusin', (e) => {
|
||||||
|
const thumb = e.target && e.target.closest && e.target.closest(THUMB_SEL);
|
||||||
|
if (!thumb || thumb === current) return;
|
||||||
|
let kbd = true;
|
||||||
|
try { kbd = thumb.matches(':focus-visible'); } catch (_) {}
|
||||||
|
if (!kbd) return; // mouse click focus: mouseover already placed it
|
||||||
|
const m = magnetFor(thumb.parentElement);
|
||||||
|
const wasHidden = !m.classList.contains('is-on');
|
||||||
|
release(current);
|
||||||
|
current = thumb;
|
||||||
|
thumb.classList.add('magnet-target');
|
||||||
|
place(m, thumb, 0, 0, wasHidden);
|
||||||
|
m.classList.add('is-on');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('focusout', (e) => {
|
||||||
|
const from = e.target && e.target.closest && e.target.closest(THUMB_SEL);
|
||||||
|
if (!from || from !== current) return;
|
||||||
|
const to = e.relatedTarget;
|
||||||
|
if (to && to.closest && to.closest(THUMB_SEL)) return; // focusin on the next thumb moves it
|
||||||
|
if (from.matches(':hover')) return; // mouse is still on it: keep the ring
|
||||||
|
const m = from.parentElement && from.parentElement.querySelector(':scope > .thumb-magnet');
|
||||||
|
if (m) m.classList.remove('is-on');
|
||||||
|
release(from);
|
||||||
|
current = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Grid re-rendered / scrolled away under a resting cursor: forget the old thumb
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
if (current && !current.isConnected) current = null;
|
||||||
|
}, { passive: true, capture: true });
|
||||||
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user