// ==UserScript== // @name 智能剪切板 // @namespace https://scriptcat.org/smart-clipboard // @version 2.3.0 // @description 生产级网页剪贴板工具:复制时主动捕获选中文本/输入框内容,解决必须点击读取的问题;支持图片手动读、智能分类、7天/50条/容量维护。 // @author ChatGPT // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij4KPGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agc3RvcC1jb2xvcj0iIzdjM2FlZCIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzA2YjZkNCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPgo8cmVjdCB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCIgcng9IjMwIiBmaWxsPSJ1cmwoI2cpIi8+CjxwYXRoIGQ9Ik00NCAzMmg0MGE4IDggMCAwIDEgOCA4djU2YTggOCAwIDAgMS04IDhINDRhOCA4IDAgMCAxLTgtOFY0MGE4IDggMCAwIDEgOC04eiIgZmlsbD0id2hpdGUiIG9wYWNpdHk9Ii45MiIvPgo8cmVjdCB4PSI0OCIgeT0iMjIiIHdpZHRoPSIzMiIgaGVpZ2h0PSIyMiIgcng9IjkiIGZpbGw9IiMwZjE3MmEiLz4KPHBhdGggZD0iTTUyIDYybDE0IDE0IDI0LTMwIiBmaWxsPSJub25lIiBzdHJva2U9IiM3YzNhZWQiIHN0cm9rZS13aWR0aD0iMTAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4= // @match *://*/* // @run-at document-start // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_listValues // @grant GM_setClipboard // @grant GM_registerMenuCommand // @grant GM_addStyle // @connect * // @license MIT // ==/UserScript== (function () { 'use strict'; const APP = { name: '智能剪切板', version: '2.3.0', storageKey: 'smart_clipboard_records_v23', oldKeys: ['smart_clipboard_records_v22', 'smart_clipboard_records_v21', 'smart_clipboard_records_v2'], settingsKey: 'smart_clipboard_settings_v23', oldSettingKeys: ['smart_clipboard_settings_v22', 'smart_clipboard_settings_v21', 'smart_clipboard_settings_v2'], rulesKey: 'smart_clipboard_rules_v23', oldRulesKeys: ['smart_clipboard_rules_v22', 'smart_clipboard_rules_v21'], maxImageBytes: 2.8 * 1024 * 1024, hotkey: 'Alt+Shift+C' }; const defaultSettings = { autoCapture: true, captureKeyboardCopy: true, captureCopyEvent: true, captureSelectionOnMouse: false, autoReadClipboardAfterCopy: false, saveImages: true, compressImages: true, imageMaxWidth: 1280, imageQuality: 0.82, maxRecords: 50, maxDays: 7, maxStorageMB: 8, theme: 'dark', floatTop: 260, privacyMode: true, skipSecrets: true, updateDuplicateTime: true, duplicateWindowMinutes: 1440, maintainOnStart: true, showCaptureToast: false }; const defaultRules = [ { name: '财务', pattern: '发票|报销|invoice|receipt|税|金额|付款|收款', type: '财务' }, { name: '待办', pattern: 'TODO|待办|记得|提醒|deadline|ddl', type: '待办' }, { name: '账号', pattern: '登录|账号|用户名|user(name)?', type: '账号' } ]; let state = { records: [], settings: loadSettings(), rules: loadRules(), root: null, panel: null, visible: false, activeType: '全部', keyword: '', lastFingerprint: '', selectedId: null, selectedIds: new Set(), lastCaptureAt: 0, recentSelectionText: '', recentSelectionAt: 0 }; function nowText() { const d = new Date(); const pad = n => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; } function uid() { return 'sc_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 9); } function safeGMGet(key, fallback) { try { const v = GM_getValue(key); return v === undefined || v === null ? fallback : v; } catch (e) { try { const raw = localStorage.getItem(key); return raw ? JSON.parse(raw) : fallback; } catch (_) { return fallback; } } } function safeGMSet(key, value) { try { GM_setValue(key, value); } catch (e) { localStorage.setItem(key, JSON.stringify(value)); } } function firstExisting(keys, fallback) { for (const key of keys) { const v = safeGMGet(key, undefined); if (v !== undefined) return v; } return fallback; } function loadSettings() { return Object.assign({}, defaultSettings, firstExisting(APP.oldSettingKeys, {}), safeGMGet(APP.settingsKey, {})); } function saveSettings() { safeGMSet(APP.settingsKey, state.settings); } function loadRules() { let rules = safeGMGet(APP.rulesKey, undefined); if (!rules) rules = firstExisting(APP.oldRulesKeys, defaultRules); return Array.isArray(rules) ? rules : defaultRules; } function saveRules() { safeGMSet(APP.rulesKey, state.rules); } function loadRecords() { const data = safeGMGet(APP.storageKey, firstExisting(APP.oldKeys, [])); state.records = Array.isArray(data) ? data : []; if (state.settings.maintainOnStart) { maintainRecords(false); safeGMSet(APP.storageKey, state.records); } } function saveRecords() { maintainRecords(false); safeGMSet(APP.storageKey, state.records); } function estimateStorageBytes(records = state.records) { try { return new Blob([JSON.stringify(records)]).size; } catch (_) { return JSON.stringify(records).length * 2; } } function parseRecordTime(r) { const s = r.updatedAt || r.createdAt || ''; const t = Date.parse(String(s).replace(/-/g, '/')); return Number.isFinite(t) ? t : 0; } function maintainRecords(showToast) { const maxRecords = Math.max(1, Number(state.settings.maxRecords || 50)); const maxDays = Math.max(1, Number(state.settings.maxDays || 7)); const maxBytes = Math.max(1, Number(state.settings.maxStorageMB || 8)) * 1024 * 1024; const expire = Date.now() - maxDays * 86400000; let removedByDays = 0, removedByCount = 0, removedBySize = 0; state.records = state.records.filter(r => { if (r.favorite) return true; const t = parseRecordTime(r); const keep = !t || t >= expire; if (!keep) removedByDays++; return keep; }); if (state.records.length > maxRecords) { const favorites = state.records.filter(r => r.favorite); const normal = state.records.filter(r => !r.favorite); const keepNormalCount = Math.max(0, maxRecords - favorites.length); removedByCount = Math.max(0, normal.length - keepNormalCount); state.records = [...favorites, ...normal.slice(0, keepNormalCount)].sort((a, b) => parseRecordTime(b) - parseRecordTime(a)); } while (estimateStorageBytes(state.records) > maxBytes) { const idx = findOldestNonFavoriteIndex(); if (idx < 0) break; state.records.splice(idx, 1); removedBySize++; } if (showToast) toast(`维护完成:过期${removedByDays},超条数${removedByCount},超容量${removedBySize}`); } function findOldestNonFavoriteIndex() { let idx = -1, min = Infinity; state.records.forEach((r, i) => { if (r.favorite) return; const t = parseRecordTime(r) || 0; if (t < min) { min = t; idx = i; } }); return idx; } function escapeHtml(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } function looksSecret(text) { const t = String(text || '').trim(); if (!t) return false; const secretPatterns = [ /(?:password|passwd|pwd|token|secret|api[_-]?key|access[_-]?key|authorization|bearer)\s*[:=]\s*['"]?[A-Za-z0-9_\-\.={}\/+]{8,}/i, /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/, /\bsk-[A-Za-z0-9]{20,}\b/, /\b\d{6}\b/, /\b\d{13,19}\b/, /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/ ]; return secretPatterns.some(re => re.test(t)); } function maskSecret(text) { let t = String(text || ''); t = t.replace(/((?:password|passwd|pwd|token|secret|api[_-]?key|access[_-]?key|authorization|bearer)\s*[:=]\s*['"]?)([^\s'"]{6,})/ig, '$1••••••••'); t = t.replace(/\b\d{13,19}\b/g, m => m.slice(0, 4) + ' **** **** ' + m.slice(-4)); t = t.replace(/\b\d{6}\b/g, '••••••'); return t; } function classifyText(text) { const t = String(text || '').trim(); for (const rule of state.rules || []) { try { if (rule.pattern && new RegExp(rule.pattern, 'i').test(t)) return rule.type || rule.name || '自定义'; } catch (_) {} } if (!t) return '文本'; if (looksSecret(t)) return '隐私'; if (/^https?:\/\/[^\s]+$/i.test(t)) return '链接'; if (/^mailto:/i.test(t) || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)) return '邮箱'; if ((t.startsWith('{') && t.endsWith('}')) || (t.startsWith('[') && t.endsWith(']'))) { try { JSON.parse(t); return 'JSON'; } catch (_) {} } if (/^\s*(function|const|let|var|class|import|export|async|def |class |public |private |#include|package |SELECT |INSERT |UPDATE |DELETE |CREATE |<\?php|using namespace)/im.test(t)) return '代码'; if (/^\s*[-+]?\d+(\.\d+)?\s*[,,]\s*[-+]?\d+(\.\d+)?\s*$/.test(t)) return '坐标'; if (t.length > 500) return '长文本'; return '文本'; } function summarize(content, type) { if (type === '图片') return '图片剪贴板内容'; const clean = String(content || '').replace(/\s+/g, ' ').trim(); return clean.length > 90 ? clean.slice(0, 90) + '…' : clean || '(空内容)'; } function getSelectedTextDeep() { const active = document.activeElement; let text = ''; try { if (active && (active.tagName === 'TEXTAREA' || (active.tagName === 'INPUT' && /text|search|url|email|tel|password|number/.test(active.type || 'text')))) { const start = active.selectionStart; const end = active.selectionEnd; if (typeof start === 'number' && typeof end === 'number' && end > start) { text = active.value.slice(start, end); } } } catch (_) {} if (!text) { try { text = String(window.getSelection ? window.getSelection() : '').trim(); } catch (_) {} } if (!text && active && active.isContentEditable) { try { text = String(window.getSelection ? window.getSelection() : '').trim(); } catch (_) {} } return String(text || '').trim(); } function captureText(text, source = 'copy-selection') { if (!text || !String(text).trim()) return false; if (state.settings.skipSecrets && looksSecret(text)) { if (source !== 'auto') toast('检测到隐私内容,已跳过保存'); return false; } const content = state.settings.privacyMode ? maskSecret(text) : text; const ok = addRecordSmart({ type: classifyText(text), content, source }); if (ok) { saveRecords(); render(); if (state.settings.showCaptureToast) toast('已自动捕获复制内容'); } return ok; } function captureCurrentSelection(source = 'copy-selection') { const text = getSelectedTextDeep() || ((Date.now() - state.recentSelectionAt < 1500) ? state.recentSelectionText : ''); return captureText(text, source); } async function readClipboard(source = 'manual') { if (!navigator.clipboard) { const ok = captureCurrentSelection('selection-fallback'); if (!ok && source !== 'auto') toast('当前页面不支持 Clipboard API,也没有选中文本'); return ok ? 1 : 0; } let added = 0; const suppressToast = source === 'auto'; try { if (navigator.clipboard.read && state.settings.saveImages && source !== 'selection') { const items = await navigator.clipboard.read(); for (const item of items) { const imgType = item.types.find(t => t.startsWith('image/')); if (imgType) { const blob = await item.getType(imgType); const processed = state.settings.compressImages ? await compressImageBlob(blob) : blob; if (processed.size > APP.maxImageBytes) { if (!suppressToast) toast('图片太大,已跳过'); continue; } const dataUrl = await blobToDataURL(processed); const ok = addRecordSmart({ type: '图片', content: dataUrl, mime: processed.type || imgType, size: processed.size, source }); if (ok) added++; } } } } catch (_) {} try { const text = await navigator.clipboard.readText(); if (text && captureText(text, source === 'auto' ? 'clipboard-auto' : 'clipboard-manual')) added++; } catch (_) { if (captureCurrentSelection(source === 'auto' ? 'selection-auto' : 'selection-fallback')) added++; } if (added) { saveRecords(); render(); if (!suppressToast) toast(`已收集 ${added} 条剪贴板内容`); } else if (!suppressToast) { toast('没有发现新的剪贴板内容;可先选中文字再复制'); } return added; } function addRecordSmart(input) { const rec = { id: uid(), type: input.type, content: input.content, mime: input.mime || 'text/plain', size: input.size || String(input.content || '').length, summary: summarize(input.content, input.type), createdAt: nowText(), updatedAt: nowText(), pageTitle: document.title || '', pageUrl: location.href, source: input.source || 'manual', favorite: false, note: '', tags: autoTags(input) }; const fingerprint = makeFingerprint(rec); if (fingerprint === state.lastFingerprint) return false; state.lastFingerprint = fingerprint; const old = findDuplicate(rec); if (old && state.settings.updateDuplicateTime) { old.updatedAt = nowText(); old.createdAt = nowText(); old.pageTitle = rec.pageTitle; old.pageUrl = rec.pageUrl; old.source = rec.source; old.size = rec.size; old.summary = rec.summary; old.tags = [...new Set([...(old.tags || []), ...(rec.tags || [])])].slice(0, 8); state.records = [old, ...state.records.filter(r => r.id !== old.id)]; state.selectedId = old.id; return true; } state.records.unshift(rec); state.selectedId = rec.id; return true; } function stableTrim(s) { return String(s || '').replace(/\s+/g, ' ').trim(); } function makeFingerprint(rec) { if (rec.type === '图片') return 'image:' + String(rec.content).slice(0, 180) + ':' + rec.size; return 'text:' + stableTrim(rec.content); } function findDuplicate(rec) { const normalized = stableTrim(rec.content); return state.records.find(r => { if (r.type !== rec.type) return false; if (rec.type === '图片') return r.content === rec.content || (String(r.content).slice(0, 220) === String(rec.content).slice(0, 220) && r.size === rec.size); return stableTrim(r.content) === normalized; }); } function blobToDataURL(blob) { return new Promise((resolve, reject) => { const r = new FileReader(); r.onload = () => resolve(r.result); r.onerror = reject; r.readAsDataURL(blob); }); } async function compressImageBlob(blob) { try { const img = await blobToImage(blob); const maxW = Number(state.settings.imageMaxWidth || 1280); let w = img.naturalWidth || img.width; let h = img.naturalHeight || img.height; if (w > maxW) { h = Math.round(h * maxW / w); w = maxW; } const canvas = document.createElement('canvas'); canvas.width = w; canvas.height = h; canvas.getContext('2d').drawImage(img, 0, 0, w, h); const type = blob.type === 'image/png' ? 'image/png' : 'image/jpeg'; const quality = Math.max(.35, Math.min(.95, Number(state.settings.imageQuality || .82))); return await new Promise(resolve => canvas.toBlob(b => resolve(b || blob), type, quality)); } catch (_) { return blob; } } function blobToImage(blob) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { URL.revokeObjectURL(img.src); resolve(img); }; img.onerror = reject; img.src = URL.createObjectURL(blob); }); } function autoTags(input) { const tags = [input.type]; if (input.source) tags.push(input.source); const text = String(input.content || ''); if (input.type === '链接') { try { tags.push(new URL(text).hostname.replace(/^www\./, '')); } catch (_) {} } if (input.type === '代码') { if (/function|const|let|=>/.test(text)) tags.push('JavaScript'); if (/def |import |print\(/.test(text)) tags.push('Python'); if (/SELECT|INSERT|UPDATE|DELETE/i.test(text)) tags.push('SQL'); } if (input.type === '图片') tags.push('图片'); if (/TODO|todo|待办/.test(text)) tags.push('TODO'); return [...new Set(tags)].slice(0, 8); } function getFilteredRecords() { const kw = state.keyword.trim().toLowerCase(); return state.records.filter(r => { const typeOK = state.activeType === '全部' || (state.activeType === '收藏' ? r.favorite : r.type === state.activeType); if (!typeOK) return false; if (!kw) return true; return [r.summary, r.content, r.createdAt, r.updatedAt, r.pageTitle, r.pageUrl, r.source, (r.tags || []).join(' '), r.note] .join(' ').toLowerCase().includes(kw); }); } function types() { const customTypes = [...new Set((state.rules || []).map(r => r.type || r.name).filter(Boolean))]; return [...new Set(['全部', '收藏', '文本', '链接', '代码', 'JSON', '图片', '长文本', '隐私', '邮箱', '坐标', ...customTypes])]; } function ensureUI() { if (document.getElementById('sc-smart-clipboard-root')) return; GM_addStyle(` #sc-smart-clipboard-root, #sc-smart-clipboard-root * { box-sizing: border-box; } #sc-smart-clipboard-root { position: fixed; z-index: 2147483647; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif; color: #eef2ff; } .sc-float { position: fixed; right: 0; top: var(--sc-top,260px); width: 46px; height: 58px; border-radius: 18px 0 0 18px; border: 1px solid rgba(255,255,255,.24); border-right:0; background: linear-gradient(135deg,#7c3aed,#06b6d4); color: white; font-size: 24px; box-shadow: 0 18px 45px rgba(0,0,0,.35); cursor: grab; display:flex; align-items:center; justify-content:center; user-select:none; touch-action:none; } .sc-float:active { cursor: grabbing; } .sc-float::before { content:""; position:absolute; left:5px; top:16px; width:3px; height:26px; border-radius:999px; background:rgba(255,255,255,.45); box-shadow:6px 0 0 rgba(255,255,255,.35); } .sc-panel { position: fixed; right: 56px; top: max(16px, min(var(--sc-panel-top,90px), calc(100vh - min(740px, calc(100vh - 32px))))); width: min(1080px, calc(100vw - 76px)); height: min(740px, calc(100vh - 32px)); background: rgba(15,23,42,.94); backdrop-filter: blur(18px); border: 1px solid rgba(148,163,184,.28); border-radius: 24px; box-shadow: 0 30px 90px rgba(0,0,0,.55); overflow: hidden; display: none; } .sc-panel.sc-show { display: grid; grid-template-rows: 68px 1fr 42px; } .sc-light .sc-panel { background: rgba(255,255,255,.96); color:#0f172a; } .sc-head { display:flex; align-items:center; gap:12px; padding: 14px 16px; border-bottom: 1px solid rgba(148,163,184,.22); } .sc-logo { width:40px; height:40px; border-radius: 14px; display:flex; align-items:center; justify-content:center; background: linear-gradient(135deg,#8b5cf6,#22d3ee); color:white; font-size:21px; } .sc-title { font-weight:800; font-size:18px; line-height:1.1; } .sc-sub { font-size:12px; color:#94a3b8; margin-top:3px; } .sc-search { flex:1; display:flex; gap:8px; align-items:center; margin-left:10px; } .sc-input { width:100%; background: rgba(255,255,255,.08); color: inherit; border: 1px solid rgba(148,163,184,.26); outline:none; border-radius: 14px; padding: 11px 13px; } .sc-light .sc-input { background:#f8fafc; } .sc-btn { border:1px solid rgba(148,163,184,.25); background:rgba(255,255,255,.08); color:inherit; border-radius: 13px; padding: 10px 12px; cursor:pointer; font-weight:650; white-space:nowrap; } .sc-btn:hover { background: rgba(125,92,246,.25); } .sc-primary { background: linear-gradient(135deg,#7c3aed,#0891b2); border:0; color:white; } .sc-body { display:grid; grid-template-columns: 188px 370px 1fr; min-height:0; } .sc-side { border-right:1px solid rgba(148,163,184,.2); padding:12px; overflow:auto; } .sc-type { display:flex; justify-content:space-between; align-items:center; width:100%; padding:10px 12px; border-radius:14px; cursor:pointer; color:#cbd5e1; margin-bottom:5px; } .sc-type:hover, .sc-type.active { background:rgba(124,58,237,.25); color:white; } .sc-light .sc-type { color:#334155; } .sc-light .sc-type:hover, .sc-light .sc-type.active { color:#0f172a; background:#e0e7ff; } .sc-list { border-right:1px solid rgba(148,163,184,.2); overflow:auto; padding:12px; } .sc-toolbar { display:flex; gap:6px; margin-bottom:10px; flex-wrap:wrap; } .sc-mini { padding:7px 9px; font-size:12px; border-radius:11px; } .sc-card { border:1px solid rgba(148,163,184,.18); background:rgba(255,255,255,.055); border-radius:18px; padding:12px; margin-bottom:10px; cursor:pointer; position:relative; } .sc-card:hover, .sc-card.active { border-color:rgba(34,211,238,.65); background:rgba(34,211,238,.12); } .sc-card.checked { outline:2px solid rgba(124,58,237,.7); } .sc-check { position:absolute; right:10px; bottom:10px; transform:scale(1.1); } .sc-light .sc-card { background:#f8fafc; } .sc-card-top { display:flex; justify-content:space-between; gap:8px; align-items:center; margin-bottom:8px; padding-right:20px; } .sc-badge { font-size:12px; padding:4px 8px; border-radius:999px; background:rgba(99,102,241,.22); color:#c7d2fe; } .sc-light .sc-badge { background:#e0e7ff; color:#3730a3; } .sc-time { color:#94a3b8; font-size:11px; } .sc-summary { color:#e2e8f0; font-size:13px; line-height:1.45; word-break:break-all; } .sc-light .sc-summary { color:#0f172a; } .sc-tags { display:flex; flex-wrap:wrap; gap:5px; margin-top:9px; padding-right:28px; } .sc-tag { color:#94a3b8; background:rgba(148,163,184,.12); border-radius:999px; padding:2px 7px; font-size:11px; } .sc-preview { padding:16px; overflow:auto; min-width:0; } .sc-empty { color:#94a3b8; height:100%; display:flex; align-items:center; justify-content:center; text-align:center; line-height:1.8; } .sc-preview-head { display:flex; justify-content:space-between; align-items:flex-start; gap:10px; margin-bottom:14px; } .sc-preview-title { font-weight:800; font-size:18px; } .sc-actions { display:flex; flex-wrap:wrap; gap:8px; justify-content:flex-end; } .sc-content { white-space:pre-wrap; word-break:break-word; border:1px solid rgba(148,163,184,.22); border-radius:18px; padding:14px; background:rgba(2,6,23,.35); line-height:1.55; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:13px; } .sc-light .sc-content { background:#f8fafc; } .sc-img { max-width:100%; border-radius:18px; border:1px solid rgba(148,163,184,.25); background:#0f172a; } .sc-meta { margin:12px 0; color:#94a3b8; font-size:12px; line-height:1.7; word-break:break-all; } .sc-foot { display:flex; align-items:center; justify-content:space-between; padding:0 16px; color:#94a3b8; font-size:12px; border-top:1px solid rgba(148,163,184,.2); } .sc-note, .sc-rules { width:100%; min-height:70px; resize:vertical; background:rgba(255,255,255,.07); color:inherit; border:1px solid rgba(148,163,184,.25); border-radius:14px; padding:10px; outline:none; } .sc-rules { min-height:150px; font-family:ui-monospace, Menlo, Consolas, monospace; font-size:12px; } .sc-light .sc-note, .sc-light .sc-rules { background:#f8fafc; } .sc-danger:hover { background:rgba(239,68,68,.25); } .sc-switch { display:flex; justify-content:space-between; align-items:center; gap:10px; padding:8px 0; color:#cbd5e1; font-size:13px; } .sc-number { width:70px; background:rgba(255,255,255,.08); color:inherit; border:1px solid rgba(148,163,184,.25); border-radius:10px; padding:6px; } .sc-light .sc-switch { color:#334155; } .sc-light .sc-number { background:#f8fafc; } @media (max-width: 900px) { .sc-panel { right:50px; width:calc(100vw - 60px); height:calc(100vh - 32px); } .sc-body { grid-template-columns: 120px 1fr; } .sc-preview { display:none; } .sc-panel.sc-detail .sc-side,.sc-panel.sc-detail .sc-list{display:none} .sc-panel.sc-detail .sc-preview{display:block; grid-column:1/3} .sc-head { gap:8px; } .sc-title { font-size:16px; } .sc-sub { display:none; } } `); const root = document.createElement('div'); root.id = 'sc-smart-clipboard-root'; if (state.settings.theme === 'light') root.classList.add('sc-light'); root.style.setProperty('--sc-top', `${Number(state.settings.floatTop || 260)}px`); root.style.setProperty('--sc-panel-top', `${Math.max(16, Number(state.settings.floatTop || 260) - 20)}px`); root.innerHTML = `