From 9df92fe8df3cdddb3365c945fa981941db34a700 Mon Sep 17 00:00:00 2001 From: nercone-dev Date: Thu, 23 Apr 2026 20:44:35 +0900 Subject: [PATCH] -- --- public/assets/css/style-extra.css | 2 + public/assets/js/script-extra.js | 292 ++++++++++++++++++++++++++++++ public/assets/js/script-light.js | 0 public/assets/js/script.js | 292 ------------------------------ public/base-light.html | 2 +- public/base.html | 3 +- 6 files changed, 297 insertions(+), 294 deletions(-) create mode 100644 public/assets/js/script-extra.js delete mode 100644 public/assets/js/script-light.js diff --git a/public/assets/css/style-extra.css b/public/assets/css/style-extra.css index b2df833..5d2e9ef 100644 --- a/public/assets/css/style-extra.css +++ b/public/assets/css/style-extra.css @@ -11,6 +11,8 @@ footer { /* Progressive Blur */ header { + view-transition-name: header-content; + will-change: transform; background-color: transparent; overflow: hidden; } diff --git a/public/assets/js/script-extra.js b/public/assets/js/script-extra.js new file mode 100644 index 0000000..692171f --- /dev/null +++ b/public/assets/js/script-extra.js @@ -0,0 +1,292 @@ +(() => { + 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 ac = null; + let sig = null; + + 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; + + window.__scriptCleanup = () => { + if (ac) ac.abort(); + if (rafId) { cancelAnimationFrame(rafId); rafId = null; } + document.documentElement.style.cursor = ''; + if (cursor) cursor.classList.remove('visible', 'on-link', 'on-text'); + cursorVisible = false; + currentLinkEl = null; + }; + + 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.documentElement.style.cursor = 'none'; + + function init() { + ac = new AbortController(); + sig = ac.signal; + + cursor = document.getElementById('cursor'); + if (!cursor) return; + + document.addEventListener('touchstart', () => { lastTouchTime = Date.now(); hideCursor(); }, { passive: true, signal: sig }); + document.addEventListener('touchmove', () => { lastTouchTime = Date.now(); hideCursor(); }, { passive: true, signal: sig }); + document.addEventListener('touchend', () => { lastTouchTime = Date.now(); }, { passive: true, signal: sig }); + + 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'); + } + } + }, { signal: sig }); + + document.addEventListener('mousedown', () => { + isMouseDown = true; + cursor.style.transform = currentLinkEl ? 'none' : 'translate(-50%, -50%) scale(0.9)'; + }, { signal: sig }); + document.addEventListener('mouseup', () => { + isMouseDown = false; + cursor.style.transform = currentLinkEl ? 'none' : 'translate(-50%, -50%) scale(1)'; + }, { signal: sig }); + + window.addEventListener('scroll', () => { + if (currentLinkEl) updateCursorForLink(currentLinkEl); + }, { passive: true, signal: sig }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init, { signal: sig }); + } else { + init(); + } + + function preloadAssets(newDoc) { + const curStyleHrefs = new Set( + [...document.head.querySelectorAll('link[rel="stylesheet"]')].map(l => l.href) + ); + const curScriptSrcs = new Set( + [...document.head.querySelectorAll('script[src]')].map(s => s.src) + ); + + const tasks = []; + + [...newDoc.head.querySelectorAll('link[rel="stylesheet"]')] + .filter(l => !curStyleHrefs.has(new URL(l.href, location.href).href)) + .forEach(link => tasks.push(new Promise(resolve => { + const l = link.cloneNode(true); + l.addEventListener('load', resolve, { once: true }); + l.addEventListener('error', resolve, { once: true }); + document.head.appendChild(l); + }))); + + [...newDoc.head.querySelectorAll('script[src]')] + .filter(s => !curScriptSrcs.has(new URL(s.src, location.href).href)) + .forEach(script => tasks.push(new Promise(resolve => { + const s = document.createElement('script'); + [...script.attributes].forEach(a => s.setAttribute(a.name, a.value)); + s.addEventListener('load', resolve, { once: true }); + s.addEventListener('error', resolve, { once: true }); + document.head.appendChild(s); + }))); + + return Promise.all(tasks); + } + + function updateHead(newDoc) { + const head = document.head; + const newHead = newDoc.head; + + const t = newHead.querySelector('title'); + if (t) document.title = t.textContent; + + const META_KEEP = new Set(['charset', 'viewport', 'color-scheme', 'theme-color']); + head.querySelectorAll('meta').forEach(m => { + const key = m.getAttribute('name') || m.getAttribute('property'); + if (!key || META_KEEP.has(key)) return; + m.remove(); + }); + const insertRef = head.querySelector( + 'link[rel="preconnect"], link[rel="stylesheet"], link[rel="manifest"], link[rel="icon"], script' + ); + newHead.querySelectorAll('meta[name], meta[property]').forEach(m => { + const key = m.getAttribute('name') || m.getAttribute('property'); + if (!META_KEEP.has(key)) head.insertBefore(m.cloneNode(true), insertRef); + }); + + const nc = newHead.querySelector('link[rel="canonical"]'); + const cc = head.querySelector('link[rel="canonical"]'); + if (nc && cc) cc.href = nc.href; + + const newStyleHrefs = new Set( + [...newHead.querySelectorAll('link[rel="stylesheet"]')] + .map(l => new URL(l.href, location.href).href) + ); + head.querySelectorAll('link[rel="stylesheet"]').forEach(l => { + if (!newStyleHrefs.has(l.href)) l.remove(); + }); + + const newScriptSrcs = new Set( + [...newHead.querySelectorAll('script[src]')] + .map(s => new URL(s.src, location.href).href) + ); + head.querySelectorAll('script[src]').forEach(s => { + if (!newScriptSrcs.has(s.src)) s.remove(); + }); + + head.querySelectorAll('style').forEach(s => s.remove()); + newHead.querySelectorAll('style').forEach(s => head.appendChild(s.cloneNode(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'); + + if (typeof window.__scriptCleanup === 'function') { + window.__scriptCleanup(); + window.__scriptCleanup = null; + } + + await preloadAssets(doc); + + updateHead(doc); + + for (const tag of ['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); + + if (cursor) { + 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'); + } + } + + init(); + }); + }); + + window.addEventListener('popstate', () => location.reload()); + } +})(); diff --git a/public/assets/js/script-light.js b/public/assets/js/script-light.js deleted file mode 100644 index e69de29..0000000 diff --git a/public/assets/js/script.js b/public/assets/js/script.js index 692171f..e69de29 100644 --- a/public/assets/js/script.js +++ b/public/assets/js/script.js @@ -1,292 +0,0 @@ -(() => { - 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 ac = null; - let sig = null; - - 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; - - window.__scriptCleanup = () => { - if (ac) ac.abort(); - if (rafId) { cancelAnimationFrame(rafId); rafId = null; } - document.documentElement.style.cursor = ''; - if (cursor) cursor.classList.remove('visible', 'on-link', 'on-text'); - cursorVisible = false; - currentLinkEl = null; - }; - - 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.documentElement.style.cursor = 'none'; - - function init() { - ac = new AbortController(); - sig = ac.signal; - - cursor = document.getElementById('cursor'); - if (!cursor) return; - - document.addEventListener('touchstart', () => { lastTouchTime = Date.now(); hideCursor(); }, { passive: true, signal: sig }); - document.addEventListener('touchmove', () => { lastTouchTime = Date.now(); hideCursor(); }, { passive: true, signal: sig }); - document.addEventListener('touchend', () => { lastTouchTime = Date.now(); }, { passive: true, signal: sig }); - - 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'); - } - } - }, { signal: sig }); - - document.addEventListener('mousedown', () => { - isMouseDown = true; - cursor.style.transform = currentLinkEl ? 'none' : 'translate(-50%, -50%) scale(0.9)'; - }, { signal: sig }); - document.addEventListener('mouseup', () => { - isMouseDown = false; - cursor.style.transform = currentLinkEl ? 'none' : 'translate(-50%, -50%) scale(1)'; - }, { signal: sig }); - - window.addEventListener('scroll', () => { - if (currentLinkEl) updateCursorForLink(currentLinkEl); - }, { passive: true, signal: sig }); - } - - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', init, { signal: sig }); - } else { - init(); - } - - function preloadAssets(newDoc) { - const curStyleHrefs = new Set( - [...document.head.querySelectorAll('link[rel="stylesheet"]')].map(l => l.href) - ); - const curScriptSrcs = new Set( - [...document.head.querySelectorAll('script[src]')].map(s => s.src) - ); - - const tasks = []; - - [...newDoc.head.querySelectorAll('link[rel="stylesheet"]')] - .filter(l => !curStyleHrefs.has(new URL(l.href, location.href).href)) - .forEach(link => tasks.push(new Promise(resolve => { - const l = link.cloneNode(true); - l.addEventListener('load', resolve, { once: true }); - l.addEventListener('error', resolve, { once: true }); - document.head.appendChild(l); - }))); - - [...newDoc.head.querySelectorAll('script[src]')] - .filter(s => !curScriptSrcs.has(new URL(s.src, location.href).href)) - .forEach(script => tasks.push(new Promise(resolve => { - const s = document.createElement('script'); - [...script.attributes].forEach(a => s.setAttribute(a.name, a.value)); - s.addEventListener('load', resolve, { once: true }); - s.addEventListener('error', resolve, { once: true }); - document.head.appendChild(s); - }))); - - return Promise.all(tasks); - } - - function updateHead(newDoc) { - const head = document.head; - const newHead = newDoc.head; - - const t = newHead.querySelector('title'); - if (t) document.title = t.textContent; - - const META_KEEP = new Set(['charset', 'viewport', 'color-scheme', 'theme-color']); - head.querySelectorAll('meta').forEach(m => { - const key = m.getAttribute('name') || m.getAttribute('property'); - if (!key || META_KEEP.has(key)) return; - m.remove(); - }); - const insertRef = head.querySelector( - 'link[rel="preconnect"], link[rel="stylesheet"], link[rel="manifest"], link[rel="icon"], script' - ); - newHead.querySelectorAll('meta[name], meta[property]').forEach(m => { - const key = m.getAttribute('name') || m.getAttribute('property'); - if (!META_KEEP.has(key)) head.insertBefore(m.cloneNode(true), insertRef); - }); - - const nc = newHead.querySelector('link[rel="canonical"]'); - const cc = head.querySelector('link[rel="canonical"]'); - if (nc && cc) cc.href = nc.href; - - const newStyleHrefs = new Set( - [...newHead.querySelectorAll('link[rel="stylesheet"]')] - .map(l => new URL(l.href, location.href).href) - ); - head.querySelectorAll('link[rel="stylesheet"]').forEach(l => { - if (!newStyleHrefs.has(l.href)) l.remove(); - }); - - const newScriptSrcs = new Set( - [...newHead.querySelectorAll('script[src]')] - .map(s => new URL(s.src, location.href).href) - ); - head.querySelectorAll('script[src]').forEach(s => { - if (!newScriptSrcs.has(s.src)) s.remove(); - }); - - head.querySelectorAll('style').forEach(s => s.remove()); - newHead.querySelectorAll('style').forEach(s => head.appendChild(s.cloneNode(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'); - - if (typeof window.__scriptCleanup === 'function') { - window.__scriptCleanup(); - window.__scriptCleanup = null; - } - - await preloadAssets(doc); - - updateHead(doc); - - for (const tag of ['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); - - if (cursor) { - 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'); - } - } - - init(); - }); - }); - - window.addEventListener('popstate', () => location.reload()); - } -})(); diff --git a/public/base-light.html b/public/base-light.html index ff4e760..e9fffff 100644 --- a/public/base-light.html +++ b/public/base-light.html @@ -23,8 +23,8 @@ + - {% block extra_head %}{% endblock %} diff --git a/public/base.html b/public/base.html index 30cf4a8..7955f4b 100644 --- a/public/base.html +++ b/public/base.html @@ -43,9 +43,10 @@ + + -