您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
导入本地字体更换网页字体,支持悬浮球控制和自定义字体颜色,新增字体清晰度调整和网页编辑模式
// ==UserScript== // @name 网页字体修改器 // @namespace greasyfork.org // @version 4.1 // @description 导入本地字体更换网页字体,支持悬浮球控制和自定义字体颜色,新增字体清晰度调整和网页编辑模式 // @author ^o^ // @run-at document-start // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_addStyle // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADkAAAA5CAYAAACMGIOFAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAANZSURBVGiB7drLixxVFMfxz0yHYdKTTDKmVXwRxYgu4pOAiBsX4k5QBNe6c+3C/8GNCxcJrgOKa1ERNwERxPgIKsYxjviMUScaE42aSRwXp4SmUre6qqc7qSnrC5cp6j5/99x77lTfQ0c7mKlQZjuuxNYpj6WMdfyOVZytW3mUyJvxMO7WDJFv4XX8WKfylhH5d+DJ7G8TuB7LaoqcHZE/yFJTWMpSLcpEzmLXOI1OkR1ZqkXZcp3LGizai6v4EF+X1L0HewvyjuID/JmoO4s7cRd6ubyxLFkmciv6ibyjOIB3E/nb8bRikYfxnJioImbwOK4We3CYHWJ19XAhNfA8ZSL70h71c7yH7xP5i8IbFvEHTih3Hu+IicyLnBMTOJ+1U4myPdlXbMmT2QBqebiarAgvWmSthcS4kpSJXMhSns/wEc7V6agmq/gEPxTk9RPjSjKOyC+yNE0u4GOxYvKkxpVklMhtuXenhCWLZnjSrOAY/sm932aCIov25DExw3/V6WRMTmZ95Sd0PkuVKRO5gjeE5dbwt9iLy3U62ADncSTrcx2n8TZexTd1Gio7Qj7FfuEA7s06fU362JgGyziY9fkzDuF9YeWJModrxOE86h/6/1gUB/56QdqftVWVvjgvB6p9Gl5ElUGfc2kcTYqzxviGHGbUV0gr6ES2hU5kW+hEtoVOZFvoRLaFTmRb6ES2hU5kW+hENohH8SJewS11K1f9Yepy8xQeyp5fFr//VmazWPLU0HPtS9jNIvK3oefFupU3i8j/hSU7kaNomnftievyJXE9Ny+uCXYPlbkdz2CniB8YiEvbIyKO4SKaJvJ8hTJ78Wwi76CCWIWmLdeN3HmcTmU0zZKP4EHsEXE+P4mr+x6ez8ocxz6cEXemu8Sl1C+XerCTvLqDG4bq/1p3ME1brimG4wYuq3ddwHXCE+4RYWNF3IrH8J1Yet+Km+MypzMsckYYJx8wkWQSIneKUNH7hefbLZbXVYny+3CTOPuO40sRa3cIXyXq5AX1Ct5NlfvwkrBG0R4cldaEc3mipI9Brs7Eoj+qcq2w4BVj1t8iIqRvLClTZMlaHWyUEyJSZKBmEFHGmtifKyVlzuAFPIA3szqVGSuaIseSiE+9zcZEHhZOqGMc/gXbNLbbcR2PuQAAAABJRU5ErkJggg== // ==/UserScript== (() => { 'use strict'; const DEFAULT_COLOR='#333333',FAB_SIZE=50,DEFAULT_CLARITY=0; let editMode = false; // 编辑模式状态变量 const main=()=>{ const defaultFont={name:'system-ui(默认)',fontFamily:'system-ui',isDefault:true}; let fontData=GM_getValue('fontData',{fonts:[defaultFont],currentFont:defaultFont.name,fontColor:DEFAULT_COLOR,fabPosition:null,clarityValue:DEFAULT_CLARITY}); if(fontData.clarityValue===undefined){ fontData.clarityValue=DEFAULT_CLARITY; GM_setValue('fontData',fontData); } const cachedFontBlobUrls={}; let fab=null,panel=null,overlay=null,isFabVisible=GM_getValue('fabVisible',true); GM_addStyle(` #via-font-fab{position:fixed;width:${FAB_SIZE}px;height:${FAB_SIZE}px;background:linear-gradient(45deg,#2196F3,#9C27B0);color:white;border-radius:50%;text-align:center;line-height:${FAB_SIZE}px;font-size:24px;font-weight:bold;box-shadow:0 4px 8px rgba(0,0,0,0.3);z-index:999999;touch-action:none;user-select:none;transition:left 0.2s,top 0.2s,transform 0.2s ease,opacity 0.2s ease;opacity:0.7;transform:scale(0.8);cursor:pointer;} #via-font-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.3);z-index:999997;display:none;opacity:0;transition:opacity 0.3s ease;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);} #via-font-panel{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(0.8);background:rgba(255,255,255,0.85);border-radius:16px;box-shadow:0 12px 30px rgba(0,0,0,0.25);padding:20px;max-height:80vh;overflow-y:auto;z-index:999998;width:90%;max-width:420px;opacity:0;transition:all 0.3s cubic-bezier(0.175,0.885,0.32,1.275);display:none;backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border:1px solid rgba(255,255,255,0.3);} .panel-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;padding-bottom:12px;border-bottom:1px solid rgba(0,0,0,0.1);} .panel-title{margin:0;font-weight:600;color:#333;font-size:18px;} .close-btn{background:none;border:none;font-size:24px;cursor:pointer;color:#777;transition:color 0.2s;width:36px;height:36px;display:flex;align-items:center;justify-content:center;border-radius:50%;background:rgba(0,0,0,0.05);} .close-btn:hover{background:rgba(0,0,0,0.08);color:#333;} .setting-group{margin-bottom:15px;padding:12px;background:rgba(255,255,255,0.5);border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,0.05);} .setting-label{display:block;margin-bottom:6px;font-weight:500;color:#444;font-size:14px;} .font-select{width:100%;padding:10px 12px;border-radius:8px;border:1px solid rgba(0,0,0,0.1);font-size:14px;background:rgba(255,255,255,0.8);box-shadow:0 1px 4px rgba(0,0,0,0.05);transition:border-color 0.2s;} .font-select:focus{border-color:#2196F3;outline:none;box-shadow:0 0 0 2px rgba(33,150,243,0.2);} .color-controls{display:flex;align-items:center;gap:8px;} .color-picker{width:40px;height:32px;padding:1px;border-radius:6px;border:1px solid rgba(0,0,0,0.1);background:rgba(255,255,255,0.8);cursor:pointer;} .color-input{flex:1;padding:8px 12px;border-radius:8px;border:1px solid rgba(0,0,0,0.1);font-size:13px;background:rgba(255,255,255,0.8);box-shadow:0 1px 4px rgba(0,0,0,0.05);} .color-reset-btn{padding:6px 12px;background:rgba(240,240,240,0.7);border:1px solid rgba(0,0,0,0.1);border-radius:6px;cursor:pointer;transition:all 0.2s;font-size:13px;} .color-reset-btn:hover{background:rgba(224,224,224,0.7);} .upload-area{margin:20px 0;padding:15px 12px;background:rgba(249,249,249,0.5);border-radius:10px;border:2px dashed rgba(0,0,0,0.1);text-align:center;transition:background 0.2s;} .upload-area:hover{background:rgba(240,240,240,0.6);} .installed-fonts-title{margin-bottom:12px;font-size:16px;color:#444;} .fonts-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px;} .font-item{background:rgba(245,245,245,0.7);padding:12px;border-radius:10px;text-align:center;transition:transform 0.2s,box-shadow 0.2s;box-shadow:0 1px 4px rgba(0,0,0,0.05);} .font-item:hover{transform:translateY(-3px);box-shadow:0 3px 12px rgba(0,0,0,0.1);background:rgba(255,255,255,0.8);} .font-name{font-size:14px;margin-bottom:10px;color:#333;font-weight:500;} .delete-btn{padding:7px 12px;background:rgba(255,77,79,0.9);color:white;border:none;border-radius:6px;cursor:pointer;width:100%;transition:background 0.2s;font-size:13px;} .delete-btn:hover{background:rgba(255,51,54,0.9);} .clarity-controls{display:flex;flex-direction:column;gap:8px;margin-top:12px;padding:12px;background:rgba(255,255,255,0.5);border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,0.05);} .clarity-slider{width:100%;height:6px;-webkit-appearance:none;appearance:none;background:#e0e0e0;border-radius:3px;outline:none;} .clarity-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:18px;height:18px;border-radius:50%;background:#2196F3;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,0.2);border:2px solid white;} .clarity-value-display{text-align:center;font-size:13px;color:#444;font-weight:500;} .clarity-description{font-size:12px;color:#666;margin-top:3px;text-align:center;} .edit-mode-indicator{position:fixed;top:10px;right:10px;background:rgba(255,0,0,0.7);color:white;padding:5px 10px;border-radius:4px;font-size:12px;z-index:1000000;display:none;} @media (max-width:500px){#via-font-panel{width:95%;padding:15px 10px;}.fonts-grid{grid-template-columns:repeat(auto-fill,minmax(130px,1fr));}}`); // 创建编辑模式指示器 const createEditIndicator = () => { const indicator = document.createElement('div'); indicator.className = 'edit-mode-indicator'; indicator.textContent = '编辑模式已启用'; indicator.id = 'edit-mode-indicator'; document.body.appendChild(indicator); return indicator; }; // 切换编辑模式 const toggleEditMode = () => { if (editMode) { exitEditMode(); } else { enterEditMode(); } editMode = !editMode; }; // 进入编辑模式 const enterEditMode = () => { document.body.contentEditable = 'true'; document.getElementById('edit-mode-indicator').style.display = 'block'; GM_registerMenuCommand('🛑 退出编辑模式', toggleEditMode); }; // 退出编辑模式 const exitEditMode = () => { document.body.contentEditable = 'false'; document.getElementById('edit-mode-indicator').style.display = 'none'; GM_registerMenuCommand('✏️ 进入编辑模式', toggleEditMode); }; const createFAB=()=>{ fab=document.createElement('div'); fab.id='via-font-fab'; if(fontData.fabPosition){ fab.style.left=`${fontData.fabPosition.x}px`; fab.style.top=`${fontData.fabPosition.y}px`; }else{ fab.style.right='20px'; fab.style.bottom='30px'; } fab.innerHTML='Aa'; document.body.appendChild(fab); if(!isFabVisible){fab.style.display='none';} return fab;}; const createPanel=()=>{ overlay=document.createElement('div'); overlay.id='via-font-overlay'; document.body.appendChild(overlay); panel=document.createElement('div'); panel.id='via-font-panel'; panel.innerHTML=`<div class="panel-header"><h3 class="panel-title">字体设置</h3><button class="close-btn">×</button></div><div class="panel-content"></div>`; document.body.appendChild(panel); return panel;}; const setupDrag=()=>{ let startX,startY,initialX,initialY,dragging=false,fabTimer=null,edgeTimer=null; const onTouchStart=e=>{ if(e.touches[0]){ const touch=e.touches[0]; startX=touch.clientX; startY=touch.clientY; initialX=fab.offsetLeft; initialY=fab.offsetTop; fab.style.transition='none'; fab.style.opacity='1'; fab.style.transform='scale(1)'; clearTimeout(fabTimer); clearTimeout(edgeTimer); document.addEventListener('touchmove',onTouchMove); document.addEventListener('touchend',onTouchEnd);}}; const onTouchMove=e=>{ if(e.touches[0]){ const touch=e.touches[0]; const diffX=touch.clientX-startX; const diffY=touch.clientY-startY; if(Math.abs(diffX)>5||Math.abs(diffY)>5){dragging=true;} let newX=initialX+diffX; let newY=initialY+diffY; const maxX=window.innerWidth-fab.offsetWidth; const maxY=window.innerHeight-fab.offsetHeight; newX=Math.max(0,Math.min(newX,maxX)); newY=Math.max(0,Math.min(newY,maxY)); fab.style.left=`${newX}px`; fab.style.top=`${newY}px`; fab.style.right='auto'; fab.style.bottom='auto';}}; const onTouchEnd=()=>{ document.removeEventListener('touchmove',onTouchMove); document.removeEventListener('touchend',onTouchEnd); fab.style.transition='left 0.2s, top 0.2s, transform 0.2s ease, opacity 0.2s ease'; if(dragging){ fontData.fabPosition={x:fab.offsetLeft,y:fab.offsetTop}; GM_setValue('fontData',fontData); dragging=false;} clearTimeout(fabTimer); fabTimer=setTimeout(()=>{ fab.style.opacity='0.7'; fab.style.transform='scale(0.8)'; edgeTimer=setTimeout(()=>checkEdge(),100);},800);}; fab.addEventListener('touchstart',onTouchStart);}; const checkEdge=()=>{ if(!fab)return; const fabRect=fab.getBoundingClientRect(); const windowWidth=window.innerWidth; const edgeThreshold=10; if(fabRect.left<edgeThreshold){ fab.style.transform='scale(0.8) translateX(-40%)'; fab.style.opacity='0.5'; }else if(windowWidth-fabRect.right<edgeThreshold){ fab.style.transform='scale(0.8) translateX(40%)'; fab.style.opacity='0.5'; }else{ fab.style.transform='scale(0.8)'; fab.style.opacity='0.7';}}; const createStyleElement=(elementId,highPriority=false)=>{ let styleElement=document.getElementById(elementId); if(!styleElement){ styleElement=document.createElement('style'); styleElement.id=elementId; if(highPriority){document.head.insertBefore(styleElement,document.head.firstChild);} else{document.head.appendChild(styleElement);}} return styleElement;}; const fontFaceStyleElement=createStyleElement('font-face-style',true); const commonStyleElement=createStyleElement('font-common-style',true); const colorStyleElement=createStyleElement('font-color-style'); const clarityStyleElement=createStyleElement('font-clarity-style'); const updateCommonStyles=()=>{ const selectedFont=fontData.fonts.find(font=>font.name===fontData.currentFont); if(!selectedFont)return; const cssRules=`html *:not(i):not(em):not(:empty) { font-family: "${selectedFont.fontFamily}" !important; }`; commonStyleElement.textContent=cssRules;}; const applyColor=()=>{ if(fontData.fontColor===DEFAULT_COLOR){ colorStyleElement.textContent=''; return;} colorStyleElement.textContent=`body, body * { color: ${fontData.fontColor} !important; }`;}; const updateFontFaces=selectedFont=>{ if(!selectedFont||!selectedFont.storageKey){ fontFaceStyleElement.textContent=''; updateCommonStyles(); return;} const fontBlobUrl=cachedFontBlobUrls[selectedFont.storageKey]||''; if(fontBlobUrl){ const fontFaceCss=`@font-face { font-family: "${selectedFont.fontFamily}"; src: url(${fontBlobUrl}) format('${selectedFont.format}'); }`; fontFaceStyleElement.textContent=fontFaceCss; updateCommonStyles(); return;} const fontChunks=GM_getValue(`font_${selectedFont.storageKey}_chunks`,[]); const totalChunks=GM_getValue(`font_${selectedFont.storageKey}_total`,0); if(fontChunks.length===totalChunks){ Promise.all(fontChunks.map(index=>GM_getValue(`font_${selectedFont.storageKey}_chunk_${index}`))) .then(base64Chunks=>{ const base64Data=base64Chunks.join(''); const blob=base64ToBlob(base64Data,selectedFont.mimeType); const fontBlobUrl=URL.createObjectURL(blob); cachedFontBlobUrls[selectedFont.storageKey]=fontBlobUrl; const fontFaceCss=`@font-face { font-family: "${selectedFont.fontFamily}"; src: url(${fontBlobUrl}) format('${selectedFont.format}'); }`; fontFaceStyleElement.textContent=fontFaceCss; updateCommonStyles();});}}; const updateClarityStyle=()=>{ const value=fontData.clarityValue; clarityStyleElement.textContent=`*:not(pre):not(code):not(tt):not(kbd):not(samp) {-webkit-text-stroke:${value}px !important;text-stroke:${value}px !important;}`;}; const togglePanel=()=>{ if(panel.style.display==='none'||panel.style.display===''){ overlay.style.display='block'; panel.style.display='block'; setTimeout(()=>{ overlay.style.opacity='1'; panel.style.opacity='1'; panel.style.transform='translate(-50%,-50%) scale(1)';},10); refreshPanel(); }else{ overlay.style.opacity='0'; panel.style.opacity='0'; panel.style.transform='translate(-50%,-50%) scale(0.8)'; setTimeout(()=>{ overlay.style.display='none'; panel.style.display='none';},300);}}; const refreshPanel=()=>{ const content=panel.querySelector('.panel-content'); if(!content)return; content.innerHTML=` <div class="setting-group"> <label class="setting-label">当前字体</label> <select class="font-select"> ${fontData.fonts.map(font=>`<option value="${font.name}"${fontData.currentFont===font.name?' selected':''}>${font.name}</option>`).join('')} </select></div> <div class="setting-group"> <label class="setting-label">字体颜色</label> <div class="color-controls"> <input type="color" class="color-picker" value="${fontData.fontColor}"> <input type="text" class="color-input" value="${fontData.fontColor}"> <button class="color-reset-btn">重置</button></div></div> <div class="setting-group"> <label class="setting-label">字体清晰度</label> <div class="clarity-controls"> <input type="range" min="0" max="0.8" step="0.05" value="${fontData.clarityValue}" class="clarity-slider"> <div class="clarity-value-display">当前值:${fontData.clarityValue.toFixed(2)}</div> <div class="clarity-description">调整字体边缘清晰度(0为默认,0.8为最清晰)</div></div></div> <div class="upload-area"> <label class="setting-label">上传字体文件(.ttf,.otf,.woff,.woff2)</label> <input type="file" accept=".ttf,.otf,.woff,.woff2" multiple style="width:100%;padding:8px"></div> <div class="setting-group"> <h4 class="installed-fonts-title">已安装字体(${fontData.fonts.length})</h4> <div class="fonts-grid"> ${fontData.fonts.filter(f=>!f.isDefault).map(font=>`<div class="font-item"><div class="font-name">${font.name}</div><button data-font="${font.name}" class="delete-btn">删除</button></div>`).join('')}</div></div>`; setupPanelEvents();}; const setupPanelEvents=()=>{ panel.querySelector('.font-select').addEventListener('change',e=>{ fontData.currentFont=e.target.value; const selectedFont=fontData.fonts.find(f=>f.name===fontData.currentFont); if(selectedFont){ updateFontFaces(selectedFont); GM_setValue('fontData',fontData);}}); const colorPicker=panel.querySelector('.color-picker'); const colorInput=panel.querySelector('.color-input'); const colorResetBtn=panel.querySelector('.color-reset-btn'); colorPicker.addEventListener('input',e=>{ fontData.fontColor=e.target.value; colorInput.value=e.target.value; applyColor(); GM_setValue('fontData',fontData);}); colorInput.addEventListener('input',e=>{ const value=e.target.value; if(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value)){ fontData.fontColor=value; colorPicker.value=value; applyColor(); GM_setValue('fontData',fontData);}}); colorResetBtn.addEventListener('click',()=>{ fontData.fontColor=DEFAULT_COLOR; colorPicker.value=DEFAULT_COLOR; colorInput.value=DEFAULT_COLOR; applyColor(); GM_setValue('fontData',fontData);}); panel.querySelector('input[type="file"]').addEventListener('change',e=>{ handleFontUpload(Array.from(e.target.files));}); panel.querySelectorAll('.delete-btn').forEach(btn=>{ btn.addEventListener('click',()=>{ const fontName=btn.dataset.font; handleDeleteFont(fontName);});}); panel.querySelector('.close-btn').addEventListener('click',togglePanel); overlay.addEventListener('click',e=>{if(e.target===overlay)togglePanel();}); const claritySlider=panel.querySelector('.clarity-slider'); if(claritySlider){ claritySlider.addEventListener('input',e=>{ const value=parseFloat(e.target.value); fontData.clarityValue=value; panel.querySelector('.clarity-value-display').textContent=`当前值:${value.toFixed(2)}`; updateClarityStyle(); GM_setValue('fontData',fontData);});}}; const handleFontUpload=async files=>{ for(const file of files){await processFontFile(file);} refreshPanel();}; const processFontFile=file=>{ return new Promise(resolve=>{ const originalName=file.name.replace(/\.[^/.]+$/,""); const extension=file.name.slice(file.name.lastIndexOf('.')); let newName=originalName; let index=2; while(fontData.fonts.some(f=>f.name===newName)){ newName=`${originalName}(${index})`; index++;} const reader=new FileReader(); reader.onload=()=>{ const result=reader.result; const base64Data=result.split(',')[1]; const mimeType=result.split(',')[0].split(':')[1]; const storageKey='font_'+Date.now(); const format=getFontFormat(file.name); const chunkSize=500000; const chunks=[]; for(let i=0;i<base64Data.length;i+=chunkSize){ const chunk=base64Data.substring(i,i+chunkSize); GM_setValue(`font_${storageKey}_chunk_${chunks.length}`,chunk); chunks.push(chunks.length);} GM_setValue(`font_${storageKey}_chunks`,chunks); GM_setValue(`font_${storageKey}_total`,chunks.length); fontData.fonts.push({ name:newName, fontFamily:newName, originalFileName:file.name, mimeType:mimeType, storageKey:storageKey, format:format, fileSize:file.size}); fontData.currentFont=newName; GM_setValue('fontData',fontData); const selectedFont=fontData.fonts.find(f=>f.name===newName); if(selectedFont){updateFontFaces(selectedFont);} resolve();}; reader.readAsDataURL(file);});}; const handleDeleteFont=fontName=>{ if(!confirm(`确定要删除字体"${fontName}"吗?`))return; const fontIndex=fontData.fonts.findIndex(f=>f.name===fontName); if(fontIndex===-1)return; const font=fontData.fonts[fontIndex]; if(font.storageKey){ const fontChunks=GM_getValue(`font_${font.storageKey}_chunks`,[]); fontChunks.forEach((_,i)=>GM_deleteValue(`font_${font.storageKey}_chunk_${i}`)); GM_deleteValue(`font_${font.storageKey}_chunks`); GM_deleteValue(`font_${font.storageKey}_total`); if(cachedFontBlobUrls[font.storageKey]){ URL.revokeObjectURL(cachedFontBlobUrls[font.storageKey]); delete cachedFontBlobUrls[font.storageKey];}} fontData.fonts.splice(fontIndex,1); if(fontData.currentFont===fontName){ fontData.currentFont=fontData.fonts[0].name;} GM_setValue('fontData',fontData); const selectedFont=fontData.fonts.find(f=>f.name===fontData.currentFont); if(selectedFont){updateFontFaces(selectedFont);} refreshPanel();}; const base64ToBlob=(base64String,mimeType)=>{ const byteCharacters=atob(base64String); const byteArrays=[]; for(let i=0;i<byteCharacters.length;i+=512){ const slice=byteCharacters.slice(i,i+512); const byteNumbers=new Array(slice.length); for(let j=0;j<slice.length;j++){byteNumbers[j]=slice.charCodeAt(j);} byteArrays.push(new Uint8Array(byteNumbers));} return new Blob(byteArrays,{type:mimeType});}; const getFontFormat=fileName=>{ const ext=fileName.split('.').pop().toLowerCase(); return{'ttf':'truetype','otf':'opentype','woff':'woff','woff2':'woff2'}[ext]||'truetype';}; fab=createFAB(); createPanel(); setupDrag(); createEditIndicator(); // 创建编辑模式指示器 window.addEventListener('resize',checkEdge); checkEdge(); fab.addEventListener('click',togglePanel); const selectedFont=fontData.fonts.find(font=>font.name===fontData.currentFont); if(selectedFont){updateFontFaces(selectedFont);} applyColor(); updateClarityStyle(); GM_registerMenuCommand('🎨 打开字体设置',togglePanel); GM_registerMenuCommand('🔄 切换悬浮球显示',()=>{ isFabVisible=!isFabVisible; fab.style.display=isFabVisible?'block':'none'; GM_setValue('fabVisible',isFabVisible);}); // 初始化编辑模式菜单 GM_registerMenuCommand('✏️ 进入编辑模式', toggleEditMode);}; if(document.readyState==='loading'){ document.addEventListener('DOMContentLoaded',main); }else{main();} })();