// ==UserScript== // @name Steam 好友动态 - 现代化增强版 // @namespace http://tampermonkey.net/ // @version 4.5.1 // @description 保留原始页面性能,添加精致分类筛选、懒加载、美化好友浮窗(显示全部好友、等级/国旗/封禁/游戏数)、个人面板Hero组件完善、独立设置窗口、个人游戏库浮窗(游戏库标签页)、好友动态弹窗(游戏库/时长/最近)、图片浮层优化、全屏瀑布流模式、精美浮动按钮;v4.5.0 修复绝版数据展示(成就-1/0误显、卡片无封面、列表封面display:none错位),列表视图每页增至30,引用lib core多CDN封面兜底+filters=basic+占位div+GetItems批量加载,精简浮动按钮移除独立设置入口,打开游戏库/好友动态未设API时先弹设置浮窗。v4.5.1 绝版数据源切换至xi-xu.me,新增年度大作标签页(2020-2026大作按年度展示+已拥有筛选+封面加载)。 // @author SmallFork & SmallRob // @match https://steamcommunity.com/id/*/home/ // @match https://steamcommunity.com/id/*/home // @match https://steamcommunity.com/profiles/*/home/ // @match https://steamcommunity.com/profiles/*/home // @match https://steamcommunity.com/sharedfiles/filedetails* // @match https://steamcommunity.com/sharedfiles/filedetails/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_getResourceText // @resource delistedData https://xget.xi-xu.me/gh/SmallRob/steam-namespace/raw/refs/heads/main/data/steam_delisted_apps.json // @resource annualAaaData https://xget.xi-xu.me/gh/SmallRob/steam-namespace/raw/refs/heads/main/data/2026.json // @connect store.steampowered.com // @connect aiseartifacts.blob.core.windows.net // @connect api.steampowered.com // @connect xget.xi-xu.me // @run-at document-start // @license MIT // ==/UserScript== (function() { 'use strict'; // ==================== CSS变量 ==================== const CSS_VARS = ':root{--sf-bg:#1b2838;--sf-bg2:#171a21;--sf-card:rgba(23,26,33,.8);--sf-border:rgba(255,255,255,.06);--sf-hover:rgba(255,255,255,.08);--sf-text:#c7d5e0;--sf-dim:#8f98a0;--sf-accent:#66c0f4;--sf-green:#4caf50;--sf-orange:#ff9800;--sf-radius:12px;--sf-shadow:0 2px 12px rgba(0,0,0,.2);--sf-transition:all .3s ease;--sf-purple:#8b5cf6;--sf-rose:#f43f5e;--sf-amber:#f59e0b;--sf-blue:#3b82f6;--sf-blue-strong:#2563eb;--sf-fp-bg-card:rgba(30,41,59,0.95);--sf-fp-border:rgba(102,192,244,0.2);--sf-fp-border-focus:rgba(102,192,244,0.5);--sf-fp-text-primary:#f8fafc;--sf-fp-text-secondary:#94a3b8;--sf-fp-text-dim:#64748b;--sf-fp-blue:#66c0f4;--sf-fp-blue-strong:#3b82f6;--sf-fp-green:#10b981;--sf-fp-green-text:#34d399;--sf-fp-purple:#8b5cf6;--sf-fp-purple-text:#c084fc;--sf-fp-amber:#f59e0b;--sf-fp-amber-text:#fbbf24;--sf-fp-rose:#f43f5e;--sf-fp-rose-text:#f87171;--sf-fp-radius:14px;--sf-fp-shadow:0 10px 40px rgba(0,0,0,0.5);--sf-fp-transition:all 0.25s cubic-bezier(0.4,0,0.2,1);--sf-popup-w:420px;--sf-lib-w:780px;--sf-lib-h:80vh}'; // ==================== 主布局 ==================== const CSS_LAYOUT = '.pagecontent,.bluebg{background:linear-gradient(180deg,var(--sf-bg) 0%,var(--sf-bg2) 100%)!important}#blotter_page{display:block!important;padding:0!important;max-width:940px!important;margin:0 auto!important}.blotter_content,#blotter_content{float:none!important;width:auto!important;min-width:0!important;max-width:none!important;padding:0 16px!important;box-sizing:border-box!important}#friendactivity_right_column{display:none!important}.blotter_block,.blotter_status_entry,.blotter_eventsblock,.friend_activity_header{margin-left:0!important;margin-right:0!important}.blotter_daily_rollup_line>span{max-width:none!important}'; // ==================== 卡片样式 ==================== const CSS_CARDS = '.blotter_page_title h1{font-size:28px!important;font-weight:700!important;color:#fff!important;margin-bottom:20px!important}.friend_activity_header{padding:20px!important;background:rgba(23,26,33,.6)!important;border-radius:var(--sf-radius)!important;margin-bottom:16px!important}.blotter_block{background:var(--sf-card)!important;border-radius:var(--sf-radius)!important;border:1px solid var(--sf-border)!important;margin-bottom:16px!important;overflow:hidden!important;box-shadow:var(--sf-shadow)!important;transition:var(--sf-transition)!important}.blotter_block:hover{transform:translateY(-4px)!important;box-shadow:0 8px 24px rgba(0,0,0,.4)!important;border-color:rgba(102,192,244,.2)!important}.blotter_block.sf-hidden,.blotter_block.sf-lazy-hidden{display:none!important}.blotter_author_block{padding:14px 16px!important;background:rgba(0,0,0,.2)!important}.blotter_avatar_holder .playerAvatar{border-radius:8px!important;box-shadow:0 2px 8px rgba(0,0,0,.3)!important}.blotter_avatar_holder .playerAvatar img{border-radius:8px!important}.blotter_author_block a[data-miniprofile]{font-size:14px!important;font-weight:600!important;color:var(--sf-accent)!important;cursor:pointer!important}.blotter_author_block a[data-miniprofile]:hover{color:#fff!important}.blotter_author_block a[data-miniprofile]:focus-visible{outline:2px solid var(--sf-accent)!important;outline-offset:2px!important;border-radius:3px!important}'; // ==================== 截图样式 ==================== const CSS_SCREENSHOTS = '.blotter_screenshot{border-radius:0!important;border:none!important;overflow:visible!important}.blotter_screenshot_image{border-radius:0!important;overflow:hidden!important}.blotter_screenshot_image img{transition:transform .3s ease!important}.blotter_block:hover .blotter_screenshot_image img{transform:scale(1.03)!important}.blotter_screenshot_title{padding:12px 16px!important;font-size:13px!important;color:var(--sf-text)!important;line-height:1.5!important;background:rgba(0,0,0,.1)!important}.blotter_screenshot_gallery_image{border-radius:8px!important;margin:0!important;padding:0!important;display:inline-block!important;flex-shrink:0!important;height:72px!important;width:auto!important;overflow:hidden!important;cursor:pointer!important}.blotter_screenshot_gallery_image img{height:72px!important;width:auto!important;max-width:none!important;object-fit:cover!important;display:block!important;border-radius:6px!important}.highlight_strip{position:relative!important;width:100%!important;min-height:84px!important;height:auto!important;padding:6px 0!important;background:rgba(0,0,0,.35)!important;display:block!important;clear:both!important;overflow:visible!important;border-top:1px solid rgba(255,255,255,.05)!important}.highlight_strip_scroll{position:relative!important;width:100%!important;height:auto!important;min-height:72px!important;display:flex!important;flex-wrap:nowrap!important;overflow-x:auto!important;overflow-y:hidden!important;gap:4px!important;padding:0 8px!important;align-items:center!important}.highlight_strip_scroll::-webkit-scrollbar{height:6px}.highlight_strip_scroll::-webkit-scrollbar-thumb{background:rgba(255,255,255,.15);border-radius:3px}.highlight_strip_scroll::-webkit-scrollbar-track{background:transparent}.blotter_screenshot_fullscreen{background:rgba(0,0,0,.95)!important}.blotter_screenshot_fullscreen img{border-radius:8px!important;max-width:95vw!important;max-height:90vh!important}'; // ==================== 游戏样式 ==================== const CSS_GAMES = '.blotter_userstatus_game{height:auto!important}.blotter_userstatus_game img{height:auto!important}.blotter_gamepurchase{border:none!important}.blotter_gamepurchase_content{padding:16px!important;display:flex!important;flex-wrap:wrap!important;gap:8px!important}.blotter_gamepurchase_logo{border-radius:8px!important;overflow:hidden!important;transition:transform .2s ease!important;box-shadow:0 2px 8px rgba(0,0,0,.3)!important}.blotter_gamepurchase_logo:hover{transform:scale(1.05)!important}.blotter_gamepurchase_logo img{border-radius:8px!important}.blotter_gamepurchase_logo.single_item{width:200px!important}.plus_block{background:rgba(255,255,255,.05)!important;border-radius:8px!important;color:var(--sf-dim)!important;font-weight:600!important}.blotter_gamepurchase_details{padding:12px 16px!important;font-size:12px!important;color:var(--sf-dim)!important}.blotter_recommendation{padding:16px!important}.blotter_recommendation_game{display:flex!important;align-items:center!important;gap:12px!important;margin-bottom:12px!important}.blotter_recommendation_game img{border-radius:8px!important}.blotter_recommendation_game_name{font-size:15px!important;font-weight:600!important;color:#fff!important}.blotter_recommendation_rating{display:flex!important;align-items:center!important;gap:8px!important;margin-bottom:10px!important}.blotter_recommendation_rating img{width:24px!important;height:24px!important}.blotter_recommendation_desc{font-size:13px!important;color:var(--sf-text)!important;line-height:1.6!important;padding:12px!important;background:rgba(0,0,0,.15)!important;border-radius:8px!important}'; // ==================== 工坊 ==================== const CSS_WORKSHOP = '.blotter_workshopitempublished{border:none!important}.blotter_workshopitem{padding:16px!important}.dynamiclink_box{border-radius:8px!important;overflow:hidden!important;background:rgba(0,0,0,.3)!important;border:1px solid var(--sf-border)!important;transition:all .2s ease!important}.dynamiclink_box:hover{border-color:rgba(102,192,244,.3)!important}.dynamiclink_preview{border-radius:8px 8px 0 0!important}.dynamiclink_content{padding:12px!important}.dynamiclink_name{font-size:14px!important;font-weight:600!important;color:#fff!important}.dynamiclink_type{font-size:12px!important;color:var(--sf-dim)!important}.dynamiclink_author{font-size:12px!important;color:var(--sf-text)!important}.dynamiclink_authorname{color:var(--sf-accent)!important}.dynamiclink_description{font-size:12px!important;color:var(--sf-dim)!important;margin-top:8px!important}'; // ==================== 控件 ==================== const CSS_CONTROLS = '.blotter_control_container{padding:12px 16px!important;background:rgba(0,0,0,.15)!important;border-top:1px solid rgba(255,255,255,.05)!important}.btn_grey_grey,.btn_grey_white_innerfade{background:rgba(255,255,255,.08)!important;border:1px solid rgba(255,255,255,.1)!important;border-radius:6px!important;color:var(--sf-text)!important;transition:all .2s ease!important}.btn_grey_grey:hover,.btn_grey_white_innerfade:hover{background:rgba(255,255,255,.15)!important;color:#fff!important;border-color:rgba(255,255,255,.2)!important}.ico16.thumb_up{filter:brightness(1.2)!important}.blotter_voters_names{font-size:12px!important;color:var(--sf-dim)!important}.blotter_voters_names a{color:var(--sf-accent)!important}.blotter_comment_thread{background:rgba(0,0,0,.1)!important;border-radius:8px!important;margin:12px 16px!important}.comment_footer_ctn{display:none!important}.commentthread_area{background:transparent!important;border:none!important}.commentthread_textarea{background:rgba(0,0,0,.4)!important;border:1px solid rgba(255,255,255,.1)!important;border-radius:8px!important;color:#fff!important;font-size:13px!important;padding:10px 12px!important;height:60px!important;min-height:60px!important;max-height:150px!important;resize:vertical!important;box-sizing:border-box!important}.commentthread_textarea:focus{border-color:rgba(102,192,244,.5)!important;box-shadow:inset 0 0 0 1px rgba(102,192,244,.3)!important}.btn_green_white_innerfade{background:linear-gradient(135deg,#4caf50 0%,#3d8b40 100%)!important;border-radius:6px!important;font-weight:500!important}'; // ==================== 状态日期 ==================== const CSS_DATE_STATUS = '.blotter_day{margin-top:20px!important}.blotter_day_header{display:flex!important;align-items:center!important;padding:10px 16px!important;background:rgba(0,0,0,.3)!important;border-radius:8px!important;margin-bottom:12px!important}.blotter_day_header h2{font-size:14px!important;font-weight:600!important;color:var(--sf-dim)!important}.blotter_status_entry{background:var(--sf-card)!important;border-radius:var(--sf-radius)!important;border:1px solid var(--sf-border)!important;padding:12px!important;margin-bottom:16px!important;box-sizing:border-box!important}#blotter_statuspost_textarea{background:rgba(0,0,0,.4)!important;border:1px solid rgba(255,255,255,.1)!important;border-radius:8px!important;color:#fff!important;font-size:14px!important;padding:12px!important;height:80px!important;min-height:80px!important;max-height:200px!important;resize:vertical!important;box-sizing:border-box!important}#blotter_statuspost_textarea:focus{border-color:rgba(102,192,244,.5)!important;box-shadow:inset 0 0 0 1px rgba(102,192,244,.3)!important}.blotter_appselect{background:rgba(255,255,255,.05)!important;border-radius:6px!important;border:1px solid rgba(255,255,255,.1)!important}.blotter_status_submit_ctn{display:flex!important;align-items:center!important;gap:10px!important;justify-content:flex-end!important}.blotter_eventsblock{background:rgba(23,26,33,.6)!important;border-radius:var(--sf-radius)!important;border:1px solid var(--sf-border)!important;padding:16px!important;margin-bottom:16px!important}.blotter_eventsblock_title{font-size:14px!important;font-weight:600!important;color:#fff!important;margin-bottom:12px!important}'; // ==================== 模态 ==================== const CSS_MODAL = '#modalContent{background:var(--sf-card)!important;border-radius:var(--sf-radius)!important;border:1px solid var(--sf-border)!important;box-shadow:0 8px 40px rgba(0,0,0,.5)!important;overflow:hidden!important}#modalContentTitleBar{background:linear-gradient(135deg,var(--sf-bg) 0%,#2a475e 100%)!important;border-bottom:1px solid var(--sf-border)!important;padding:10px 16px!important;display:flex!important;align-items:center!important;gap:10px!important}#modalContentTitleBarLink{color:var(--sf-accent)!important;font-size:13px!important;font-weight:500!important;text-decoration:none!important;flex:1!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}#modalContentTitleBarLink:hover{color:#fff!important}#modalContentTitleBarImageLink img{opacity:.5!important;transition:opacity .2s!important}#modalContentTitleBarImageLink:hover img{opacity:1!important}#modalContentDismiss{width:28px!important;height:28px!important;border-radius:50%!important;background:rgba(255,255,255,.1)!important;border:none!important;cursor:pointer!important;display:flex!important;align-items:center!important;justify-content:center!important;transition:all .2s!important;padding:0!important}#modalContentDismiss:hover{background:rgba(255,255,255,.2)!important}#modalContentDismiss img{display:none!important}#modalContentDismiss::after{content:\'✕\';color:var(--sf-text)!important;font-size:14px!important}#modalContentFrameContainer{background:rgba(0,0,0,.2)!important}.modalContent_iFrame{border:none!important;border-radius:0!important}'; // ==================== 筛选栏 ==================== const CSS_FILTER = '#sf-filter-bar{position:sticky!important;top:0!important;z-index:1000!important;background:rgba(23,26,33,.95)!important;backdrop-filter:blur(10px)!important;-webkit-backdrop-filter:blur(10px)!important;padding:10px 16px!important;border-radius:var(--sf-radius)!important;margin-bottom:16px!important;box-shadow:0 4px 20px rgba(0,0,0,.3)!important}.sf-filter-row{display:flex!important;align-items:center!important;gap:6px!important;overflow:hidden!important;transition:max-height .35s cubic-bezier(.4,0,.2,1)!important}.sf-filter-row.sf-compact{max-height:42px!important;flex-wrap:nowrap!important}.sf-filter-row.sf-expanded{max-height:200px!important;flex-wrap:wrap!important}.sf-filter-btn{display:inline-flex!important;align-items:center!important;gap:5px!important;padding:6px 12px!important;border-radius:8px!important;background:rgba(255,255,255,.05)!important;border:1px solid transparent!important;color:var(--sf-text)!important;font-size:13px!important;font-weight:500!important;cursor:pointer!important;transition:all .2s!important;white-space:nowrap!important;flex-shrink:0!important}.sf-filter-btn:hover{background:var(--sf-hover)!important;color:#fff!important}.sf-filter-btn.active{background:linear-gradient(135deg,var(--sf-accent) 0%,#4a9bc7 100%)!important;color:#fff!important;border-color:rgba(102,192,244,.3)!important;box-shadow:0 2px 10px rgba(102,192,244,.3)!important}.sf-filter-icon{width:15px!important;height:15px!important;fill:currentColor!important;flex-shrink:0!important}.sf-filter-count{margin-left:3px!important;padding:1px 5px!important;background:rgba(255,255,255,.15)!important;border-radius:10px!important;font-size:11px!important;min-width:18px!important;text-align:center!important}.sf-filter-btn.active .sf-filter-count{background:rgba(255,255,255,.25)!important}.sf-filter-more{display:inline-flex!important;align-items:center!important;gap:4px!important;padding:6px 12px!important;border-radius:8px!important;background:rgba(255,255,255,.04)!important;border:1px solid var(--sf-border)!important;color:var(--sf-text)!important;font-size:13px!important;font-weight:500!important;cursor:pointer!important;transition:all .2s!important;white-space:nowrap!important;flex-shrink:0!important}.sf-filter-more:hover{background:var(--sf-hover)!important;color:#fff!important}.sf-filter-more svg{width:14px!important;height:14px!important;fill:currentColor!important;transition:transform .25s ease!important}.sf-filter-more.sf-open svg{transform:rotate(180deg)!important}'; // ==================== 加载/空状态 ==================== const CSS_LOAD_EMPTY = '#sf-load-more{display:flex!important;justify-content:center!important;padding:20px!important}.sf-load-btn{padding:12px 32px!important;border-radius:8px!important;background:linear-gradient(135deg,var(--sf-accent) 0%,#4a9bc7 100%)!important;color:#fff!important;font-size:14px!important;font-weight:600!important;cursor:pointer!important;transition:all .2s!important;border:none!important}.sf-load-btn:hover{transform:scale(1.05)!important;box-shadow:0 4px 15px rgba(102,192,244,.4)!important}.sf-load-btn:disabled{opacity:.6!important;cursor:not-allowed!important;transform:none!important}.sf-empty-state{text-align:center!important;padding:60px 20px!important;color:var(--sf-dim)!important}.sf-empty-icon{font-size:48px!important;margin-bottom:16px!important}.sf-empty-text{font-size:16px!important}'; // ==================== FAB 按钮(精美 SVG 图标 + 玻璃拟态)==================== const CSS_FAB_BASE = '#sf-waterfall-btn,#sf-friend-btn,#sf-library-btn,#sf-top-btn,#sf-layout-btn{position:fixed!important;right:30px!important;width:50px!important;height:50px!important;border-radius:50%!important;border:none!important;cursor:pointer!important;z-index:9999!important;background:rgba(30,41,59,0.85)!important;backdrop-filter:blur(12px)!important;-webkit-backdrop-filter:blur(12px)!important;box-shadow:0 4px 20px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.08)!important;transition:all .25s cubic-bezier(.34,1.56,.64,1)!important;display:flex!important;align-items:center!important;justify-content:center!important;color:#f8fafc!important}#sf-waterfall-btn svg,#sf-friend-btn svg,#sf-library-btn svg,#sf-top-btn svg,#sf-layout-btn svg{width:24px!important;height:24px!important;fill:none!important;stroke:currentColor!important;stroke-width:2!important;stroke-linecap:round!important;stroke-linejoin:round!important;transition:all .25s ease!important}'; const CSS_FAB_EACH = '#sf-waterfall-btn{bottom:30px!important;background:linear-gradient(135deg,rgba(139,92,246,0.95) 0%,rgba(124,58,237,0.95) 100%)!important}#sf-friend-btn{bottom:90px!important;background:linear-gradient(135deg,rgba(59,130,246,0.95) 0%,rgba(37,99,235,0.95) 100%)!important}#sf-library-btn{bottom:150px!important;background:linear-gradient(135deg,rgba(139,92,246,0.95) 0%,rgba(109,40,217,0.95) 100%)!important}#sf-layout-btn{bottom:210px!important;background:linear-gradient(135deg,rgba(38,166,154,0.95) 0%,rgba(0,121,107,0.95) 100%)!important}#sf-top-btn{bottom:270px!important;background:linear-gradient(135deg,rgba(84,110,122,0.95) 0%,rgba(55,71,79,0.95) 100%)!important;opacity:0!important;visibility:hidden!important;transform:scale(.8)!important}'; const CSS_FAB_HOVER = '#sf-waterfall-btn:hover,#sf-friend-btn:hover,#sf-library-btn:hover,#sf-top-btn:hover,#sf-layout-btn:hover{transform:scale(1.12)!important;box-shadow:0 8px 28px rgba(0,0,0,.45),0 0 20px rgba(102,192,244,.25)!important;filter:brightness(1.1)!important}#sf-waterfall-btn:hover{box-shadow:0 8px 28px rgba(0,0,0,.45),0 0 22px rgba(139,92,246,.45)!important}#sf-friend-btn:hover{box-shadow:0 8px 28px rgba(0,0,0,.45),0 0 22px rgba(59,130,246,.45)!important}#sf-library-btn:hover{box-shadow:0 8px 28px rgba(0,0,0,.45),0 0 22px rgba(139,92,246,.45)!important}#sf-layout-btn:hover{box-shadow:0 8px 28px rgba(0,0,0,.45),0 0 22px rgba(38,166,154,.45)!important}#sf-top-btn:hover{box-shadow:0 6px 20px rgba(84,110,122,.6)!important;background:linear-gradient(135deg,rgba(96,125,139,0.95) 0%,rgba(69,90,100,0.95) 100%)!important}#sf-top-btn.sf-show{opacity:1!important;visibility:visible!important;transform:scale(1)!important}'; const CSS_FAB_LABEL = '.sf-fab-label{position:absolute;right:60px;background:rgba(23,26,33,0.9);border:1px solid rgba(255,255,255,0.1);padding:6px 12px;border-radius:8px;font-size:12px;font-weight:600;color:#fff;white-space:nowrap;opacity:0;visibility:hidden;transform:translateX(-8px);transition:all .2s ease;pointer-events:none;box-shadow:0 4px 12px rgba(0,0,0,.3)}#sf-waterfall-btn:hover .sf-fab-label,#sf-friend-btn:hover .sf-fab-label,#sf-library-btn:hover .sf-fab-label,#sf-top-btn:hover .sf-fab-label,#sf-layout-btn:hover .sf-fab-label{opacity:1;visibility:visible;transform:translateX(0)}'; // ==================== 瀑布流 ==================== const CSS_WATERFALL = '#sf-wf-overlay{position:fixed!important;inset:0!important;z-index:99990!important;background:linear-gradient(180deg,#1b2838 0%,#171a21 100%)!important;opacity:0!important;visibility:hidden!important;transition:opacity .35s ease,visibility .35s ease!important;overflow-y:auto!important;overflow-x:hidden!important}#sf-wf-overlay.sf-wf-open{opacity:1!important;visibility:visible!important}.sf-wf-header{position:sticky!important;top:0!important;z-index:99992!important;background:rgba(23,26,33,.97)!important;backdrop-filter:blur(12px)!important;-webkit-backdrop-filter:blur(12px)!important;padding:14px 24px!important;display:flex!important;align-items:center!important;justify-content:space-between!important;border-bottom:1px solid rgba(255,255,255,.08)!important;box-shadow:0 4px 20px rgba(0,0,0,.3)!important}.sf-wf-title{font-size:18px!important;font-weight:700!important;color:#fff!important;display:flex!important;align-items:center!important;gap:10px!important}.sf-wf-title svg{width:22px!important;height:22px!important;fill:#ab47bc!important}.sf-wf-count{font-size:13px!important;color:var(--sf-dim)!important;font-weight:400!important;margin-left:8px!important}.sf-wf-close{width:36px!important;height:36px!important;border-radius:50%!important;background:rgba(255,255,255,.08)!important;border:1px solid rgba(255,255,255,.1)!important;cursor:pointer!important;display:flex!important;align-items:center!important;justify-content:center!important;transition:all .2s ease!important;color:var(--sf-text)!important;font-size:18px!important}.sf-wf-close:hover{background:rgba(255,82,82,.3)!important;border-color:rgba(255,82,82,.4)!important;color:#ff5252!important}.sf-wf-close svg{width:18px!important;height:18px!important;fill:currentColor!important}.sf-wf-body{padding:20px 24px 40px!important;max-width:1600px!important;margin:0 auto!important}.sf-wf-grid{display:flex!important;gap:16px!important;align-items:flex-start!important;justify-content:center!important}.sf-wf-col{flex:1!important;min-width:0!important;max-width:380px!important;display:flex!important;flex-direction:column!important;gap:14px!important}'; const CSS_WF_OVERRIDES = '#sf-wf-overlay .blotter_block{background:var(--sf-card)!important;border-radius:var(--sf-radius)!important;border:1px solid var(--sf-border)!important;overflow:visible!important;box-shadow:var(--sf-shadow)!important;transition:var(--sf-transition)!important;margin-bottom:0!important;transform:none!important}#sf-wf-overlay .blotter_block:hover{transform:translateY(-4px)!important;box-shadow:0 8px 24px rgba(0,0,0,.4)!important;border-color:rgba(171,71,188,.25)!important}#sf-wf-overlay .blotter_block.sf-hidden{display:none!important}#sf-wf-overlay .blotter_control_container{padding:10px 14px!important}#sf-wf-overlay .blotter_author_block{padding:12px 14px!important}#sf-wf-overlay .blotter_screenshot_title{padding:10px 14px!important}#sf-wf-overlay .blotter_gamepurchase{overflow:hidden!important}#sf-wf-overlay .blotter_gamepurchase_content{padding:10px!important;display:flex!important;flex-wrap:wrap!important;gap:8px!important;justify-content:flex-start!important}#sf-wf-overlay .blotter_gamepurchase_logo{flex:1 1 calc(50% - 4px)!important;min-width:0!important;max-width:none!important;border-radius:6px!important;overflow:hidden!important}#sf-wf-overlay .blotter_gamepurchase_logo:only-of-type{flex:1 1 100%!important}#sf-wf-overlay .blotter_gamepurchase_logo img{width:100%!important;height:auto!important;display:block!important;border-radius:6px!important}#sf-wf-overlay .blotter_gamepurchase_logo img[alt*="No Image" i],#sf-wf-overlay .blotter_gamepurchase_logo img[src*="data:image/svg+xml"]{display:none!important}#sf-wf-overlay .plus_block{display:none!important}#sf-wf-overlay .blotter_gamepurchase_details{padding:8px 10px!important;font-size:11px!important;color:var(--sf-dim)!important;line-height:1.4!important;word-break:break-word!important}#sf-wf-overlay .blotter_recommendation{padding:14px!important;overflow:hidden!important;display:flex!important;flex-direction:column!important;gap:10px!important}#sf-wf-overlay .blotter_recommendation_game{flex-shrink:0!important}'; const CSS_WF_LOADING = '.sf-wf-loading{text-align:center!important;padding:40px 20px!important;color:var(--sf-dim)!important}.sf-wf-loading-spinner{width:32px!important;height:32px!important;border:3px solid rgba(171,71,188,.3)!important;border-top-color:#ab47bc!important;border-radius:50%!important;animation:sf-spin .8s linear infinite!important;margin:0 auto 12px!important}@keyframes sf-spin{to{transform:rotate(360deg)}}#sf-wf-loadmore{display:flex!important;justify-content:center!important;padding:24px!important}.sf-wf-load-btn{padding:12px 32px!important;border-radius:8px!important;background:linear-gradient(135deg,#ab47bc 0%,#7b1fa2 100%)!important;color:#fff!important;font-size:14px!important;font-weight:600!important;cursor:pointer!important;transition:all .2s!important;border:none!important}.sf-wf-load-btn:hover{transform:scale(1.05)!important;box-shadow:0 4px 15px rgba(171,71,188,.4)!important}.sf-wf-load-btn:disabled{opacity:.6!important;cursor:not-allowed!important;transform:none!important}.sf-wf-empty{text-align:center!important;padding:80px 20px!important;color:var(--sf-dim)!important}.sf-wf-empty-icon{font-size:56px!important;margin-bottom:16px!important}.sf-wf-empty-text{font-size:18px!important}'; // ==================== 好友面板 v4.0(参考 1.0.16 好友管理器)==================== const CSS_FRIEND_PANEL = '#sf-friend-panel{position:fixed!important;bottom:150px!important;right:30px!important;width:380px!important;max-height:78vh!important;background:rgba(23,26,33,0.97)!important;border:1px solid var(--sf-fp-border)!important;border-radius:var(--sf-fp-radius)!important;box-shadow:var(--sf-fp-shadow)!important;z-index:9998!important;overflow:hidden!important;opacity:0!important;visibility:hidden!important;transform:translateY(20px) scale(0.96)!important;transition:var(--sf-fp-transition)!important;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:var(--sf-fp-text-primary);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);display:flex!important;flex-direction:column}#sf-friend-panel.open{opacity:1!important;visibility:visible!important;transform:translateY(0) scale(1)!important}.sf-fp-header{background:linear-gradient(135deg,rgba(59,130,246,0.18) 0%,rgba(15,23,42,0.7) 100%);padding:12px 16px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--sf-fp-border);flex-shrink:0;position:relative;overflow:hidden}.sf-fp-header::after{content:"";position:absolute;top:-30px;right:-30px;width:80px;height:80px;background:radial-gradient(circle,rgba(102,192,244,0.12) 0%,transparent 70%);pointer-events:none}.sf-fp-title{font-size:14px;font-weight:700;color:#fff;display:flex;align-items:center;gap:8px;position:relative;z-index:1}.sf-fp-title svg{width:18px;height:18px;stroke:var(--sf-fp-blue);stroke-width:2.2;fill:none}.sf-fp-close{width:28px;height:28px;border-radius:6px;background:rgba(255,255,255,0.06);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;color:#94a3b8;position:relative;z-index:1}.sf-fp-close:hover{background:rgba(244,63,94,0.18);color:var(--sf-fp-rose-text)}.sf-fp-close svg{width:14px;height:14px;stroke:currentColor;stroke-width:2.2;fill:none}.sf-fp-content{flex:1;overflow-y:auto;padding:10px;scrollbar-width:thin;scrollbar-color:rgba(102,192,244,0.3) transparent}.sf-fp-content::-webkit-scrollbar{width:5px}.sf-fp-content::-webkit-scrollbar-thumb{background:rgba(102,192,244,0.3);border-radius:3px}.sf-fp-content::-webkit-scrollbar-track{background:transparent}.sf-fp-header-actions{display:flex;align-items:center;gap:4px;position:relative;z-index:1}.sf-fp-settings{width:28px;height:28px;border-radius:6px;background:rgba(255,255,255,0.06);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;color:#94a3b8}.sf-fp-settings:hover{background:rgba(102,192,244,0.15);color:var(--sf-fp-blue)}.sf-fp-settings:disabled{opacity:.5;cursor:not-allowed}.sf-fp-settings svg{width:14px;height:14px;stroke:currentColor;stroke-width:2.2;fill:none}.sf-fp-syncing svg{animation:sf-spin 1s linear infinite}'; const CSS_FRIEND_OWN = '.sf-fp-own{background:linear-gradient(135deg,rgba(59,130,246,0.12) 0%,rgba(30,41,59,0.9) 100%);border:1px solid rgba(102,192,244,0.25);border-radius:var(--sf-fp-radius);padding:12px;display:flex;align-items:center;gap:12px;margin-bottom:10px;position:relative;overflow:hidden;transition:var(--sf-fp-transition)}.sf-fp-own:hover{border-color:rgba(102,192,244,0.4);transform:translateY(-1px)}.sf-fp-own::before{content:"";position:absolute;top:-20px;right:-20px;width:80px;height:80px;background:radial-gradient(circle,rgba(59,130,246,0.12) 0%,transparent 70%);pointer-events:none}.sf-fp-own-avatar{position:relative;width:46px;height:46px;flex-shrink:0}.sf-fp-own-avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover;border:2px solid rgba(102,192,244,0.4);transition:var(--sf-fp-transition)}.sf-fp-own:hover .sf-fp-own-avatar img{border-color:var(--sf-fp-blue-strong);box-shadow:0 0 14px rgba(59,130,246,0.4)}.sf-fp-own-frame{position:absolute;inset:-6px;pointer-events:none}.sf-fp-own-frame img{width:100%;height:100%}.sf-fp-own-dot{position:absolute;bottom:0;right:0;width:11px;height:11px;border-radius:50%;border:2px solid var(--sf-fp-bg-card);background:#94a3b8}.sf-fp-own-dot.online{background:var(--sf-fp-green);box-shadow:0 0 6px rgba(16,185,129,0.5)}.sf-fp-own-dot.in-game{background:var(--sf-fp-purple);box-shadow:0 0 6px rgba(139,92,246,0.5)}.sf-fp-own-info{flex:1;min-width:0;position:relative;z-index:1}.sf-fp-own-row{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:3px}.sf-fp-own-name{font-size:14px;font-weight:700;color:#f8fafc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:170px;cursor:pointer;text-decoration:none}.sf-fp-own-name:hover{color:var(--sf-fp-blue);text-decoration:underline}.sf-fp-own-level{display:inline-flex;align-items:center;gap:2px;background:rgba(245,158,11,0.15);border:1px solid rgba(245,158,11,0.3);border-radius:6px;padding:1px 6px;font-size:10px;font-weight:700;color:var(--sf-fp-amber-text)}.sf-fp-own-level svg{width:10px;height:10px;fill:currentColor}.sf-fp-own-meta{display:flex;align-items:center;gap:5px;flex-wrap:wrap;font-size:11px;color:var(--sf-fp-text-secondary)}.sf-fp-own-flag{width:16px;height:12px;display:inline-block;vertical-align:middle;border-radius:2px;flex-shrink:0}.sf-fp-own-stat{display:inline-flex;align-items:center;gap:3px;background:rgba(255,255,255,0.05);padding:1px 6px;border-radius:4px;font-size:10px;color:var(--sf-fp-text-secondary)}.sf-fp-own-actions{display:flex;flex-direction:column;gap:4px;position:relative;z-index:1}.sf-fp-own-act{width:28px;height:28px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);border-radius:6px;color:#94a3b8;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease}.sf-fp-own-act:hover{background:rgba(59,130,246,0.15);border-color:var(--sf-fp-blue);color:var(--sf-fp-blue)}.sf-fp-own-act svg{width:14px;height:14px;stroke:currentColor;stroke-width:2.2;fill:none}.sf-fp-own-stats{display:flex;align-items:center;gap:5px;flex-wrap:wrap;margin:4px 0 2px}.sf-fp-own-stats .sf-fp-own-stat svg{width:11px;height:11px;flex-shrink:0}.sf-fp-own-stats .sf-fp-own-stat svg path{stroke-width:2}'; // ==================== 好友列表分类 ==================== const CSS_FRIEND_CATEGORY = '.sf-fp-section-title{font-size:11px;font-weight:700;color:var(--sf-fp-text-secondary);text-transform:uppercase;letter-spacing:0.5px;padding:8px 10px 6px;display:flex;align-items:center;justify-content:space-between;border-top:1px solid rgba(255,255,255,0.04);margin-top:6px}.sf-fp-section-title .sf-fp-section-count{font-size:10px;font-weight:700;padding:1px 7px;border-radius:8px;background:rgba(255,255,255,0.06);color:var(--sf-fp-text-secondary);text-transform:none;letter-spacing:0}.sf-fp-section-title.cat-ingame{border-top-color:rgba(139,92,246,0.3);color:var(--sf-fp-purple-text)}.sf-fp-section-title.cat-ingame .sf-fp-section-count{background:rgba(139,92,246,0.2);color:var(--sf-fp-purple-text)}.sf-fp-section-title.cat-online{border-top-color:rgba(16,185,129,0.3);color:var(--sf-fp-green-text)}.sf-fp-section-title.cat-online .sf-fp-section-count{background:rgba(16,185,129,0.2);color:var(--sf-fp-green-text)}.sf-fp-section-title.cat-offline{border-top-color:rgba(255,255,255,0.06);color:var(--sf-fp-text-secondary)}.sf-fp-section-title.cat-banned{color:var(--sf-fp-rose-text)}.sf-fp-section-title.cat-banned .sf-fp-section-count{background:rgba(244,63,94,0.2);color:var(--sf-fp-rose-text)}.sf-fp-list{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.sf-fp-section-title.collapsible{cursor:pointer;user-select:none;transition:var(--sf-fp-transition)}.sf-fp-section-title.collapsible:hover{background:rgba(255,255,255,0.04)}.sf-fp-section-title.collapsible::after{content:"▾";font-size:10px;margin-left:4px;transition:transform .2s ease}.sf-fp-section-title.collapsible.collapsed::after{transform:rotate(-90deg)}.sf-fp-menu-wrap{display:flex;flex-direction:column;gap:2px}.sf-fp-menu-wrap.collapsed{display:none}'; // ==================== 好友项(v4.0 强化版)==================== const CSS_FRIEND_ITEM = '.sf-fp-item{display:flex;align-items:center;gap:10px;padding:8px 10px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04);border-radius:10px;transition:var(--sf-fp-transition);text-decoration:none!important;color:inherit!important;cursor:pointer;position:relative}.sf-fp-item:hover{background:rgba(255,255,255,0.05);border-color:rgba(102,192,244,0.2);transform:translateX(2px)}.sf-fp-avatar{position:relative;width:36px;height:36px;flex-shrink:0}.sf-fp-avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover;border:2px solid #3d4450;transition:var(--sf-fp-transition)}.sf-fp-avatar.in-game img{border-color:var(--sf-fp-purple)}.sf-fp-avatar.online img{border-color:var(--sf-fp-green)}.sf-fp-avatar.banned img{border-color:var(--sf-fp-rose-text)}.sf-fp-dot{position:absolute;bottom:-2px;right:-2px;width:11px;height:11px;border-radius:50%;border:2px solid rgba(23,26,33,0.95);background:#94a3b8}.sf-fp-dot.online{background:var(--sf-fp-green);box-shadow:0 0 6px rgba(16,185,129,0.5)}.sf-fp-dot.in-game{background:var(--sf-fp-purple);box-shadow:0 0 6px rgba(139,92,246,0.5)}.sf-fp-dot.offline{background:var(--sf-fp-text-secondary)}.sf-fp-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px;overflow:hidden}.sf-fp-name-row{display:flex;align-items:center;gap:5px;min-width:0}.sf-fp-name{font-size:13px;font-weight:600;color:#f8fafc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.sf-fp-level,.sf-fp-level-tag{font-size:10px;font-weight:700;color:var(--sf-fp-amber-text);background:rgba(245,158,11,0.12);border-radius:4px;padding:1px 4px;flex-shrink:0}.sf-fp-level-tag{display:none}.sf-fp-level-tag.sf-fp-level-show{display:inline-flex}.sf-fp-flag{width:14px;height:10px;border-radius:1px;flex-shrink:0}.sf-fp-shield{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px}.sf-fp-shield svg{width:14px;height:14px;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.sf-fp-shield.clean svg{stroke:var(--sf-fp-green)}.sf-fp-shield.banned svg{stroke:var(--sf-fp-rose-text)}.sf-fp-shield.devban svg{stroke:var(--sf-fp-amber-text)}.sf-fp-shield.banned-both svg{stroke:var(--sf-fp-rose-text)}.sf-fp-tags{display:flex;align-items:center;gap:4px;flex-wrap:nowrap;font-size:10px;color:var(--sf-fp-text-secondary);min-width:0;overflow:hidden}.sf-fp-tag{display:inline-flex;align-items:center;gap:2px;background:rgba(255,255,255,0.04);padding:1px 5px;border-radius:4px;white-space:nowrap}.sf-fp-tag.in-game{background:rgba(139,92,246,0.15);color:var(--sf-fp-purple-text)}.sf-fp-tag.online{background:rgba(16,185,129,0.12);color:var(--sf-fp-green-text)}.sf-fp-tag.games{background:rgba(59,130,246,0.12);color:#60a5fa}.sf-fp-tag.new-friend{background:rgba(244,63,94,0.12);color:var(--sf-fp-rose-text)}.sf-fp-action-btn{width:26px;height:26px;border-radius:6px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;flex-shrink:0}.sf-fp-action-btn:hover{background:rgba(139,92,246,0.15);border-color:var(--sf-fp-purple);color:var(--sf-fp-purple)}.sf-fp-action-btn svg{width:13px;height:13px;stroke:currentColor;stroke-width:2.2;fill:none}.sf-fp-item-wrap{display:flex;align-items:center;gap:5px;margin-bottom:4px}.sf-fp-item-wrap .sf-fp-item{flex:1;min-width:0}.sf-fp-level-tag{display:inline-flex;align-items:center;gap:2px}.sf-fp-level-tag svg{width:10px;height:10px;fill:currentColor;flex-shrink:0}'; // ==================== 好友面板杂项 ==================== const CSS_FRIEND_MISC = '.sf-fp-stats{background:linear-gradient(135deg,rgba(59,130,246,0.08) 0%,rgba(15,23,42,0.4) 100%);border:1px solid rgba(102,192,244,0.15);border-radius:10px;padding:8px 12px;font-size:12px;color:var(--sf-fp-text-primary);margin-bottom:10px;font-weight:600;display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap}.sf-fp-stats-left{display:flex;align-items:center;gap:6px}.sf-fp-stats-right{display:flex;align-items:center;gap:4px}.sf-fp-stat-pill{font-size:10px;font-weight:700;padding:2px 6px;border-radius:6px;background:rgba(255,255,255,0.05)}.sf-fp-stat-pill.games{background:rgba(59,130,246,0.15);color:#60a5fa}.sf-fp-stat-pill.banned{background:rgba(244,63,94,0.15);color:var(--sf-fp-rose-text)}.sf-fp-stats svg{width:14px;height:14px;fill:none;stroke:var(--sf-fp-blue);stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.sf-fp-action{display:flex;align-items:center;justify-content:center;gap:6px;padding:9px;margin-top:6px;border-radius:10px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.06);color:var(--sf-fp-text-primary);font-size:12px;font-weight:600;cursor:pointer;transition:var(--sf-fp-transition);text-decoration:none!important}.sf-fp-action:hover{background:rgba(59,130,246,0.12);border-color:rgba(59,130,246,0.3);color:var(--sf-fp-blue);transform:translateY(-1px)}.sf-fp-action svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.sf-fp-action.primary{background:linear-gradient(135deg,var(--sf-fp-blue) 0%,var(--sf-fp-blue-strong) 100%);color:#fff;border-color:transparent}.sf-fp-action.primary:hover{filter:brightness(1.15);transform:translateY(-1px);color:#fff}.sf-fp-action.purple{background:linear-gradient(135deg,var(--sf-fp-purple) 0%,#6d28d9 100%);color:#fff;border-color:transparent}.sf-fp-action.purple:hover{filter:brightness(1.15);transform:translateY(-1px);color:#fff}.sf-fp-menu-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:8px;background:rgba(255,255,255,0.03);color:var(--sf-fp-text-primary);font-size:12px;cursor:pointer;transition:var(--sf-fp-transition);margin-bottom:4px;text-decoration:none!important}.sf-fp-menu-item:hover{background:rgba(102,192,244,0.1);color:var(--sf-fp-blue);transform:translateX(2px)}.sf-fp-menu-item svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0}.sf-fp-empty{text-align:center;padding:24px 20px;color:var(--sf-fp-text-secondary);font-size:12px;background:rgba(255,255,255,0.02);border-radius:10px;border:1px dashed rgba(255,255,255,0.06)}.sf-fp-empty svg{width:32px;height:32px;fill:none;stroke:currentColor;stroke-width:1.5;opacity:0.5;margin-bottom:8px}.sf-fp-toast{position:fixed;bottom:30px;left:50%;transform:translateX(-50%) translateY(20px);background:rgba(23,26,33,0.97);color:#fff;padding:10px 18px;border-radius:10px;font-size:13px;font-weight:500;z-index:100002;opacity:0;pointer-events:none;transition:all .3s ease;border:1px solid rgba(102,192,244,0.3);box-shadow:0 4px 20px rgba(0,0,0,0.4)}.sf-fp-toast.sfp-show{opacity:1;transform:translateX(-50%) translateY(0)}'; const CSS_FRIEND_FOOTER = '.sf-fp-footer{padding:8px 14px;background:rgba(15,23,42,0.6);border-top:1px solid var(--sf-fp-border);font-size:11px;color:var(--sf-fp-text-secondary);display:flex;justify-content:space-between;align-items:center;flex-shrink:0;gap:6px}.sf-fp-footer .sf-fp-footer-tip{display:flex;align-items:center;gap:5px}.sf-fp-footer .sf-fp-footer-tip svg{width:12px;height:12px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;color:var(--sf-fp-blue)}.sf-fp-footer .sf-fp-footer-key{font-size:10px;background:rgba(255,255,255,0.05);padding:2px 5px;border-radius:4px;cursor:pointer;transition:all .2s ease}.sf-fp-footer .sf-fp-footer-key:hover{background:rgba(59,130,246,0.15);color:var(--sf-fp-blue)}'; // ==================== 好友动态浮窗(右侧滑出)==================== const CSS_FA_PANEL = '#sf-fa-panel{position:fixed!important;top:0!important;right:0!important;width:var(--sf-popup-w)!important;max-width:96vw!important;height:100vh!important;background:rgba(17,24,39,0.98)!important;border-left:1px solid var(--sf-fp-border)!important;box-shadow:-12px 0 50px rgba(0,0,0,0.6)!important;z-index:10000!important;display:flex!important;flex-direction:column;transform:translateX(100%)!important;transition:transform .35s cubic-bezier(0.4,0,0.2,1)!important;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:var(--sf-fp-text-primary);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}#sf-fa-panel.sfp-open{transform:translateX(0)!important}.sf-fa-header{background:linear-gradient(135deg,rgba(139,92,246,0.18) 0%,rgba(15,23,42,0.85) 100%);padding:12px 16px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--sf-fp-border);flex-shrink:0;position:relative;overflow:hidden}.sf-fa-header::after{content:"";position:absolute;top:-30px;right:-30px;width:100px;height:100px;background:radial-gradient(circle,rgba(139,92,246,0.15) 0%,transparent 70%);pointer-events:none}.sf-fa-title{font-size:14px;font-weight:700;color:#fff;display:flex;align-items:center;gap:8px;position:relative;z-index:1;min-width:0;flex:1;overflow:hidden}.sf-fa-title .sf-fa-avatar{width:24px;height:24px;border-radius:50%;object-fit:cover;border:1.5px solid rgba(139,92,246,0.4);flex-shrink:0}.sf-fa-title .sf-fa-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sf-fa-close{width:28px;height:28px;border-radius:6px;background:rgba(255,255,255,0.06);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;color:#94a3b8;position:relative;z-index:1;flex-shrink:0}.sf-fa-close:hover{background:rgba(244,63,94,0.18);color:var(--sf-fp-rose-text)}.sf-fa-close svg{width:14px;height:14px;stroke:currentColor;stroke-width:2.2;fill:none}.sf-fa-tabs{display:flex;align-items:center;gap:2px;padding:6px 8px;background:rgba(15,23,42,0.5);border-bottom:1px solid var(--sf-fp-border);flex-shrink:0}.sf-fa-tab{flex:1;display:flex;align-items:center;justify-content:center;gap:5px;padding:7px 8px;border-radius:6px;background:transparent;border:none;color:var(--sf-fp-text-secondary);font-size:11px;font-weight:600;cursor:pointer;transition:all .2s ease;white-space:nowrap}.sf-fa-tab:hover{background:rgba(255,255,255,0.05);color:#fff}.sf-fa-tab.active{background:linear-gradient(135deg,rgba(139,92,246,0.25) 0%,rgba(139,92,246,0.1) 100%);color:var(--sf-fp-purple-text)}.sf-fa-tab svg{width:13px;height:13px;stroke:currentColor;stroke-width:2;fill:none;flex-shrink:0}.sf-fa-body{flex:1;overflow-y:auto;padding:12px;scrollbar-width:thin;scrollbar-color:rgba(139,92,246,0.3) transparent}.sf-fa-body::-webkit-scrollbar{width:5px}.sf-fa-body::-webkit-scrollbar-thumb{background:rgba(139,92,246,0.3);border-radius:3px}.sf-fa-body::-webkit-scrollbar-track{background:transparent}.sf-fa-loading{text-align:center;padding:40px 16px;color:var(--sf-fp-text-secondary);font-size:12px}.sf-fa-loading-spinner{width:28px;height:28px;border:3px solid rgba(139,92,246,0.2);border-top-color:var(--sf-fp-purple);border-radius:50%;animation:sf-spin .8s linear infinite;margin:0 auto 10px}'; // ==================== FA 概览卡片 ==================== const CSS_FA_OVERVIEW = '.sf-fa-hero{background:linear-gradient(135deg,rgba(59,130,246,0.12) 0%,rgba(15,23,42,0.7) 100%);border:1px solid rgba(102,192,244,0.25);border-radius:var(--sf-fp-radius);padding:14px;display:flex;align-items:center;gap:12px;margin-bottom:12px;position:relative;overflow:hidden}.sf-fa-hero::before{content:"";position:absolute;top:-30px;right:-30px;width:100px;height:100px;background:radial-gradient(circle,rgba(59,130,246,0.15) 0%,transparent 70%);pointer-events:none}.sf-fa-hero-avatar{position:relative;width:64px;height:64px;flex-shrink:0}.sf-fa-hero-avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover;border:2.5px solid rgba(102,192,244,0.4);box-shadow:0 4px 14px rgba(59,130,246,0.2)}.sf-fa-hero-info{flex:1;min-width:0;position:relative;z-index:1}.sf-fa-hero-name{font-size:15px;font-weight:800;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-bottom:4px}.sf-fa-hero-meta{display:flex;align-items:center;gap:5px;flex-wrap:wrap;font-size:11px;color:var(--sf-fp-text-secondary)}.sf-fa-hero-meta .sf-fp-tag{margin:0}.sf-fa-stats-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-bottom:12px}.sf-fa-stat{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.06);border-radius:8px;padding:8px 10px;text-align:center;transition:var(--sf-fp-transition)}.sf-fa-stat:hover{background:rgba(255,255,255,0.06);transform:translateY(-1px)}.sf-fa-stat-val{font-size:16px;font-weight:700;color:#fff;line-height:1.2}.sf-fa-stat-lbl{font-size:10px;color:var(--sf-fp-text-secondary);margin-top:2px}.sf-fa-stat.games .sf-fa-stat-val{color:#60a5fa}.sf-fa-stat.level .sf-fa-stat-val{color:var(--sf-fp-amber-text)}.sf-fa-stat.playtime .sf-fa-stat-val{color:var(--sf-fp-green-text)}.sf-fa-stat.banned .sf-fa-stat-val{color:var(--sf-fp-rose-text)}.sf-fa-section{background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04);border-radius:10px;padding:10px 12px;margin-bottom:10px}.sf-fa-section-title{font-size:12px;font-weight:700;color:#fff;margin-bottom:8px;display:flex;align-items:center;gap:5px}.sf-fa-section-title svg{width:14px;height:14px;stroke:var(--sf-fp-purple-text);stroke-width:2;fill:none}.sf-fa-section-row{display:flex;justify-content:space-between;align-items:center;padding:5px 0;font-size:12px;border-top:1px solid rgba(255,255,255,0.04)}.sf-fa-section-row:first-of-type{border-top:none}.sf-fa-section-key{color:var(--sf-fp-text-secondary)}.sf-fa-section-val{color:#fff;font-weight:600}.sf-fa-link-row{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}.sf-fa-link-row .sf-fp-action{flex:1;min-width:100px;margin-top:0}'; // ==================== FA 游戏项(共用)==================== const CSS_FA_GAMES = '.sf-fa-game-item{display:flex;align-items:center;gap:9px;padding:6px 8px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04);border-radius:8px;transition:var(--sf-fp-transition);margin-bottom:4px;cursor:pointer;text-decoration:none!important;color:inherit!important}.sf-fa-game-item:hover{background:rgba(255,255,255,0.05);border-color:rgba(139,92,246,0.25);transform:translateX(2px)}.sf-fa-game-icon{width:40px;height:40px;border-radius:6px;background:rgba(0,0,0,0.4);object-fit:cover;flex-shrink:0;border:1px solid rgba(255,255,255,0.05)}.sf-fa-game-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;overflow:hidden}.sf-fa-game-name{font-size:12px;font-weight:600;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sf-fa-game-meta{display:flex;align-items:center;gap:6px;font-size:10px;color:var(--sf-fp-text-secondary)}.sf-fa-game-meta .sf-fa-tag{display:inline-flex;align-items:center;gap:2px;background:rgba(255,255,255,0.05);padding:1px 5px;border-radius:4px;color:var(--sf-fp-text-secondary)}.sf-fa-game-meta .sf-fa-tag.recent{background:rgba(16,185,129,0.12);color:var(--sf-fp-green-text)}.sf-fa-game-meta .sf-fa-tag.2w{background:rgba(245,158,11,0.12);color:var(--sf-fp-amber-text)}.sf-fa-game-rank{width:22px;height:22px;border-radius:5px;background:rgba(255,255,255,0.05);font-size:10px;font-weight:700;color:var(--sf-fp-text-secondary);display:flex;align-items:center;justify-content:center;flex-shrink:0}.sf-fa-game-rank.r1{background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff}.sf-fa-game-rank.r2{background:linear-gradient(135deg,#94a3b8,#64748b);color:#fff}.sf-fa-game-rank.r3{background:linear-gradient(135deg,#b45309,#92400e);color:#fff}.sf-fa-game-bar{flex:1;height:4px;background:rgba(255,255,255,0.05);border-radius:2px;overflow:hidden;min-width:40px}.sf-fa-game-bar-fill{height:100%;background:linear-gradient(90deg,var(--sf-fp-purple) 0%,var(--sf-fp-blue) 100%);transition:width .4s ease}.sf-fa-donut-wrap{display:flex;align-items:center;gap:12px;padding:8px 0}.sf-fa-donut-svg{flex-shrink:0}.sf-fa-donut-legend{flex:1;display:flex;flex-direction:column;gap:4px;font-size:11px;min-width:0}.sf-fa-donut-legend-item{display:flex;align-items:center;gap:5px;color:var(--sf-fp-text-secondary)}.sf-fa-donut-legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.sf-fa-empty-block{text-align:center;padding:24px 12px;color:var(--sf-fp-text-secondary);font-size:12px;background:rgba(255,255,255,0.02);border-radius:8px;border:1px dashed rgba(255,255,255,0.06)}.sf-fa-empty-block svg{width:32px;height:32px;fill:none;stroke:currentColor;stroke-width:1.5;opacity:0.5;margin-bottom:6px}.sf-fa-delisted-type-pills{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:10px}.sf-fa-delisted-type-pill{padding:3px 8px;border-radius:14px;background:rgba(255,255,255,0.05);border:1px solid transparent;color:var(--sf-fp-text-secondary);font-size:10px;font-weight:600;cursor:pointer;transition:all .2s ease}.sf-fa-delisted-type-pill.active{background:rgba(139,92,246,0.2);border-color:rgba(139,92,246,0.4);color:var(--sf-fp-purple-text)}.sf-fa-subcats{display:flex;gap:4px;padding:0 0 10px 0;border-bottom:1px solid rgba(255,255,255,0.06);margin-bottom:10px}.sf-fa-subcat{flex:1;display:flex;align-items:center;justify-content:center;gap:5px;padding:8px 10px;border-radius:8px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.06);color:var(--sf-fp-text-secondary);font-size:11px;font-weight:600;cursor:pointer;transition:all .2s ease;white-space:nowrap}.sf-fa-subcat:hover{background:rgba(255,255,255,0.06);color:#fff}.sf-fa-subcat.active{background:linear-gradient(135deg,rgba(139,92,246,0.25) 0%,rgba(139,92,246,0.1) 100%);border-color:rgba(139,92,246,0.35);color:var(--sf-fp-purple-text)}.sf-fa-subcat svg{width:13px;height:13px;stroke:currentColor;stroke-width:2;fill:none;flex-shrink:0}.sf-fa-pagination{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:8px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.04)}.sf-fa-page-btn{width:28px;height:28px;border-radius:6px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.06);color:#fff;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center}.sf-fa-page-btn:hover:not(:disabled){background:rgba(139,92,246,0.2);border-color:rgba(139,92,246,0.4)}.sf-fa-page-btn:disabled{opacity:0.3;cursor:not-allowed}.sf-fa-page-info{font-size:11px;color:var(--sf-fp-text-secondary);font-weight:600}'; // ==================== 个人游戏库浮窗(中央模态)==================== const CSS_LIBRARY_PANEL = '#sf-lib-panel{position:fixed!important;top:50%!important;left:50%!important;width:var(--sf-lib-w)!important;max-width:96vw!important;height:var(--sf-lib-h)!important;max-height:92vh!important;background:rgba(17,24,39,0.98)!important;border:1px solid var(--sf-fp-border)!important;border-radius:18px!important;box-shadow:0 30px 100px rgba(0,0,0,0.6)!important;z-index:10001!important;display:flex!important;flex-direction:column;transform:translate(-50%,-48%) scale(0.94)!important;opacity:0!important;visibility:hidden!important;transition:opacity .3s ease,transform .35s cubic-bezier(0.4,0,0.2,1),visibility .3s!important;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:var(--sf-fp-text-primary);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);overflow:hidden}#sf-lib-panel.sfl-open{opacity:1!important;visibility:visible!important;transform:translate(-50%,-50%) scale(1)!important}.sf-lib-header{background:linear-gradient(135deg,rgba(139,92,246,0.2) 0%,rgba(15,23,42,0.85) 100%);padding:12px 18px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--sf-fp-border);flex-shrink:0;position:relative;overflow:hidden}.sf-lib-header::after{content:"";position:absolute;top:-30px;right:-30px;width:100px;height:100px;background:radial-gradient(circle,rgba(139,92,246,0.18) 0%,transparent 70%);pointer-events:none}.sf-lib-title{font-size:15px;font-weight:800;color:#fff;display:flex;align-items:center;gap:8px;position:relative;z-index:1}.sf-lib-title svg{width:18px;height:18px;stroke:var(--sf-fp-purple);stroke-width:2.2;fill:none}.sf-lib-close{width:30px;height:30px;border-radius:8px;background:rgba(255,255,255,0.06);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;color:#94a3b8;position:relative;z-index:1;flex-shrink:0}.sf-lib-close:hover{background:rgba(244,63,94,0.18);color:var(--sf-fp-rose-text)}.sf-lib-close svg{width:16px;height:16px;stroke:currentColor;stroke-width:2.2;fill:none}.sf-lib-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;padding:12px 18px;background:rgba(15,23,42,0.4);border-bottom:1px solid var(--sf-fp-border);flex-shrink:0}.sf-lib-stat{text-align:center;padding:6px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04);border-radius:8px;transition:var(--sf-fp-transition)}.sf-lib-stat:hover{background:rgba(255,255,255,0.05);transform:translateY(-1px)}.sf-lib-stat-val{font-size:18px;font-weight:800;color:#fff;line-height:1.2}.sf-lib-stat-lbl{font-size:10px;color:var(--sf-fp-text-secondary);margin-top:2px;font-weight:500}.sf-lib-stat.total .sf-lib-stat-val{color:var(--sf-fp-purple-text)}.sf-lib-stat.time .sf-lib-stat-val{color:var(--sf-fp-blue)}.sf-lib-stat.avg .sf-lib-stat-val{color:var(--sf-fp-green-text)}.sf-lib-stat.unplayed .sf-lib-stat-val{color:var(--sf-fp-amber-text)}.sf-lib-tabs{display:flex;align-items:center;gap:4px;padding:8px 12px;background:rgba(15,23,42,0.5);border-bottom:1px solid var(--sf-fp-border);flex-shrink:0;overflow-x:auto;scrollbar-width:thin}.sf-lib-tabs::-webkit-scrollbar{height:3px}.sf-lib-tabs::-webkit-scrollbar-thumb{background:rgba(139,92,246,0.3);border-radius:2px}.sf-lib-tab{display:flex;align-items:center;justify-content:center;gap:5px;padding:7px 12px;border-radius:8px;background:transparent;border:1px solid transparent;color:var(--sf-fp-text-secondary);font-size:12px;font-weight:600;cursor:pointer;transition:all .2s ease;white-space:nowrap;flex-shrink:0}.sf-lib-tab:hover{background:rgba(255,255,255,0.05);color:#fff}.sf-lib-tab.active{background:linear-gradient(135deg,rgba(139,92,246,0.25) 0%,rgba(139,92,246,0.1) 100%);border-color:rgba(139,92,246,0.35);color:var(--sf-fp-purple-text)}.sf-lib-tab svg{width:13px;height:13px;stroke:currentColor;stroke-width:2;fill:none;flex-shrink:0}.sf-lib-body{flex:1;overflow-y:auto;padding:14px 18px;scrollbar-width:thin;scrollbar-color:rgba(139,92,246,0.3) transparent}.sf-lib-body::-webkit-scrollbar{width:5px}.sf-lib-body::-webkit-scrollbar-thumb{background:rgba(139,92,246,0.3);border-radius:3px}.sf-lib-body::-webkit-scrollbar-track{background:transparent}.sf-lib-loading{text-align:center;padding:40px 16px;color:var(--sf-fp-text-secondary);font-size:13px}.sf-lib-loading-spinner{width:32px;height:32px;border:3px solid rgba(139,92,246,0.2);border-top-color:var(--sf-fp-purple);border-radius:50%;animation:sf-spin .8s linear infinite;margin:0 auto 10px}.sf-lib-section{background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04);border-radius:10px;padding:12px;margin-bottom:10px}.sf-lib-section-title{font-size:13px;font-weight:700;color:#fff;margin-bottom:10px;display:flex;align-items:center;gap:6px}.sf-lib-section-title svg{width:14px;height:14px;stroke:var(--sf-fp-purple-text);stroke-width:2;fill:none;flex-shrink:0}.sf-lib-empty-block{text-align:center;padding:30px 16px;color:var(--sf-fp-text-secondary);font-size:12px;background:rgba(255,255,255,0.02);border-radius:8px;border:1px dashed rgba(255,255,255,0.06)}.sf-lib-empty-block svg{width:36px;height:36px;fill:none;stroke:currentColor;stroke-width:1.5;opacity:0.5;margin-bottom:8px}.sf-lib-key-hint{text-align:center;padding:40px 20px;color:var(--sf-fp-text-secondary);font-size:13px;background:rgba(255,255,255,0.02);border-radius:10px;border:1px dashed rgba(255,255,255,0.08);margin:20px 0}.sf-lib-key-hint svg{width:48px;height:48px;fill:none;stroke:var(--sf-fp-amber);stroke-width:1.5;margin-bottom:10px}.sf-lib-key-hint a{color:var(--sf-fp-blue);text-decoration:none;font-weight:600;font-size:12px;display:inline-block;margin-top:6px;padding:4px 10px;background:rgba(59,130,246,0.1);border-radius:6px;border:1px solid rgba(59,130,246,0.3)}.sf-lib-key-hint a:hover{background:rgba(59,130,246,0.2);text-decoration:underline}.sf-lib-key-input-row{display:flex;gap:6px;margin-top:10px}.sf-lib-key-input-row input{flex:1;padding:8px 10px;background:rgba(15,23,42,0.6);border:1px solid var(--sf-fp-border);border-radius:6px;color:#fff;font-size:12px;outline:none;font-family:monospace}.sf-lib-key-input-row input:focus{border-color:var(--sf-fp-blue);box-shadow:0 0 0 2px rgba(59,130,246,0.15)}.sf-lib-key-input-row button{padding:8px 14px;background:linear-gradient(135deg,var(--sf-fp-blue) 0%,var(--sf-fp-blue-strong) 100%);color:#fff;border:none;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;transition:all .2s ease;flex-shrink:0}.sf-lib-key-input-row button:hover{filter:brightness(1.15);transform:translateY(-1px)}.sf-lib-playtime-row{display:flex;gap:14px;flex-wrap:wrap}.sf-lib-playtime-col{flex:1;min-width:280px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04);border-radius:10px;padding:12px}.sf-lib-playtime-col .sf-lib-section-title{margin-bottom:10px}.sf-lib-trend-chart{position:relative}.sf-lib-trend-filter{display:flex;gap:0;border:1px solid rgba(255,255,255,0.08);border-radius:6px;overflow:hidden}.sf-lib-trend-btn{background:rgba(0,0,0,0.2);border:none;border-right:1px solid rgba(255,255,255,0.08);border-radius:0;padding:4px 10px;font-size:11px;height:24px;color:#94a3b8;cursor:pointer;transition:all .2s}.sf-lib-trend-btn:last-child{border-right:none}.sf-lib-trend-btn:hover{background:rgba(59,130,246,0.15);color:#f1f5f9}.sf-lib-trend-btn.active{background:rgba(102,192,244,0.2);color:#66c0f4}.sf-lib-trend-svg{display:block;border-radius:6px;overflow:hidden}'; // ==================== 库游戏项(共用 CSS)==================== const CSS_LIB_GAMES = '.sf-lib-game-item{display:flex;align-items:center;gap:10px;padding:7px 10px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04);border-radius:8px;transition:all .2s ease;margin-bottom:5px;cursor:pointer;text-decoration:none!important;color:inherit!important}.sf-lib-game-item:hover{background:rgba(255,255,255,0.05);border-color:rgba(139,92,246,0.25);transform:translateX(2px)}.sf-lib-game-icon{width:44px;height:44px;border-radius:6px;background:rgba(0,0,0,0.4);object-fit:cover;flex-shrink:0;border:1px solid rgba(255,255,255,0.05)}.sf-lib-game-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;overflow:hidden}.sf-lib-game-name{font-size:13px;font-weight:600;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sf-lib-game-meta{display:flex;align-items:center;gap:6px;font-size:10px;color:var(--sf-fp-text-secondary);flex-wrap:wrap}.sf-lib-game-meta .sf-lib-game-time{background:rgba(139,92,246,0.12);color:var(--sf-fp-purple-text);padding:1px 5px;border-radius:4px;font-weight:600}.sf-lib-game-meta .sf-lib-game-last{color:var(--sf-fp-text-secondary)}.sf-lib-game-meta .sf-lib-game-tag{background:rgba(255,255,255,0.05);padding:1px 5px;border-radius:4px;color:var(--sf-fp-text-secondary)}.sf-lib-game-rank{width:24px;height:24px;border-radius:5px;background:rgba(255,255,255,0.05);font-size:11px;font-weight:700;color:var(--sf-fp-text-secondary);display:flex;align-items:center;justify-content:center;flex-shrink:0}.sf-lib-game-rank.r1{background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff}.sf-lib-game-rank.r2{background:linear-gradient(135deg,#94a3b8,#64748b);color:#fff}.sf-lib-game-rank.r3{background:linear-gradient(135deg,#b45309,#92400e);color:#fff}.sf-lib-game-bar{flex:1;height:5px;background:rgba(255,255,255,0.05);border-radius:3px;overflow:hidden;min-width:50px;max-width:90px}.sf-lib-game-bar-fill{height:100%;background:linear-gradient(90deg,var(--sf-fp-purple) 0%,var(--sf-fp-blue) 100%);transition:width .4s ease;border-radius:3px}.sf-lib-delisted-badge{font-size:9px;font-weight:700;padding:2px 6px;border-radius:4px;flex-shrink:0}.sf-lib-delisted-badge.type-delisted{background:rgba(244,67,54,0.2);color:#f87171}.sf-lib-delisted-badge.type-purchase_disabled{background:rgba(255,152,0,0.2);color:#fb923c}.sf-lib-delisted-badge.type-f2p_unavailable{background:rgba(156,39,176,0.2);color:#c084fc}.sf-lib-delisted-badge.type-retail_only{background:rgba(33,150,243,0.2);color:#60a5fa}.sf-lib-delisted-badge.type-test_app{background:rgba(96,125,139,0.2);color:#94a3b8}.sf-lib-timeline-divider{display:flex;align-items:center;gap:8px;margin:14px 0 8px;font-size:11px;color:var(--sf-fp-text-secondary)}.sf-lib-timeline-divider::before,.sf-lib-timeline-divider::after{content:"";flex:1;height:1px;background:rgba(255,255,255,0.06)}.sf-lib-timeline-header{display:flex;justify-content:space-between;align-items:center;padding:6px 10px;background:rgba(139,92,246,0.08);border:1px solid rgba(139,92,246,0.2);border-radius:6px;margin-bottom:6px;font-size:11px;font-weight:600}.sf-lib-timeline-label{display:flex;align-items:center;gap:4px}.sf-lib-timeline-count{color:var(--sf-fp-text-secondary);font-weight:500}'; // ==================== 库分页 ==================== const CSS_LIB_PAGINATION = '.sf-lib-pagination{display:flex;align-items:center;justify-content:center;gap:4px;margin-top:10px;flex-wrap:wrap}.sf-lib-page-btn{padding:5px 10px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-radius:6px;color:var(--sf-fp-text-secondary);font-size:11px;font-weight:600;cursor:pointer;transition:all .2s ease;min-width:30px;text-align:center}.sf-lib-page-btn:hover{background:rgba(139,92,246,0.15);color:var(--sf-fp-purple-text);border-color:rgba(139,92,246,0.3)}.sf-lib-page-btn.active{background:linear-gradient(135deg,var(--sf-fp-purple) 0%,#6d28d9 100%);color:#fff;border-color:transparent}.sf-lib-page-btn:disabled{opacity:.4;cursor:not-allowed;background:rgba(255,255,255,0.02)}.sf-lib-page-info{font-size:11px;color:var(--sf-fp-text-secondary);padding:0 6px}'; // ==================== 绝版游戏增强展示(参考 Steam-License-Classifier)==================== const CSS_DELISTED_ENHANCED = '.sf-delisted-progress{height:5px;background:rgba(255,255,255,0.06);border-radius:3px;overflow:hidden;margin:0 0 10px}.sf-delisted-progress-fill{height:100%;background:linear-gradient(90deg,#22c55e,#66c0f4);border-radius:3px;transition:width .3s ease}.sf-delisted-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:10px}.sf-delisted-search{flex:1;min-width:140px;background:rgba(0,0,0,0.25);border:1px solid rgba(255,255,255,0.08);border-radius:20px;padding:6px 12px;color:#f1f5f9;font-size:12px;outline:none;transition:all .2s}.sf-delisted-search::placeholder{color:#64748b}.sf-delisted-search:focus{border-color:rgba(102,192,244,0.5);box-shadow:0 0 0 2px rgba(102,192,244,0.15)}.sf-delisted-sort-group{display:flex;gap:0;border:1px solid rgba(255,255,255,0.08);border-radius:6px;overflow:hidden}.sf-delisted-sort-btn{background:rgba(0,0,0,0.2)!important;border:none!important;border-right:1px solid rgba(255,255,255,0.08)!important;border-radius:0!important;padding:5px 10px!important;font-size:11px!important;height:28px!important;color:#94a3b8!important;cursor:pointer;transition:all .2s}.sf-delisted-sort-btn:last-child{border-right:none!important}.sf-delisted-sort-btn:hover{background:rgba(59,130,246,0.15)!important;color:#f1f5f9!important}.sf-delisted-sort-btn.active{background:rgba(102,192,244,0.2)!important;color:#66c0f4!important}.sf-delisted-view-toggle{display:flex;gap:0;border:1px solid rgba(255,255,255,0.08);border-radius:6px;overflow:hidden}.sf-delisted-view-btn{background:rgba(0,0,0,0.2)!important;border:none!important;border-right:1px solid rgba(255,255,255,0.08)!important;border-radius:0!important;padding:5px 10px!important;font-size:11px!important;height:28px!important;color:#94a3b8!important;cursor:pointer;transition:all .2s}.sf-delisted-view-btn:last-child{border-right:none!important}.sf-delisted-view-btn:hover{background:rgba(59,130,246,0.15)!important;color:#f1f5f9!important}.sf-delisted-view-btn.active{background:rgba(102,192,244,0.2)!important;color:#66c0f4!important}.sf-delisted-cover-btn{background:rgba(0,0,0,0.2)!important;border:1px solid rgba(255,255,255,0.08)!important;border-radius:6px!important;padding:5px 8px!important;font-size:11px!important;height:28px!important;color:#94a3b8!important;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center}.sf-delisted-cover-btn:hover{background:rgba(59,130,246,0.15)!important;color:#f1f5f9!important}.sf-delisted-cover-btn.active{background:rgba(102,192,244,0.2)!important;border-color:rgba(102,192,244,0.4)!important;color:#66c0f4!important}.sf-delisted-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px}.sf-delisted-card{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.05);border-radius:8px;padding:10px 12px;transition:all .2s;cursor:pointer;position:relative}.sf-delisted-card:hover{background:rgba(255,255,255,0.06);border-color:rgba(244,67,54,0.25);transform:translateY(-1px)}.sf-delisted-card-name{font-size:12px;font-weight:600;color:#f1f5f9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:4px}.sf-delisted-card-meta{font-size:10px;color:#64748b;display:flex;align-items:center;gap:6px;flex-wrap:wrap}.sf-delisted-card-meta .sf-lib-delisted-badge{font-size:9px}.sf-delisted-list{display:flex;flex-direction:column;gap:4px}.sf-delisted-list-item{display:flex;align-items:center;gap:10px;padding:7px 10px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04);border-radius:8px;cursor:pointer;transition:all .2s;text-decoration:none!important;color:inherit!important}.sf-delisted-list-item:hover{background:rgba(255,255,255,0.05);border-color:rgba(244,67,54,0.2)}.sf-delisted-list-item.has-cover{gap:12px}.sf-delisted-cover{width:120px;height:45px;border-radius:4px;object-fit:cover;flex-shrink:0;background:rgba(0,0,0,0.3)}.sf-delisted-cover-slot{width:120px;height:45px;flex-shrink:0;border-radius:4px;overflow:hidden}.sf-delisted-cover-slot>div{width:100%;height:100%;border-radius:4px}.sf-delisted-list-item .name{flex:1;font-size:12px;color:#f1f5f9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sf-delisted-list-item .meta{display:flex;align-items:center;gap:6px;flex-shrink:0}.sf-delisted-card-cover{width:100%;height:74px;border-radius:6px;overflow:hidden;margin-bottom:8px;background:rgba(0,0,0,0.3)}.sf-delisted-cover-img{width:100%;height:100%;object-fit:cover;display:block}.sf-delisted-card-cover>div{width:100%;height:100%}.sf-delisted-owned-badge{position:absolute;bottom:6px;right:6px;font-size:9px;padding:2px 6px;border-radius:3px;font-weight:600}.sf-delisted-owned-badge.owned{background:rgba(34,197,94,0.15);color:#22c55e}.sf-delisted-owned-badge.missing{background:rgba(244,67,54,0.12);color:#f44336}'; // ==================== 设置面板(独立模态)==================== const CSS_SETTINGS_PANEL = '#sf-settings-panel{position:fixed!important;top:50%!important;left:50%!important;width:440px!important;max-width:92vw!important;background:rgba(17,24,39,0.98)!important;border:1px solid var(--sf-fp-border)!important;border-radius:18px!important;box-shadow:0 30px 100px rgba(0,0,0,0.6)!important;z-index:10002!important;display:flex!important;flex-direction:column;transform:translate(-50%,-48%) scale(0.94)!important;opacity:0!important;visibility:hidden!important;transition:opacity .3s ease,transform .35s cubic-bezier(0.4,0,0.2,1),visibility .3s!important;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:var(--sf-fp-text-primary);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);overflow:hidden}#sf-settings-panel.sf-settings-open{opacity:1!important;visibility:visible!important;transform:translate(-50%,-50%) scale(1)!important}.sf-settings-header{background:linear-gradient(135deg,rgba(99,102,241,0.2) 0%,rgba(15,23,42,0.85) 100%);padding:14px 18px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--sf-fp-border);flex-shrink:0}.sf-settings-title{font-size:15px;font-weight:800;color:#fff;display:flex;align-items:center;gap:8px}.sf-settings-title svg{width:18px;height:18px;stroke:var(--sf-fp-purple);stroke-width:2.2;fill:none}.sf-settings-close{width:30px;height:30px;border-radius:8px;background:rgba(255,255,255,0.06);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;color:#94a3b8}.sf-settings-close:hover{background:rgba(244,63,94,0.18);color:var(--sf-fp-rose-text)}.sf-settings-close svg{width:16px;height:16px;stroke:currentColor;stroke-width:2.2;fill:none}.sf-settings-body{padding:18px;overflow-y:auto;max-height:70vh}.sf-settings-group{margin-bottom:18px}.sf-settings-group-title{font-size:12px;font-weight:700;color:#fff;margin-bottom:10px;display:flex;align-items:center;gap:5px}.sf-settings-group-title svg{width:14px;height:14px;stroke:var(--sf-fp-purple-text);stroke-width:2;fill:none}.sf-settings-desc{font-size:11px;color:var(--sf-fp-text-secondary);margin-bottom:8px;line-height:1.5}.sf-settings-input{width:100%;padding:10px 12px;background:rgba(15,23,42,0.6);border:1px solid var(--sf-fp-border);border-radius:8px;color:#fff;font-size:13px;outline:none;font-family:monospace;box-sizing:border-box;transition:all .2s ease}.sf-settings-input:focus{border-color:var(--sf-fp-blue);box-shadow:0 0 0 3px rgba(59,130,246,0.15)}.sf-settings-input::placeholder{color:var(--sf-fp-text-dim)}.sf-settings-actions{display:flex;gap:8px;margin-top:10px}.sf-settings-btn{flex:1;padding:9px 14px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;transition:all .2s ease;border:none}.sf-settings-btn.primary{background:linear-gradient(135deg,var(--sf-fp-blue) 0%,var(--sf-fp-blue-strong) 100%);color:#fff}.sf-settings-btn.primary:hover{filter:brightness(1.15);transform:translateY(-1px)}.sf-settings-btn.secondary{background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);color:var(--sf-fp-text-primary)}.sf-settings-btn.secondary:hover{background:rgba(255,255,255,0.1)}.sf-settings-status{font-size:11px;margin-top:8px;padding:8px 10px;border-radius:6px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.06);color:var(--sf-fp-text-secondary);display:none}.sf-settings-status.show{display:block}.sf-settings-status.ok{color:#34d399;border-color:rgba(16,185,129,0.2);background:rgba(16,185,129,0.08)}.sf-settings-status.err{color:var(--sf-fp-rose-text);border-color:rgba(244,63,94,0.2);background:rgba(244,63,94,0.08)}.sf-settings-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:10001;opacity:0;visibility:hidden;transition:all .3s ease;backdrop-filter:blur(2px)}.sf-settings-overlay.sf-show{opacity:1;visibility:visible}'; // ==================== 用户信息弹窗 ==================== const CSS_USERINFO = '.sf-userinfo-popup{position:absolute!important;top:100%!important;left:0!important;width:280px!important;background:rgba(23,26,33,.98)!important;border:1px solid rgba(255,255,255,.1)!important;border-radius:12px!important;padding:16px!important;z-index:10000!important;box-shadow:0 8px 32px rgba(0,0,0,.6)!important;opacity:0!important;visibility:hidden!important;transform:translateY(-10px)!important;transition:all .2s ease!important}.sf-userinfo-popup.visible{opacity:1!important;visibility:visible!important;transform:translateY(0)!important}.sf-ui-header{display:flex!important;align-items:center!important;gap:12px!important;margin-bottom:12px!important}.sf-ui-avatar{width:48px!important;height:48px!important;border-radius:8px!important}.sf-ui-name{font-size:14px!important;font-weight:600!important;color:#fff!important}.sf-ui-level{font-size:12px!important;color:var(--sf-dim)!important}.sf-ui-stats{display:grid!important;grid-template-columns:repeat(2,1fr)!important;gap:8px!important;margin-bottom:12px!important}.sf-ui-stat{padding:8px!important;background:rgba(255,255,255,.05)!important;border-radius:8px!important;text-align:center!important}.sf-ui-stat-value{font-size:16px!important;font-weight:600!important;color:var(--sf-accent)!important}.sf-ui-stat-label{font-size:11px!important;color:var(--sf-dim)!important;margin-top:2px!important}.sf-ui-actions{display:flex!important;gap:8px!important}.sf-ui-action-btn{flex:1!important;padding:8px!important;border-radius:6px!important;background:rgba(102,192,244,.15)!important;border:1px solid rgba(102,192,244,.3)!important;color:var(--sf-accent)!important;font-size:12px!important;cursor:pointer!important;transition:all .2s!important;text-align:center!important}.sf-ui-action-btn:hover{background:rgba(102,192,244,.25)!important}.sf-ui-loading{text-align:center!important;padding:20px!important;color:var(--sf-dim)!important}.sf-ui-loading-spinner{width:24px!important;height:24px!important;border:3px solid rgba(102,192,244,.2);border-top-color:var(--sf-accent);border-radius:50%;animation:sf-spin .8s linear infinite;margin:0 auto 8px}.sf-ui-flag{width:16px;height:12px;border-radius:2px;display:inline-block;vertical-align:middle;margin-left:4px}'; // ==================== 左侧分类导航 ==================== const CSS_SIDEBAR = '#sf-sidebar-nav{position:fixed!important;left:0!important;top:0!important;width:200px!important;height:100vh!important;background:rgba(23,26,33,.97)!important;backdrop-filter:blur(12px)!important;-webkit-backdrop-filter:blur(12px)!important;border-right:1px solid rgba(255,255,255,.08)!important;z-index:9990!important;transform:translateX(-100%)!important;transition:transform .35s cubic-bezier(.4,0,.2,1)!important;overflow-y:auto!important;overflow-x:hidden!important;padding-top:60px!important;box-shadow:4px 0 20px rgba(0,0,0,.3)!important}#sf-sidebar-nav.sf-sidebar-open{transform:translateX(0)!important}#sf-sidebar-nav::-webkit-scrollbar{width:4px!important}#sf-sidebar-nav::-webkit-scrollbar-thumb{background:rgba(255,255,255,.15)!important;border-radius:2px!important}#sf-sidebar-nav::-webkit-scrollbar-track{background:transparent!important}.sf-sidebar-title{padding:12px 18px 16px!important;font-size:14px!important;font-weight:700!important;color:#fff!important;letter-spacing:.5px!important;display:flex!important;align-items:center!important;gap:8px!important}.sf-sidebar-title svg{width:18px!important;height:18px!important;fill:var(--sf-accent)!important}.sf-sidebar-item{display:flex!important;align-items:center!important;gap:10px!important;padding:10px 18px!important;color:var(--sf-text)!important;font-size:13px!important;font-weight:500!important;cursor:pointer!important;transition:all .2s ease!important;border-left:3px solid transparent!important;white-space:nowrap!important}.sf-sidebar-item:hover{background:var(--sf-hover)!important;color:#fff!important}.sf-sidebar-item.active{background:rgba(102,192,244,.1)!important;color:var(--sf-accent)!important;border-left-color:var(--sf-accent)!important}.sf-sidebar-item svg{width:16px!important;height:16px!important;fill:currentColor!important;flex-shrink:0!important}.sf-sidebar-item .sf-sidebar-count{margin-left:auto!important;padding:1px 6px!important;background:rgba(255,255,255,.1)!important;border-radius:8px!important;font-size:10px!important;color:var(--sf-dim)!important}.sf-sidebar-item.active .sf-sidebar-count{background:rgba(102,192,244,.2)!important;color:var(--sf-accent)!important}.sf-sidebar-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:9989;opacity:0;visibility:hidden;transition:all .3s ease;backdrop-filter:blur(2px)}.sf-sidebar-overlay.sf-show{opacity:1;visibility:visible}body.sf-sidebar-mode .responsive_page_content{margin-left:200px!important;margin-right:0!important;max-width:calc(100vw - 232px)!important;width:auto!important;box-sizing:border-box!important;transition:margin-left .35s cubic-bezier(.4,0,.2,1),max-width .35s cubic-bezier(.4,0,.2,1)!important}body.sf-sidebar-mode .responsive_page_frame{padding-left:0!important}body.sf-sidebar-mode #blotter_page{max-width:none!important}'; // ==================== 兼容 Steam 游戏库展示个人信息面板 ==================== const CSS_COMPAT_SGIS = '/* 兼容 Steam 游戏库展示 (sgis) 个人信息面板:面板打开时瀑布流浮窗让出右侧侧边栏 */body.sgis-open #sf-wf-overlay{right:440px!important;left:0!important;width:calc(100vw - 440px)!important;transition:opacity .35s ease,visibility .35s ease,right .4s cubic-bezier(.4,0,.2,1),width .4s cubic-bezier(.4,0,.2,1)!important}body.sgis-open #sf-wf-overlay .sf-wf-header{left:0!important;width:100%!important}body.sgis-open #sf-wf-overlay .sf-wf-body{max-width:calc(100vw - 488px)!important;margin:0!important;padding-left:24px!important}body.sgis-open #sf-wf-overlay .sf-wf-grid{justify-content:flex-start!important}body.sgis-open #sf-waterfall-btn,body.sgis-open #sf-friend-btn,body.sgis-open #sf-library-btn,body.sgis-open #sf-settings-btn,body.sgis-open #sf-top-btn{right:470px!important}@media(max-width:600px){body.sgis-open #sf-wf-overlay{right:0!important;width:100vw!important}body.sgis-open #sf-wf-overlay .sf-wf-body{max-width:none!important;padding-left:12px!important}body.sgis-open #sf-waterfall-btn,body.sgis-open #sf-friend-btn,body.sgis-open #sf-library-btn,body.sgis-open #sf-settings-btn,body.sgis-open #sf-top-btn{right:16px!important}}'; // ==================== 图片浮层 ==================== const CSS_OVERLAY = '.sf-overlay{position:fixed!important;inset:0!important;z-index:99999!important;background:rgba(0,0,0,.92)!important;display:flex!important;flex-direction:column!important;align-items:center!important;justify-content:center!important;opacity:0!important;visibility:hidden!important;transition:all .3s ease!important}.sf-overlay.sf-show{opacity:1!important;visibility:visible!important}.sf-overlay-img-wrap{flex:1!important;display:flex;align-items:center;justify-content:center!important;width:100%!important;padding:20px!important;position:relative!important;overflow:hidden!important}.sf-overlay-img{max-width:calc(100vw - 40px)!important;max-height:calc(100vh - 120px)!important;object-fit:contain!important;border-radius:4px!important;user-select:none!important;transition:transform .2s ease!important;cursor:grab!important}.sf-overlay-img:active{cursor:grabbing!important}.sf-overlay-img.sf-can-drag{cursor:grab!important}.sf-overlay-img.sf-dragging{cursor:grabbing!important}.sf-overlay-toolbar{width:100%!important;padding:12px 24px!important;background:rgba(0,0,0,.6)!important;backdrop-filter:blur(10px)!important;display:flex!important;align-items:center!important;justify-content:center!important;gap:20px!important;flex-shrink:0!important}.sf-overlay-action{display:flex!important;flex-direction:column!important;align-items:center!important;gap:4px!important;color:#8f98a0!important;font-size:11px!important;cursor:pointer!important;transition:color .2s!important;padding:6px 12px!important;border-radius:8px!important;background:transparent!important;border:none!important;font-family:inherit!important}.sf-overlay-action:hover{color:#66c0f4!important;background:rgba(255,255,255,.05)!important}.sf-overlay-action:disabled,.sf-overlay-action[disabled]{opacity:.35!important;cursor:not-allowed!important}.sf-overlay-action svg{width:22px!important;height:22px!important;fill:currentColor!important}.sf-overlay-close{position:fixed!important;top:16px!important;right:16px!important;width:40px!important;height:40px!important;border-radius:50%!important;background:rgba(255,255,255,.1)!important;border:none!important;cursor:pointer!important;display:flex!important;align-items:center!important;justify-content:center!important;color:#fff!important;transition:all .2s!important;z-index:100000!important}.sf-overlay-close:hover{background:rgba(244,67,54,.4)!important}.sf-overlay-close svg{width:20px!important;height:20px!important;fill:currentColor!important}.sf-overlay-nav{position:fixed!important;top:50%!important;transform:translateY(-50%)!important;width:48px!important;height:48px!important;border-radius:50%!important;background:rgba(0,0,0,.5)!important;border:1px solid rgba(255,255,255,.1)!important;cursor:pointer!important;display:flex!important;align-items:center!important;justify-content:center!important;color:#fff!important;transition:all .2s!important;z-index:100000!important}.sf-overlay-nav:hover:not([disabled]){background:rgba(102,192,244,.3)!important;transform:translateY(-50%) scale(1.1)!important}.sf-overlay-nav:disabled{opacity:.3!important;cursor:not-allowed!important}.sf-overlay-nav svg{width:24px!important;height:24px!important;fill:currentColor!important}.sf-overlay-nav-prev{left:16px!important}.sf-overlay-nav-next{right:16px!important}.sf-overlay-counter{position:fixed!important;top:20px!important;left:50%!important;transform:translateX(-50%)!important;color:#fff!important;font-size:14px!important;font-weight:600!important;background:rgba(0,0,0,.5)!important;padding:4px 12px!important;border-radius:12px!important;z-index:100000!important}.sf-ov-zoom{position:fixed!important;bottom:80px!important;left:20px!important;color:#fff!important;font-size:13px!important;font-weight:600!important;background:rgba(0,0,0,.5)!important;padding:4px 10px!important;border-radius:8px!important;z-index:100000!important}.sf-ov-hint{position:fixed!important;bottom:80px!important;right:20px!important;color:#8f98a0!important;font-size:12px!important;background:rgba(0,0,0,.5)!important;padding:4px 10px!important;border-radius:8px!important;opacity:0!important;transition:opacity .3s ease!important;z-index:100000!important}.sf-ov-hint.sf-show{opacity:1!important}.sf-comments-collapsed{display:none!important}.sf-comments-expanded{display:block!important}.sf-collapse-hint{padding:10px 14px!important;background:rgba(0,0,0,.3)!important;border-radius:8px!important;margin:12px 16px!important;display:flex!important;align-items:center!important;justify-content:space-between!important;cursor:pointer!important;font-size:13px!important;color:var(--sf-text)!important;transition:all .2s ease!important}.sf-collapse-hint:hover{background:rgba(0,0,0,.4)!important;color:#fff!important}.sf-collapse-hint-text{display:flex!important;align-items:center!important;gap:8px!important}.sf-collapse-hint-text svg{width:16px!important;height:16px!important;fill:var(--sf-accent)!important;flex-shrink:0!important}.sf-collapse-hint-arrow{transition:transform .25s ease!important;display:flex!important;align-items:center!important}.sf-collapse-hint-arrow svg{width:18px!important;height:18px!important;fill:var(--sf-text)!important}.sf-collapse-hint.sf-expanded .sf-collapse-hint-arrow{transform:rotate(180deg)!important}'; // ==================== 页面增强 ==================== const CSS_PAGE_ENHANCED = '.sf-page-enhanced .rightCol,.sf-page-enhanced .rightDetails,.sf-page-enhanced [class*="rightCol"],.sf-page-enhanced [class*="rightDetails"]{display:none!important}.sf-page-enhanced .workshopItemPreviewHolder,.sf-page-enhanced #sharedfiles_previewarea,.sf-page-enhanced [class*="previewHolder"],.sf-page-enhanced [class*="previewArea"]{max-width:100%!important;width:100%!important;margin:0 auto!important}.sf-page-enhanced .actualScreenshotImg,.sf-page-enhanced #screenshotImage,.sf-page-enhanced .workshopItemPreviewHolder img{max-width:100%!important;width:auto!important;height:auto!important;cursor:zoom-in!important;display:block!important;margin:0 auto!important}'; // ==================== Toast ==================== const CSS_TOAST = '.sf-toast{position:fixed!important;top:24px!important;left:50%!important;transform:translateX(-50%) translateY(-10px)!important;padding:10px 24px!important;border-radius:8px!important;font-size:14px!important;font-weight:500!important;z-index:100001!important;opacity:0!important;transition:all .3s ease!important;pointer-events:none!important;box-shadow:0 4px 16px rgba(0,0,0,.5)!important}.sf-toast.sf-toast--visible{opacity:1!important;transform:translateX(-50%) translateY(0)!important}.sf-toast--info{background:#2a475e!important;color:#c7d5e0!important;border:1px solid #4a6a7a!important}.sf-toast--warn{background:rgba(234,179,8,.9)!important;color:#1a1a1a!important}.sf-toast--error{background:rgba(244,67,54,.9)!important;color:#fff!important}'; // ==================== 移动端 ==================== const CSS_MOBILE = '@media(max-width:600px){#sf-waterfall-btn,#sf-friend-btn,#sf-library-btn,#sf-settings-btn,#sf-top-btn{right:16px!important;width:42px!important;height:42px!important}#sf-waterfall-btn{bottom:14px!important}#sf-friend-btn{bottom:62px!important}#sf-library-btn{bottom:110px!important}#sf-settings-btn{bottom:158px!important}#sf-top-btn{bottom:206px!important}#sf-filter-bar{padding:8px 10px!important;gap:6px!important}.sf-filter-btn{padding:6px 10px!important;font-size:12px!important}.sf-filter-label{display:none!important}.sf-wf-grid{flex-direction:column!important}.sf-wf-col{max-width:none!important;min-width:0!important}.sf-wf-body{padding:12px!important}#sf-sidebar-nav{width:180px!important;padding-top:50px!important}body.sf-sidebar-mode .responsive_page_content{margin-left:180px!important;margin-right:0!important;max-width:calc(100vw - 196px)!important;width:auto!important;box-sizing:border-box!important}body.sf-sidebar-mode #blotter_page{max-width:none!important}#sf-friend-panel{width:calc(100vw - 32px)!important;right:16px!important;max-height:75vh!important}#sf-fa-panel{width:100vw!important;max-width:100vw!important}#sf-lib-panel{width:96vw!important;height:90vh!important}#sf-settings-panel{width:92vw!important}}'; // ==================== 合并所有 CSS ==================== const CSS = [ CSS_VARS, CSS_LAYOUT, CSS_CARDS, CSS_SCREENSHOTS, CSS_GAMES, CSS_WORKSHOP, CSS_CONTROLS, CSS_DATE_STATUS, CSS_MODAL, CSS_FILTER, CSS_LOAD_EMPTY, CSS_FAB_BASE, CSS_FAB_EACH, CSS_FAB_HOVER, CSS_FAB_LABEL, CSS_WATERFALL, CSS_WF_OVERRIDES, CSS_WF_LOADING, CSS_FRIEND_PANEL, CSS_FRIEND_OWN, CSS_FRIEND_CATEGORY, CSS_FRIEND_ITEM, CSS_FRIEND_MISC, CSS_FRIEND_FOOTER, CSS_FA_PANEL, CSS_FA_OVERVIEW, CSS_FA_GAMES, CSS_LIBRARY_PANEL, CSS_LIB_GAMES, CSS_LIB_PAGINATION, CSS_DELISTED_ENHANCED, CSS_USERINFO, CSS_SETTINGS_PANEL, CSS_SIDEBAR, CSS_COMPAT_SGIS, CSS_OVERLAY, CSS_PAGE_ENHANCED, CSS_TOAST, CSS_MOBILE ].join(''); // ==================== SVG 图标 ==================== const ICONS = { all: '', screenshot: '', game: '', review: '', workshop: '', favorite: '', wishlist: '', achievement: '', close: '', expand: '', collapse: '', fullscreen: '', comment: '', chevronLeft: '', chevronRight: '', chevronDown: '', zoomIn: '', zoomOut: '', refresh: '', download: '', history: '', top: '', waterfall: '', sidebar: '', topbar: '', library: '', activity: '', chartBar: '', shield: '', key: '', trending: '', trash: '', settings: '', friends: '', sync: '', award: '', }; // ==================== SVG 国旗库(精选 50+ 国家)==================== const FLAGS = { CN:'', US:'', JP:'', KR:'', TW:'', HK:'', MO:'', GB:'', DE:'', FR:'', RU:'', AU:'', CA:'', BR:'', IN:'', SG:'', MY:'', TH:'', VN:'', ID:'', PH:'', NZ:'', SE:'', NO:'', FI:'', DK:'', NL:'', IT:'', ES:'', PL:'', UA:'', TR:'', SA:'', AE:'', EG:'', ZA:'', IL:'', MX:'', AR:'', CL:'', CH:'', AT:'', BE:'', PT:'', GR:'', CZ:'', HU:'', RO:'', IE:'', LT:'', LV:'', EE:'', IS:'', }; function flagSvg(cc) { if (!cc || cc.length !== 2) return ''; const uc = cc.toUpperCase(); const svg = FLAGS[uc]; const style = 'display:inline-block;width:14px;height:10px;vertical-align:middle;flex-shrink:0;border-radius:1px;'; if (svg) return svg.replace('`; } // ==================== 工具函数 ==================== const $ = sel => document.querySelector(sel); const $$ = sel => document.querySelectorAll(sel); const esc = s => { if (s == null) return ''; const d = document.createElement('div'); d.textContent = String(s); return d.innerHTML; }; const el = (tag, attrs, children) => { const e = document.createElement(tag); if (attrs) { const { dataset, ...rest } = attrs; Object.assign(e, rest); if (dataset) Object.assign(e.dataset, dataset); } if (children) e.innerHTML = children; return e; }; const debounce = (fn, wait) => { let t; return function(...args) { clearTimeout(t); t = setTimeout(() => fn.apply(this, args), wait); }; }; const findShortestCol = cols => { let shortest = cols[0], minH = shortest.offsetHeight; for (let i = 1; i < cols.length; i++) { if (cols[i].offsetHeight < minH) { shortest = cols[i]; minH = cols[i].offsetHeight; } } return shortest; }; const sleep = ms => new Promise(r => setTimeout(r, ms)); // ==================== 统一 Toast 系统 ==================== let _toastTimer = null; let _currentToast = null; function showToast(msg, type = 'info', ms = 1500) { if (_currentToast) { _currentToast.remove(); _currentToast = null; } if (_toastTimer) { clearTimeout(_toastTimer); _toastTimer = null; } const t = document.createElement('div'); t.className = `sf-toast sf-toast--${type}`; t.textContent = msg; document.body.appendChild(t); _currentToast = t; requestAnimationFrame(() => requestAnimationFrame(() => t.classList.add('sf-toast--visible'))); _toastTimer = setTimeout(() => { t.classList.remove('sf-toast--visible'); setTimeout(() => { t.remove(); if (_currentToast === t) _currentToast = null; }, 300); }, ms); } // ==================== 资源清理钩子 ==================== const cleanupFns = []; function registerCleanup(fn) { cleanupFns.push(fn); } window.addEventListener('beforeunload', () => { cleanupFns.forEach(fn => { try { fn(); } catch (e) { /* ignore */ } }); cleanupFns.length = 0; }); // ==================== 配置常量 ==================== const CONFIG = { INITIAL_LOAD: 100, LOAD_MORE_STEP: 50, CACHE_TTL: 300000, CACHE_MAX_SIZE: 200, REQUEST_TIMEOUT: 10000, WF_BATCH_SIZE: 20, WF_LOAD_THRESHOLD: 800, WF_MAX_SHOW: 50, MAIN_MAX_SHOW: 8, OBSERVER_DEBOUNCE: 100, STEAM_API_BASE: 'https://api.steampowered.com', DELISTED_DB_URL: 'https://xget.xi-xu.me/gh/SmallRob/steam-namespace/raw/refs/heads/main/data/steam_delisted_apps.json', ANNUAL_AAA_URL: 'https://xget.xi-xu.me/gh/SmallRob/steam-namespace/raw/refs/heads/main/data/2026.json', STEAMAPI_BATCH: 100, }; const STEAM_ORIGINAL_IMAGE_SIZE = 5000; const GAME_CAPSULE_URL = 'https://cdn.cloudflare.steamstatic.com/steam/apps/{appid}/capsule_231x87.jpg'; // 多 CDN 基址(参考 steam-game-library-viewer lib core:cloudflare → akamai → shared) const CDN_BASES = [ 'https://cdn.cloudflare.steamstatic.com/steam/apps/', 'https://cdn.akamai.steamstatic.com/steam/apps/', 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/', ]; const CDN_BASE = CDN_BASES[0]; const SVG_PLACEHOLDER = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='231' height='87'%3E%3Crect fill='%23333' width='231' height='87'/%3E%3Ctext x='50%25' y='50%25' text-anchor='middle' dy='.3em' fill='%23999' font-size='14'%3ENo Image%3C/text%3E%3C/svg%3E`; const FALLBACK_CAPSULE_PATHS = [ 'capsule_231x87.jpg', 'capsule_231x87.png', 'header.jpg', 'capsule_184x69.jpg', 'capsule_467x181.jpg', ]; const GAME_ICON_URL = appid => `https://cdn.cloudflare.steamstatic.com/steam/apps/${appid}/capsule_sm_120.jpg`; const GAME_STORE_URL = appid => `https://store.steampowered.com/app/${appid}`; // 成功 URL 内存缓存(参考 lib core _posterGoodUrl:命中后直接用,避免重走回退链) const _coverGoodUrl = new Map(); function recordCoverGood(appid, url) { if (appid && url) _coverGoodUrl.set(String(appid), url); } function getCoverGood(appid) { return _coverGoodUrl.get(String(appid)); } // 多 CDN 回退链构造(参考 lib core coverChainUrls) function coverChainUrls(appid) { const id = String(appid); const chain = []; for (const base of CDN_BASES) { for (const path of ['capsule_231x87.jpg', 'header.jpg']) { chain.push(base + id + '/' + path); } } return chain; } // 占位 div 替换(参考 lib core _posterImgToPlaceholder:失败时用 div 占位而非 display:none,避免错位) function coverImgToPlaceholder(img) { if (!img || !img.parentElement) return; const holder = document.createElement('div'); holder.className = img.className; holder.style.cssText = 'display:flex;align-items:center;justify-content:center;color:#5a6a7a;font-size:11px;background:rgba(0,0,0,0.3);border-radius:6px'; holder.textContent = '无封面'; img.parentElement.replaceChild(holder, img); } // 封面图 API 缓存与去重 const _capsuleCache = new Map(); const _capsulePending = new Set(); const _capsuleWaiters = new Map(); function fetchCapsuleFromAPI(appid) { return new Promise((resolve, reject) => { if (_capsuleCache.has(appid)) { resolve(_capsuleCache.get(appid)); return; } if (_capsulePending.has(appid)) { if (!_capsuleWaiters.has(appid)) _capsuleWaiters.set(appid, []); _capsuleWaiters.get(appid).push({ resolve, reject }); return; } _capsulePending.add(appid); if (!_capsuleWaiters.has(appid)) _capsuleWaiters.set(appid, []); _capsuleWaiters.get(appid).push({ resolve, reject }); const apiUrl = `https://store.steampowered.com/api/appdetails?appids=${appid}&filters=basic`; const doRequest = typeof GM_xmlhttpRequest !== 'undefined' ? GM_xmlhttpRequest : null; const notifyWaiters = (value, isError) => { const waiters = _capsuleWaiters.get(appid) || []; _capsuleWaiters.delete(appid); waiters.forEach(w => { if (isError) w.reject(); else w.resolve(value); }); }; if (doRequest) { doRequest({ method: 'GET', url: apiUrl, onload: (resp) => { let url = ''; try { const json = JSON.parse(resp.responseText); const data = json[appid]; if (data && data.success && data.data) { // filters=basic 同时返回 capsule_image 与 header_image,优先 capsule url = data.data.capsule_image || data.data.header_image || ''; } } catch (e) { url = ''; } _capsuleCache.set(appid, url); if (url) recordCoverGood(appid, url); _capsulePending.delete(appid); notifyWaiters(url, !url); }, onerror: () => { _capsuleCache.set(appid, ''); _capsulePending.delete(appid); notifyWaiters('', true); }, ontimeout: () => { _capsuleCache.set(appid, ''); _capsulePending.delete(appid); notifyWaiters('', true); }, }); } else { fetch(apiUrl) .then(r => r.json()) .then(json => { const data = json[appid]; const url = (data && data.success && data.data && data.data.capsule_image) ? data.data.capsule_image : ''; _capsuleCache.set(appid, url); _capsulePending.delete(appid); notifyWaiters(url, !url); }) .catch(() => { _capsuleCache.set(appid, ''); _capsulePending.delete(appid); notifyWaiters('', true); }); } }); } function loadCapsuleWithFallback(img, appid) { if (!img || !appid) return; // 1. 命中成功缓存直接用 const good = getCoverGood(appid); const chain = good ? [good] : coverChainUrls(appid); let idx = 0; // 2. 回退链走完后走 API 兜底 function tryApi() { fetchCapsuleFromAPI(appid).then(url => { if (url) { img.onload = () => recordCoverGood(appid, img.src); img.onerror = () => { img.onload = null; img.onerror = null; coverImgToPlaceholder(img); }; img.src = url; } else { coverImgToPlaceholder(img); } }).catch(() => coverImgToPlaceholder(img)); } function tryNext() { if (idx >= chain.length) { tryApi(); return; } img.onload = () => recordCoverGood(appid, img.src); img.onerror = () => { img.onload = null; idx++; tryNext(); }; img.src = chain[idx]; } tryNext(); } function extractAppidFromContext(logoElement) { let m; const dataAppid = logoElement.getAttribute('data-appid'); if (dataAppid) return dataAppid; m = (logoElement.getAttribute('href') || '').match(/\/app\/(\d+)/); if (m) return m[1]; const innerA = logoElement.querySelector('a[href*="/app/"]'); if (innerA) { m = (innerA.getAttribute('href') || '').match(/\/app\/(\d+)/); if (m) return m[1]; } const block = logoElement.closest('.blotter_gamepurchase'); if (!block) return null; const usedAppids = new Set(); block.querySelectorAll('.blotter_gamepurchase_logo').forEach(l => { if (l === logoElement) return; const a = l.getAttribute('data-appid'); if (a) usedAppids.add(a); const h = (l.getAttribute('href') || '').match(/\/app\/(\d+)/); if (h) usedAppids.add(h[1]); }); const detailLinks = block.querySelectorAll('.blotter_gamepurchase_details a[href*="/app/"]'); for (const link of detailLinks) { m = (link.getAttribute('href') || '').match(/\/app\/(\d+)/); if (m && !usedAppids.has(m[1])) { logoElement.setAttribute('data-appid', m[1]); return m[1]; } } return null; } function fixGamePurchaseImages() { $$('.blotter_gamepurchase_logo').forEach(logo => { if (logo.dataset.sfImgFixed) return; logo.dataset.sfImgFixed = '1'; const appid = extractAppidFromContext(logo); if (!appid) return; const img = logo.querySelector('img'); const inlineSvg = logo.querySelector('svg'); if (img) { const src = img.src || ''; const isSvgDataUri = src.startsWith('data:image/svg'); const isSvgFile = /\.svg(\?|$)/i.test(src); const isEmpty = !src || src === 'about:blank'; if (isSvgDataUri || isSvgFile || isEmpty) { loadCapsuleWithFallback(img, appid); } else { img.addEventListener('error', () => loadCapsuleWithFallback(img, appid), { once: true }); } } else if (inlineSvg) { inlineSvg.remove(); const newImg = document.createElement('img'); newImg.alt = `Game #${appid}`; newImg.loading = 'lazy'; logo.appendChild(newImg); loadCapsuleWithFallback(newImg, appid); } }); } // ==================== 状态管理 ==================== const state = { currentFilter: 'all', visibleCount: 0, totalBlocks: [], blockTypes: new WeakMap(), wfOpen: false, wfBlocks: [], wfLoadedCount: 0, wfAllLoaded: false, layoutMode: (() => { try { return localStorage.getItem('sf-layout-mode') || 'topbar'; } catch (e) { return 'topbar'; } })(), sidebarVisible: false, // === v4.0/v4.1 新增 === apiKey: '', mySteamId: '', delistedDB: null, annualAaaDB: null, // 好友浮窗内每个好友的扩展信息(游戏数 / 封禁 / 等级) friendExtras: new Map(), // 当前打开的好友动态浮窗状态 faPanel: { open: false, steamId: '', tab: 'overview', subcat: 'all', data: null, delisted: null, annual: null }, // 当前打开的个人游戏库状态:library=全部游戏, recent=最近游玩, playtime=时长, activity=入库列表, delisted=绝版收藏 libPanel: { open: false, tab: 'library', data: null, delisted: null, activity: null, recent: null, allGames: null, annual: null }, // 独立设置窗口状态 settingsPanel: { open: false, pendingAction: null }, // 好友浮窗数据缓存 fpData: null, }; // ==================== 缓存管理(带 LRU 简化)==================== const cache = { data: new Map(), get(key) { const item = this.data.get(key); if (!item) return null; if (Date.now() - item.timestamp > CONFIG.CACHE_TTL) { this.data.delete(key); return null; } this.data.delete(key); this.data.set(key, item); return item.value; }, set(key, value) { if (this.data.size >= CONFIG.CACHE_MAX_SIZE) { const firstKey = this.data.keys().next().value; if (firstKey !== undefined) this.data.delete(firstKey); } this.data.set(key, { value, timestamp: Date.now() }); }, clear() { this.data.clear(); }, }; // ==================== Steam API Key 与 SteamID 管理(持久化)==================== function getApiKey() { if (state.apiKey) return state.apiKey; try { state.apiKey = (typeof GM_getValue === 'function' ? GM_getValue('sf_api_key', '') : localStorage.getItem('sf_api_key') || '') || ''; } catch (e) { state.apiKey = ''; } return state.apiKey; } function setApiKey(key) { state.apiKey = key || ''; try { if (typeof GM_setValue === 'function') GM_setValue('sf_api_key', state.apiKey); else localStorage.setItem('sf_api_key', state.apiKey); } catch (e) { /* ignore */ } } function getStoredSteamId() { try { return (typeof GM_getValue === 'function' ? GM_getValue('sf_steam_id', '') : localStorage.getItem('sf_steam_id') || '') || ''; } catch (e) { return ''; } } function setStoredSteamId(id) { try { if (typeof GM_setValue === 'function') GM_setValue('sf_steam_id', id || ''); else localStorage.setItem('sf_steam_id', id || ''); } catch (e) { /* ignore */ } } function miniIdToSteamId(miniId) { if (!miniId || !/^\d+$/.test(String(miniId))) return ''; try { const sid = BigInt(miniId) + 76561197960265728n; const str = String(sid); return /^\d{17}$/.test(str) ? str : ''; } catch (e) { return ''; } } function getMySteamId() { if (state.mySteamId) return state.mySteamId; // 优先使用用户手动配置的 SteamID const stored = getStoredSteamId(); if (/^\d{17}$/.test(stored)) { state.mySteamId = stored; return stored; } // 其次用 activity 页面 URL 的 steamid const m = (window.location.pathname || '').match(/\/(?:id|profiles)\/([^/]+)/); if (m) { const id = m[1]; if (/^\d{17}$/.test(id)) { state.mySteamId = id; return state.mySteamId; } } // 自定义 URL 或未在 URL 中暴露数字 ID:只能从当前用户专属元素获取 // 1) g_steamID 全局变量 const gsid = (typeof window.g_steamID !== 'undefined' && window.g_steamID) || (typeof window.g_steamid !== 'undefined' && window.g_steamid) || ''; if (/^\d{17}$/.test(gsid)) { state.mySteamId = gsid; return gsid; } // 2) 当前用户头像/链接的 data-miniprofile(好友列表面板 .webchat_own_avatar 或好友列表入口) const ownMiniSelectors = [ '.webchat_own_avatar [data-miniprofile]', '.friendslist_entry_content .linkFriend[data-miniprofile]', '#global_actions .user_avatar_frame[data-miniprofile]', '#account_pulldown [data-miniprofile]', '.account_name [data-miniprofile]', 'a[href="/id/"][data-miniprofile]', 'a[href="/profiles/"][data-miniprofile]', ]; for (const sel of ownMiniSelectors) { const el = document.querySelector(sel); const mini = el?.dataset?.miniprofile || el?.getAttribute('data-miniprofile'); const sid = mini ? miniIdToSteamId(mini) : ''; if (/^\d{17}$/.test(sid)) { state.mySteamId = sid; return sid; } } // 3) 当前用户专属链接(排除可能匹配到好友的通用 a[href*="/profiles/"]) const ownLinkSelectors = [ '.webchat_own_avatar a[href*="/profiles/"]', '.friendslist_entry_content .linkFriend[href*="/profiles/"]', '#global_actions a[href*="/profiles/"]', '#account_pulldown a[href*="/profiles/"]', ]; for (const sel of ownLinkSelectors) { const el = document.querySelector(sel); const href = el?.getAttribute('href') || ''; const match = href.match(/profiles\/(\d{17})/); if (match && /^\d{17}$/.test(match[1])) { state.mySteamId = match[1]; return match[1]; } } return state.mySteamId; } // ==================== 智能并发池(参考 1.0.16,遇 429 自动降速)==================== async function runWithConcurrency(items, limit, worker) { const results = new Array(items.length); let cursor = 0, active = 0; return new Promise((resolve) => { const launch = () => { while (active < limit && cursor < items.length) { const idx = cursor++; active++; Promise.resolve() .then(() => worker(items[idx], idx)) .then(r => { results[idx] = r; }) .catch(err => { results[idx] = { error: err }; }) .finally(() => { active--; if (cursor < items.length) launch(); else if (active === 0) resolve(results); }); } }; launch(); }); } // ==================== Steam Web API 请求(带超时 + 简单重试)==================== async function steamApi(path, params = {}, opts = {}) { const url = new URL(path.startsWith('http') ? path : `${CONFIG.STEAM_API_BASE}${path}`); Object.entries(params).forEach(([k, v]) => { if (v != null) url.searchParams.set(k, v); }); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), opts.timeout || CONFIG.REQUEST_TIMEOUT); try { const resp = await fetch(url.href, { signal: controller.signal, credentials: 'omit' }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); return await resp.json(); } finally { clearTimeout(timeout); } } // ==================== 游戏中文名获取(参考 steam-friend-manager-1.1.2 fetchGameZhName) ==================== const SF_GAME_NAME_KEY = 'sf_game_name_cache'; const SF_NAME_CACHE_TTL = 30 * 864e5; // 30天过期 let _sfGameNameCache = null; const _sfGameNamePending = new Map(); function isZhLocale() { const l = (document.documentElement.lang || '').toLowerCase(); return l.startsWith('zh'); } function sfGameNameCacheLoad() { if (_sfGameNameCache !== null) return _sfGameNameCache; _sfGameNameCache = {}; try { const raw = GM_getValue(SF_GAME_NAME_KEY); if (raw && typeof raw === 'object') _sfGameNameCache = raw; } catch (e) {} return _sfGameNameCache; } function sfGameNameCacheSave() { try { GM_setValue(SF_GAME_NAME_KEY, _sfGameNameCache || {}); } catch (e) {} } function fetchGameZhName(appid) { if (!isZhLocale()) return Promise.resolve(''); const id = String(appid); const cache = sfGameNameCacheLoad(); const cached = cache[id]; if (cached && cached.name && Date.now() - (cached.ts || 0) < SF_NAME_CACHE_TTL) { return Promise.resolve(cached.name); } // 如果正在批量加载中,等待批量完成后再查缓存,避免重复单条请求 if (_batchNameLock.has(id)) { return new Promise(resolve => { const check = (retries = 20) => { const c = sfGameNameCacheLoad()[id]; if (c && c.name) { resolve(c.name); return; } if (!_batchNameLock.has(id) || retries <= 0) { // 批量结束仍未命中,回退到单条请求 _fetchGameZhNameSingle(id).then(resolve); } else { setTimeout(() => check(retries - 1), 300); } }; check(); }); } if (_sfGameNamePending.has(id)) return _sfGameNamePending.get(id); return _fetchGameZhNameSingle(id); } // 单条 appdetails 请求(兜底) function _fetchGameZhNameSingle(id) { if (_sfGameNamePending.has(id)) return _sfGameNamePending.get(id); const cache = sfGameNameCacheLoad(); const p = new Promise((resolve) => { const doReq = typeof GM_xmlhttpRequest !== 'undefined' ? GM_xmlhttpRequest : null; if (!doReq) { resolve(''); return; } doReq({ method: 'GET', url: `https://store.steampowered.com/api/appdetails?appids=${id}&l=schinese&filters=basic`, timeout: 15000, onload: (resp) => { let name = ''; try { const d = JSON.parse(resp.responseText); if (d && d[id] && d[id].success && d[id].data && d[id].data.name) name = d[id].data.name; } catch (e) {} if (name) { cache[id] = { name, ts: Date.now() }; sfGameNameCacheSave(); } resolve(name); }, onerror: () => resolve(''), ontimeout: () => resolve(''), }); }); _sfGameNamePending.set(id, p); p.finally(() => { _sfGameNamePending.delete(id); }); return p; } // 异步加载中文名并更新 DOM 元素 function loadGameZhName(el, appid, originalName) { if (!el || !appid) return; fetchGameZhName(appid).then(zhName => { if (zhName && zhName !== originalName) { el.textContent = zhName; el.title = `${zhName} (${originalName})`; // 清除占位名(App xxxxxx / 空)遗留的内联样式,让 CSS 类样式生效 el.style.color = ''; el.style.fontStyle = ''; el.style.fontWeight = ''; } }); } // ==================== 批量中文名预加载(参考 lib core IStoreBrowseService/GetItems) ==================== // 100 个/批、3 路并发,匿名可用,几秒完成数百条,替代绝大多数 appdetails 单条请求 const _batchNameLock = new Set(); // 防止同一批 appid 重复请求 function getCountryCode() { try { const win = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window; return win.g_strCountryCode || 'CN'; } catch { return 'CN'; } } async function batchPreloadZhNames(appids) { if (!isZhLocale() || !appids || appids.length === 0) return; const cache = sfGameNameCacheLoad(); // 过滤出需要请求的 appid(缓存未命中或过期) const need = []; const seen = new Set(); for (const id of appids) { const sid = String(id); if (seen.has(sid)) continue; seen.add(sid); const c = cache[sid]; if (!c || !c.name || Date.now() - (c.ts || 0) >= SF_NAME_CACHE_TTL) { if (!_batchNameLock.has(sid)) need.push(sid); } } if (need.length === 0) return; // 标记锁定 need.forEach(id => _batchNameLock.add(id)); const CHUNK = 100, CONCURRENCY = 3; const chunks = []; for (let i = 0; i < need.length; i += CHUNK) chunks.push(need.slice(i, i + CHUNK)); const cc = getCountryCode(); const lang = 'schinese'; let qIdx = 0; const worker = async () => { while (qIdx < chunks.length) { const chunk = chunks[qIdx++]; try { const input = { ids: chunk.map(id => ({ appid: Number(id) })), context: { language: lang, country_code: cc, steam_realm: 1 }, data_request: { include_release: true, include_all_purchase_options: false, include_platforms: false, include_assets: true } }; const url = 'https://api.steampowered.com/IStoreBrowseService/GetItems/v1?input_json=' + encodeURIComponent(JSON.stringify(input)); const doReq = typeof GM_xmlhttpRequest !== 'undefined' ? GM_xmlhttpRequest : null; if (!doReq) { await new Promise(r => setTimeout(r, 50)); continue; } const respText = await new Promise((resolve) => { doReq({ method: 'GET', url, timeout: 20000, onload: (r) => resolve(r.responseText || ''), onerror: () => resolve(''), ontimeout: () => resolve(''), }); }); if (respText) { const resp = JSON.parse(respText); const storeItems = resp?.response?.store_items || []; for (const si of storeItems) { if (si && si.appid) { const sid = String(si.appid); // 缓存中文名 if (si.name) { cache[sid] = { name: String(si.name), ts: Date.now() }; } // 预缓存封面 URL(GetItems assets 返回完整 URL) const assets = si.assets; if (assets) { const coverUrl = assets.header_image || assets.main_capsule || assets.small_capsule || ''; if (coverUrl) { _capsuleCache.set(sid, coverUrl); recordCoverGood(sid, coverUrl); } } } } } } catch (e) { /* 忽略单批失败 */ } await new Promise(r => setTimeout(r, 200)); } }; const workers = []; for (let i = 0; i < CONCURRENCY; i++) workers.push(worker()); await Promise.all(workers); // 解锁并持久化缓存 need.forEach(id => _batchNameLock.delete(id)); try { sfGameNameCacheSave(); } catch (e) {} } // 批量异步加载容器内所有 [data-sf-appid] 元素的中文名 // 先用 GetItems 批量预加载缓存,再逐个从缓存读取更新 DOM async function loadGameZhNamesInContainer(container) { if (!container || !isZhLocale()) return; const els = container.querySelectorAll('[data-sf-appid]'); if (els.length === 0) return; // 收集所有 appid const appids = []; els.forEach(el => { const id = el.getAttribute('data-sf-appid'); if (id) appids.push(id); }); // 批量预加载 await batchPreloadZhNames(appids); // 逐个从缓存读取更新 DOM(缓存未命中的回退到单条 appdetails) els.forEach(el => { const id = el.getAttribute('data-sf-appid'); if (!id) return; const cache = sfGameNameCacheLoad(); const c = cache[String(id)]; if (c && c.name) { const originalName = el.textContent; if (c.name !== originalName) { el.textContent = c.name; el.title = `${c.name} (${originalName})`; el.style.color = ''; el.style.fontStyle = ''; el.style.fontWeight = ''; } } else { // 缓存未命中,回退到单条请求 loadGameZhName(el, id, el.textContent); } }); } // ==================== 绝版游戏 DB 加载(单次下载,本地缓存)==================== // 优先 GM_getResourceText(@resource 预下载,无 CORS 问题),其次 GM_xmlhttpRequest,最后 fetch async function loadDelistedDB() { if (state.delistedDB) return state.delistedDB; const cacheKey = 'delisted_db'; const cached = cache.get(cacheKey); if (cached) { state.delistedDB = cached; return cached; } try { let jsonText = ''; // 1. 优先使用 @resource 预下载的数据(无 CORS 问题) if (typeof GM_getResourceText === 'function') { try { jsonText = GM_getResourceText('delistedData') || ''; } catch (e) { jsonText = ''; } } // 2. 回退到 GM_xmlhttpRequest(绕过 CORS) if (!jsonText && typeof GM_xmlhttpRequest !== 'undefined') { jsonText = await new Promise((resolve, reject) => { const controller = setTimeout(() => reject(new Error('GM_xhr timeout')), 30000); GM_xmlhttpRequest({ method: 'GET', url: CONFIG.DELISTED_DB_URL, onload: (resp) => { clearTimeout(controller); resolve(resp.responseText || ''); }, onerror: () => { clearTimeout(controller); reject(new Error('GM_xhr error')); }, ontimeout: () => { clearTimeout(controller); reject(new Error('GM_xhr timeout')); }, }); }); } // 3. 最后回退到 fetch(可能受 CORS 限制) if (!jsonText) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 30000); const resp = await fetch(CONFIG.DELISTED_DB_URL, { signal: controller.signal }); clearTimeout(timeout); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); jsonText = await resp.text(); } const data = JSON.parse(jsonText); state.delistedDB = data; cache.set(cacheKey, data); console.log('[SF] 绝版游戏 DB 加载成功,条目数', (data.data || data.delisted?.apps || []).length); return data; } catch (e) { console.warn('[SF] 绝版游戏 DB 加载失败:', e); state.delistedDB = { data: [] }; cache.set(cacheKey, state.delistedDB); return state.delistedDB; } } function getDelistedApps() { const db = state.delistedDB; if (!db) return []; let rawApps = []; if (Array.isArray(db.data)) rawApps = db.data; else if (db.delisted && Array.isArray(db.delisted.apps)) rawApps = db.delisted.apps; return rawApps.map(app => ({ appid: app.appId || app.appid, name: app.name, type: app.delistType || app.type || 'Delisted', changed: app.delistedAt || app.changed, owners: app.ownerPercentage != null ? app.ownerPercentage + '%' : (app.owners || '-'), // achievementCount: -1 表示无数据,0 表示无成就;仅保留正数,避免误显 "🏆 -1" achievements: (typeof app.achievementCount === 'number' && app.achievementCount > 0) ? app.achievementCount : (app.achievements || 0), keyshopPrice: app.keyshopPrice, source: app.source, })); } function getDelistedTypeKey(type) { if (!type) return 'delisted'; if (typeof type === 'string' && type.includes('_')) return type; const map = { 'Delisted': 'delisted', 'Purchase disabled': 'purchase_disabled', 'F2P (unavailable)': 'f2p_unavailable', 'Retail only': 'retail_only', 'Test app': 'test_app', }; return map[type] || 'delisted'; } function getDelistedTypeLabel(type) { if (!type) return '绝版'; const map = { 'delisted': '绝版', 'purchase_disabled': '停售', 'f2p_unavailable': 'F2P下架', 'retail_only': '仅实体', 'test_app': '测试', }; return map[type] || type; } // ==================== 年度大作游戏 DB 加载(单次下载,本地缓存)==================== // 数据源:[{name, date, appid}],date 格式如 "2026年1月22日" async function loadAnnualAaaDB() { if (state.annualAaaDB) return state.annualAaaDB; const cacheKey = 'annual_aaa_db'; const cached = cache.get(cacheKey); if (cached) { state.annualAaaDB = cached; return cached; } try { let jsonText = ''; // 1. 优先使用 @resource 预下载的数据 if (typeof GM_getResourceText === 'function') { try { jsonText = GM_getResourceText('annualAaaData') || ''; } catch (e) { jsonText = ''; } } // 2. 回退到 GM_xmlhttpRequest if (!jsonText && typeof GM_xmlhttpRequest !== 'undefined') { jsonText = await new Promise((resolve, reject) => { const controller = setTimeout(() => reject(new Error('GM_xhr timeout')), 30000); GM_xmlhttpRequest({ method: 'GET', url: CONFIG.ANNUAL_AAA_URL, onload: (resp) => { clearTimeout(controller); resolve(resp.responseText || ''); }, onerror: () => { clearTimeout(controller); reject(new Error('GM_xhr error')); }, ontimeout: () => { clearTimeout(controller); reject(new Error('GM_xhr timeout')); }, }); }); } // 3. 最后回退到 fetch if (!jsonText) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 30000); try { const resp = await fetch(CONFIG.ANNUAL_AAA_URL, { signal: controller.signal, credentials: 'omit' }); jsonText = await resp.text(); } finally { clearTimeout(timeout); } } const data = JSON.parse(jsonText); state.annualAaaDB = data; cache.set(cacheKey, data, 24 * 3600 * 1000); // 缓存 24 小时 console.log('[SF] 年度大作 DB 加载成功,条目数', data.length); return data; } catch (e) { console.warn('[SF] 年度大作 DB 加载失败:', e); state.annualAaaDB = []; return []; } } // 从 date 字符串(如 "2026年1月22日")提取年份 function parseYearFromDate(dateStr) { if (!dateStr) return null; const m = String(dateStr).match(/(\d{4})年/); return m ? parseInt(m[1], 10) : null; } // 获取按年分组的年度大作列表 function getAnnualAaaGames() { const raw = state.annualAaaDB; if (!raw || !Array.isArray(raw)) return []; return raw.map(item => ({ appid: item.appid, name: item.name || `App ${item.appid}`, date: item.date || '', year: parseYearFromDate(item.date), })); } // 检测用户拥有的年度大作 async function fetchAnnualAaaFor(steamId, ownedData) { if (!steamId) return { games: [], ownedSet: new Set(), total: 0, byYear: {} }; if (!ownedData || !ownedData.games?.length) return { games: [], ownedSet: new Set(), total: 0, byYear: {} }; await loadAnnualAaaDB(); const allAaa = getAnnualAaaGames(); if (!allAaa.length) return { games: [], ownedSet: new Set(), total: 0, byYear: {} }; const myAppIds = new Set(ownedData.games.map(g => Number(g.appid))); const gameMap = new Map(ownedData.games.map(g => [Number(g.appid), g])); const games = allAaa.map(item => { const owned = myAppIds.has(Number(item.appid)); const gameInfo = owned ? gameMap.get(Number(item.appid)) || {} : {}; return { ...item, owned, playtime_forever: gameInfo.playtime_forever || 0, rtime_last_played: gameInfo.rtime_last_played || 0, }; }); // 按年分组 const byYear = {}; for (const g of games) { const y = g.year || '未知'; if (!byYear[y]) byYear[y] = []; byYear[y].push(g); } // 每年内按日期排序 for (const y of Object.keys(byYear)) { byYear[y].sort((a, b) => (a.date || '').localeCompare(b.date || '', 'zh-CN')); } const ownedCount = games.filter(g => g.owned).length; console.log('[SF] 年度大作检测:总大作', games.length, '已拥有', ownedCount); return { games, ownedSet: new Set(games.filter(g => g.owned).map(g => g.appid)), total: games.length, byYear, ownedCount }; } // ==================== 通用 miniprofile 抓取(含等级、游戏数、地区、好友时长、状态)==================== // Steam miniprofile ID = steamId - 76561197960265728 function steamIdToMiniId(steamId) { if (!/^\d{17}$/.test(steamId)) return ''; return String(Number(steamId) - 76561197960265728); } async function fetchMiniprofile(steamId) { if (!steamId) return null; const cacheKey = `mini_${steamId}`; const cached = cache.get(cacheKey); if (cached) return cached; try { const miniId = steamIdToMiniId(steamId); if (!miniId) return null; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), CONFIG.REQUEST_TIMEOUT); const resp = await fetch(`https://steamcommunity.com/miniprofile/${miniId}`, { signal: controller.signal, credentials: 'include' }); clearTimeout(timeout); if (!resp.ok) return null; const html = await resp.text(); const doc = new DOMParser().parseFromString(html, 'text/html'); // 等级(miniprofile 使用 friendPlayerLevelNum) const lvlEl = doc.querySelector('.friendPlayerLevelNum, .friend_small_level_num, .friend_player_level_num'); const level = lvlEl ? parseInt(lvlEl.textContent, 10) : null; // 在线状态 let status = 'offline'; if (doc.querySelector('.in-game, .miniprofile_ingame')) status = 'in-game'; else if (doc.querySelector('.online') && !doc.querySelector('.offline')) status = 'online'; // 当前游戏 const gameName = doc.querySelector('.miniprofile_ingame_name, .in-game-name')?.textContent?.trim() || ''; // 国家(miniprofile 通常不显示国旗,资料页才有) const flag = doc.querySelector('img[src*="flags/"]'); let countryCode = ''; let countryName = ''; if (flag) { const m = (flag.getAttribute('src') || '').match(/flags\/([a-z]{2,3})/i); if (m) countryCode = m[1].toUpperCase().slice(0, 2); countryName = flag.getAttribute('title') || flag.getAttribute('alt') || ''; } // 好友时长 const sinceEl = doc.querySelector('.friendPlayerSince, .friend_since')?.textContent?.trim() || ''; // 头像框 const frame = doc.querySelector('.playersection_avatar_frame img, .profile_avatar_frame img')?.getAttribute('src') || ''; // 游戏库数量 const gameLink = doc.querySelector('a[href*="/games?tab=all"]'); let gameCount = null; if (gameLink) { const match = gameLink.textContent.match(/(\d+)/); gameCount = match ? parseInt(match[1], 10) : null; } const info = { level, status, gameName, countryCode, countryName, since: sinceEl, frame, gameCount }; cache.set(cacheKey, info); return info; } catch (e) { console.warn('[SF] miniprofile 抓取失败:', e); return null; } } // ==================== 个人游戏库 scraping(无 API Key 也可用)==================== async function fetchGamesPage(steamId, tab = 'all') { if (!steamId) return { games: [], total: 0 }; const url = `https://steamcommunity.com/profiles/${steamId}/games/?tab=${tab}`; try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), CONFIG.REQUEST_TIMEOUT); const resp = await fetch(url, { signal: controller.signal, credentials: 'omit' }); clearTimeout(timeout); if (!resp.ok) return { games: [], total: 0 }; const html = await resp.text(); const doc = new DOMParser().parseFromString(html, 'text/html'); return parseGamesFromHtml(doc, tab); } catch (e) { console.warn('[SF] games 页抓取失败:', e); return { games: [], total: 0 }; } } function parseGamesFromHtml(doc, tab = 'all') { const rows = doc.querySelectorAll('.gameListRow'); const games = []; rows.forEach(row => { const appid = parseInt(row.id?.replace('game_', '') || row.dataset?.appid || '0', 10); if (!appid) return; const name = row.querySelector('.gameListRowItemName, .gameListRowItem h2, .gameListRowItem .gameListRowItemName')?.textContent?.trim() || row.querySelector('a[href*="/app/"]')?.textContent?.trim() || ''; const logoImg = row.querySelector('img.gameListLogo, img[src*="capsule"], img[src*="header"]'); const logoSrc = logoImg?.getAttribute('src') || ''; const playtimeEl = row.querySelector('.gameListRowItem .gameListRowItemHours, .hours_played, .gameListRowItem .gameListRowItemTime, .gameListRowItem .gameListRowItemStat'); const playtimeText = playtimeEl?.textContent?.trim() || ''; const playtime = parsePlaytimeTextToMinutes(playtimeText); const lastPlayedText = row.querySelector('.gameListRowItem .gameListRowItemLastPlayed, .last_played, .gameListRowItem .gameListRowItemTime')?.textContent?.trim() || ''; const lastPlayed = parseLastPlayedToTs(lastPlayedText); games.push({ appid, name: name || `App ${appid}`, playtime_forever: playtime, playtime_2weeks: 0, rtime_last_played: lastPlayed, img_icon_url: '', logoSrc }); }); // 数字总数 const totalEl = doc.querySelector('.gameListRowItem .gameListRowItemCount, .gameListRow .gameListRowItem .gameListRowItemCount'); const totalText = totalEl?.textContent || ''; const totalMatch = totalText.match(/(\d+)/); const total = totalMatch ? parseInt(totalMatch[1], 10) : games.length; return { games, total }; } function parsePlaytimeTextToMinutes(text) { if (!text) return 0; // "1.2 hrs" / "2小时30分钟" / "30 minutes" const t = text.trim(); const m1 = t.match(/([\d.]+)\s*hrs?/i); if (m1) return Math.round(parseFloat(m1[1]) * 60); const m2 = t.match(/([\d.]+)\s*小时/); if (m2) { const h = parseFloat(m2[1]); const minM = t.match(/(\d+)\s*分钟/); const min = minM ? parseInt(minM[1], 10) : 0; return Math.round(h * 60 + min); } const m3 = t.match(/(\d+)\s*minutes?/i); if (m3) return parseInt(m3[1], 10); return 0; } function parseLastPlayedToTs(text) { if (!text) return 0; const t = text.trim(); if (!/last|最后|上次|play|今天|昨天|前天|\d{4}年|days?|hours?/i.test(t)) return 0; // 简单估算:"今天"=现在,"昨天"=1天前,"2 天前"等 const now = Math.floor(Date.now() / 1000); if (/今天|just now|几分钟前|a few minutes ago/i.test(t)) return now; if (/昨天|yesterday/i.test(t)) return now - 86400; if (/前天/i.test(t)) return now - 2 * 86400; const dayM = t.match(/(\d+)\s*天前|(\d+)\s*days?\s*ago/i); if (dayM) return now - parseInt(dayM[1] || dayM[2], 10) * 86400; // 默认按最近处理,避免缺失 return now; } function formatPlaytimeShort(min) { if (!min || min === 0) return '0h'; const h = Math.floor(min / 60); if (h < 1) return `${min}m`; return `${h}h`; } function formatLastPlayedShort(ts) { if (!ts) return ''; const diff = Date.now() / 1000 - ts; if (diff < 0) return ''; if (diff < 86400) return '今天'; if (diff < 604800) return `${Math.floor(diff / 86400)}天前`; if (diff < 2592000) return `${Math.floor(diff / 604800)}周前`; if (diff < 31536000) return `${Math.floor(diff / 2592000)}个月前`; return `${Math.floor(diff / 31536000)}年前`; } // ==================== 获取拥有的游戏:优先 API Key,否则 fallback 抓取页面 ==================== async function fetchOwnedGamesApi(steamId) { if (!steamId) return null; const cacheKey = `owned_api_${steamId}`; const cached = cache.get(cacheKey); if (cached) return cached; const apiKey = getApiKey(); // 1. 优先使用 API Key if (apiKey) { try { const data = await steamApi('/IPlayerService/GetOwnedGames/v0001/', { key: apiKey, steamid: steamId, include_appinfo: 1, include_played_free_games: 1, format: 'json', }); const games = (data.response?.games || []).map(g => ({ appid: g.appid, name: g.name || `App ${g.appid}`, playtime_forever: g.playtime_forever || 0, playtime_2weeks: g.playtime_2weeks || 0, img_icon_url: g.img_icon_url || '', rtime_last_played: g.rtime_last_played || 0, })); const result = { games, total: data.response?.game_count || games.length }; cache.set(cacheKey, result); return result; } catch (e) { console.warn('[SF] GetOwnedGames 失败,尝试页面抓取:', e); } } // 2. 无 API Key 或 API 失败时,抓取个人游戏库页面 try { const result = await fetchGamesPage(steamId); cache.set(cacheKey, result); return result; } catch (e) { console.warn('[SF] 抓取游戏库页面失败:', e); return null; } } async function fetchRecentlyPlayedApi(steamId) { const apiKey = getApiKey(); if (!apiKey || !steamId) return null; const cacheKey = `recent_api_${steamId}`; const cached = cache.get(cacheKey); if (cached) return cached; try { const data = await steamApi('/IPlayerService/GetRecentlyPlayedGames/v0001/', { key: apiKey, steamid: steamId, format: 'json', count: 20, }); const games = (data.response?.games || []).map(g => ({ appid: g.appid, name: g.name || `App ${g.appid}`, playtime_forever: g.playtime_forever || 0, playtime_2weeks: g.playtime_2weeks || 0, img_icon_url: g.img_icon_url || '', })); cache.set(cacheKey, games); return games; } catch (e) { console.warn('[SF] GetRecentlyPlayedGames 失败:', e); return null; } } async function fetchPlayerBansApi(steamIds) { const apiKey = getApiKey(); if (!apiKey || !steamIds?.length) return null; // 限制单次最多 100(API 上限) const ids = steamIds.slice(0, CONFIG.STEAMAPI_BATCH); try { const data = await steamApi('/ISteamUser/GetPlayerBans/v1/', { key: apiKey, steamids: ids.join(','), }); return (data.players || []).reduce((m, p) => { m[p.SteamId] = { vac: p.VACBanned || false, gameBan: p.GameBanned || false, vacCount: p.NumberOfVACBans || 0, gameBanCount: p.NumberOfGameBans || 0, daysSinceLastBan: p.DaysSinceLastBan || 0, communityBanned: p.CommunityBanned || false, tradeBan: p.EconomyBan || 'none', }; return m; }, {}); } catch (e) { console.warn('[SF] GetPlayerBans 失败:', e); return null; } } async function fetchPlayerSummariesApi(steamIds) { const apiKey = getApiKey(); if (!apiKey || !steamIds?.length) return null; const ids = steamIds.slice(0, CONFIG.STEAMAPI_BATCH); try { const data = await steamApi('/ISteamUser/GetPlayerSummaries/v2/', { key: apiKey, steamids: ids.join(','), }); return (data.response?.players || []).reduce((m, p) => { m[p.steamid] = { name: p.personaname || '', avatar: p.avatarfull || p.avatarmedium || '', profileUrl: p.profileurl || '', countryCode: p.loccountrycode || '', gameName: p.gameextrainfo || '', gameId: p.gameid || '', status: p.personastate || 0, // 0=offline, 1=online, 2=busy, 3=away, 4=snooze, 5=looking to trade, 6=looking to play lastLogoff: p.lastlogoff || 0, timeCreated: p.timecreated || 0, realName: p.realname || '', city: p.loccityid || '', }; return m; }, {}); } catch (e) { console.warn('[SF] GetPlayerSummaries 失败:', e); return null; } } // ==================== 获取 Steam 等级(GetSteamLevel API,参考 friend-manager 1.0.16)==================== async function fetchSteamLevelApi(steamId) { const apiKey = getApiKey(); if (!apiKey || !steamId) return null; const cacheKey = `level_${steamId}`; const cached = cache.get(cacheKey); if (cached != null) return cached; try { const data = await steamApi('/IPlayerService/GetSteamLevel/v1/', { key: apiKey, steamid: steamId, }); const level = data?.response?.player_level || 0; cache.set(cacheKey, level); return level; } catch (e) { console.warn('[SF] GetSteamLevel 失败:', e); return null; } } // ==================== 综合获取等级:优先 API,回退 miniprofile ==================== async function fetchLevel(steamId) { if (!steamId) return null; // 1. 优先 API(有 key 时最可靠) if (getApiKey()) { const apiLevel = await fetchSteamLevelApi(steamId); if (apiLevel != null && apiLevel > 0) return apiLevel; } // 2. 回退到 miniprofile scraping const mini = await fetchMiniprofile(steamId); if (mini && mini.level != null) return mini.level; return null; } // ==================== 更新好友等级 DOM 显示 ==================== function updateFriendLevelDisplay(steamId, level) { if (level == null || isNaN(level) || level <= 0) return; const host = document.querySelector(`.sf-fp-level-host[data-sfid="${steamId}"]`); if (host) { host.style.display = ''; host.innerHTML = `${level}`; } } // ==================== 获取好友列表(参考 friend-manager 1.0.16)==================== async function fetchFriendListApi(steamId) { const apiKey = getApiKey(); if (!apiKey || !steamId) return null; const cacheKey = `friendlist_api_${steamId}`; const cached = cache.get(cacheKey); if (cached) return cached; try { const data = await steamApi('/ISteamUser/GetFriendList/v0001/', { key: apiKey, steamid: steamId, relationship: 'friend', format: 'json', }); const friends = data.friendslist?.friends || []; // 批量获取摘要(含自己) const idsToFetch = [steamId, ...friends.map(f => f.steamid)]; const summaries = {}; const BATCH = CONFIG.STEAMAPI_BATCH; for (let i = 0; i < idsToFetch.length; i += BATCH) { const batch = idsToFetch.slice(i, i + BATCH); const sum = await fetchPlayerSummariesApi(batch); if (sum) Object.assign(summaries, sum); } const ownSummary = summaries[steamId] || {}; const enriched = friends.map(f => { const s = summaries[f.steamid] || {}; return { steamid: f.steamid, friend_since: f.friend_since || 0, personaname: s.name || f.steamid, avatar: s.avatar || 'https://avatars.steamstatic.com/fef49e7fa7e1997310dd48961da2e7d95a5c7a56_medium.jpg', profileurl: s.profileUrl || `https://steamcommunity.com/profiles/${f.steamid}/`, countryCode: s.countryCode || '', personastate: s.status !== undefined ? s.status : 0, lastlogoff: s.lastLogoff || 0, gameextrainfo: s.gameName || '', }; }); const result = { own: { steamid: steamId, personaname: ownSummary.name || '', avatar: ownSummary.avatar || '', profileurl: ownSummary.profileUrl || '', countryCode: ownSummary.countryCode || '', personastate: ownSummary.status || 0, gameextrainfo: ownSummary.gameName || '' }, friends: enriched, }; cache.set(cacheKey, result); return result; } catch (e) { console.warn('[SF] GetFriendList 失败:', e); return null; } } // ==================== 绝版游戏检测(与个人游戏库交叉对比)==================== async function fetchDelistedFor(steamId, ownedData) { if (!steamId) return { owned: [], total: 0 }; if (!ownedData || !ownedData.games?.length) return { owned: [], total: 0 }; await loadDelistedDB(); const apps = getDelistedApps(); if (!apps.length) { console.warn('[SF] 绝版数据库为空,跳过检测'); return { owned: [], total: 0 }; } const myAppIds = new Set(ownedData.games.map(g => Number(g.appid))); console.log('[SF] 绝版检测:用户游戏数', myAppIds.size, '绝版库数量', apps.length); const owned = []; for (const app of apps) { if (!app.appid) continue; const aid = Number(app.appid); if (isNaN(aid)) continue; if (myAppIds.has(aid)) { const gameInfo = ownedData.games.find(g => Number(g.appid) === aid) || {}; owned.push({ appid: aid, name: gameInfo.name || app.name || `App ${aid}`, playtime_forever: gameInfo.playtime_forever || 0, rtime_last_played: gameInfo.rtime_last_played || 0, img_icon_url: gameInfo.img_icon_url || '', delistedType: getDelistedTypeKey(app.type), delistedDate: app.changed || '', ownersPct: app.owners || '', achievements: app.achievements || 0, }); } } owned.sort((a, b) => (b.playtime_forever || 0) - (a.playtime_forever || 0)); console.log('[SF] 绝版检测结果:命中', owned.length, '款'); return { owned, total: apps.length }; } // ==================== 动态类型识别(v4.0:移除 friend 分类)==================== function getBlockType(block) { if (state.blockTypes.has(block)) return state.blockTypes.get(block); let type = 'other'; if (block.querySelector('.blotter_screenshot')) type = 'screenshot'; else if (block.querySelector('.blotter_gamepurchase')) type = 'game'; else if (block.querySelector('.blotter_recommendation')) type = 'review'; else if (block.querySelector('.blotter_workshopitempublished') || block.querySelector('.dynamiclink_box')) type = 'workshop'; else if (block.querySelector('.blotter_favorite') || block.querySelector('.blotter_ugc_favorite')) type = 'favorite'; else if (block.querySelector('.blotter_wishlist') || block.querySelector('[data-featuretarget="wishlist"]')) type = 'wishlist'; // v4.0: 移除 friend 分类(好友相关动态会被上面的 screenshot/review 等更具体的分类吃掉) else if (block.querySelector('.blotter_achievement') || block.querySelector('.blotter_award')) type = 'achievement'; state.blockTypes.set(block, type); block.dataset.sfType = type; return type; } // ==================== 筛选栏(v4.0:移除好友项)==================== const FILTER_ITEMS = [ { type: 'all', icon: 'all', label: '全部' }, { type: 'screenshot', icon: 'screenshot', label: '截图' }, { type: 'game', icon: 'game', label: '游戏' }, { type: 'review', icon: 'review', label: '评测' }, { type: 'workshop', icon: 'workshop', label: '工坊' }, { type: 'favorite', icon: 'favorite', label: '收藏' }, { type: 'wishlist', icon: 'wishlist', label: '愿望单' }, { type: 'achievement', icon: 'achievement', label: '成就' }, ]; function createFilterBar() { const blotterPage = $('#blotter_page'); if (!blotterPage) return; const filterBar = el('div', { id: 'sf-filter-bar' }); const row = el('div', { className: 'sf-filter-row sf-compact', id: 'sf-filter-row' }); FILTER_ITEMS.forEach(item => { const btn = el('button', { className: `sf-filter-btn${item.type === 'all' ? ' active' : ''}`, dataset: { filter: item.type }, }, `${ICONS[item.icon]}${item.label}0`); row.appendChild(btn); }); const moreBtn = el('button', { className: 'sf-filter-more', id: 'sf-filter-more' }, `更多${ICONS.collapse}`); row.appendChild(moreBtn); filterBar.appendChild(row); const statusEntry = blotterPage.querySelector('.blotter_status_entry'); blotterPage.insertBefore(filterBar, statusEntry?.nextElementSibling || blotterPage.firstChild); row.addEventListener('click', e => { const btn = e.target.closest('.sf-filter-btn'); if (!btn) return; row.querySelectorAll('.sf-filter-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); state.currentFilter = btn.dataset.filter; applyFilter(); }); moreBtn.addEventListener('click', e => { e.stopPropagation(); const isExpanded = row.classList.contains('sf-expanded'); row.classList.toggle('sf-expanded', !isExpanded); row.classList.toggle('sf-compact', isExpanded); moreBtn.classList.toggle('sf-open', !isExpanded); moreBtn.innerHTML = isExpanded ? `更多${ICONS.collapse}` : `收起${ICONS.expand}`; }); } // ==================== 左侧分类导航 ==================== function createSidebarNav() { if ($('#sf-sidebar-nav')) return; const sidebar = el('div', { id: 'sf-sidebar-nav' }); sidebar.innerHTML = `
`; FILTER_ITEMS.forEach(item => { const navItem = el('div', { className: `sf-sidebar-item${item.type === state.currentFilter ? ' active' : ''}`, dataset: { filter: item.type }, }, `${ICONS[item.icon]}${item.label}`); navItem.addEventListener('click', () => { sidebar.querySelectorAll('.sf-sidebar-item').forEach(n => n.classList.remove('active')); navItem.classList.add('active'); state.currentFilter = item.type; applyFilter(); syncFilterBarState(); }); sidebar.appendChild(navItem); }); document.body.appendChild(sidebar); const overlay = el('div', { className: 'sf-sidebar-overlay', id: 'sf-sidebar-overlay' }); overlay.addEventListener('click', () => toggleSidebarVisibility(false)); document.body.appendChild(overlay); } function toggleSidebarVisibility(show) { const sidebar = $('#sf-sidebar-nav'); const overlay = $('#sf-sidebar-overlay'); if (!sidebar) return; state.sidebarVisible = typeof show === 'boolean' ? show : !state.sidebarVisible; sidebar.classList.toggle('sf-sidebar-open', state.sidebarVisible); document.body.classList.toggle('sf-sidebar-visible', state.sidebarVisible); // 桌面端 sidebar 模式为持久布局,不显示遮罩;仅在移动端临时弹出时显示 const isPersistent = document.body.classList.contains('sf-sidebar-mode'); overlay?.classList.toggle('sf-show', state.sidebarVisible && !isPersistent); } // ==================== 布局模式切换 ==================== function createLayoutToggleButton() { if ($('#sf-layout-btn')) return; const btn = el('button', { id: 'sf-layout-btn' }); btn.innerHTML = `${state.layoutMode === 'sidebar' ? ICONS.topbar : ICONS.sidebar}${state.layoutMode === 'sidebar' ? '顶部筛选' : '侧边导航'}`; btn.title = state.layoutMode === 'sidebar' ? '切换为顶部筛选' : '切换为侧边导航'; document.body.appendChild(btn); btn.addEventListener('click', () => switchLayoutMode(state.layoutMode === 'topbar' ? 'sidebar' : 'topbar')); } function switchLayoutMode(mode) { state.layoutMode = mode; try { localStorage.setItem('sf-layout-mode', mode); } catch (e) { /* 隐私模式可能抛错 */ } const filterBar = $('#sf-filter-bar'); const sidebar = $('#sf-sidebar-nav'); const layoutBtn = $('#sf-layout-btn'); const isSidebar = mode === 'sidebar'; document.body.classList.toggle('sf-sidebar-mode', isSidebar); if (filterBar) filterBar.style.display = isSidebar ? 'none' : ''; if (isSidebar && !sidebar) createSidebarNav(); toggleSidebarVisibility(isSidebar); if (layoutBtn) { layoutBtn.innerHTML = isSidebar ? ICONS.topbar : ICONS.sidebar; layoutBtn.title = isSidebar ? '切换为顶部筛选' : '切换为侧边导航'; } } function syncFilterBarState() { const filterBar = $('#sf-filter-bar'); if (filterBar) { filterBar.querySelectorAll('.sf-filter-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.filter === state.currentFilter); }); } } // ==================== 应用筛选 ==================== function applyFilter() { let visibleCount = 0; state.totalBlocks.forEach(block => { const type = getBlockType(block); const shouldShow = state.currentFilter === 'all' || type === state.currentFilter; if (shouldShow && !block.classList.contains('sf-lazy-hidden')) { block.classList.remove('sf-hidden'); visibleCount++; } else { block.classList.add('sf-hidden'); } }); state.visibleCount = visibleCount; updateEmptyState(); const sidebar = $('#sf-sidebar-nav'); if (sidebar) { sidebar.querySelectorAll('.sf-sidebar-item').forEach(item => { item.classList.toggle('active', item.dataset.filter === state.currentFilter); }); } } // ==================== 更新计数(v4.0:移除 friend)==================== function updateCounts() { const counts = { all: 0, screenshot: 0, game: 0, review: 0, workshop: 0, favorite: 0, wishlist: 0, achievement: 0 }; state.totalBlocks.forEach(block => { const type = getBlockType(block); if (counts[type] !== undefined) counts[type]++; counts.all++; }); Object.keys(counts).forEach(type => { const e2 = $(`#sf-count-${type}`); if (e2) e2.textContent = counts[type]; const sidebarEl = $(`#sf-sidebar-count-${type}`); if (sidebarEl) sidebarEl.textContent = counts[type]; }); } // ==================== 空状态 ==================== function updateEmptyState() { const blotterContent = $('#blotter_content'); if (!blotterContent) return; blotterContent.querySelector('.sf-empty-state')?.remove(); if (state.visibleCount === 0 && state.totalBlocks.length > 0) { blotterContent.appendChild(el('div', { className: 'sf-empty-state' }, '