From 120e5d65c33d856e3898eb0f1731f2159bdff390 Mon Sep 17 00:00:00 2001 From: Quan Date: Sun, 10 Aug 2025 13:41:48 +0800 Subject: [PATCH] =?UTF-8?q?refactor(script):=20=E4=BC=98=E5=8C=96=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=BC=B9=E7=AA=97=E6=8F=90=E7=A4=BA=E7=9A=84=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=E7=95=8C=E9=9D=A2=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/Release_Notes.md | 3 +- static/XHS-Downloader.js | 743 ++++++++++++++++++++------------------- 2 files changed, 374 insertions(+), 372 deletions(-) diff --git a/static/Release_Notes.md b/static/Release_Notes.md index 8337840..c6f6bf0 100644 --- a/static/Release_Notes.md +++ b/static/Release_Notes.md @@ -24,4 +24,5 @@ 2. 添加未登录状态下无法提取作品链接的提示 3. 脚本设置新增 `菜单始终显示` 配置项 4. 修复用户脚本 CSS 样式冲突问题 -5. 优化提取作品 ID 的正则表达式 +5. 优化部分弹窗提示的交互界面样式 +6. 优化提取作品 ID 的正则表达式 diff --git a/static/XHS-Downloader.js b/static/XHS-Downloader.js index 55f3095..610cd1b 100644 --- a/static/XHS-Downloader.js +++ b/static/XHS-Downloader.js @@ -1,7 +1,7 @@ // ==UserScript== // @name XHS-Downloader // @namespace https://github.com/JoeanAmier/XHS-Downloader -// @version 2.1.2 +// @version 2.1.3 // @description 提取小红书作品/用户链接,下载小红书无水印图文/视频作品文件 // @author JoeanAmier // @match http*://xhslink.com/* @@ -45,17 +45,17 @@ }, }, // 位置配置 position: { - bottom: '8rem', left: '2rem' + bottom: '6rem', left: '1rem' }, // 动画配置 animation: { - duration: 0.35, // 动画时长(s) + duration: 0.25, // 动画时长(s) easing: 'cubic-bezier(0.4, 0, 0.2, 1)' } }; - const readme = () => { + const readme = async () => { const instructions = ` -XHS-Downloader 用户脚本 功能清单: +功能清单: 1. 下载小红书无水印作品文件 2. 提取推荐页面作品链接 3. 提取账号发布作品链接 @@ -65,7 +65,7 @@ XHS-Downloader 用户脚本 功能清单: 7. 提取搜索结果作品链接 8. 提取搜索结果用户链接 -XHS-Downloader 用户脚本 详细说明: +注意事项: 1. 下载小红书无水印作品文件时,脚本需要花费时间处理文件,请等待片刻,请勿多次点击下载按钮 2. 无水印作品文件较大,可能需要较长的时间处理,页面跳转可能会导致下载失败 3. 提取账号发布、收藏、点赞、专辑作品链接时,脚本可以自动滚动页面直至加载全部作品 @@ -77,30 +77,35 @@ XHS-Downloader 用户脚本 详细说明: 项目开源地址:https://github.com/JoeanAmier/XHS-Downloader ` const disclaimer_content = ` -关于 XHS-Downloader 的 免责声明: - 1. 使用者对本项目的使用由使用者自行决定,并自行承担风险。作者对使用者使用本项目所产生的任何损失、责任、或风险概不负责。 -2. 本项目的作者提供的代码和功能是基于现有知识和技术的开发成果。作者尽力确保代码的正确性和安全性,但不保证代码完全没有错误或缺陷。 -3. 使用者在使用本项目时必须严格遵守 GNU General Public License v3.0 的要求,并在适当的地方注明使用了 GNU General Public License v3.0 的代码。 -4. 使用者在任何情况下均不得将本项目的作者、贡献者或其他相关方与使用者的使用行为联系起来,或要求其对使用者使用本项目所产生的任何损失或损害负责。 +2. 本项目的作者提供的代码和功能是基于现有知识和技术的开发成果。作者按现有技术水平努力确保代码的正确性和安全性,但不保证代码完全没有错误或缺陷。 +3. 本项目依赖的所有第三方库、插件或服务各自遵循其原始开源或商业许可,使用者需自行查阅并遵守相应协议,作者不对第三方组件的稳定性、安全性及合规性承担任何责任。 +4. 使用者在使用本项目时必须严格遵守 GNU General Public License v3.0 的要求,并在适当的地方注明使用了 GNU General Public License v3.0 的代码。 5. 使用者在使用本项目的代码和功能时,必须自行研究相关法律法规,并确保其使用行为合法合规。任何因违反法律法规而导致的法律责任和风险,均由使用者自行承担。 -6. 本项目的作者不会提供 XHS-Downloader 项目的付费版本,也不会提供与 XHS-Downloader 项目相关的任何商业服务。 -7. 基于本项目进行的任何二次开发、修改或编译的程序与原创作者无关,原创作者不承担与二次开发行为或其结果相关的任何责任,使用者应自行对因二次开发可能带来的各种情况负全部责任。 +6. 使用者不得使用本工具从事任何侵犯知识产权的行为,包括但不限于未经授权下载、传播受版权保护的内容,开发者不参与、不支持、不认可任何非法内容的获取或分发。 +7. 本项目不对使用者涉及的数据收集、存储、传输等处理活动的合规性承担责任。使用者应自行遵守相关法律法规,确保处理行为合法正当;因违规操作导致的法律责任由使用者自行承担。 +8. 使用者在任何情况下均不得将本项目的作者、贡献者或其他相关方与使用者的使用行为联系起来,或要求其对使用者使用本项目所产生的任何损失或损害负责。 +9. 本项目的作者不会提供 XHS-Downloader 项目的付费版本,也不会提供与 XHS-Downloader 项目相关的任何商业服务。 +10. 基于本项目进行的任何二次开发、修改或编译的程序与原创作者无关,原创作者不承担与二次开发行为或其结果相关的任何责任,使用者应自行对因二次开发可能带来的各种情况负全部责任。 +11. 本项目不授予使用者任何专利许可;若使用本项目导致专利纠纷或侵权,使用者自行承担全部风险和责任。未经作者或权利人书面授权,不得使用本项目进行任何商业宣传、推广或再授权。 +12. 作者保留随时终止向任何违反本声明的使用者提供服务的权利,并可能要求其销毁已获取的代码及衍生作品。 +13. 作者保留在不另行通知的情况下更新本声明的权利,使用者持续使用即视为接受修订后的条款。 在使用本项目的代码和功能之前,请您认真考虑并接受以上免责声明。如果您对上述声明有任何疑问或不同意,请不要使用本项目的代码和功能。如果您使用了本项目的代码和功能,则视为您已完全理解并接受上述免责声明,并自愿承担使用本项目的一切风险和后果。 - -是否已阅读 XHS-Downloader 功能说明与免责声明(YES/NO) ` - alert(instructions); + + await showTextModal({ + title: 'XHS-Downloader 脚本说明', text: instructions, mode: 'info', // info: 仅关闭 + closeText: '关闭' + }); if (!config.disclaimer) { - const answer = prompt(disclaimer_content, ""); - if (!answer) { - GM_setValue("disclaimer", false); - config.disclaimer = false; - } else { - GM_setValue("disclaimer", answer.toUpperCase() === "YES" || answer.toUpperCase() === "Y"); - config.disclaimer = GM_getValue("disclaimer"); - } + showTextModal({ + title: 'XHS-Downloader 免责声明', text: disclaimer_content, mode: 'confirm', // confirm: 确认+关闭 + confirmText: '我已知晓', closeText: '关闭' + }).then(answer => { + GM_setValue("disclaimer", answer); + config.disclaimer = answer; + }); } }; @@ -149,9 +154,21 @@ XHS-Downloader 用户脚本 详细说明: } const abnormal = (text) => { - alert(`${text}请向作者反馈!\n项目开源地址:https://github.com/JoeanAmier/XHS-Downloader`); + showTextModal({ + title: '发生异常', + text: `${text}请向作者反馈!\n项目开源地址:https://github.com/JoeanAmier/XHS-Downloader`, + mode: 'info', // info: 仅关闭 + closeText: '关闭' + }); }; + const runTips = (text) => { + showTextModal({ + title: '脚本提示', text: text, mode: 'info', // info: 仅关闭 + closeText: '关闭' + }); + } + const generateVideoUrl = note => { try { return [`https://sns-video-bd.xhscdn.com/${note.video.consumer.originVideoKey}`]; @@ -518,7 +535,7 @@ XHS-Downloader 用户脚本 详细说明: if (order >= 0 && order <= 2) { data = extractNotesInfo(order); if (!invalidDetection(data)) { - alert("提取作品链接失败!受平台限制,未登录状态下无法通过账号主页浏览作品!"); + runTips("提取作品链接失败!受平台限制,未登录状态下无法通过账号主页浏览作品详情!请登录后重试!"); return; } } else if (order === 3) { @@ -550,217 +567,282 @@ XHS-Downloader 用户脚本 详细说明: }; if (typeof JSZip === 'undefined') { - alert("XHS-Downloader 用户脚本依赖库 JSZip 加载失败,作品文件打包下载功能无法使用,请尝试刷新网页或者向作者反馈!"); + runTips("XHS-Downloader 用户脚本依赖库 JSZip 加载失败,作品文件打包下载功能无法使用,请尝试刷新网页或者向作者反馈!"); } - /* ==================== 样式定义 ==================== */ let style = document.createElement('style'); style.textContent = ` - /* 弹窗基础样式 */ - #SettingsOverlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0,0,0,0.32); - backdrop-filter: blur(4px); - display: flex; - justify-content: center; - align-items: center; - z-index: 10000; - animation: fadeIn 0.3s; - } + /* 通用 Overlay(三个弹窗共用) */ + #SettingsOverlay, + #imageSelectionOverlay, + #textGenericOverlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.32); + backdrop-filter: blur(4px); + display: flex; + justify-content: center; + align-items: center; + z-index: 10000; + animation: fadeIn 0.3s; + } - .optimized-scroll-modal { - background: white; - border-radius: 16px; - width: 380px; /* 缩小窗口宽度 */ - max-width: 95vw; - box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); - overflow: hidden; - animation: scaleUp 0.3s; - } + /* Settings 容器,仅本块特有尺寸 */ + .optimized-scroll-modal { + background: white; + border-radius: 16px; + width: 380px; /* 缩小窗口宽度 */ + max-width: 95vw; + box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); + overflow: hidden; + animation: scaleUp 0.3s; + } - /* 头部样式 */ - .modal-header { - padding: 1rem; - border-bottom: 1px solid #eee; - text-align: center; - } + /* 通用头部/内容/底部/按钮(三个弹窗共用) */ + .modal-header { + padding: 1rem; + border-bottom: 1px solid #eee; + text-align: center; + } + .modal-header span { + font-size: 1.25rem; + font-weight: 500; + color: #212121; + } + .modal-body { + flex: 1; + padding: 1rem; + overflow-y: auto; + } + .modal-footer { + padding: 1rem; + border-top: 1px solid #eee; + display: flex; + justify-content: flex-end; + gap: 12px; + } + .primary-btn { + background: #2196F3; + color: white; + padding: 8px 24px; + border-radius: 24px; + cursor: pointer; + transition: all 0.2s; + } + .secondary-btn { + background: #f0f0f0; + color: #666; + padding: 8px 24px; + border-radius: 24px; + cursor: pointer; + transition: all 0.2s; + } - .modal-header span { - font-size: 1.25rem; - font-weight: 500; - color: #212121; - } + /* Settings 专用的设置项样式(保持不变) */ + .setting-item { + margin: 0.5rem 0; + padding: 10px; + border-radius: 8px; + transition: background 0.2s; + } + .setting-item:hover { background: #f0f0f0; } + .setting-item label { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + } + .setting-item label span { + font-size: 1rem; + font-weight: 500; + color: #333; + } - /* 内容区域 */ - .modal-body { - padding: 1rem; /* 减小内边距 */ - } + .toggle-switch { + position: relative; + width: 40px; + height: 20px; + } + .toggle-switch input { opacity: 0; width: 0; height: 0; } + .slider { + position: absolute; + cursor: pointer; + top: 0; left: 0; right: 0; bottom: 0; + background: #ccc; + transition: 0.4s; + border-radius: 34px; + } + .slider:before { + content: ""; + position: absolute; + height: 16px; width: 16px; + left: 2px; bottom: 2px; + background: white; + border-radius: 50%; + transition: 0.4s; + } + input:checked + .slider { background: #2196F3; } + input:checked + .slider:before { transform: translateX(20px); } - /* 设置项样式 */ - .setting-item { - margin: 0.5rem 0; /* 减少设置项间距 */ - padding: 10px; - border-radius: 8px; - transition: background 0.2s; - } + .number-input { + display: flex; + align-items: center; + border: 1px solid #ddd; + border-radius: 8px; + overflow: hidden; + margin: 6px 0; + } + .number-input input { + width: 60px; + text-align: center; + border: none; + } + .number-button { + padding: 4px 8px; + background: #f0f0f0; + border: none; + cursor: pointer; + transition: all 0.2s; + } + .text-input { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 0.9rem; + margin-top: 8px; + transition: border-color 0.2s; + } + .text-input:focus { + outline: none; + border-color: #2196F3; + box-shadow: 0 0 4px rgba(33, 150, 243, 0.3); + } + .setting-description { + font-size: 0.875rem; + color: #757575; + margin-top: 4px; + line-height: 1.4; + text-align: left; + } - .setting-item:hover { - background: #f0f0f0; - } - - .setting-item label { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - } - - /* 设置项标题 */ - .setting-item label span { - font-size: 1rem; /* 增大标题文字 */ - font-weight: 500; - color: #333; - } - - /* 开关样式 */ - .toggle-switch { - position: relative; - width: 40px; - height: 20px; - } - - .toggle-switch input { - opacity: 0; - width: 0; - height: 0; - } - - .slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: #ccc; - transition: 0.4s; - border-radius: 34px; - } - - .slider:before { - content: ""; - position: absolute; - height: 16px; - width: 16px; - left: 2px; - bottom: 2px; - background: white; - border-radius: 50%; - transition: 0.4s; - } - - input:checked + .slider { - background: #2196F3; - } - - input:checked + .slider:before { - transform: translateX(20px); - } - - /* 数值输入 */ - .number-input { - display: flex; - align-items: center; - border: 1px solid #ddd; - border-radius: 8px; - overflow: hidden; - margin: 6px 0; - } - - .number-input input { - width: 60px; - text-align: center; - border: none; - } - - .number-button { - padding: 4px 8px; - background: #f0f0f0; - border: none; - cursor: pointer; - transition: all 0.2s; - } - - /* 文本输入框 */ - .text-input { - width: 100%; - padding: 8px; - border: 1px solid #ddd; - border-radius: 4px; - font-size: 0.9rem; - margin-top: 8px; /* 增加与标题的距离 */ - transition: border-color 0.2s; - } - - .text-input:focus { - outline: none; - border-color: #2196F3; - box-shadow: 0 0 4px rgba(33, 150, 243, 0.3); - } - - /* 设置项说明 */ - .setting-description { - font-size: 0.875rem; - color: #757575; - margin-top: 4px; - line-height: 1.4; - text-align: left; /* 左对齐 */ - } - - /* 底部按钮 */ - .modal-footer { - padding: 1rem; - border-top: 1px solid #eee; - display: flex; - justify-content: flex-end; - gap: 12px; - } - - .primary-btn { - background: #2196F3; - color: white; - padding: 8px 24px; - border-radius: 24px; - cursor: pointer; - transition: all 0.2s; - } - - .secondary-btn { - background: #f0f0f0; - color: #666; - padding: 8px 24px; - border-radius: 24px; - cursor: pointer; - transition: all 0.2s; - } - - /* 动画 */ - @keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } - } - - @keyframes scaleUp { - from { transform: scale(0.98); } - to { transform: scale(1); } - } + /* 通用动画:统一定义一次 */ + @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } + @keyframes scaleUp { from { transform: scale(0.98); } to { transform: scale(1); } } + @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } `; document.head.appendChild(style); + // 覆盖修改:文本弹窗样式 + (() => { + if (!document.getElementById('textModalStyle')) { + const style = document.createElement('style'); + style.id = 'textModalStyle'; + style.textContent = ` + /* 仅文本弹窗容器特有的尺寸与外观 */ + .text-generic-modal { + background: #fff; + border-radius: 16px; + width: 80%; + max-width: 700px; + max-height: 80vh; + box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); + overflow: hidden; + animation: scaleUp 0.3s; + display: flex; + flex-direction: column; + } + /* 仅该弹窗使用的文本内容样式 */ + .text-content { + white-space: pre-wrap; + word-break: break-word; + color: #1e272e; + line-height: 1.6; + font-size: 0.95rem; + user-select: text; + } + `; + document.head.appendChild(style); + } + })(); + + /** + * 显示文本弹窗(手动调用) + * @param {Object} opts + * @param {string} opts.title 标题 + * @param {string} opts.text 文本内容 + * @param {'confirm'|'info'} opts.mode 模式:confirm=确认+关闭;info=仅关闭 + * @param {string} [opts.confirmText='确认'] 确认按钮文案(仅 confirm 模式生效) + * @param {string} [opts.closeText='关闭'] 关闭按钮文案 + * @returns {Promise} confirm 返回 true;关闭/点遮罩返回 false + */ + function showTextModal(opts) { + const { + title = '提示', text = '', mode = 'info', confirmText = '确认', closeText = '关闭', + } = opts || {}; + + if (document.getElementById('textGenericOverlay')) { + return Promise.resolve(false); + } + + return new Promise((resolve) => { + const overlay = document.createElement('div'); + overlay.id = 'textGenericOverlay'; + + const modal = document.createElement('div'); + modal.className = 'text-generic-modal'; + + const header = document.createElement('div'); + header.className = 'modal-header'; + header.innerHTML = `${title}`; + + const body = document.createElement('div'); + body.className = 'modal-body'; + const content = document.createElement('div'); + content.className = 'text-content'; + content.textContent = text ?? ''; + body.appendChild(content); + + const footer = document.createElement('div'); + footer.className = 'modal-footer'; + + if (mode === 'confirm') { + const okBtn = document.createElement('button'); + okBtn.className = 'primary-btn'; + okBtn.textContent = confirmText; + okBtn.addEventListener('click', () => close(true)); + footer.appendChild(okBtn); + } + + const closeBtn = document.createElement('button'); + closeBtn.className = 'secondary-btn'; + closeBtn.textContent = closeText; + closeBtn.addEventListener('click', () => close(false)); + footer.appendChild(closeBtn); + + modal.appendChild(header); + modal.appendChild(body); + modal.appendChild(footer); + overlay.appendChild(modal); + document.body.appendChild(overlay); + + function close(result) { + overlay.style.animation = 'fadeOut 0.2s'; + setTimeout(() => { + overlay.remove(); + resolve(result); + }, 200); + } + + overlay.addEventListener('click', (e) => { + if (e.target === overlay) close(false); + }); + }); + } + // 创建开关项 const createSettingItem = ({label, description, checked}) => { const item = document.createElement('div'); @@ -962,155 +1044,74 @@ XHS-Downloader 用户脚本 详细说明: /* ==================== 样式定义 ==================== */ style = document.createElement('style'); style.textContent = ` - /* 弹窗基础样式 */ - #imageSelectionOverlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0,0,0,0.32); - backdrop-filter: blur(4px); - display: flex; - justify-content: center; - align-items: center; - z-index: 10000; - animation: fadeIn 0.3s; - } + /* 图片选择弹窗:仅容器尺寸与自身网格等特有样式 */ + .image-selection-modal { + background: white; + border-radius: 16px; + width: 80%; + max-width: 900px; + max-height: 90vh; + box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); + overflow: hidden; + animation: scaleUp 0.3s; + display: flex; + flex-direction: column; + } - .image-selection-modal { - background: white; - border-radius: 16px; - width: 80%; - max-width: 900px; - max-height: 90vh; - box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); - overflow: hidden; - animation: scaleUp 0.3s; - display: flex; - flex-direction: column; - } + /* 图片网格等仅此弹窗拥有的样式 */ + .image-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 12px; + } + .image-item { + position: relative; + border-radius: 8px; + overflow: hidden; + cursor: pointer; + transition: all 0.2s; + border: 2px solid transparent; + } + .image-item img { + width: 100%; + height: 100px; + object-fit: cover; + border-radius: 6px; + } + .image-item.selected { border-color: #2196F3; } - /* 头部样式 */ - .modal-header { - padding: 1rem; - border-bottom: 1px solid #eee; - text-align: center; - } - - .modal-header span { - font-size: 1.25rem; - font-weight: 500; - color: #212121; - } - - /* 内容区域 */ - .modal-body { - flex: 1; - padding: 1rem; - overflow-y: auto; - } - - /* 图片网格 */ - .image-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); - gap: 12px; - } - - .image-item { - position: relative; - border-radius: 8px; - overflow: hidden; - cursor: pointer; - transition: all 0.2s; - border: 2px solid transparent; - } - - .image-item img { - width: 100%; - height: 100px; - object-fit: cover; - border-radius: 6px; - } - - .image-item.selected { - border-color: #2196F3; - } - - .image-checkbox { - position: absolute; - top: 8px; - right: 8px; - width: 20px; - height: 20px; - opacity: 0; - } - - .image-checkbox + label { - position: absolute; - top: 8px; - right: 8px; - width: 20px; - height: 20px; - background: white; - border: 1px solid #ccc; - border-radius: 50%; - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; - transition: all 0.2s; - } - - .image-checkbox:checked + label { - background: #2196F3; - border-color: #2196F3; - } - - .image-checkbox:checked + label::after { - content: "✓"; - color: white; - font-size: 12px; - } - - /* 底部按钮 */ - .modal-footer { - padding: 1rem; - border-top: 1px solid #eee; - display: flex; - justify-content: flex-end; - gap: 12px; - } - - .primary-btn { - background: #2196F3; - color: white; - padding: 8px 24px; - border-radius: 24px; - cursor: pointer; - transition: all 0.2s; - } - - .secondary-btn { - background: #f0f0f0; - color: #666; - padding: 8px 24px; - border-radius: 24px; - cursor: pointer; - transition: all 0.2s; - } - - /* 动画 */ - @keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } - } - - @keyframes scaleUp { - from { transform: scale(0.98); } - to { transform: scale(1); } - } + .image-checkbox { + position: absolute; + top: 8px; + right: 8px; + width: 20px; + height: 20px; + opacity: 0; + } + .image-checkbox + label { + position: absolute; + top: 8px; + right: 8px; + width: 20px; + height: 20px; + background: white; + border: 1px solid #ccc; + border-radius: 50%; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + transition: all 0.2s; + } + .image-checkbox:checked + label { + background: #2196F3; + border-color: #2196F3; + } + .image-checkbox:checked + label::after { + content: "✓"; + color: white; + font-size: 12px; + } `; document.head.appendChild(style);