Files
SillyTavern_replica/data/regex/presets/MyPreset.json

323 lines
40 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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
}