323 lines
40 KiB
JSON
323 lines
40 KiB
JSON
{
|
||
"id": "4b674802-fcff-47d3-baf5-8acb21e055aa",
|
||
"name": "MyPreset 规则集",
|
||
"description": "从 SillyTavern 导入的规则",
|
||
"rules": [
|
||
{
|
||
"id": "78b99f83-1fca-4a74-aa23-c19081774315",
|
||
"scriptName": "隐藏-thinking",
|
||
"findRegex": "<thinking>.*?<\\/thinking>",
|
||
"replaceString": "",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": true,
|
||
"promptOnly": true,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": null,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": false,
|
||
"order": 0,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
},
|
||
{
|
||
"id": "565c2e23-be93-44b8-bfba-e48103484dd2",
|
||
"scriptName": "隐藏-MVU变量更新",
|
||
"findRegex": "<UpdateVariable>[\\s\\S]*?</UpdateVariable>",
|
||
"replaceString": "",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": true,
|
||
"promptOnly": true,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": null,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": false,
|
||
"order": 1,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
},
|
||
{
|
||
"id": "a3411920-e1b2-4cea-8513-dbb41972648a",
|
||
"scriptName": "隐藏-COT-正文前",
|
||
"findRegex": "<think-pre-main-body>.*</think-pre-main-body>",
|
||
"replaceString": "",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": true,
|
||
"promptOnly": true,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": null,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": false,
|
||
"order": 2,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
},
|
||
{
|
||
"id": "0e93402e-f9f3-4049-bfc0-4b21b86b9bb4",
|
||
"scriptName": "隐藏-COT-正文中",
|
||
"findRegex": "<COT-MID>.*?<\\/COT-MID>",
|
||
"replaceString": "",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": true,
|
||
"promptOnly": true,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": null,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": false,
|
||
"order": 3,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
},
|
||
{
|
||
"id": "dbf06b5e-f694-475d-8ed8-4fdc62833944",
|
||
"scriptName": "隐藏-表格",
|
||
"findRegex": "<tableEdit>.*?<\\/tableEdit>",
|
||
"replaceString": "",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": true,
|
||
"promptOnly": true,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": null,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": false,
|
||
"order": 4,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
},
|
||
{
|
||
"id": "7808d064-d7d1-4f5d-b7a1-f490e5b7b934",
|
||
"scriptName": "隐藏-表格-确保",
|
||
"findRegex": "<填写表格>.*?<\\/填写表格>",
|
||
"replaceString": "",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": true,
|
||
"promptOnly": true,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": null,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": false,
|
||
"order": 5,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
},
|
||
{
|
||
"id": "26beb992-6895-41fc-96a5-0f87c481d034",
|
||
"scriptName": "对AI隐藏图像提示词",
|
||
"findRegex": "\\[img-gen\\].*?\\[/img-gen\\]",
|
||
"replaceString": "",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": false,
|
||
"promptOnly": true,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": null,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": false,
|
||
"order": 6,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
},
|
||
{
|
||
"id": "f9632716-675d-4d66-b022-32ad035a544e",
|
||
"scriptName": "美化-pornhub小剧场",
|
||
"findRegex": "(?<pornhub_video><pornhub_video>.*?<\\/pornhub_video>)",
|
||
"replaceString": "```\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Pornhub Style Video Card</title>\n <style>\n :root {\n --bg-primary: #000000;\n --bg-card: #111111;\n --bg-secondary: #1f1f1f;\n --accent-orange: #ff9000;\n --accent-orange-dark: #e68200;\n --text-primary: #ffffff;\n --text-secondary: #cccccc;\n --text-muted: #808080;\n --border-color: #333333;\n --play-button: rgba(255, 255, 255, 0.9);\n }\n\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n background: var(--bg-primary);\n color: var(--text-primary);\n min-height: 100vh;\n padding: 10px 0;\n }\n\n .pornhub-container {\n max-width: 960px;\n margin: 0 auto;\n background: var(--bg-card);\n border-radius: 8px;\n overflow: hidden;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.8);\n }\n\n /* 剧情介绍(原缩略图描述)放在最上方 */\n .video-preview {\n position: relative;\n width: 100%;\n padding: 30px 20px;\n background: linear-gradient(to bottom, #222, #111);\n text-align: center;\n }\n\n .preview-text {\n font-size: clamp(18px, 4.5vw, 28px);\n font-weight: bold;\n line-height: 1.4;\n text-shadow: 0 2px 10px rgba(0,0,0,0.8);\n color: var(--text-primary);\n }\n\n /* 真正的缩略图区域 - 放在剧情介绍后面 */\n .video-thumbnail {\n position: relative;\n width: 100%;\n padding-bottom: 56.25%; /* 16:9 */\n background: #000 center/cover no-repeat;\n cursor: pointer;\n }\n\n .thumbnail-overlay {\n position: absolute;\n inset: 0;\n background: linear-gradient(to top, rgba(0,0,0,0.7), transparent 40%);\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .play-button {\n width: 90px;\n height: 90px;\n background: var(--play-button);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 8px 25px rgba(0,0,0,0.6);\n transition: transform 0.2s;\n }\n\n .play-button::after {\n content: '';\n width: 0;\n height: 0;\n border-left: 30px solid var(--accent-orange);\n border-top: 20px transparent solid;\n border-bottom: 20px transparent solid;\n margin-left: 8px;\n }\n\n .play-button:hover {\n transform: scale(1.1);\n }\n\n /* 内容区域 */\n .video-info {\n padding: 24px 20px;\n }\n\n .video-title {\n font-size: clamp(22px, 5vw, 30px);\n font-weight: bold;\n line-height: 1.3;\n margin-bottom: 20px;\n }\n\n .video-description {\n font-size: 16px;\n color: var(--text-secondary);\n line-height: 1.7;\n margin-bottom: 28px;\n white-space: pre-wrap;\n }\n\n .stats-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n gap: 16px;\n padding: 20px 0;\n border-top: 1px solid var(--border-color);\n border-bottom: 1px solid var(--border-color);\n margin-bottom: 28px;\n }\n\n .stat-item {\n font-size: 15px;\n color: var(--text-secondary);\n }\n\n .stat-value {\n color: var(--text-primary);\n font-weight: bold;\n font-size: 16px;\n }\n\n .actions {\n display: flex;\n gap: 16px;\n margin-bottom: 32px;\n flex-wrap: wrap;\n }\n\n .subscribe-btn {\n background: var(--accent-orange);\n color: white;\n border: none;\n padding: 14px 28px;\n border-radius: 4px;\n font-size: 16px;\n font-weight: bold;\n cursor: pointer;\n transition: background 0.3s;\n }\n\n .subscribe-btn:hover {\n background: var(--accent-orange-dark);\n }\n\n .tags {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n margin-bottom: 40px;\n }\n\n .tag {\n background: var(--bg-secondary);\n color: var(--text-primary);\n padding: 10px 18px;\n border-radius: 30px;\n font-size: 15px;\n transition: background 0.2s;\n }\n\n .tag:hover {\n background: #333;\n }\n\n .comments-section {\n border-top: 1px solid var(--border-color);\n padding-top: 32px;\n }\n\n .comments-title {\n font-size: 22px;\n font-weight: bold;\n margin-bottom: 24px;\n }\n\n .comment {\n display: flex;\n gap: 16px;\n padding: 20px 0;\n border-bottom: 1px solid var(--border-color);\n }\n\n .comment:last-child {\n border-bottom: none;\n }\n\n .comment-avatar {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: #444;\n flex-shrink: 0;\n }\n\n .comment-content {\n flex: 1;\n }\n\n .comment-header {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-bottom: 8px;\n }\n\n .comment-user {\n font-weight: bold;\n font-size: 16px;\n }\n\n .verified {\n color: var(--accent-orange);\n font-size: 14px;\n font-weight: bold;\n }\n\n .comment-text {\n color: var(--text-secondary);\n line-height: 1.6;\n font-size: 16px;\n margin-bottom: 10px;\n }\n\n .comment-likes {\n color: var(--text-muted);\n font-size: 15px;\n }\n\n /* 手机端优化 */\n @media (max-width: 768px) {\n body { padding: 0; }\n .video-info { padding: 20px 16px; }\n .video-preview { padding: 24px 16px; }\n .stats-grid { grid-template-columns: 1fr 1fr; }\n .actions { justify-content: center; }\n .play-button { width: 70px; height: 70px; }\n .play-button::after { border-left-width: 24px; border-top-width: 16px; border-bottom-width: 16px; }\n }\n </style>\n</head>\n<body>\n <div class=\"pornhub-container\" id=\"pornhubCard\">\n <!-- 剧情介绍(原 thumbnail 内容)放在最上方 -->\n <div class=\"video-preview\">\n <div class=\"preview-text\" id=\"thumbnailText\"></div>\n </div>\n\n <!-- 缩略图区域(带大播放按钮) -->\n <div class=\"video-thumbnail\" id=\"thumbnailWrapper\">\n <div class=\"thumbnail-overlay\">\n <div class=\"play-button\"></div>\n </div>\n </div>\n\n <div class=\"video-info\">\n <h1 class=\"video-title\" id=\"title\"></h1>\n <p class=\"video-description\" id=\"description\"></p>\n\n <div class=\"stats-grid\" id=\"stats\"></div>\n\n <div class=\"actions\">\n <button class=\"subscribe-btn\" id=\"subscribe\"></button>\n </div>\n\n <div class=\"tags\" id=\"tags\"></div>\n\n <div class=\"comments-section\">\n <h2 class=\"comments-title\">评论</h2>\n <div id=\"comments\"></div>\n </div>\n </div>\n </div>\n\n <script>\n // 正则已改为包含根标签,所以替换后是完整 XML\n const videoData = `$<pornhub_video>`;\n\n function parsePornhubVideo(xmlString) {\n if (!xmlString || !xmlString.includes('<pornhub_video>')) return null;\n \n const parser = new DOMParser();\n const doc = parser.parseFromString(xmlString.trim(), 'text/xml');\n\n if (doc.querySelector('parsererror')) {\n console.error('XML 解析失败');\n return null;\n }\n\n const getText = (tag) => {\n const el = doc.querySelector(tag);\n return el ? el.textContent.trim() : '';\n };\n\n return {\n title: getText('title'),\n thumbnail: getText('thumbnail'), // 剧情介绍文字\n description: getText('description'),\n views: getText('views'),\n like_rate: getText('like_rate'),\n upload_time: getText('upload_time'),\n uploader: getText('uploader'),\n subscribe: getText('subscribe'),\n tags: getText('tags'),\n comments: Array.from(doc.querySelectorAll('comment')).map(c => ({\n user: c.getAttribute('user') || '匿名',\n verified: c.getAttribute('verified') === 'true',\n text: c.childNodes[0] ? c.childNodes[0].textContent.trim() : '',\n likes: getTextFromElement(c, 'likes')\n }))\n };\n }\n\n function getTextFromElement(parent, tag) {\n const el = parent.querySelector(tag);\n return el ? el.textContent.trim() : '';\n }\n\n function renderVideoCard(data) {\n if (!data) return;\n\n document.getElementById('title').textContent = data.title;\n document.getElementById('description').textContent = data.description;\n document.getElementById('thumbnailText').textContent = data.thumbnail; // 剧情介绍放在最上\n document.getElementById('subscribe').textContent = data.subscribe;\n\n // 统计信息\n const stats = document.getElementById('stats');\n stats.innerHTML = `\n <div class=\"stat-item\"><span class=\"stat-value\">${data.views}</span></div>\n <div class=\"stat-item\"><span class=\"stat-value\">${data.like_rate}</span></div>\n <div class=\"stat-item\"><span class=\"stat-value\">${data.upload_time}</span></div>\n <div class=\"stat-item\"><span class=\"stat-value\">${data.uploader}</span></div>\n `;\n\n // 标签\n const tagsContainer = document.getElementById('tags');\n tagsContainer.innerHTML = '';\n data.tags.split(/\\s+/).filter(t => t.length > 0).forEach(tag => {\n const span = document.createElement('span');\n span.className = 'tag';\n span.textContent = tag;\n tagsContainer.appendChild(span);\n });\n\n // 评论\n const commentsContainer = document.getElementById('comments');\n commentsContainer.innerHTML = '';\n data.comments.forEach(comment => {\n const div = document.createElement('div');\n div.className = 'comment';\n div.innerHTML = `\n <div class=\"comment-avatar\"></div>\n <div class=\"comment-content\">\n <div class=\"comment-header\">\n <span class=\"comment-user\">${escapeHtml(comment.user)}</span>\n ${comment.verified ? '<span class=\"verified\">✔ 已认证会员</span>' : ''}\n </div>\n <div class=\"comment-text\">${escapeHtml(comment.text)}</div>\n <div class=\"comment-likes\">${comment.likes}</div>\n </div>\n `;\n commentsContainer.appendChild(div);\n });\n }\n\n function escapeHtml(text) {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n // 执行渲染\n const data = parsePornhubVideo(videoData);\n if (data) {\n renderVideoCard(data);\n } else {\n document.getElementById('title').textContent = '加载失败:XML 解析错误';\n }\n </script>\n</body>\n</html>\n```",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": true,
|
||
"promptOnly": false,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": null,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": false,
|
||
"order": 7,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
},
|
||
{
|
||
"id": "5f345459-8066-4df0-9641-5fa9113740ab",
|
||
"scriptName": "对AI隐藏-小剧场",
|
||
"findRegex": "<pornhub_video>.*?<\\/pornhub_video>",
|
||
"replaceString": "",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": false,
|
||
"promptOnly": true,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": null,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": false,
|
||
"order": 8,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
},
|
||
{
|
||
"id": "e301e033-fe97-4b12-b02e-7d8f76a2b324",
|
||
"scriptName": "对AI隐藏-小剧场-确保",
|
||
"findRegex": "<小剧场>.*?<\\/小剧场>",
|
||
"replaceString": "",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": false,
|
||
"promptOnly": true,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": null,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": false,
|
||
"order": 9,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
},
|
||
{
|
||
"id": "37c62238-b6d5-4445-8fa7-068b68e35109",
|
||
"scriptName": "月经追踪状态栏",
|
||
"findRegex": "<StatusPlaceHolderImpl\\/>",
|
||
"replaceString": "```\n<head>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes\">\n</head>\n<body>\n<%\nif (runType == 'render')\n{\n function SafeGetValue(value, defaultValue = \"\") {\n if (value === undefined || value === null) {\n return defaultValue;\n }\n if (Array.isArray(value)) {\n return value.length !== 0 ? value[0] : defaultValue;\n }\n return value;\n }\n \n function SafeGetDescription(value, defaultValue = \"\") {\n if (Array.isArray(value) && value.length > 1) {\n return value[1];\n }\n return defaultValue;\n }\n \n // 获取周期进度百分比\n function getCycleProgress(day) {\n return Math.min((day / 28) * 100, 100);\n }\n \n // 判断是否为危险期(根据分类判断)\n function isDangerousPeriod(category) {\n return category.includes('排卵期') || category.includes('危险期');\n }\n \n // 获取周期图标(根据全局calculateMenstrualCycle返回的分类)\n function getCycleIcon(category) {\n if (category.includes('月经期')) return '🩸';\n if (category.includes('排卵期')) return '🌸';\n if (category.includes('安全期')) return '✅';\n return '📅';\n }\n \n // 获取周期颜色(根据全局calculateMenstrualCycle返回的分类)\n function getCycleColor(category) {\n if (category.includes('月经期')) return '#DC143C';\n if (category.includes('排卵期')) return '#FF1493';\n if (category.includes('安全期')) return '#32CD32';\n return '#666';\n }\n \n // 获取受孕概率等级(基于天数,与计算器函数逻辑一致)\n function getFertilityLevel(day) {\n if (day >= 1 && day <= 5) return 0; // 月经期\n if (day >= 12 && day <= 16) return 5; // 排卵期高峰\n if (day >= 10 && day <= 19) return 4; // 排卵期\n if (day >= 6 && day <= 22) return 2; // 次级可能\n return 1; // 安全期\n }\n\nconst data = window.TavernHelper.getVariables({type: 'message', message_id: message_id});\nconst msg_data = data.stat_data;\n\n// 动态获取所有有月经数据的角色\nconst characters = [];\nfor (const key in msg_data) {\n if (msg_data[key] && msg_data[key].月经 && msg_data[key].月经.周期天数) {\n // 优先使用姓名字段,如果没有则使用键名\n const displayName = SafeGetValue(msg_data[key].姓名, key);\n characters.push({\n key: key,\n name: displayName,\n data: msg_data[key]\n });\n }\n}\n%>\n\n<style>\n.menstrual-wrapper {\n display: flex;\n justify-content: center;\n align-items: flex-start;\n padding: 10px;\n width: 100%;\n box-sizing: border-box;\n}\n\n.menstrual-container {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n width: 100%;\n max-width: 500px;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n font-size: 13px;\n background: linear-gradient(135deg, #ffeef8 0%, #fff5f7 100%);\n}\n\n.menstrual-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n background: linear-gradient(135deg, #FF69B4 0%, #FF1493 100%);\n color: white;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n touch-action: manipulation;\n}\n\n.menstrual-header:hover {\n background: linear-gradient(135deg, #FF1493 0%, #C71585 100%);\n}\n\n.menstrual-title {\n font-size: 15px;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.collapse-icon {\n font-size: 14px;\n transition: transform 0.2s ease;\n opacity: 0.9;\n}\n\n.collapse-icon.collapsed {\n transform: rotate(-90deg);\n}\n\n.menstrual-body {\n max-height: 2000px;\n overflow: hidden;\n transition: max-height 0.3s ease, opacity 0.2s ease;\n}\n\n.menstrual-body.collapsed {\n max-height: 0;\n opacity: 0;\n}\n\n.tab-nav {\n display: flex;\n gap: 4px;\n padding: 8px 8px 0;\n background: transparent;\n}\n\n.tab-btn {\n flex: 1;\n padding: 10px 14px;\n border: none;\n background: rgba(255,255,255,0.6);\n cursor: pointer;\n font-size: 14px;\n font-weight: 500;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 8px 8px 0 0;\n color: #666;\n -webkit-tap-highlight-color: transparent;\n touch-action: manipulation;\n backdrop-filter: blur(10px);\n position: relative;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.tab-btn:hover {\n background: rgba(255,255,255,0.8);\n transform: translateY(-2px);\n}\n\n.tab-btn.active {\n background: white;\n color: #FF1493;\n box-shadow: 0 -2px 8px rgba(255,20,147,0.2);\n font-weight: 600;\n}\n\n.tab-btn.active::after {\n content: '';\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 2px;\n background: linear-gradient(90deg, #FF69B4, #FF1493);\n}\n\n.tab-content {\n display: none;\n padding: 16px;\n background: white;\n border-radius: 0 0 12px 12px;\n}\n\n.tab-content.active {\n display: block;\n}\n\n.cycle-card {\n background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);\n padding: 16px;\n border-radius: 12px;\n margin-bottom: 16px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.08);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border: 1px solid rgba(255,20,147,0.1);\n}\n\n.cycle-card:hover {\n box-shadow: 0 4px 16px rgba(0,0,0,0.12);\n transform: translateY(-2px);\n}\n\n.cycle-main-info {\n display: flex;\n align-items: center;\n gap: 16px;\n margin-bottom: 16px;\n}\n\n.cycle-icon {\n font-size: 48px;\n line-height: 1;\n filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));\n}\n\n.cycle-details {\n flex: 1;\n}\n\n.cycle-phase {\n font-size: 18px;\n font-weight: 700;\n margin-bottom: 4px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.cycle-day {\n font-size: 14px;\n color: #666;\n font-weight: 500;\n}\n\n.danger-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 11px;\n font-weight: 600;\n background: #FF4444;\n color: white;\n animation: pulse 2s ease-in-out infinite;\n}\n\n@keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.7; }\n}\n\n.safe-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 11px;\n font-weight: 600;\n background: #4CAF50;\n color: white;\n}\n\n.cycle-progress {\n width: 100%;\n height: 8px;\n background: rgba(0,0,0,0.08);\n border-radius: 4px;\n overflow: hidden;\n margin: 12px 0;\n}\n\n.cycle-progress-bar {\n height: 100%;\n background: linear-gradient(90deg, #FF69B4 0%, #FF1493 50%, #C71585 100%);\n border-radius: 4px;\n transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: 0 0 10px rgba(255,20,147,0.5);\n position: relative;\n overflow: hidden;\n}\n\n.cycle-progress-bar::after {\n content: '';\n position: absolute;\n top: 0;\n left: -100%;\n width: 100%;\n height: 100%;\n background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);\n animation: shimmer 2s infinite;\n}\n\n@keyframes shimmer {\n 0% { left: -100%; }\n 100% { left: 100%; }\n}\n\n.cycle-description {\n font-size: 13px;\n color: #555;\n line-height: 1.6;\n padding: 12px;\n background: rgba(255,20,147,0.05);\n border-radius: 8px;\n border-left: 4px solid;\n transition: all 0.3s ease;\n}\n\n.cycle-description:hover {\n background: rgba(255,20,147,0.08);\n padding-left: 16px;\n}\n\n.cycle-stats {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px;\n margin-top: 12px;\n}\n\n.stat-item {\n padding: 10px;\n background: white;\n border-radius: 8px;\n border: 1px solid rgba(0,0,0,0.06);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.stat-item:hover {\n border-color: rgba(255,20,147,0.3);\n box-shadow: 0 2px 8px rgba(255,20,147,0.1);\n transform: translateY(-1px);\n}\n\n.stat-label {\n font-size: 11px;\n color: #888;\n margin-bottom: 4px;\n font-weight: 500;\n}\n\n.stat-value {\n font-size: 14px;\n font-weight: 600;\n color: #333;\n}\n\n.fertility-indicator {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px;\n background: white;\n border-radius: 8px;\n margin-top: 12px;\n border: 2px solid;\n}\n\n.fertility-label {\n font-size: 13px;\n font-weight: 600;\n}\n\n.fertility-dots {\n display: flex;\n gap: 6px;\n}\n\n.fertility-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #ddd;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.fertility-dot.active {\n background: currentColor;\n box-shadow: 0 0 8px currentColor;\n transform: scale(1.2);\n animation: pulse-dot 2s ease-in-out infinite;\n}\n\n@keyframes pulse-dot {\n 0%, 100% { \n transform: scale(1.2);\n opacity: 1; \n }\n 50% { \n transform: scale(1.4);\n opacity: 0.8; \n }\n}\n\n/* 移动端适配 */\n@media (max-width: 768px) {\n .menstrual-wrapper {\n padding: 8px;\n }\n \n .menstrual-container {\n font-size: 12px;\n }\n \n .menstrual-header {\n padding: 10px 12px;\n }\n \n .menstrual-title {\n font-size: 14px;\n }\n \n .tab-content {\n padding: 12px;\n }\n \n .cycle-card {\n padding: 12px;\n margin-bottom: 12px;\n }\n \n .cycle-icon {\n font-size: 36px;\n }\n \n .cycle-phase {\n font-size: 16px;\n }\n \n .cycle-day {\n font-size: 12px;\n }\n \n .cycle-description {\n font-size: 12px;\n padding: 10px;\n }\n \n .cycle-stats {\n gap: 8px;\n }\n \n .stat-item {\n padding: 8px;\n }\n}\n\n/* 超小屏幕适配 */\n@media (max-width: 380px) {\n .menstrual-wrapper {\n padding: 5px;\n }\n \n .cycle-main-info {\n flex-direction: column;\n text-align: center;\n gap: 12px;\n }\n \n .cycle-stats {\n grid-template-columns: 1fr;\n }\n \n .tab-btn {\n font-size: 12px;\n padding: 8px 10px;\n min-width: 60px;\n }\n}\n</style>\n\n<div class=\"menstrual-wrapper\">\n <div class=\"menstrual-container\">\n <!-- 标题栏 -->\n <div class=\"menstrual-header\" onclick=\"toggleCollapse()\">\n <div class=\"menstrual-title\">\n <span>🌸</span>\n <span>月经周期追踪</span>\n <% if (characters.length > 0) { %>\n <span style=\"font-size: 12px; font-weight: 400; opacity: 0.85; margin-left: 4px;\">(<%- characters.length %> 人)</span>\n <% } %>\n </div>\n <div class=\"collapse-icon\" id=\"collapseIcon\">▼</div>\n </div>\n\n <!-- 内容区域 -->\n <div class=\"menstrual-body\" id=\"menstrualBody\">\n <% if (characters.length === 0) { %>\n <div class=\"tab-content active\" style=\"text-align: center; padding: 40px 20px;\">\n <div style=\"font-size: 64px; margin-bottom: 16px; opacity: 0.5;\">📅</div>\n <div style=\"font-size: 16px; color: #999; margin-bottom: 8px; font-weight: 500;\">暂无月经周期数据</div>\n <div style=\"font-size: 13px; color: #bbb;\">请在角色数据中添加月经周期信息</div>\n </div>\n <% } else { %>\n <!-- Tab导航 -->\n <div class=\"tab-nav\">\n <% characters.forEach((char, index) => { %>\n <button class=\"tab-btn <%= index === 0 ? 'active' : '' %>\" onclick=\"switchTab(event, 'char_<%- index %>')\">\n <%- char.name %>\n </button>\n <% }); %>\n </div>\n\n <!-- 动态生成角色月经状态 -->\n <% characters.forEach((char, index) => { \n const cycleDay = SafeGetValue(char.data.月经.周期天数, 1);\n const cycleInfo = typeof calculateMenstrualCycle !== 'undefined' \n ? calculateMenstrualCycle(Number(cycleDay))\n : ['未知', '数据加载中'];\n const cycleCategory = cycleInfo[0];\n const cyclePhase = cycleInfo[1];\n const cycleIcon = getCycleIcon(cycleCategory);\n const cycleColor = getCycleColor(cycleCategory);\n const cycleProgress = getCycleProgress(cycleDay);\n const isDanger = isDangerousPeriod(cycleCategory);\n // 计算距离下次月经的天数(如果当前在月经期,显示0;否则显示剩余天数)\n const daysUntilPeriod = cycleDay <= 5 ? 0 : (29 - cycleDay);\n const fertilityLevel = getFertilityLevel(cycleDay);\n %>\n <div id=\"char_<%- index %>\" class=\"tab-content <%= index === 0 ? 'active' : '' %>\">\n <div class=\"cycle-card\">\n <div class=\"cycle-main-info\">\n <div class=\"cycle-icon\"><%- cycleIcon %></div>\n <div class=\"cycle-details\">\n <div class=\"cycle-phase\" style=\"color: <%- cycleColor %>;\">\n <%- cycleCategory.replace(/(.*?)/g, '') %>\n <% if (isDanger) { %>\n <span class=\"danger-badge\">⚠️ 危险期</span>\n <% } else if (cycleDay >= 1 && cycleDay <= 5) { %>\n <!-- 月经期不显示徽章 -->\n <% } else { %>\n <span class=\"safe-badge\">✓ 安全期</span>\n <% } %>\n </div>\n <div class=\"cycle-day\">周期第 <strong><%- cycleDay %></strong> 天 / 共 28 天</div>\n </div>\n </div>\n \n <div class=\"cycle-progress\">\n <div class=\"cycle-progress-bar\" style=\"width: <%- cycleProgress %>%; background: linear-gradient(90deg, <%- cycleColor %> 0%, <%- cycleColor %>dd 100%);\"></div>\n </div>\n \n <div class=\"cycle-description\" style=\"border-left-color: <%- cycleColor %>;\">\n <%- cyclePhase %>\n </div>\n \n <div class=\"cycle-stats\">\n <div class=\"stat-item\">\n <div class=\"stat-label\">距离下次月经</div>\n <div class=\"stat-value\" style=\"color: <%- cycleColor %>;\">\n <% if (daysUntilPeriod === 0) { %>\n 进行中\n <% } else { %>\n <%- daysUntilPeriod %> 天\n <% } %>\n </div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-label\">周期进度</div>\n <div class=\"stat-value\" style=\"color: <%- cycleColor %>;\"><%- Math.round(cycleProgress) %>%</div>\n </div>\n </div>\n \n <div class=\"fertility-indicator\" style=\"border-color: <%- cycleColor %>; color: <%- cycleColor %>;\">\n <div class=\"fertility-label\">\n <% if (cycleDay >= 1 && cycleDay <= 5) { %>\n 🩸 月经期间\n <% } else if (fertilityLevel >= 4) { %>\n 💋 受孕概率:高\n <% } else if (fertilityLevel >= 2) { %>\n 🤔 受孕概率:中\n <% } else { %>\n 😌 受孕概率:低\n <% } %>\n </div>\n <div class=\"fertility-dots\">\n <% for (let i = 0; i < 5; i++) { %>\n <div class=\"fertility-dot <%= i < fertilityLevel ? 'active' : '' %>\"></div>\n <% } %>\n </div>\n </div>\n </div>\n </div>\n <% }); %>\n <% } %>\n </div>\n </div>\n</div>\n\n<script>\n// 检查月经周期计算器是否可用\nif (typeof calculateMenstrualCycle === 'undefined') {\n console.warn('⚠️ 月经周期计算器函数未找到,请确保已加载 生理周期计算器.js');\n console.info('💡 函数应返回: [安全期分类, 生理阶段描述]');\n} else {\n console.log('✅ 月经周期计算器已加载');\n}\n\n// Tab切换功能\nfunction switchTab(evt, tabId) {\n try {\n // 隐藏所有tab内容\n const contents = document.querySelectorAll('.tab-content');\n contents.forEach(c => c.classList.remove('active'));\n \n // 移除所有按钮的active状态\n const buttons = document.querySelectorAll('.tab-btn');\n buttons.forEach(b => b.classList.remove('active'));\n \n // 显示当前tab\n const targetTab = document.getElementById(tabId);\n if (targetTab) {\n targetTab.classList.add('active');\n evt.currentTarget.classList.add('active');\n }\n } catch (error) {\n console.error('切换标签页失败:', error);\n }\n}\n\n// 折叠/展开功能\nfunction toggleCollapse() {\n try {\n const menstrualBody = document.getElementById('menstrualBody');\n const collapseIcon = document.getElementById('collapseIcon');\n \n if (menstrualBody && collapseIcon) {\n menstrualBody.classList.toggle('collapsed');\n collapseIcon.classList.toggle('collapsed');\n }\n } catch (error) {\n console.error('折叠切换失败:', error);\n }\n}\n</script>\n\n<% } %>\n</body>\n```\n",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": true,
|
||
"promptOnly": false,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": null,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": true,
|
||
"order": 10,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
},
|
||
{
|
||
"id": "26ec780e-149f-489d-a177-de875e559e35",
|
||
"scriptName": "[选项]主题",
|
||
"findRegex": "<options>((?:(?!<options>).)*?)</options>",
|
||
"replaceString": "```html\n<body>\n <script>\n $('body').load('https://testingcf.jsdelivr.net/gh/emo-lsp/SillyTavern@refs/heads/main/QingShuHuai/选项1.0.html')\n </script>\n </body>\n```",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": true,
|
||
"promptOnly": false,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": 2,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": false,
|
||
"order": 11,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
},
|
||
{
|
||
"id": "e61a23ac-bc46-4d49-9f3f-a1ec4756c857",
|
||
"scriptName": "[选项]隐藏",
|
||
"findRegex": "<options>(.*?)<\\/options>",
|
||
"replaceString": "",
|
||
"trimStrings": [],
|
||
"placement": [
|
||
2
|
||
],
|
||
"substituteRegex": 0,
|
||
"markdownOnly": false,
|
||
"promptOnly": true,
|
||
"runOnEdit": true,
|
||
"minDepth": null,
|
||
"maxDepth": null,
|
||
"scope": "preset",
|
||
"characterName": null,
|
||
"presetName": "MyPreset",
|
||
"disabled": false,
|
||
"order": 12,
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"description": null
|
||
}
|
||
],
|
||
"createdAt": 1778078941,
|
||
"updatedAt": 1778078941,
|
||
"version": 1,
|
||
"isSillyTavernFormat": true
|
||
} |