/** * IKUN 主题皮肤 — DSH 动态 Cordis 插件(Client 端) * * 使用方式:把本文件的函数体原样粘贴到 cordis_define 的 code.client 字段。 * 详见 README.md / docs/INSTALL.md。 * * 功能: * - 三套主题(星蓝·昼/星蓝·夜/背带裤黑金),浅色为默认,插入系统外观列表 * - 全屏真实照片壁纸轮播(蔡徐坤照片 + 经典模因图) * - 音乐盒(基尼太美主题音乐,坤坤跳舞 + 篮球旋转) * - 点击「发送」自动播放「你干嘛~哎哟」经典台词 * - 右下角常驻跳舞小坤坤、每条回复后的坤坤彩蛋 * - IKUN 设置页 * * 审查修复记录(v1.0 打包版相对运行版): * - onSendRef 在卸载时清空引用,避免对已脱离 DOM 的元素调用 play() * - 发送音效音量跟随音乐盒音量(原为元素默认音量) * - 移除未使用的 themes[].def 字段 * - 魔法数字提取为具名常量(壁纸间隔/发送去抖/自动弹出延迟/音效起点) */ return { apply(ctx) { const theme = ctx.get('theme') const slots = ctx.get('slots') const timer = ctx.get('timer') if (!theme || !slots) return // ---------- 音频与参数 ---------- const AUDIO_SRC = 'https://down.ear0.com:3321/index/preview?soundid=39338&type=mp3&audio=sound.mp3' // 基尼太美主题音乐 const SEND_SRC = 'https://cdn.jsdelivr.net/gh/dreamhunter2333/ikun-whacamole@main/resources/audios/niganma.mp3' // 「你干嘛~哎哟」台词 const SEND_START = 0 // 音效起点(秒) const WALLPAPER_INTERVAL = 7000 // 壁纸轮播间隔(毫秒) const SEND_DEDUPE_MS = 800 // 发送音效去抖窗口(毫秒),避免双信号重复触发 const AUTO_OPEN_DELAY = 900 // 激活后自动弹出音乐盒的延迟(毫秒) // ---------- 真实图片全屏壁纸 ---------- const WALLPAPERS = [ 'https://cdn.jsdelivr.net/gh/dreamhunter2333/ikun-whacamole@main/resources/images/kun.png', 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/KUN_Charlotte_Tilbury_in_Shanghai_January_2026.jpg/960px-KUN_Charlotte_Tilbury_in_Shanghai_January_2026.jpg', 'https://cdn.jsdelivr.net/gh/dreamhunter2333/ikun-whacamole@main/resources/images/ji.png', 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/88/Caixukun_concert_full_image.jpg/960px-Caixukun_concert_full_image.jpg', 'https://cdn.jsdelivr.net/gh/dreamhunter2333/ikun-whacamole@main/resources/images/background.png', 'https://cdn.jsdelivr.net/gh/MNTMDEV/cxk_gif@master/pic/cxk.gif', ] const BANNER_PHOTO = 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/KUN_Charlotte_Tilbury_in_Shanghai_January_2026.jpg/250px-KUN_Charlotte_Tilbury_in_Shanghai_January_2026.jpg' let wallpaperIdx = 0 let wallpaperDisposer = null function isIkunTheme(id) { return id === 'ikun-night' || id === 'ikun-day' || id === 'ikun-gold' } function applyWallpaper(url) { if (wallpaperDisposer) { try { wallpaperDisposer() } catch (e) {} } // 第二层渐变是图片加载失败时的兜底,浅色主题配浅色渐变 const grad = store.activeTheme === 'ikun-day' ? 'linear-gradient(160deg,rgba(242,245,253,.82),rgba(255,255,255,.86))' : 'linear-gradient(160deg,rgba(8,10,24,.55),rgba(16,8,32,.55))' wallpaperDisposer = styles.insert('body{background-image:url("' + url + '"),' + grad + '!important;background-size:cover,cover!important;background-position:center,center!important;background-repeat:no-repeat,no-repeat!important;background-attachment:fixed,fixed!important}') } function removeWallpaper() { if (wallpaperDisposer) { try { wallpaperDisposer() } catch (e) {} } wallpaperDisposer = null } function syncWallpaper() { if (isIkunTheme(store.activeTheme)) { applyWallpaper(WALLPAPERS[wallpaperIdx % WALLPAPERS.length]) } else { removeWallpaper() // 切回系统主题时不干扰 } } // ---------- 共享状态(apply 闭包内的微型 store)---------- const store = { open: false, playing: false, volume: 0.7, el: null, sendEl: null, error: false, activeTheme: '', preference: 'system' } const subs = new Set() function setStore(patch) { Object.assign(store, patch) subs.forEach(function (fn) { fn() }) } function useStore(select) { const pair = React.useState(function () { return select(store) }) const v = pair[0] const setV = pair[1] React.useEffect(function () { const fn = function () { setV(select(store)) } subs.add(fn) return function () { subs.delete(fn) } }, []) return v } // ---------- 音频控制 ---------- function onAudioRef(el) { if (el) { store.el = el; el.volume = store.volume } else { store.el = null } } function onSendRef(el) { if (el) { store.sendEl = el; el.volume = store.volume } else { store.sendEl = null } // 卸载时清空引用 } function onAudioError() { setStore({ error: true, playing: false }) } function doPlay(el) { const p = el.play() if (p && typeof p.then === 'function') { p.then(function () { setStore({ playing: true, error: false }) }).catch(function () { setStore({ error: true }) }) } else { setStore({ playing: true, error: false }) } } function togglePlay() { const el = store.el if (!el) return if (store.playing) { el.pause(); setStore({ playing: false }) } else { doPlay(el) } } function restart() { const el = store.el if (!el) return el.currentTime = 0 if (!store.playing) doPlay(el) } function retryAudio() { const el = store.el if (!el) return setStore({ error: false }) el.load() doPlay(el) } function setVolume(v) { setStore({ volume: v }) if (store.el) store.el.volume = v if (store.sendEl) store.sendEl.volume = v // 发送音效跟随音量 } // 发送时自动播放「你干嘛~哎哟」 function playSendSound() { const el = store.sendEl if (!el) { console.log('ikun: send audio element not mounted'); return } try { el.currentTime = SEND_START } catch (e) {} const p = el.play() if (p && typeof p.then === 'function') { p.catch(function (e) { console.log('ikun: send sound play rejected: ' + e.message) }) } } function setThemeSafe(id) { try { theme.setTheme(id) } catch (e) { console.log('ikun: setTheme failed ' + e.message) } } // ---------- 坤坤形象(内联 SVG,避免 img 光栅化裁剪)---------- function Kunkun(props) { const size = props.size || 96 const cls = 'ikun-avatar' + (props.className ? ' ' + props.className : '') return React.createElement('svg', { viewBox: '0 0 120 180', width: size, height: Math.round(size * 1.5), className: cls, 'aria-hidden': true }, React.createElement('ellipse', { cx: 60, cy: 56, rx: 21, ry: 24, fill: '#f6d3ac' }), React.createElement('ellipse', { cx: 38, cy: 58, rx: 4, ry: 6, fill: '#f0c394' }), React.createElement('ellipse', { cx: 82, cy: 58, rx: 4, ry: 6, fill: '#f0c394' }), React.createElement('path', { d: 'M58 18 C 38 18, 24 32, 26 56 C 27 65, 35 70, 43 65 C 48 58, 48 46, 50 38 C 52 32, 56 28, 58 26 Z', fill: '#1c1c1c' }), React.createElement('path', { d: 'M62 18 C 82 18, 96 32, 94 56 C 93 65, 85 70, 77 65 C 72 58, 72 46, 70 38 C 68 32, 64 28, 62 26 Z', fill: '#1c1c1c' }), React.createElement('path', { d: 'M46 46 Q 50 43 55 45', stroke: '#3a2a1e', strokeWidth: 2, fill: 'none', strokeLinecap: 'round' }), React.createElement('path', { d: 'M65 45 Q 70 43 74 46', stroke: '#3a2a1e', strokeWidth: 2, fill: 'none', strokeLinecap: 'round' }), React.createElement('circle', { cx: 50, cy: 52, r: 2.6, fill: '#1c1c1c' }), React.createElement('circle', { cx: 70, cy: 52, r: 2.6, fill: '#1c1c1c' }), React.createElement('path', { d: 'M60 56 L 59 62', stroke: '#d89a6a', strokeWidth: 1.6, fill: 'none', strokeLinecap: 'round' }), React.createElement('path', { d: 'M53 66 Q 60 71 67 66', stroke: '#c97f5a', strokeWidth: 1.8, fill: 'none', strokeLinecap: 'round' }), React.createElement('rect', { x: 54, y: 76, width: 12, height: 12, fill: '#f6d3ac' }), React.createElement('path', { d: 'M36 84 L 84 84 L 88 132 L 32 132 Z', fill: '#fdfdfd' }), React.createElement('path', { d: 'M60 82 L 51 90 L 60 86 L 69 90 Z', fill: '#eef3f8' }), React.createElement('path', { d: 'M46 84 L 40 132', stroke: '#1c1c1c', strokeWidth: 7, fill: 'none', strokeLinecap: 'round' }), React.createElement('path', { d: 'M74 84 L 80 132', stroke: '#1c1c1c', strokeWidth: 7, fill: 'none', strokeLinecap: 'round' }), React.createElement('path', { d: 'M37 88 Q 24 104 27 122', stroke: '#f6d3ac', strokeWidth: 9, fill: 'none', strokeLinecap: 'round' }), React.createElement('path', { d: 'M83 88 Q 100 100 104 114', stroke: '#f6d3ac', strokeWidth: 9, fill: 'none', strokeLinecap: 'round' }), React.createElement('path', { d: 'M34 132 L 44 176 L 53 176 L 56 134 L 64 134 L 67 176 L 76 176 L 86 132 Z', fill: '#1c1c1c' }), React.createElement('g', { className: 'ikun-ball-group' }, React.createElement('circle', { cx: 100, cy: 124, r: 19, fill: '#e8751a' }), React.createElement('path', { d: 'M100 105 a19 19 0 0 1 0 38', fill: 'none', stroke: '#8a4410', strokeWidth: 1.8 }), React.createElement('path', { d: 'M100 105 a19 19 0 0 0 0 38', fill: 'none', stroke: '#8a4410', strokeWidth: 1.8 }), React.createElement('path', { d: 'M83 116 Q 100 130 117 116', fill: 'none', stroke: '#8a4410', strokeWidth: 1.8 }), React.createElement('path', { d: 'M83 132 Q 100 118 117 132', fill: 'none', stroke: '#8a4410', strokeWidth: 1.8 }) ) ) } function KunkunFace(props) { const size = props.size || 20 return React.createElement('svg', { viewBox: '0 0 48 48', width: size, height: size, className: 'ikun-face', 'aria-hidden': true }, React.createElement('ellipse', { cx: 24, cy: 27, rx: 15, ry: 16, fill: '#f6d3ac' }), React.createElement('path', { d: 'M22 8 C 11 8, 4 16, 5 28 C 5.5 33, 10 36, 14 33 C 17 27, 18 20, 20 16 C 21 13, 22 12, 23 12 Z', fill: '#1c1c1c' }), React.createElement('path', { d: 'M26 8 C 37 8, 44 16, 43 28 C 42.5 33, 38 36, 34 33 C 31 27, 30 20, 28 16 C 27 13, 26 12, 25 12 Z', fill: '#1c1c1c' }), React.createElement('circle', { cx: 20, cy: 26, r: 2, fill: '#1c1c1c' }), React.createElement('circle', { cx: 28, cy: 26, r: 2, fill: '#1c1c1c' }), React.createElement('path', { d: 'M21 34 Q 24 36.5 27 34', stroke: '#c97f5a', strokeWidth: 1.5, fill: 'none', strokeLinecap: 'round' }) ) } // 白色中分头发的小黄鸡(经典模因图) function Chick(props) { const size = props.size || 40 return React.createElement('svg', { viewBox: '0 0 120 132', width: size, height: Math.round(size * 1.1), className: 'ikun-chick', 'aria-hidden': true }, React.createElement('path', { d: 'M34 74 Q 18 66 22 52 Q 26 62 34 66 Z', fill: '#ffc61a' }), React.createElement('ellipse', { cx: 62, cy: 90, rx: 36, ry: 32, fill: '#ffd93d' }), React.createElement('ellipse', { cx: 62, cy: 90, rx: 36, ry: 32, fill: 'none', stroke: '#e8b800', strokeWidth: 2 }), React.createElement('ellipse', { cx: 32, cy: 92, rx: 14, ry: 10, fill: '#ffc61a', transform: 'rotate(-12 32 92)' }), React.createElement('path', { d: 'M48 118 L 48 126 M42 126 L 54 126', stroke: '#ff8c1a', strokeWidth: 5, strokeLinecap: 'round' }), React.createElement('path', { d: 'M72 118 L 72 126 M66 126 L 78 126', stroke: '#ff8c1a', strokeWidth: 5, strokeLinecap: 'round' }), React.createElement('circle', { cx: 62, cy: 44, r: 27, fill: '#ffd93d' }), React.createElement('circle', { cx: 62, cy: 44, r: 27, fill: 'none', stroke: '#e8b800', strokeWidth: 2 }), React.createElement('path', { d: 'M62 12 C 44 12, 33 22, 33 37 C 33 46, 38 50, 44 48 C 47 42, 49 34, 52 31 C 54 28, 58 26, 62 24 C 66 26, 70 28, 72 31 C 75 34, 77 42, 80 48 C 86 50, 91 46, 91 37 C 91 22, 80 12, 62 12 Z', fill: '#ffffff', stroke: '#e6e6e6', strokeWidth: 1.5 }), React.createElement('circle', { cx: 52, cy: 45, r: 3.2, fill: '#222222' }), React.createElement('circle', { cx: 72, cy: 45, r: 3.2, fill: '#222222' }), React.createElement('circle', { cx: 44, cy: 52, r: 4.5, fill: '#ffb3a7', opacity: 0.75 }), React.createElement('circle', { cx: 80, cy: 52, r: 4.5, fill: '#ffb3a7', opacity: 0.75 }), React.createElement('path', { d: 'M62 51 L 55 57 L 62 62 L 69 57 Z', fill: '#ff8c1a' }) ) } function Eq(props) { return React.createElement('div', { className: 'ikun-eq' + (props.playing ? ' playing' : '') }, React.createElement('span', { className: 'ikun-eq-bar' }), React.createElement('span', { className: 'ikun-eq-bar' }), React.createElement('span', { className: 'ikun-eq-bar' }), React.createElement('span', { className: 'ikun-eq-bar' }) ) } // ---------- 系统外观行(接管 appearance,插入系统主题列表)---------- function AppearanceRow2() { const pref = useStore(function (s) { return s.preference }) const options = [ { id: 'light', label: '☀️ 浅色', group: 'sys' }, { id: 'dark', label: '🌙 深色', group: 'sys' }, { id: 'system', label: '💻 跟随系统', group: 'sys' }, { id: 'ikun-day', label: '☀️ 星蓝·昼', group: 'ikun' }, { id: 'ikun-night', label: '🌌 星蓝·夜', group: 'ikun' }, { id: 'ikun-gold', label: '🖤 背带裤黑金', group: 'ikun' }, ] return React.createElement('div', { className: 'ikun-app-row' }, React.createElement('div', { className: 'ikun-app-title' }, '外观 · 主题', React.createElement('span', { className: 'ikun-app-badge' }, 'IKUN') ), React.createElement('div', { className: 'ikun-app-opts' }, options.map(function (o) { return React.createElement('button', { key: o.id, type: 'button', className: 'ikun-app-opt' + (o.group === 'ikun' ? ' ikun-opt-ikun' : '') + (pref === o.id ? ' ikun-active' : ''), title: o.label, onClick: function () { setThemeSafe(o.id) }, }, o.label) }) ) ) } // ---------- 发送自动播「你干嘛~哎哟」(相位 + 队列双信号,防 React 批处理漏检)---------- let lastSendSound = 0 function SendWatcher(props) { const phase = props.input.phase const qlen = props.input.queue.length const pState = React.useState('plain') const prev = pState[0] const setPrev = pState[1] const qState = React.useState(0) const qprev = qState[0] const setQprev = qState[1] React.useEffect(function () { const busyStart = prev === 'plain' && phase !== 'plain' const queuedMore = qlen > qprev const now = Date.now() if ((busyStart || queuedMore) && now - lastSendSound > SEND_DEDUPE_MS) { lastSendSound = now playSendSound() } if (phase !== prev) setPrev(phase) if (qlen !== qprev) setQprev(qlen) }) return React.createElement('div', { className: 'ikun-sendwatch' }) } // ---------- 侧边栏底部入口 ---------- function FooterAction(props) { const open = useStore(function (s) { return s.open }) const playing = useStore(function (s) { return s.playing }) const cls = 'ikun-footer-btn' + (open ? ' ikun-on' : '') + (playing ? ' ikun-playing' : '') return React.createElement('button', { type: 'button', className: cls, title: 'IKUN 音乐盒', onClick: function () { setStore({ open: !store.open }) }, }, React.createElement(KunkunFace, { size: 20 }), props.wide ? React.createElement('span', { className: 'ikun-footer-label' }, 'IKUN 音乐') : null ) } // ---------- 悬浮音乐盒 ---------- function Player() { const open = useStore(function (s) { return s.open }) const playing = useStore(function (s) { return s.playing }) const volume = useStore(function (s) { return s.volume }) const error = useStore(function (s) { return s.error }) const audio = React.createElement('audio', { ref: onAudioRef, src: AUDIO_SRC, loop: true, preload: 'auto', onError: onAudioError }) const sendAudio = React.createElement('audio', { ref: onSendRef, src: SEND_SRC, preload: 'auto' }) // 两个 audio 常驻挂载(即使面板关闭),保证发送音效随时可用 if (!open) return React.createElement(React.Fragment, null, audio, sendAudio) return React.createElement(React.Fragment, null, audio, sendAudio, React.createElement('div', { className: 'ikun-player' }, React.createElement('div', { className: 'ikun-player-head' }, React.createElement('div', { className: 'ikun-player-title' }, React.createElement(KunkunFace, { size: 20 }), React.createElement('span', null, 'IKUN 音乐盒') ), React.createElement('button', { type: 'button', className: 'ikun-x', title: '收起', onClick: function () { setStore({ open: false }) } }, '✕') ), React.createElement('div', { className: 'ikun-cover' }, React.createElement('div', { className: 'ikun-cover-avatar' + (playing ? ' ikun-dance' : '') }, React.createElement(Kunkun, { size: 88 }) ), React.createElement('div', { className: 'ikun-cover-meta' }, React.createElement('div', { className: 'ikun-track' }, '只因你太美'), React.createElement('div', { className: 'ikun-sub' }, '蔡徐坤 · 鸡你太美(经典整活)'), React.createElement(Eq, { playing: playing }) ) ), error ? React.createElement('div', { className: 'ikun-error' }, React.createElement('span', null, '音频加载失败,换个网络再试'), React.createElement('button', { type: 'button', className: 'ikun-pbtn', onClick: retryAudio }, '重试') ) : null, React.createElement('div', { className: 'ikun-controls' }, React.createElement('button', { type: 'button', className: 'ikun-pbtn', title: '再来亿遍', onClick: restart }, '↻ 再来亿遍'), React.createElement('button', { type: 'button', className: 'ikun-pbtn ikun-play-main', onClick: togglePlay }, playing ? '⏸ 暂停' : '▶ 播放'), React.createElement('div', { className: 'ikun-volwrap' }, React.createElement('span', null, '🔈'), React.createElement('input', { type: 'range', className: 'ikun-vol', min: 0, max: 1, step: 0.05, value: volume, onChange: function (e) { setVolume(Number(e.target.value)) } }) ) ), React.createElement('div', { className: 'ikun-foot' }, '🎤 唱 · 跳 · RAP · 篮球 🏀') ) ) } // ---------- 右下角常驻跳舞小坤坤 ---------- function Mascot() { const playing = useStore(function (s) { return s.playing }) return React.createElement('div', { className: 'ikun-mascot' + (playing ? ' ikun-mascot-dance' : '') }, React.createElement(Kunkun, { size: 36 }) ) } // ---------- 每条回复后的坤坤彩蛋 ---------- const TAIL_STICKERS = [ React.createElement(KunkunFace, { size: 15 }), React.createElement(Chick, { size: 15 }), '🏀', '⭐', '🎤', ] function TurnSticker(props) { const pick = TAIL_STICKERS[Math.abs(props.seq) % TAIL_STICKERS.length] return React.createElement('div', { className: 'ikun-tail', title: 'ikun 彩蛋' }, pick) } // ---------- 设置页 ---------- function SettingsPage() { const playing = useStore(function (s) { return s.playing }) const active = useStore(function (s) { return s.activeTheme }) const themes = [ { id: 'ikun-day', name: '星蓝·昼', desc: '浅色主题 · 默认', emoji: '☀️' }, { id: 'ikun-night', name: '星蓝·夜', desc: '深蓝舞台 · 应援星蓝', emoji: '🌌' }, { id: 'ikun-gold', name: '背带裤黑金', desc: '全黑背带 · 鎏金应援', emoji: '🖤' }, ] return React.createElement('div', { className: 'ikun-settings' }, React.createElement('div', { className: 'ikun-banner' }, React.createElement('div', { className: 'ikun-banner-avatar' }, React.createElement('img', { src: BANNER_PHOTO, className: 'ikun-banner-photo', alt: '蔡徐坤', loading: 'lazy' }) ), React.createElement('div', { className: 'ikun-banner-text' }, React.createElement('div', { className: 'ikun-banner-title' }, '🏀 IKUN 主题皮肤'), React.createElement('div', { className: 'ikun-banner-sub' }, '一位虔诚的 ikun 收集与整理 · 唱跳RAP篮球'), React.createElement('div', { className: 'ikun-banner-badge' }, '只因你太美 · 鸡你太美') ) ), React.createElement('h3', { className: 'ikun-h3' }, '🎨 选择主题'), React.createElement('div', { className: 'ikun-theme-cards' }, themes.map(function (t) { return React.createElement('button', { key: t.id, type: 'button', className: 'ikun-theme-card' + (active === t.id ? ' ikun-active' : ''), onClick: function () { setThemeSafe(t.id) }, }, React.createElement('div', { className: 'ikun-theme-emoji' }, t.emoji), React.createElement('div', { className: 'ikun-theme-name' }, t.name), React.createElement('div', { className: 'ikun-theme-desc' }, t.desc), active === t.id ? React.createElement('div', { className: 'ikun-theme-tag' }, '使用中') : null ) }) ), React.createElement('h3', { className: 'ikun-h3' }, '🖼 全屏壁纸轮播'), React.createElement('div', { className: 'ikun-wall-row' }, React.createElement('div', { className: 'ikun-music-info' }, React.createElement('div', { className: 'ikun-track' }, '名场面美照 · 全屏底图'), React.createElement('div', { className: 'ikun-sub' }, '真实照片与经典模因图,每 7 秒自动切换'), React.createElement(Eq, { playing: playing }) ), React.createElement('div', { className: 'ikun-music-actions' }, React.createElement('button', { type: 'button', className: 'ikun-pbtn', title: '手动换一张', onClick: function () { wallpaperIdx += 1; syncWallpaper() } }, '🖼 换一张') ) ), React.createElement('h3', { className: 'ikun-h3' }, '🎵 音乐盒'), React.createElement('div', { className: 'ikun-music-row' }, React.createElement('div', { className: 'ikun-music-info' }, React.createElement('div', { className: 'ikun-track' }, '只因你太美'), React.createElement('div', { className: 'ikun-sub' }, '经典基尼太美主题音乐 · 单曲循环'), React.createElement(Eq, { playing: playing }) ), React.createElement('div', { className: 'ikun-music-actions' }, React.createElement('button', { type: 'button', className: 'ikun-pbtn', title: '再来亿遍', onClick: restart }, '↻'), React.createElement('button', { type: 'button', className: 'ikun-pbtn ikun-play-main', onClick: togglePlay }, playing ? '⏸ 暂停' : '▶ 播放'), React.createElement('button', { type: 'button', className: 'ikun-pbtn', onClick: function () { setStore({ open: true }) } }, '📺 打开面板') ) ), React.createElement('h3', { className: 'ikun-h3' }, '📋 皮肤设计说明'), React.createElement('ul', { className: 'ikun-list' }, React.createElement('li', null, '📨 点击「发送」或按回车时,自动播放坤坤「你干嘛~哎哟」经典台词。'), React.createElement('li', null, '☀️ 默认浅色主题,已插入系统「外观」主题列表(设置 → 通用)。'), React.createElement('li', null, '🖼 全屏壁纸轮播:蔡徐坤真实照片 + 经典模因图,整界面作为底图。'), React.createElement('li', null, '🧑‍🎤 跳舞小坤坤、🎵 基尼太美音乐、每条回复后的坤坤彩蛋。'), React.createElement('li', null, '本主题为整活向设计,仅供娱乐,理性追星,快乐玩梗。') ) ) } // ---------- 主题定义(底色半透明,透出全屏壁纸)---------- const DAY = { id: 'ikun-day', colorScheme: 'light', tokens: { '--dsw-alias-bg-base': 'rgba(248,250,255,0.55)', '--dsw-alias-bg-layer-1': 'rgba(255,255,255,0.9)', '--dsw-alias-bg-layer-2': 'rgba(240,244,252,0.9)', '--dsw-alias-bg-overlay': 'rgba(255,255,255,0.97)', '--dsw-alias-border-l1': '#dde3f2', '--dsw-alias-border-l2': '#c6cfe8', '--dsw-alias-brand-primary': '#2f7fe0', '--dsw-alias-label-primary': '#1b2340', '--dsw-alias-label-secondary': '#5d6a96', '--dsw-alias-state-error-primary': '#e5484d', '--dsw-alias-state-success-primary': '#1fae6f', '--dsw-alias-state-warn-primary': '#d98a1f', '--dsw-specific-sidebar-fill': 'rgba(244,247,255,0.6)', }, } const NIGHT = { id: 'ikun-night', colorScheme: 'dark', tokens: { '--dsw-alias-bg-base': 'rgba(10,14,31,0.66)', '--dsw-alias-bg-layer-1': '#121832', '--dsw-alias-bg-layer-2': '#1b2345', '--dsw-alias-bg-overlay': 'rgba(20,27,56,0.92)', '--dsw-alias-border-l1': '#2a3560', '--dsw-alias-border-l2': '#3d4c85', '--dsw-alias-brand-primary': '#5bb8ff', '--dsw-alias-label-primary': '#eef3ff', '--dsw-alias-label-secondary': '#a7b2d9', '--dsw-alias-state-error-primary': '#ff6f91', '--dsw-alias-state-success-primary': '#3edc9a', '--dsw-alias-state-warn-primary': '#ffd166', '--dsw-specific-sidebar-fill': 'rgba(12,17,38,0.7)', }, } const GOLD = { id: 'ikun-gold', colorScheme: 'dark', tokens: { '--dsw-alias-bg-base': 'rgba(12,12,14,0.68)', '--dsw-alias-bg-layer-1': '#17171a', '--dsw-alias-bg-layer-2': '#212127', '--dsw-alias-bg-overlay': 'rgba(25,25,29,0.94)', '--dsw-alias-border-l1': '#2c2c33', '--dsw-alias-border-l2': '#45454f', '--dsw-alias-brand-primary': '#f2c94c', '--dsw-alias-label-primary': '#f7f3e6', '--dsw-alias-label-secondary': '#b9b29c', '--dsw-alias-state-error-primary': '#ff6b6b', '--dsw-alias-state-success-primary': '#4ade80', '--dsw-alias-state-warn-primary': '#ffd166', '--dsw-specific-sidebar-fill': 'rgba(17,17,20,0.72)', }, } // ---------- 样式(全部 .ikun- 前缀,不污染产品样式)---------- const CSS = [ '.ikun-app-row{padding:2px 2px 6px;font-family:inherit}', '.ikun-app-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#1b2340);display:flex;align-items:center;gap:8px;margin-bottom:10px}', '.ikun-app-badge{font-size:10px;font-weight:700;padding:1px 7px;border-radius:999px;background:var(--dsw-alias-brand-primary,#2f7fe0);color:#fff}', '.ikun-app-opts{display:flex;flex-wrap:wrap;gap:8px}', '.ikun-app-opt{border:1px solid var(--dsw-alias-border-l2,#c6cfe8);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#1b2340);border-radius:999px;padding:6px 12px;font:inherit;font-size:12.5px;cursor:pointer;transition:all .15s ease}', '.ikun-app-opt:hover{border-color:var(--dsw-alias-brand-primary,#2f7fe0)}', '.ikun-app-opt.ikun-active{border-color:var(--dsw-alias-brand-primary,#2f7fe0);background:var(--dsw-alias-brand-primary,#2f7fe0);color:#fff}', '.ikun-opt-ikun{border-style:dashed}', '.ikun-sendwatch{position:relative;height:0;overflow:visible;pointer-events:none}', '.ikun-footer-btn{display:flex;align-items:center;gap:8px;width:100%;min-height:34px;padding:5px 10px;box-sizing:border-box;border:1px solid transparent;border-radius:10px;background:transparent;color:var(--dsw-alias-label-secondary,#9aa4c7);font:inherit;font-size:13px;cursor:pointer;text-align:left;transition:background .15s ease,color .15s ease,border-color .15s ease}', '.ikun-footer-btn:hover{background:rgba(91,184,255,.12);color:var(--dsw-alias-label-primary,#eef3ff)}', '.ikun-footer-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5bb8ff);outline-offset:1px}', '.ikun-footer-btn.ikun-on{color:var(--dsw-alias-brand-primary,#5bb8ff);border-color:rgba(91,184,255,.35)}', '.ikun-footer-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}', '.ikun-face{flex:none;display:block}', '.ikun-avatar{display:block}', '.ikun-chick{display:block}', '.ikun-tail{display:inline-flex;align-items:center;gap:4px;margin:2px 0 0 2px;opacity:.85;vertical-align:middle}', '.ikun-player{position:fixed;right:20px;bottom:84px;z-index:9999;width:320px;max-width:calc(100vw - 32px);padding:14px 16px 12px;box-sizing:border-box;border-radius:16px;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#c6cfe8);box-shadow:0 14px 44px rgba(0,0,0,.35);color:var(--dsw-alias-label-primary,#1b2340);font-size:13px;font-family:inherit;pointer-events:auto;animation:ikun-pop .22s ease-out}', '.ikun-player-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}', '.ikun-player-title{display:flex;align-items:center;gap:8px;font-weight:700;font-size:14px}', '.ikun-x{border:none;background:transparent;color:var(--dsw-alias-label-secondary,#5d6a96);font-size:14px;cursor:pointer;padding:4px 6px;border-radius:8px}', '.ikun-x:hover{color:var(--dsw-alias-label-primary,#1b2340);background:rgba(0,0,0,.06)}', '.ikun-cover{display:flex;gap:14px;align-items:center;padding:10px;border-radius:12px;background:var(--dsw-alias-bg-layer-2,#f0f4fc)}', '.ikun-cover-avatar{flex:none;line-height:0}', '.ikun-cover-avatar .ikun-avatar{filter:drop-shadow(0 6px 14px rgba(0,0,0,.25))}', '.ikun-cover-meta{min-width:0}', '.ikun-track{font-weight:700;font-size:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}', '.ikun-sub{color:var(--dsw-alias-label-secondary,#5d6a96);margin-top:2px;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}', '.ikun-controls{display:flex;align-items:center;gap:8px;margin-top:12px;flex-wrap:wrap}', '.ikun-pbtn{border:1px solid var(--dsw-alias-border-l2,#c6cfe8);background:var(--dsw-alias-bg-layer-2,#f0f4fc);color:var(--dsw-alias-label-primary,#1b2340);border-radius:999px;padding:6px 13px;font:inherit;font-size:12.5px;cursor:pointer;transition:all .15s ease}', '.ikun-pbtn:hover{border-color:var(--dsw-alias-brand-primary,#2f7fe0);color:var(--dsw-alias-brand-primary,#2f7fe0)}', '.ikun-pbtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2f7fe0);outline-offset:1px}', '.ikun-play-main{background:var(--dsw-alias-brand-primary,#2f7fe0);border-color:transparent;color:#fff;font-weight:700}', '.ikun-play-main:hover{color:#fff;filter:brightness(1.08)}', '.ikun-volwrap{display:flex;align-items:center;gap:6px;margin-left:auto}', '.ikun-vol{accent-color:var(--dsw-alias-brand-primary,#2f7fe0);width:80px}', '.ikun-foot{margin-top:10px;text-align:center;color:var(--dsw-alias-label-secondary,#5d6a96);font-size:12px;letter-spacing:1px}', '.ikun-error{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:10px;padding:8px 10px;border-radius:10px;background:rgba(229,72,77,.1);color:var(--dsw-alias-state-error-primary,#e5484d);font-size:12px}', '.ikun-eq{display:flex;gap:3px;align-items:flex-end;height:18px;margin-top:8px}', '.ikun-eq-bar{width:4px;border-radius:2px;background:var(--dsw-alias-brand-primary,#2f7fe0);animation:ikun-eq .9s ease-in-out infinite;animation-play-state:paused}', '.ikun-eq.playing .ikun-eq-bar{animation-play-state:running}', '.ikun-eq-bar:nth-child(1){animation-delay:0s}', '.ikun-eq-bar:nth-child(2){animation-delay:.15s}', '.ikun-eq-bar:nth-child(3){animation-delay:.3s}', '.ikun-eq-bar:nth-child(4){animation-delay:.45s}', '.ikun-settings{padding:2px 2px 28px;color:var(--dsw-alias-label-primary,#1b2340);font-family:inherit}', '.ikun-banner{display:flex;gap:16px;align-items:center;border-radius:14px;padding:16px;color:#fff;background:linear-gradient(135deg,var(--dsw-alias-brand-primary,#2f7fe0) 0%,#6a5cff 55%,#9b4dff 100%);box-shadow:0 8px 24px rgba(91,120,255,.25)}', '.ikun-banner-avatar{flex:none;line-height:0}', '.ikun-banner-photo{width:76px;height:76px;border-radius:50%;object-fit:cover;border:3px solid rgba(255,255,255,.75);box-shadow:0 4px 14px rgba(0,0,0,.3)}', '.ikun-banner-text{min-width:0}', '.ikun-banner-title{font-size:18px;font-weight:800}', '.ikun-banner-sub{margin-top:4px;font-size:13px;opacity:.92}', '.ikun-banner-badge{display:inline-block;margin-top:10px;padding:3px 10px;border-radius:999px;background:rgba(255,255,255,.18);font-size:12px}', '.ikun-h3{margin:22px 0 10px;font-size:14px;font-weight:700;color:var(--dsw-alias-label-primary,#1b2340)}', '.ikun-theme-cards{display:flex;gap:10px}', '.ikun-theme-card{flex:1;min-width:0;text-align:left;cursor:pointer;border-radius:12px;padding:12px;border:2px solid var(--dsw-alias-border-l2,#c6cfe8);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#1b2340);font:inherit;transition:border-color .15s ease,transform .15s ease}', '.ikun-theme-card:hover{transform:translateY(-1px)}', '.ikun-theme-card.ikun-active{border-color:var(--dsw-alias-brand-primary,#2f7fe0)}', '.ikun-theme-emoji{font-size:22px}', '.ikun-theme-name{font-weight:700;margin-top:6px;font-size:13.5px}', '.ikun-theme-desc{font-size:12px;color:var(--dsw-alias-label-secondary,#5d6a96);margin-top:2px}', '.ikun-theme-tag{display:inline-block;margin-top:8px;padding:2px 8px;border-radius:999px;background:var(--dsw-alias-brand-primary,#2f7fe0);color:#fff;font-size:11px;font-weight:700}', '.ikun-wall-row,.ikun-music-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px;border-radius:12px;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l1,#dde3f2)}', '.ikun-music-info{min-width:0}', '.ikun-music-actions{display:flex;gap:6px;flex:none}', '.ikun-list{margin:0;padding-left:18px;line-height:1.9;font-size:13px;color:var(--dsw-alias-label-secondary,#5d6a96)}', '.ikun-dance{animation:ikun-bounce .65s ease-in-out infinite}', '.ikun-ball-group{animation:none;transform-box:fill-box;transform-origin:center}', '.ikun-dance .ikun-ball-group{animation:ikun-spin 1.4s linear infinite}', '.ikun-mascot{position:fixed;right:14px;bottom:12px;z-index:9998;pointer-events:none;line-height:0;animation:ikun-idle 2.4s ease-in-out infinite}', '.ikun-mascot .ikun-avatar{filter:drop-shadow(0 4px 10px rgba(0,0,0,.35))}', '.ikun-mascot-dance{animation:ikun-bounce .5s ease-in-out infinite}', '.ikun-mascot-dance .ikun-ball-group{animation:ikun-spin .8s linear infinite}', '@keyframes ikun-pop{from{transform:translateY(10px);opacity:0}to{transform:none;opacity:1}}', '@keyframes ikun-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}', '@keyframes ikun-eq{0%,100%{height:4px}50%{height:18px}}', '@keyframes ikun-bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}', '@keyframes ikun-idle{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}', ].join('\n') // ---------- 挂载(所有副作用都收集进 disposers,卸载时一并清理)---------- ctx.effect(function () { const disposers = [] function safe(fn) { try { return fn() } catch (e) { console.log('ikun: ' + e.message); return null } } disposers.push(safe(function () { return theme.register(DAY) })) disposers.push(safe(function () { return theme.register(NIGHT) })) disposers.push(safe(function () { return theme.register(GOLD) })) store.activeTheme = theme.getTheme().active.id store.preference = theme.getTheme().preference disposers.push(ctx.on('theme/change', function (snap) { setStore({ activeTheme: snap.active.id, preference: snap.preference }) syncWallpaper() })) disposers.push(styles.insert(CSS)) syncWallpaper() if (timer) { disposers.push(timer.interval(function () { if (!isIkunTheme(store.activeTheme)) return wallpaperIdx += 1 applyWallpaper(WALLPAPERS[wallpaperIdx % WALLPAPERS.length]) }, WALLPAPER_INTERVAL)) disposers.push(timer.timeout(function () { if (!store.open) setStore({ open: true }) }, AUTO_OPEN_DELAY)) } else { console.warn('ikun: timer service missing — wallpaper rotation and auto-open disabled') } disposers.push(slots.inject('settings.general.item', function () { // priority -1 低于系统的 0:顶替(shadow)系统外观行而非同 priority 抛错,卸载后系统行自动恢复 return slots.register({ name: 'settings.general.item', id: 'appearance', order: 10, priority: -1 }, function () { return React.createElement(AppearanceRow2, null) }) })) disposers.push(slots.inject('conversation.input.dock', function () { return slots.register({ name: 'conversation.input.dock', id: 'ikun-sendwatch', order: 30 }, function (props) { return React.createElement(SendWatcher, props) }) })) disposers.push(slots.inject('sidebar.footer.action', function () { return slots.register({ name: 'sidebar.footer.action', id: 'ikun-music', label: 'IKUN 音乐' }, function (props) { return React.createElement(FooterAction, props) }) })) disposers.push(slots.inject('shell.overlay', function () { return slots.register({ name: 'shell.overlay', id: 'ikun-player', order: 100 }, function () { return React.createElement(Player, null) }) })) disposers.push(slots.inject('shell.overlay', function () { return slots.register({ name: 'shell.overlay', id: 'ikun-mascot', order: 110 }, function () { return React.createElement(Mascot, null) }) })) disposers.push(slots.inject('settings.section', function () { return slots.register({ name: 'settings.section', id: 'ikun', order: 45, label: 'IKUN 主题' }, function (props) { return React.createElement(SettingsPage, props) }) })) disposers.push(slots.inject('conversation.chat.turnTail', function () { return slots.register({ name: 'conversation.chat.turnTail', select: function (owner) { return { ikun: true } } }, function (props) { return React.createElement(TurnSticker, props) }) })) // 全部注册成功后再应用默认主题:避免中途失败把用户全局偏好持久化到 ikun-day 却无法回滚 const pref = theme.getTheme().preference if (!isIkunTheme(pref)) { safe(function () { theme.setTheme('ikun-day') }) } return function () { removeWallpaper() disposers.forEach(function (d) { if (d) { try { d() } catch (e) {} } }) } }, 'ikun-theme: mount') }, }