您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
增强 Fab.com“我的库”页面的使用体验。它允许您创建自己的分类体系来管理日益增多的资产,并通过拖拽、点击等直观操作,轻松地将素材归类和筛选。
// ==UserScript== // @name Fab 库分类管理器 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 增强 Fab.com“我的库”页面的使用体验。它允许您创建自己的分类体系来管理日益增多的资产,并通过拖拽、点击等直观操作,轻松地将素材归类和筛选。 // @author Gemini & Gemini // @match https://www.fab.com/library* // @match https://www.fab.com/*/library* // @exclude https://www.fab.com/library?q=* // @exclude https://www.fab.com/*/library?q=* // @grant GM_setValue // @grant GM_getValue // @run-at document-end // @license MIT // ==/UserScript== (function() { 'use strict'; // --- SECTION 0: I18N & LANGUAGE SUPPORT --- const i18n = { 'en': { lang_code: 'en-US', quixel_link_text: "Browse Quixel Megascans", custom_categories_title: "Custom Categories", add_category_tooltip: "Add new category", show_all_button: "Show All", show_uncategorized_button: "Show Uncategorized", no_categories_placeholder: "No categories yet", rename_tooltip: "Rename", delete_tooltip: "Delete", delete_confirm: "Confirm?", new_category_placeholder: "Enter name and press Enter", set_category_tooltip: "Set Categories", toast_added_to: 'Added to "{categoryName}"', toast_removed_from: 'Removed from "{categoryName}"', toast_already_in: 'Already in "{categoryName}"', toast_error_category_exists: 'Error: Category "{name}" already exists.', toast_category_added: 'Category "{name}" was added.', toast_renamed_to: 'Renamed to "{newName}".', toast_category_deleted: 'Category "{name}" was deleted.', default_category_1: "Character Assets (Demo)", default_category_2: "Environment Props (Demo)", default_category_3: "Textures & Materials (Demo)", }, 'zh-cn': { lang_code: 'zh-CN', quixel_link_text: "浏览 Quixel Megascans", custom_categories_title: "自定义分类", add_category_tooltip: "添加分类", show_all_button: "显示全部", show_uncategorized_button: "显示未分类", no_categories_placeholder: "暂无分类", rename_tooltip: "重命名", delete_tooltip: "删除", delete_confirm: "确认?", new_category_placeholder: "输入分类名后按回车", set_category_tooltip: "设置分类", toast_added_to: '已添加到 "{categoryName}"', toast_removed_from: '已从 "{categoryName}" 中移除', toast_already_in: '"{categoryName}" 分类已存在', toast_error_category_exists: '错误:分类 "{name}" 已存在。', toast_category_added: '分类 "{name}" 已添加。', toast_renamed_to: '已重命名为 "{newName}"。', toast_category_deleted: '分类 "{name}" 已删除。', default_category_1: "角色资产 (测试)", default_category_2: "环境道具 (测试)", default_category_3: "贴图材质 (测试)", }, 'ja': { lang_code: 'ja-JP', quixel_link_text: "Quixel Megascansを閲覧", custom_categories_title: "カスタムカテゴリ", add_category_tooltip: "新しいカテゴリを追加", show_all_button: "すべて表示", show_uncategorized_button: "未分類を表示", no_categories_placeholder: "カテゴリがありません", rename_tooltip: "名前を変更", delete_tooltip: "削除", delete_confirm: "確認しますか?", new_category_placeholder: "名前を入力してEnter", set_category_tooltip: "カテゴリを設定", toast_added_to: '"{categoryName}" に追加しました', toast_removed_from: '"{categoryName}" から削除しました', toast_already_in: 'すでに "{categoryName}" に存在します', toast_error_category_exists: 'エラー:カテゴリ "{name}" はすでに存在します。', toast_category_added: 'カテゴリ "{name}" を追加しました。', toast_renamed_to: '"{newName}" に名前を変更しました。', toast_category_deleted: 'カテゴリ "{name}" を削除しました。', default_category_1: "キャラクターアセット (デモ)", default_category_2: "環境プロップ (デモ)", default_category_3: "テクスチャとマテリアル (デモ)", }, 'ko': { lang_code: 'ko-KR', quixel_link_text: "Quixel Megascans 찾아보기", custom_categories_title: "사용자 지정 카테고리", add_category_tooltip: "새 카테고리 추가", show_all_button: "모두 보기", show_uncategorized_button: "미분류 보기", no_categories_placeholder: "카테고리가 없습니다", rename_tooltip: "이름 바꾸기", delete_tooltip: "삭제", delete_confirm: "확인?", new_category_placeholder: "이름 입력 후 Enter", set_category_tooltip: "카테고리 설정", toast_added_to: '"{categoryName}"에 추가됨', toast_removed_from: '"{categoryName}"에서 제거됨', toast_already_in: '이미 "{categoryName}"에 있습니다', toast_error_category_exists: '오류: 카테고리 "{name}"이(가) 이미 존재합니다.', toast_category_added: '카테고리 "{name}"이(가) 추가되었습니다.', toast_renamed_to: '"{newName}"(으)로 이름이 변경되었습니다.', toast_category_deleted: '카테고리 "{name}"이(가) 삭제되었습니다.', default_category_1: "캐릭터 에셋 (데모)", default_category_2: "환경 소품 (데모)", default_category_3: "텍스처 및 재료 (데모)", } }; function getLangInfo() { const path = window.location.pathname; const match = path.match(/^\/([a-z]{2}(-[a-z]{2})?)\//); const langCode = match ? match[1] : 'en'; const translationKey = i18n.hasOwnProperty(langCode) ? langCode : 'en'; const urlPrefix = (langCode === 'en') ? '' : `${langCode}/`; return { key: translationKey, prefix: urlPrefix }; } const langInfo = getLangInfo(); const currentLangKey = langInfo.key; function t(key, replacements = {}) { let text = i18n[currentLangKey]?.[key] || i18n['en']?.[key] || key; for (const placeholder in replacements) { text = text.replace(`{${placeholder}}`, replacements[placeholder]); } return text; } // --- SECTION 1: DATA & STATE MANAGEMENT --- let state = { categories: [], itemAssignments: {}, activeFilter: null }; let draggedCategory = null; const STORAGE_KEYS = { CATEGORIES: 'fab_custom_categories_storage', ASSIGNMENTS: 'fab_item_assignments_storage' }; function loadData() { state.categories = GM_getValue(STORAGE_KEYS.CATEGORIES, []); state.itemAssignments = GM_getValue(STORAGE_KEYS.ASSIGNMENTS, {}); if (state.categories.length === 0) { state.categories = [ { id: '1', name: t('default_category_1') }, { id: '2', name: t('default_category_2') }, { id: '3', name: t('default_category_3') } ]; saveData(); } } function saveData() { GM_setValue(STORAGE_KEYS.CATEGORIES, state.categories); GM_setValue(STORAGE_KEYS.ASSIGNMENTS, state.itemAssignments); } function showToast(message, isError = false) { const existingToast = document.getElementById('fab-custom-toast'); if (existingToast) existingToast.remove(); const toast = document.createElement('div'); toast.id = 'fab-custom-toast'; toast.textContent = message; toast.style.cssText = `position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); padding: 12px 20px; border-radius: 6px; color: white; font-weight: 500; background-color: ${isError ? '#D32F2F' : '#43A047'}; box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 10000; opacity: 0; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);`; document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = '1'; toast.style.bottom = '30px'; }, 10); setTimeout(() => { toast.style.opacity = '0'; toast.style.bottom = '20px'; setTimeout(() => toast.remove(), 300); }, 3000); } // --- SECTION 2: LEFT PANEL UI & LOGIC --- function handleCategoryDragStart(e) { if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') { e.preventDefault(); return; } draggedCategory = e.currentTarget; e.dataTransfer.setData('text/plain--fab-category-id', draggedCategory.dataset.categoryId); e.dataTransfer.effectAllowed = 'move'; setTimeout(() => draggedCategory.classList.add('dragging-category'), 0); } function handleCategoryDragEnd(e) { if (!draggedCategory) return; draggedCategory.classList.remove('dragging-category'); document.querySelectorAll('.custom-category-item').forEach(item => { item.classList.remove('drop-indicator-top', 'drop-indicator-bottom'); }); draggedCategory = null; } function handleCategoryDragOver(e) { e.preventDefault(); const target = e.currentTarget; if (!draggedCategory || target === draggedCategory) return; const rect = target.getBoundingClientRect(); const midY = rect.top + rect.height / 2; if (e.clientY < midY) { target.classList.add('drop-indicator-top'); target.classList.remove('drop-indicator-bottom'); } else { target.classList.add('drop-indicator-bottom'); target.classList.remove('drop-indicator-top'); } } function handleCategoryDragEnter(e) { e.preventDefault(); const target = e.currentTarget; if (!draggedCategory || target === draggedCategory) return; target.classList.add('drag-over-sort'); } function handleCategoryDragLeave(e) { const target = e.currentTarget; if (!draggedCategory || target === draggedCategory) return; target.classList.remove('drop-indicator-top', 'drop-indicator-bottom', 'drag-over-sort'); } function handleCategoryDrop(e) { e.preventDefault(); e.stopPropagation(); const dropTarget = e.currentTarget; if (!draggedCategory || dropTarget === draggedCategory) return; const draggedId = e.dataTransfer.getData('text/plain--fab-category-id'); if (!draggedId) return; const targetId = dropTarget.dataset.categoryId; const draggedIndex = state.categories.findIndex(c => c.id === draggedId); const targetIndex = state.categories.findIndex(c => c.id === targetId); if (draggedIndex === -1 || targetIndex === -1) return; const [draggedItem] = state.categories.splice(draggedIndex, 1); const newTargetIndex = state.categories.findIndex(c => c.id === targetId); const rect = dropTarget.getBoundingClientRect(); const midY = rect.top + rect.height / 2; if (e.clientY < midY) { state.categories.splice(newTargetIndex, 0, draggedItem); } else { state.categories.splice(newTargetIndex + 1, 0, draggedItem); } saveData(); renderCategoryList(); } function renderCategoryList() { const listContainer = document.getElementById('custom-category-list-container'); if (!listContainer) return; listContainer.innerHTML = ''; if (state.categories.length === 0) { listContainer.innerHTML = `<li class="custom-category-item-placeholder">${t('no_categories_placeholder')}</li>`; } else { state.categories.forEach(category => { const li = document.createElement('li'); li.className = 'custom-category-item'; li.dataset.categoryId = category.id; li.setAttribute('draggable', 'true'); if (state.activeFilter === category.id) li.classList.add('is-active-filter'); const nameSpan = document.createElement('span'); nameSpan.className = 'category-name'; nameSpan.textContent = category.name; nameSpan.addEventListener('click', () => applyFilter(category.id)); const actionsDiv = document.createElement('div'); actionsDiv.className = 'category-actions'; actionsDiv.innerHTML = `<button class="edit-btn" title="${t('rename_tooltip')}">✏️</button><button class="delete-btn" title="${t('delete_tooltip')}">🗑️</button>`; li.appendChild(nameSpan); li.appendChild(actionsDiv); listContainer.appendChild(li); actionsDiv.querySelector('.edit-btn').addEventListener('click', () => handleRenameCategory(category.id)); actionsDiv.querySelector('.delete-btn').addEventListener('click', (e) => handleDeleteCategory(e, category.id)); li.addEventListener('dragover', (e) => { e.preventDefault(); if(e.dataTransfer.types.includes('text/plain--fab-category-id')) return; li.classList.add('drag-over'); }); li.addEventListener('dragleave', () => { li.classList.remove('drag-over'); }); li.addEventListener('drop', (e) => { e.preventDefault(); if(e.dataTransfer.types.includes('text/plain--fab-category-id')) return; li.classList.remove('drag-over'); const itemId = e.dataTransfer.getData('text/plain'); const card = document.querySelector(`div[data-gm-item-id="${itemId}"]`); if (card) { const assignments = state.itemAssignments[itemId] || []; if (!assignments.includes(category.id)) { toggleItemCategory(card, category.id, false); showToast(t('toast_added_to', { categoryName: category.name })); } else { showToast(t('toast_already_in', { categoryName: category.name }), true); } } }); li.addEventListener('dragstart', handleCategoryDragStart); li.addEventListener('dragend', handleCategoryDragEnd); li.addEventListener('dragenter', handleCategoryDragEnter); li.addEventListener('dragleave', handleCategoryDragLeave); li.addEventListener('dragover', handleCategoryDragOver); li.addEventListener('drop', handleCategoryDrop); }); } } function createNewCategory(input) { const name = input.value.trim(); if (name) { if (state.categories.some(c => c.name.toLowerCase() === name.toLowerCase())) { showToast(t('toast_error_category_exists', { name: name }), true); input.focus(); return; } state.categories.push({ id: Date.now().toString(), name: name }); saveData(); showToast(t('toast_category_added', { name: name })); } renderCategoryList(); } function handleRenameCategory(catId) { const li = document.querySelector(`.custom-category-item[data-category-id="${catId}"]`); const cat = state.categories.find(c => c.id === catId); if (!li || !cat) return; const oldName = cat.name; li.querySelector('.category-name').style.display = 'none'; const input = document.createElement('input'); input.type = 'text'; input.value = oldName; input.className = 'inline-category-input'; li.prepend(input); input.focus(); input.select(); const done = () => { const newName = input.value.trim(); if (newName && newName.toLowerCase() !== oldName.toLowerCase()) { if (state.categories.some(c => c.id !== catId && c.name.toLowerCase() === newName.toLowerCase())) { showToast(t('toast_error_category_exists', { name: newName }), true); input.focus(); return; } cat.name = newName; saveData(); showToast(t('toast_renamed_to', { newName: newName })); } renderCategoryList(); document.querySelectorAll('div[data-gm-item-id]').forEach(updateCardTag); }; input.addEventListener('keydown', (e) => { if (e.key === 'Enter') done(); }); input.addEventListener('blur', done); } function handleDeleteCategory(event, catId) { const btn = event.currentTarget; const cat = state.categories.find(c => c.id === catId); if (!cat) return; if (btn.classList.contains('confirm-delete')) { state.categories = state.categories.filter(c => c.id !== catId); Object.keys(state.itemAssignments).forEach(itemId => { state.itemAssignments[itemId] = state.itemAssignments[itemId].filter(id => id !== catId); if (state.itemAssignments[itemId].length === 0) { delete state.itemAssignments[itemId]; } }); saveData(); renderCategoryList(); showToast(t('toast_category_deleted', { name: cat.name })); document.querySelectorAll('div[data-gm-item-id]').forEach(updateCardTag); } else { btn.classList.add('confirm-delete'); btn.textContent = t('delete_confirm'); const timer = setTimeout(() => { btn.classList.remove('confirm-delete'); btn.textContent = '🗑️'; }, 3000); btn.onmouseleave = () => { clearTimeout(timer); btn.classList.remove('confirm-delete'); btn.textContent = '🗑️'; btn.onmouseleave = null; }; } } function handleAddCategory() { if (document.getElementById('new-category-input-li')) { document.querySelector('#new-category-input-li input').focus(); return; } const listContainer = document.getElementById('custom-category-list-container'); if (!listContainer) return; const placeholder = listContainer.querySelector('.custom-category-item-placeholder'); if (placeholder) placeholder.remove(); const li = document.createElement('li'); li.id = 'new-category-input-li'; li.className = 'custom-category-item'; const input = document.createElement('input'); input.type = 'text'; input.placeholder = t('new_category_placeholder'); input.className = 'inline-category-input'; li.appendChild(input); listContainer.prepend(li); input.focus(); input.addEventListener('keydown', function(e) { if (e.key === 'Enter') { createNewCategory(this); } else if (e.key === 'Escape') { renderCategoryList(); } }); input.addEventListener('blur', function() { setTimeout(() => { renderCategoryList(); }, 100); }); } function injectLeftPanelUI() { if (document.getElementById('custom-categories-container')) return; const container = document.createElement('div'); container.id = 'custom-categories-container'; container.innerHTML = ` <a id="quixel-link-btn" href="https://www.fab.com/${langInfo.prefix}sellers/Quixel" target="_blank">${t('quixel_link_text')}</a> <div class="fabkit-Stack-root fabkit-Stack--column"> <h2 class="custom-categories-title"> <span>${t('custom_categories_title')}</span> <span id="add-category-btn" title="${t('add_category_tooltip')}">+</span> </h2> <div id="custom-category-controls"> <button id="clear-filter-btn">${t('show_all_button')}</button> <button id="uncategorized-filter-btn">${t('show_uncategorized_button')}</button> </div> <nav class="fabkit-TreeView-root"> <ul id="custom-category-list-container" class="custom-category-list"></ul> </nav> </div>`; document.body.appendChild(container); document.getElementById('add-category-btn').addEventListener('click', handleAddCategory); document.getElementById('clear-filter-btn').addEventListener('click', clearFilter); document.getElementById('uncategorized-filter-btn').addEventListener('click', applyUncategorizedFilter); renderCategoryList(); } // --- SECTION 3: RIGHT PANEL & CARD LOGIC --- const clickOutsideMenuHandler = (e) => { const menu = document.querySelector('.gm-category-popup-menu'); if (menu && !menu.contains(e.target)) { menu.remove(); document.removeEventListener('click', clickOutsideMenuHandler, true); } }; function updateCardTag(card) { const itemId = card.dataset.gmItemId; const tag = card.querySelector('.gm-category-tag'); if (!tag || !itemId) return; const assignedCategoryIds = state.itemAssignments[itemId]; if (assignedCategoryIds && assignedCategoryIds.length > 0) { const assignedNames = assignedCategoryIds.map(id => state.categories.find(c => c.id === id)?.name).filter(Boolean).join(', '); tag.textContent = assignedNames; tag.style.display = 'block'; } else { tag.textContent = ''; tag.style.display = 'none'; } } function toggleItemCategory(card, categoryId, suppressToast = false) { const itemId = card.dataset.gmItemId; if (!itemId) return; if (!state.itemAssignments[itemId]) state.itemAssignments[itemId] = []; const assignments = state.itemAssignments[itemId]; const categoryIndex = assignments.indexOf(categoryId); const categoryName = state.categories.find(c => c.id === categoryId)?.name || ''; if (categoryIndex > -1) { assignments.splice(categoryIndex, 1); if (!suppressToast) showToast(t('toast_removed_from', { categoryName: categoryName })); } else { assignments.push(categoryId); if (!suppressToast) showToast(t('toast_added_to', { categoryName: categoryName })); } if (assignments.length === 0) delete state.itemAssignments[itemId]; saveData(); updateCardTag(card); const menu = document.querySelector('.gm-category-popup-menu'); if (menu) { const menuItem = menu.querySelector(`li[data-category-id="${categoryId}"]`); if (menuItem) menuItem.classList.toggle('is-checked'); } } function showCategoryPopupMenu(event, card) { event.stopPropagation(); const existingMenu = document.querySelector('.gm-category-popup-menu'); document.removeEventListener('click', clickOutsideMenuHandler, true); if (existingMenu) { existingMenu.remove(); return; } const menu = document.createElement('ul'); menu.className = 'gm-category-popup-menu'; const itemId = card.dataset.gmItemId; const assignedCategoryIds = state.itemAssignments[itemId] || []; state.categories.forEach(cat => { const li = document.createElement('li'); li.className = 'gm-popup-item'; li.dataset.categoryId = cat.id; if (assignedCategoryIds.includes(cat.id)) li.classList.add('is-checked'); li.innerHTML = `<span class="gm-popup-checkmark">✔️</span> ${cat.name}`; li.addEventListener('click', (e) => { e.stopPropagation(); toggleItemCategory(card, cat.id); }); menu.appendChild(li); }); document.body.appendChild(menu); const btnRect = event.currentTarget.getBoundingClientRect(); menu.style.left = `${btnRect.left}px`; menu.style.top = `${btnRect.bottom + 5}px`; setTimeout(() => { document.addEventListener('click', clickOutsideMenuHandler, true); }, 0); } function applyFilter(categoryId) { if (state.activeFilter === categoryId) { clearFilter(); return; } state.activeFilter = categoryId; renderCategoryList(); } function clearFilter() { state.activeFilter = null; renderCategoryList(); } function applyUncategorizedFilter() { state.activeFilter = 'uncategorized'; renderCategoryList(); } function runFilter() { const allCards = document.querySelectorAll('div[data-gm-item-id]'); const clearBtn = document.getElementById('clear-filter-btn'); const uncatBtn = document.getElementById('uncategorized-filter-btn'); if (clearBtn) clearBtn.classList.toggle('is-active-filter', state.activeFilter === null); if (uncatBtn) uncatBtn.classList.toggle('is-active-filter', state.activeFilter === 'uncategorized'); if (state.activeFilter === null) { allCards.forEach(card => card.style.display = ''); return; } if (state.activeFilter === 'uncategorized') { allCards.forEach(card => { const itemId = card.dataset.gmItemId; const isAssigned = state.itemAssignments[itemId] && state.itemAssignments[itemId].length > 0; card.style.display = isAssigned ? 'none' : ''; }); return; } allCards.forEach(card => { const itemId = card.dataset.gmItemId; const assignments = state.itemAssignments[itemId] || []; if (assignments.includes(state.activeFilter)) { card.style.display = ''; } else { card.style.display = 'none'; } }); } function hideQuixelPanel() { const quixelLink = document.querySelector('a[href$="/sellers/Quixel"]'); if (quixelLink) { const quixelPanel = quixelLink.closest('.HAfmzF_H'); if (quixelPanel && quixelPanel.style.display !== 'none') { quixelPanel.style.display = 'none'; } } } function findAndMarkCards() { document.querySelectorAll('div[class*="nTa5u2sc"]').forEach(card => { if (card.querySelector('a[href*="/listings/"]') && !card.dataset.gmProcessed) { card.dataset.gmProcessed = 'true'; const link = card.querySelector('a[href*="/listings/"]'); const itemId = link.href.split('/listings/')[1]; card.dataset.gmItemId = itemId; card.setAttribute('draggable', 'true'); card.addEventListener('dragstart', (e) => { e.dataTransfer.setData('text/plain', itemId); setTimeout(() => card.classList.add('is-dragging'), 0); }); card.addEventListener('dragend', () => { card.classList.remove('is-dragging'); }); const uiContainer = document.createElement('div'); uiContainer.className = 'gm-ui-container'; const tag = document.createElement('div'); tag.className = 'gm-category-tag'; const button = document.createElement('button'); button.className = 'gm-category-button'; button.title = t('set_category_tooltip'); button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M10 4H4c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"></path></svg>`; button.addEventListener('click', (e) => showCategoryPopupMenu(e, card)); uiContainer.appendChild(tag); uiContainer.appendChild(button); card.appendChild(uiContainer); updateCardTag(card); } }); runFilter(); } // --- SECTION 4: STYLES & INITIALIZATION --- function injectGlobalStyles() { if (document.getElementById('fab-categorizer-global-styles')) return; const styleSheet = document.createElement('style'); styleSheet.id = 'fab-categorizer-global-styles'; styleSheet.textContent = ` #custom-categories-container { position: fixed; top: 80px; bottom: 0; left: 10px; width: 280px; z-index: 0; background-color: transparent; border: none; box-shadow: none; padding: 10px; font-family: Inter, sans-serif; display: flex; flex-direction: column; pointer-events: none; } #custom-categories-container > * { pointer-events: auto; } #custom-categories-container .fabkit-Stack-root { flex: 1; display: flex; flex-direction: column; min-height: 0; } .fabkit-TreeView-root { flex: 1; overflow-y: auto; min-height: 0; } .custom-category-list { list-style: none; padding: 0; margin: 0; } #quixel-link-btn { display: block; text-align: center; margin-bottom: 10px; width: 100%; box-sizing: border-box; padding: 8px; background-color: var(--fab-palette-background-low, #2a2a2a); border: 1px solid var(--fab-palette-border, #333); color: var(--fab-palette-foreground-primary, #FFF); border-radius: 4px; cursor: pointer; transition: all 0.2s; text-decoration: none; font-size: 14px; } #quixel-link-btn:hover { background-color: #333; } .custom-categories-title { font-size: 0.875rem; font-weight: 700; color: var(--fab-palette-foreground-primary, #FFF); padding: 0 10px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; } #add-category-btn { cursor: pointer; color: var(--fab-palette-foreground-secondary, #AAA); font-size: 1.5rem; border-radius: 4px; transition: all 0.2s; line-height: 1; } #add-category-btn:hover { color: #FFF; background-color: #333; } #custom-category-controls { margin-bottom: 8px; display: flex; gap: 8px; } #custom-category-controls button { flex-grow: 1; padding: 8px; background-color: var(--fab-palette-background-low, #2a2a2a); border: 1px solid var(--fab-palette-border, #333); color: var(--fab-palette-foreground-primary, #FFF); border-radius: 4px; cursor: pointer; transition: all 0.2s; } #custom-category-controls button:hover { background-color: #333; } #custom-category-controls button.is-active-filter, .custom-category-item.is-active-filter { background-color: var(--fab-palette-primary-container, #004C99); font-weight: bold; border-color: var(--fab-palette-primary, #0078F2) !important; } .custom-category-item { color: var(--fab-palette-foreground-primary, #FFF); padding: 8px 10px; border-radius: 4px; min-height: 36px; display: flex; align-items: center; justify-content: space-between; transition: background-color 0.2s, outline 0.2s, border-top 0.2s, border-bottom 0.2s; border-top: 2px solid transparent; border-bottom: 2px solid transparent; } .custom-category-item .category-name { flex-grow: 1; cursor: pointer; } .custom-category-item:hover { background-color: var(--fab-palette-background-low, #2a2a2a); } .custom-category-item.drag-over { background-color: #004C99 !important; outline: 2px solid #0078F2; } .custom-category-item.dragging-category { opacity: 0.5; } .custom-category-item.drop-indicator-top { border-top: 2px solid #0078F2; } .custom-category-item.drop-indicator-bottom { border-bottom: 2px solid #0078F2; } .inline-category-input { width: 100%; background-color: #333; border: 1px solid #0078F2; color: #FFF; border-radius: 4px; padding: 5px 8px; outline: none; box-sizing: border-box; } .category-actions { display: none; } .custom-category-item:hover .category-actions { display: flex; } .category-actions button { background: none; border: none; cursor: pointer; margin-left: 8px; font-size: 16px; opacity: 0.6; transition: all 0.2s; padding: 0; } .category-actions button:hover { opacity: 1; } .delete-btn.confirm-delete { color: #D32F2F; font-size: 12px; font-weight: bold; width: 50px; } div[class*="nTa5u2sc"] { position: relative !important; } div[class*="nTa5u2sc"].is-dragging { opacity: 0.5; } .gm-ui-container { position: absolute !important; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none; } .gm-category-tag { position: absolute; top: 8px; left: 8px; background-color: rgba(0, 0, 0, 0.7); color: white; padding: 2px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; display: none; max-width: calc(100% - 50px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .gm-category-button { position: absolute; top: 5px; right: 5px; background-color: rgba(30, 30, 30, 0.8); border: 1px solid rgba(255, 255, 255, 0.2); color: white; border-radius: 5px; cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.2s; pointer-events: all; } div[class*="nTa5u2sc"]:hover .gm-category-button { opacity: 1; } .gm-category-button:hover { background-color: #333; } .gm-category-popup-menu { position: fixed; z-index: 10000; background: #2b2b2b; border: 1px solid #444; border-radius: 5px; list-style: none; padding: 5px 0; margin: 0; min-width: 180px; color: white; box-shadow: 0 5px 15px rgba(0,0,0,0.5); } .gm-popup-item { padding: 8px 12px; cursor: pointer; display: flex; align-items: center; } .gm-popup-item:hover { background: #4a4a4a; } .gm-popup-checkmark { color: #4CAF50; font-size: 1.2em; margin-right: 8px; visibility: hidden; } .gm-popup-item.is-checked .gm-popup-checkmark { visibility: visible; } `; document.head.appendChild(styleSheet); } function main() { loadData(); injectGlobalStyles(); injectLeftPanelUI(); setInterval(() => { findAndMarkCards(); hideQuixelPanel(); }, 500); } main(); })();