This repository has been archived on 2026-04-27. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
website/public/assets/js/script.js
T
2026-04-14 07:16:16 +09:00

180 lines
6.9 KiB
JavaScript

(() => {
const textSelectors = 'p, h1, h2, h3, h4, h5, h6, span, li, label, td, th, pre, .code';
const linkSelectors = 'a, button, [role="button"], input[type="submit"], input[type="button"]';
const padding = 6;
let mouseX = 0, mouseY = 0;
let currentLinkEl = null;
let rafId = null;
let cursor = null;
let cursorVisible = false;
let lastTouchTime = 0;
let isMouseDown = false;
const TOUCH_MOUSE_GUARD_MS = 800;
function showCursor() {
if (!cursorVisible && cursor) {
cursorVisible = true;
cursor.classList.add('visible');
}
}
function hideCursor() {
if (cursor) {
cursorVisible = false;
cursor.classList.remove('visible');
currentLinkEl = null;
if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
cursor.classList.remove('on-link', 'on-text');
}
}
function isSyntheticMouse() {
return Date.now() - lastTouchTime < TOUCH_MOUSE_GUARD_MS;
}
function updateCursorForLink(el) {
const rect = el.getBoundingClientRect();
cursor.classList.remove('on-text');
cursor.classList.add('on-link');
cursor.style.transform = 'none';
cursor.style.left = (rect.left - padding) + 'px';
cursor.style.top = (rect.top - padding) + 'px';
cursor.style.width = (rect.width + padding * 2) + 'px';
cursor.style.height = (rect.height + padding * 2) + 'px';
}
function trackLink() {
if (currentLinkEl) {
updateCursorForLink(currentLinkEl);
rafId = requestAnimationFrame(trackLink);
}
}
document.addEventListener('DOMContentLoaded', () => {
cursor = document.getElementById('cursor');
if (!cursor) return;
document.addEventListener('touchstart', () => { lastTouchTime = Date.now(); hideCursor(); }, { passive: true });
document.addEventListener('touchmove', () => { lastTouchTime = Date.now(); hideCursor(); }, { passive: true });
document.addEventListener('touchend', () => { lastTouchTime = Date.now(); }, { passive: true });
document.addEventListener('mousemove', (e) => {
if (isSyntheticMouse()) return;
if (e.sourceCapabilities && e.sourceCapabilities.firesTouchEvents) return;
mouseX = e.clientX;
mouseY = e.clientY;
showCursor();
const el = document.elementFromPoint(mouseX, mouseY);
const linkEl = el ? el.closest(linkSelectors) : null;
if (linkEl) {
if (currentLinkEl !== linkEl) {
currentLinkEl = linkEl;
if (rafId) cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(trackLink);
}
} else {
if (currentLinkEl) {
currentLinkEl = null;
if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
}
cursor.classList.remove('on-link');
cursor.style.transform = isMouseDown ? 'translate(-50%, -50%) scale(0.9)' : 'translate(-50%, -50%)';
cursor.style.left = mouseX + 'px';
cursor.style.top = mouseY + 'px';
cursor.style.width = '';
cursor.style.height = '';
if (el && el.closest(textSelectors)) {
cursor.classList.add('on-text');
} else {
cursor.classList.remove('on-text');
}
}
});
document.addEventListener('mousedown', () => {
isMouseDown = true;
cursor.style.transform = currentLinkEl ? 'none' : 'translate(-50%, -50%) scale(0.9)';
});
document.addEventListener('mouseup', () => {
isMouseDown = false;
cursor.style.transform = currentLinkEl ? 'none' : 'translate(-50%, -50%) scale(1)';
});
window.addEventListener('scroll', () => {
if (currentLinkEl) updateCursorForLink(currentLinkEl);
}, { passive: true });
});
if (document.startViewTransition) {
document.addEventListener("click", (event) => {
const link = event.target.closest("a");
if (!link || link.hasAttribute('download')) return;
const url = new URL(link.href, location.href);
if (url.origin !== location.origin) return;
if (link.target || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
if (url.hash && url.pathname === location.pathname) {
event.preventDefault();
const target = document.querySelector(url.hash);
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
history.pushState(null, '', url.hash);
}
return;
}
event.preventDefault();
document.startViewTransition(async () => {
const response = await fetch(url.href, {
headers: { "X-Requested-With": "view-transition" }
});
const html = await response.text();
const doc = new DOMParser().parseFromString(html, "text/html");
for (const tag of ['head', 'header', 'main', 'footer']) {
const newEl = doc.querySelector(tag);
const curEl = document.querySelector(tag);
if (!newEl || !curEl) { location.href = url.href; return; }
curEl.innerHTML = newEl.innerHTML;
curEl.querySelectorAll('script').forEach(old => {
const s = document.createElement('script');
[...old.attributes].forEach(a => s.setAttribute(a.name, a.value));
s.textContent = old.textContent;
old.replaceWith(s);
});
}
history.pushState(null, "", url.href);
currentLinkEl = null;
if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
cursor.classList.remove('on-link', 'on-text');
cursor.style.transform = 'translate(-50%, -50%)';
cursor.style.left = mouseX + 'px';
cursor.style.top = mouseY + 'px';
cursor.style.width = '';
cursor.style.height = '';
const el = document.elementFromPoint(mouseX, mouseY);
const newLinkEl = el ? el.closest(linkSelectors) : null;
if (newLinkEl) {
currentLinkEl = newLinkEl;
rafId = requestAnimationFrame(trackLink);
} else if (el && el.closest(textSelectors)) {
cursor.classList.add('on-text');
}
});
});
window.addEventListener("popstate", () => location.reload());
}
})();