// ==UserScript== // @name DLsite Search Form at Top // @namespace https://x.com/kawaiiinf // @version 1.4.3 // @description DLsiteの検索結果ページで、左カラムの検索フォームをページ上部へ移動し、スクロール中も固定表示します。動的なページ更新にも対応します。 // @author Kawaii monkey // @license BSD 2-Clause // @match https://www.dlsite.com/* // @grant GM_addStyle // @run-at document-idle // @downloadURL none // ==/UserScript== (function () { 'use strict'; const MOVED_ATTR = 'data-dlst-moved'; const CONTAINER_ID = 'dlst-search-top-container'; const DEBUG = false; function log(...a){ if(DEBUG) console.log('[DLsite Search Top v1.4.2]', ...a); } function addStyle(css){ if (typeof GM_addStyle === 'function') GM_addStyle(css); else { const s=document.createElement('style'); s.textContent=css; document.head.appendChild(s); } } // 親へ sticky を適用するが、JSでクラスは触らず :has(> #CONTAINER_ID) で判定 addStyle(` /* 親が直下に #CONTAINER_ID を持つときのみ親をsticky化 */ *:has(> #${CONTAINER_ID}) { position: sticky; top: 0; z-index: 10; background: rgba(250, 250, 250, 0.85); backdrop-filter: saturate(1.05) blur(3px); border: #607194 1px solid; padding: 0.10rem 0.25rem; } /* バー自体の余白 */ #${CONTAINER_ID} { padding: 8px 10px; } /* 入力幅の最適化 */ #${CONTAINER_ID} input[type="search"] { width: calc(100% - 30px) !important; } `); function getTargets(){ const cpSearch = document.querySelector('.cp_search'); const searchTop = document.querySelector('.search_top'); if (!cpSearch || !searchTop) return null; const form = cpSearch.closest('form'); const parent = searchTop.parentElement || searchTop; if (!form || !parent) return null; return { cpSearch, form, searchTop, parent }; } function ensureContainer(searchTop){ let c = document.getElementById(CONTAINER_ID); if (!c){ c = document.createElement('div'); c.id = CONTAINER_ID; searchTop.after(c); log('Inserted container after .search_top'); } return c; } function moveFormIntoContainer(){ const t = getTargets(); if (!t) return; const { form, searchTop } = t; const container = ensureContainer(searchTop); // 既に期待の場所にあるならスキップ if (form.getAttribute(MOVED_ATTR) === '1' && form.parentElement === container) return; container.appendChild(form); // 移動(複製しない) form.setAttribute(MOVED_ATTR, '1'); log('Form moved into container'); } function bootstrap(){ moveFormIntoContainer(); // SPA対応:DOM変化を監視し、フォーム/コンテナの再配置 let timer = 0; const obs = new MutationObserver(() => { clearTimeout(timer); timer = setTimeout(moveFormIntoContainer, 60); }); obs.observe(document.documentElement, { childList: true, subtree: true }); window.addEventListener('pageshow', moveFormIntoContainer, { once: true }); window.addEventListener('popstate', () => setTimeout(moveFormIntoContainer, 50)); document.addEventListener('turbo:load', moveFormIntoContainer); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bootstrap); else bootstrap(); })();