@taloswork/ose-shared 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/package.json +3 -3
  2. package/skills/cozy-static-svg/SKILL.md +76 -0
  3. package/skills/cozy-static-svg/references/approved-preview.png +0 -0
  4. package/skills/cozy-static-svg/references/cross-platform.md +73 -0
  5. package/skills/cozy-static-svg/references/moon_keeps_watch.svg +229 -0
  6. package/skills/cozy-static-svg/references/style-anchor.md +271 -0
  7. package/skills/cozy-static-svg/scripts/artkit.py +321 -0
  8. package/skills/cozy-static-svg/scripts/to_png.py +333 -0
  9. package/skills/diagram-design/SKILL.md +568 -0
  10. package/skills/diagram-design/assets/example-architecture-dark.html +182 -0
  11. package/skills/diagram-design/assets/example-architecture-full.html +194 -0
  12. package/skills/diagram-design/assets/example-architecture.html +183 -0
  13. package/skills/diagram-design/assets/example-bar-dark.html +129 -0
  14. package/skills/diagram-design/assets/example-bar-full.html +105 -0
  15. package/skills/diagram-design/assets/example-bar.html +129 -0
  16. package/skills/diagram-design/assets/example-data-flow-dark.html +143 -0
  17. package/skills/diagram-design/assets/example-data-flow-full.html +185 -0
  18. package/skills/diagram-design/assets/example-data-flow.html +143 -0
  19. package/skills/diagram-design/assets/example-datalake-dark.html +243 -0
  20. package/skills/diagram-design/assets/example-datalake-full.html +248 -0
  21. package/skills/diagram-design/assets/example-datalake.html +269 -0
  22. package/skills/diagram-design/assets/example-dp-integration-dark.html +80 -0
  23. package/skills/diagram-design/assets/example-dp-integration-full.html +90 -0
  24. package/skills/diagram-design/assets/example-dp-integration.html +80 -0
  25. package/skills/diagram-design/assets/example-dp-security-matrix-dark.html +31 -0
  26. package/skills/diagram-design/assets/example-dp-security-matrix-full.html +32 -0
  27. package/skills/diagram-design/assets/example-dp-security-matrix.html +88 -0
  28. package/skills/diagram-design/assets/example-er-dark.html +202 -0
  29. package/skills/diagram-design/assets/example-er-full.html +205 -0
  30. package/skills/diagram-design/assets/example-er.html +202 -0
  31. package/skills/diagram-design/assets/example-flowchart-dark.html +156 -0
  32. package/skills/diagram-design/assets/example-flowchart-full.html +159 -0
  33. package/skills/diagram-design/assets/example-flowchart.html +156 -0
  34. package/skills/diagram-design/assets/example-gantt-dark.html +137 -0
  35. package/skills/diagram-design/assets/example-gantt-full.html +127 -0
  36. package/skills/diagram-design/assets/example-gantt.html +137 -0
  37. package/skills/diagram-design/assets/example-high-level-dark.html +251 -0
  38. package/skills/diagram-design/assets/example-high-level-full.html +247 -0
  39. package/skills/diagram-design/assets/example-high-level-vertical-dark.html +274 -0
  40. package/skills/diagram-design/assets/example-high-level-vertical-full.html +252 -0
  41. package/skills/diagram-design/assets/example-high-level-vertical.html +325 -0
  42. package/skills/diagram-design/assets/example-high-level.html +301 -0
  43. package/skills/diagram-design/assets/example-import-drawio.html +185 -0
  44. package/skills/diagram-design/assets/example-import-mermaid.html +70 -0
  45. package/skills/diagram-design/assets/example-it-state-dark.html +90 -0
  46. package/skills/diagram-design/assets/example-it-state-full.html +69 -0
  47. package/skills/diagram-design/assets/example-it-state.html +128 -0
  48. package/skills/diagram-design/assets/example-layers-dark.html +123 -0
  49. package/skills/diagram-design/assets/example-layers-full.html +126 -0
  50. package/skills/diagram-design/assets/example-layers.html +123 -0
  51. package/skills/diagram-design/assets/example-line-dark.html +111 -0
  52. package/skills/diagram-design/assets/example-line-full.html +105 -0
  53. package/skills/diagram-design/assets/example-line.html +111 -0
  54. package/skills/diagram-design/assets/example-loop-dark.html +100 -0
  55. package/skills/diagram-design/assets/example-loop-full.html +137 -0
  56. package/skills/diagram-design/assets/example-loop-terminal.html +347 -0
  57. package/skills/diagram-design/assets/example-loop.html +100 -0
  58. package/skills/diagram-design/assets/example-medallion-dark.html +159 -0
  59. package/skills/diagram-design/assets/example-medallion-full.html +201 -0
  60. package/skills/diagram-design/assets/example-medallion.html +159 -0
  61. package/skills/diagram-design/assets/example-nested-dark.html +132 -0
  62. package/skills/diagram-design/assets/example-nested-full.html +135 -0
  63. package/skills/diagram-design/assets/example-nested.html +136 -0
  64. package/skills/diagram-design/assets/example-org-chart-dark.html +45 -0
  65. package/skills/diagram-design/assets/example-org-chart-full.html +45 -0
  66. package/skills/diagram-design/assets/example-org-chart.html +45 -0
  67. package/skills/diagram-design/assets/example-paved-road-animated.html +546 -0
  68. package/skills/diagram-design/assets/example-policy-trace-animated.html +490 -0
  69. package/skills/diagram-design/assets/example-process-dark.html +64 -0
  70. package/skills/diagram-design/assets/example-process-full.html +74 -0
  71. package/skills/diagram-design/assets/example-process.html +64 -0
  72. package/skills/diagram-design/assets/example-pyramid-dark.html +119 -0
  73. package/skills/diagram-design/assets/example-pyramid-full.html +122 -0
  74. package/skills/diagram-design/assets/example-pyramid.html +119 -0
  75. package/skills/diagram-design/assets/example-quadrant-consultant.html +166 -0
  76. package/skills/diagram-design/assets/example-quadrant-dark.html +135 -0
  77. package/skills/diagram-design/assets/example-quadrant-full.html +138 -0
  78. package/skills/diagram-design/assets/example-quadrant.html +135 -0
  79. package/skills/diagram-design/assets/example-queue-animated.html +577 -0
  80. package/skills/diagram-design/assets/example-radar-dark.html +139 -0
  81. package/skills/diagram-design/assets/example-radar-full.html +135 -0
  82. package/skills/diagram-design/assets/example-radar.html +139 -0
  83. package/skills/diagram-design/assets/example-scatter-dark.html +141 -0
  84. package/skills/diagram-design/assets/example-scatter-full.html +116 -0
  85. package/skills/diagram-design/assets/example-scatter.html +141 -0
  86. package/skills/diagram-design/assets/example-sequence-dark.html +222 -0
  87. package/skills/diagram-design/assets/example-sequence-full.html +388 -0
  88. package/skills/diagram-design/assets/example-sequence-oauth-dark.html +188 -0
  89. package/skills/diagram-design/assets/example-sequence-oauth-full.html +350 -0
  90. package/skills/diagram-design/assets/example-sequence-oauth.html +227 -0
  91. package/skills/diagram-design/assets/example-sequence.html +222 -0
  92. package/skills/diagram-design/assets/example-state-dark.html +147 -0
  93. package/skills/diagram-design/assets/example-state-full.html +150 -0
  94. package/skills/diagram-design/assets/example-state.html +147 -0
  95. package/skills/diagram-design/assets/example-swimlane-dark.html +172 -0
  96. package/skills/diagram-design/assets/example-swimlane-full.html +175 -0
  97. package/skills/diagram-design/assets/example-swimlane.html +172 -0
  98. package/skills/diagram-design/assets/example-timeline-dark.html +135 -0
  99. package/skills/diagram-design/assets/example-timeline-full.html +138 -0
  100. package/skills/diagram-design/assets/example-timeline.html +135 -0
  101. package/skills/diagram-design/assets/example-tree-dark.html +173 -0
  102. package/skills/diagram-design/assets/example-tree-full.html +176 -0
  103. package/skills/diagram-design/assets/example-tree.html +173 -0
  104. package/skills/diagram-design/assets/example-venn-dark.html +132 -0
  105. package/skills/diagram-design/assets/example-venn-full.html +135 -0
  106. package/skills/diagram-design/assets/example-venn.html +110 -0
  107. package/skills/diagram-design/assets/icons.html +230 -0
  108. package/skills/diagram-design/assets/index.html +369 -0
  109. package/skills/diagram-design/assets/template-dark.html +85 -0
  110. package/skills/diagram-design/assets/template-full.html +413 -0
  111. package/skills/diagram-design/assets/template-motion.html +434 -0
  112. package/skills/diagram-design/assets/template-terminal.html +173 -0
  113. package/skills/diagram-design/assets/template.html +86 -0
  114. package/skills/diagram-design/references/animation.md +141 -0
  115. package/skills/diagram-design/references/export.md +136 -0
  116. package/skills/diagram-design/references/import-drawio.md +171 -0
  117. package/skills/diagram-design/references/import-mermaid.md +126 -0
  118. package/skills/diagram-design/references/onboarding.md +297 -0
  119. package/skills/diagram-design/references/output-spec.md +181 -0
  120. package/skills/diagram-design/references/primitive-annotation.md +36 -0
  121. package/skills/diagram-design/references/primitive-icons.md +827 -0
  122. package/skills/diagram-design/references/primitive-sketchy.md +43 -0
  123. package/skills/diagram-design/references/primitive-terminal.md +76 -0
  124. package/skills/diagram-design/references/semantic-patterns.md +122 -0
  125. package/skills/diagram-design/references/style-guide.md +139 -0
  126. package/skills/diagram-design/references/type-architecture.md +78 -0
  127. package/skills/diagram-design/references/type-bar.md +48 -0
  128. package/skills/diagram-design/references/type-data-flow.md +374 -0
  129. package/skills/diagram-design/references/type-dp-integration.md +410 -0
  130. package/skills/diagram-design/references/type-dp-security-matrix.md +379 -0
  131. package/skills/diagram-design/references/type-er.md +23 -0
  132. package/skills/diagram-design/references/type-flowchart.md +23 -0
  133. package/skills/diagram-design/references/type-gantt.md +45 -0
  134. package/skills/diagram-design/references/type-high-level.md +458 -0
  135. package/skills/diagram-design/references/type-it-state.md +470 -0
  136. package/skills/diagram-design/references/type-layers.md +26 -0
  137. package/skills/diagram-design/references/type-line.md +44 -0
  138. package/skills/diagram-design/references/type-loop.md +223 -0
  139. package/skills/diagram-design/references/type-medallion.md +356 -0
  140. package/skills/diagram-design/references/type-nested.md +22 -0
  141. package/skills/diagram-design/references/type-org-chart.md +44 -0
  142. package/skills/diagram-design/references/type-process.md +495 -0
  143. package/skills/diagram-design/references/type-pyramid.md +33 -0
  144. package/skills/diagram-design/references/type-quadrant.md +81 -0
  145. package/skills/diagram-design/references/type-radar.md +80 -0
  146. package/skills/diagram-design/references/type-scatter.md +39 -0
  147. package/skills/diagram-design/references/type-sequence.md +130 -0
  148. package/skills/diagram-design/references/type-state.md +21 -0
  149. package/skills/diagram-design/references/type-swimlane.md +20 -0
  150. package/skills/diagram-design/references/type-timeline.md +20 -0
  151. package/skills/diagram-design/references/type-tree.md +24 -0
  152. package/skills/diagram-design/references/type-venn.md +26 -0
  153. package/skills/diagram-design/scripts/drawio_extract.py +856 -0
  154. package/skills/diagram-design/scripts/mermaid_extract.py +1285 -0
  155. package/skills/diagram-design/scripts/self_check.py +389 -0
@@ -0,0 +1,271 @@
1
+ # 画风速查(《月亮替你值夜班》标准)
2
+
3
+ 版本:基于 2026-09-10 用户认可的《月亮替你值夜班》;改动本文件前先征得用户确认。
4
+ 用法:**每幅开画前通读本文件**;仅在需要复习完整结构时,再打开 `moon_keeps_watch.svg` 与 `approved-preview.png` 对照。速查表 + 自查清单 + artkit 质检,三者照做即可保持原作精细度。
5
+
6
+ **尺寸定位**:要学的是手法(圆润大形、渐变层次、分层空间、留白、温柔短句),**不是它的夜色**。原作是夜景只是恰好;默认出图应当**明快、明亮,明亮调与中间调为主**,夜景当少数例外。
7
+
8
+ **文件结构**:画布与技术骨架 → 硬底线(明度 / 对比 / 可读性)→ 题材轮换 → 颜色 → 层序与片段 → 动物配方 → 检查清单。
9
+
10
+ ## 画布与技术骨架
11
+
12
+ - 默认 1200×900(4:3 横幅);用户指定尺寸优先。
13
+ - 根节点起手式:
14
+
15
+ ```svg
16
+ <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="900" viewBox="0 0 1200 900" role="img" aria-labelledby="art-title art-desc">
17
+ <title id="art-title">…</title>
18
+ <desc id="art-desc">…(与画面一致,不夸大)</desc>
19
+ ```
20
+
21
+ - 全幅圆角由最外层 clipPath 统一裁切(文字也要包含在内),rx≈30:
22
+
23
+ ```svg
24
+ <clipPath id="canvas"><rect width="1200" height="900" rx="30"/></clipPath>
25
+ <g clip-path="url(#canvas)"> …全部内容… </g>
26
+ ```
27
+
28
+ - 纯静态、纯矢量、自包含:无动画 / 脚本 / 外链 / 位图 / foreignObject。
29
+
30
+ ## 硬底线(不可让步)
31
+
32
+ 三条底线,按顺序过:**明度均衡 → 对比度 → 文字可读性**;它们优先于“好不好看”。共同底线:**脸、物件轮廓、文字的对比必须清楚**(亮底尤其别用浅色字)。
33
+
34
+ ### 明度均衡
35
+
36
+ 原作是夜景,但那份深底不是默认值。成批创作时**亮调、中间调要占多数**,深色夜景只当做偶尔的一种。判断标准很简单:
37
+
38
+ - 每 3–4 张里,至少 2 张是**亮底或中间调**(晨光、米白、浅木、雾面、淡彩)。
39
+ - 连续两张不要都是深底夜景;连续三张深底就算跑偏,第三张必须换亮。
40
+ - 深色只做点缀(主体、轮廓、文字、局部阴影),**不做整幅的默认底色**。
41
+ - 亮底不等于惨白:用米白、奶乳、浅木、雾青、樱粉这类**带暖度的浅色**打底,再以中深色物体压住画面重心。
42
+
43
+ ### 对比度
44
+
45
+ 明快不等于发飘。亮底必须靠**深色锚点**立住,深底必须靠**大面积亮部**立住;整幅要有一处明确的明暗落差,不能到处差不多亮。
46
+
47
+ - 亮底立住的三类锚点(至少用两类):**深色主体**(黑猫、深墨绿杯、深蓝书)、**成片的影子**(窗格影、斜射光带、投影)、**深色结构**(深木台面、深色屋档)。
48
+ - 深底立住的亮部:灯 / 发亮的窗是核心,再加上成片的浅色面(浅色石阶、白墙、浅色台面),亮部面积不能只是几个小点。
49
+ - 量化参考:亮底画里,最深处应当接近 `#3A2C22 / #242130`(不是浅褐);深底画里,最亮处应当接近 `#FFFDF0 / #FFF6E2`。
50
+ - 关键焦点(眼睛、灯芯、窗格中心)用**两端中最强的那一级**:亮底上最深的衬着最亮的,深底上最亮的衬着最深的。
51
+ - 别用低透明黑整层压暗来“造”对比,那是相下去不是对比;对比靠亮部与暗部**各自的纯度**。
52
+ - 对比要做在**有限的几个点点**上(主体、光源、影子),其余区域保持柔和过渡;整幅处处高对比会硬、会吵。
53
+
54
+ ### 文字可读性
55
+
56
+ 文字必须能在它的实际底色上轻松读出来。这是底线,不是风格偏好——好看的配色也要为重。
57
+
58
+ - **按文字所在区域定色,不按整幅平均色定色**:落在渐变压暗处、光斑上、深色物体上的字,以该处实际背景为准染深或染亮。
59
+ - **先保证对比,再谈协调**:把标题与注脚的明度与背景拉开;相邻明度差不足时,加深文字或给文字加一层轻柔的衬底/暗影,不要靠“整体感觉差不多”蒙过去。
60
+ - **亮底用深字,暗底用亮字**:亮底文字用深褐 / 深墨(如 `#4A3B34 / #2F4B54`),不要反过来,也不要给亮底配奶油色字、给暗底配中灰字。
61
+ - **文字不要横跨明暗交界**:不让句子一半在亮区、一半在暗区;让它整体待在一种背景上,必要时挪位置。
62
+ - **字号越小,对比要求越高**:右下小字最细最易糊,它比标题更需要拉足对比。
63
+ - 仅靠字形描边或很淡的投影来“救”低对比字是不够的;对比不足就直接换色。
64
+ - 四角文字都在圆角裁切内,选色时一并考虑裁切边缘的底色。
65
+
66
+ ## 题材轮换(成批创作尤其重要)
67
+
68
+ 画风固定,题材不能固定。同一批图里反复出现「远山+月+一盏灯」就是跑偏,哪怕每张坐标都换了。
69
+
70
+ - **连续两张不同题材类型**,不要连着两张都是山景(或两张都是室内窗户)。
71
+ - **每 4 张一轮**,至少覆盖三类不同题材:自然风景 / 室内生活 / 季节天气 / 旅途与建筑 / 动物与物件。
72
+ - **同一批里一个母题最多用两次**(母题=月色、灯火、远山、窗、荷、雪、舟……);第三次就换。
73
+ - **换的不只是题材名字,还有构图骨架**:不要每张都是“横向分天地”、一个远景加一个主体;俯视、近景特写、斜线分栏都可以用。
74
+ - **主体也可以换**:猫、兔、刺猬、狐狸、鸟、小鸭、鲸都能当主角;不必总用同一只猫换姿势。
75
+ - 用户指定了题材就直接用;没指定时,按上一张的类型主动错开。
76
+
77
+ ## 颜色(自由发挥,不设固定色板)
78
+
79
+ 颜色由你按题材与情绪自由决定、可多变;下表只是原作的参考示例,**不是必用色板**,任何协调的色系都可以用。
80
+
81
+ | 用途 | 原作示例(仅供参考) |
82
+ | --- | --- |
83
+ | 室内暮紫渐变 | `#252138 → #46334D → #70515F` |
84
+ | 窗外紫粉夜色 | `#343453 → #77627D → #E8AD9B` |
85
+ | 远 / 中 / 近山 | `#97788D / #695776 / #3D364F` |
86
+ | 暖金月光 | `#FFF0C5` |
87
+ | 小灯玻璃 | `#FFE9BB → #FFDAA0 → #ECC18A` |
88
+ | 橘猫毛色 | `#D89462 → #ECAE78 → #F5CA94`(尾 `#C4855F→#EDB783`) |
89
+ | 叶绿 | `#6F9488 / #9BB09B / #B4BD9F` |
90
+ | 灯座灰绿 | `#455D59 / #61746A` |
91
+ | 文字奶油 | `#F5E1CD / #E2C9B6 / #C7AFAD / #A98F98` |
92
+ | 腮红 / 条纹 | `#D08A72(低透明) / #BB7957` |
93
+
94
+ **亮调底色参考**(不是强制色板,只是把「亮」具象化,避免每次都摸回夜色):
95
+
96
+ | 氛围 | 底色示例 | 配文字色 |
97
+ | --- | --- | --- |
98
+ | 晨光米白 | `#FBF3E6 → #F3E3CC` | `#5A4A3E` |
99
+ | 浅木暖调 | `#F1DFC4 → #D9BE9A` | `#4E3A2E` |
100
+ | 雾青淡彩 | `#EAF4F2 → #CBE3E0` | `#2F4B54` |
101
+ | 樱粉霞色 | `#FCEAE4 → #F3CFC4` | `#6A4640` |
102
+ | 岚色雪白 | `#F4F7FA → #D9E4EE` | `#3C4E63` |
103
+ | 苹果园绿 | `#EFF6DC → #D2E3B4` | `#3F5A38` |
104
+
105
+ - 每幅可换色系:森林绿、海水蓝、晨雾灰、暮紫、樱粉、秋橙、雪青……都行;成批创作尤其鼓励每张不同,避免千篇一律。
106
+ - 保持的是手法而不是色值:渐变过渡自然、层次分明、主光源统一(暖灯 / 冷月 / 落日 / 晨雾 / 日间自然光皆可)。
107
+
108
+ ## 层序总则(从后往前画)
109
+
110
+ 1. 室内底色与氛围 2. 窗外/环境远景(全部收进 clip) 3. 结构(窗框 / 屋檐 / 栏杆)
111
+ 4. 远→中→近景 5. 承托面(窗台 / 桌面 / 地面) 6. 道具 7. 主体 8. 细节(纹理 / 表情 / 高光) 9. 文字
112
+
113
+ - 每个物件:接触阴影(低透明扁椭圆)+ 明确承托;灯、盆、书不得悬空。
114
+ - 只在最后画文字,保证不被压住。
115
+
116
+ ## 可复用片段(照结构改造;色值按当幅色系替换,别照抄坐标)
117
+
118
+ ### 拱窗(框内远景不越界)
119
+
120
+ ```svg
121
+ <path d="M254 707V472C254 284 409 149 600 149S946 284 946 472V707Z" fill="#2B243A"/><!-- 外框底 -->
122
+ <path d="M262 703V472C262 289 412 158 600 158S938 289 938 472V703Z" fill="#A38189"/><!-- 框面 -->
123
+ <clipPath id="window"><path d="M270 698V472C270 294 416 166 600 166S930 294 930 472V698Z"/></clipPath>
124
+ <g clip-path="url(#window)"> …天空 / 山 / 小窗屋… </g>
125
+ <path d="M276 473C276 299 418 173 600 173" fill="none" stroke="#E2BCA5" stroke-width="2" opacity="0.4"/><!-- 内缘高光最后画 -->
126
+ ```
127
+
128
+ ### 月牙(mask 切,光晕用 2–3 个低透明同心圆)
129
+
130
+ ```svg
131
+ <mask id="crescent" maskUnits="userSpaceOnUse" x="707" y="250" width="120" height="120">
132
+ <rect x="707" y="250" width="120" height="120" fill="black"/>
133
+ <circle cx="767" cy="310" r="48" fill="white"/>
134
+ <circle cx="786" cy="293" r="43" fill="black"/>
135
+ </mask>
136
+ <circle cx="767" cy="310" r="110" fill="#FFF0C5" opacity="0.025"/>
137
+ <circle cx="767" cy="310" r="48" fill="#FFF0C5" mask="url(#crescent)"/>
138
+ ```
139
+
140
+ ### 星光(defs+use 复用,省时省体积)
141
+
142
+ ```svg
143
+ <path id="spark" d="M0-8Q1-1 8 0Q1 1 0 8Q-1 1-8 0Q-1-1 0-8Z"/>
144
+ <use href="#spark" transform="translate(565 332)" opacity="0.8"/>
145
+ <use href="#spark" transform="translate(388 352) scale(0.7)" opacity="0.65"/>
146
+ ```
147
+
148
+ ### 暖光晕(径向渐变,不靠模糊滤镜)
149
+
150
+ ```svg
151
+ <radialGradient id="lamp-halo">
152
+ <stop stop-color="#FFD29A" stop-opacity="0.35"/>
153
+ <stop offset="0.5" stop-color="#FFC989" stop-opacity="0.12"/>
154
+ <stop offset="1" stop-color="#FFC989" stop-opacity="0"/>
155
+ </radialGradient>
156
+ <circle cx="824" cy="621" r="183" fill="url(#lamp-halo)"/>
157
+ ```
158
+
159
+ ### 动物圆脸(闭眼弧线 / 鼻嘴 / 腮红)
160
+
161
+ ```svg
162
+ <path d="M438 543Q445 551 453 543" fill="none" stroke="#65454A" stroke-width="3" stroke-linecap="round"/>
163
+ <path d="M471 561V566M471 566Q464 572 461 567M471 566Q478 572 481 567" fill="none" stroke="#65454A" stroke-width="1.8" stroke-linecap="round"/>
164
+ <ellipse cx="435" cy="557" rx="8" ry="4.5" fill="#D08A72" opacity="0.55"/>
165
+ ```
166
+
167
+ ### 条纹补丁(clip 到身体路径)
168
+
169
+ ```svg
170
+ <clipPath id="cat-body"><path d="M…身体同一路径…"/></clipPath>
171
+ <g clip-path="url(#cat-body)" fill="none" stroke="#BB7957" stroke-width="13" stroke-linecap="round" opacity="0.5">…3 条弧线…</g>
172
+ ```
173
+
174
+ ### 文字组(位置与样式照做,字句随画面)
175
+
176
+ 默认版(不带诗词,右下带一句小字):
177
+
178
+ ```svg
179
+ <g font-family="'PingFang SC','Noto Sans CJK SC','Microsoft YaHei',sans-serif">
180
+ <text x="86" y="92" fill="#F5E1CD" font-size="27" font-weight="500" letter-spacing="4">标题</text>
181
+ <text x="1112" y="90" text-anchor="end" fill="#C7AFAD" font-size="15" letter-spacing="3">晚安 · 名字</text>
182
+ <text x="86" y="840" fill="#E2C9B6" font-size="17" letter-spacing="2">一句注脚</text>
183
+ <text x="1112" y="841" text-anchor="end" fill="#A98F98" font-size="12" letter-spacing="3">小字</text>
184
+ </g>
185
+ ```
186
+
187
+ (质检脚本会核对字体栈与全部文案。)
188
+
189
+ ### 诗词版:左下角放一首配得上的唐宋诗词
190
+
191
+ 画面相宜时(山水、夜景、四季、雪月、荷塘这类题材尤其合适),左下角题诗词、去掉右下小字;**左上标题、右上落款照旧保留**,全图共五处文字。
192
+
193
+ ```svg
194
+ <g font-family="'PingFang SC','Noto Sans CJK SC','Microsoft YaHei',sans-serif">
195
+ <text x="86" y="92" fill="#F2EBDC" font-size="27" font-weight="500" letter-spacing="4">今晚山里很静</text>
196
+ <text x="1112" y="90" text-anchor="end" fill="#8FA6BE" font-size="15" letter-spacing="3">夜 · 名字</text>
197
+ <text x="86" y="764" fill="#FFFFFF" font-size="28" font-weight="600" letter-spacing="6">月出惊山鸟</text>
198
+ <text x="86" y="814" fill="#FFF8E4" font-size="28" font-weight="600" letter-spacing="6">时鸣春涧中</text>
199
+ <text x="88" y="858" fill="#F4EEDC" font-size="14" letter-spacing="3">唐 · 王维《鸟鸣涧》</text>
200
+ </g>
201
+ ```
202
+
203
+ **标题与诗句分工(别写成一句话)**:
204
+
205
+ - **左上标题**:写自己的话,讲画面的感受或情境(「今晚山里很静」「帐外还下着雪」),口语、短句。
206
+ - **左下诗句**:引原典原文,只选能**与画面互相对上**的句子(画里有鸟有月就选《鸟鸣涧》,有成排灯火就选「夜深千帐灯」)。
207
+ - 两者**不重复、不互相解释**:不拿诗句当标题,也不让标题去翻译诗句。
208
+
209
+ - **结构**:诗句两行(同字号、同字距、左对齐,行间距 50)+出处一行(朝代 · 作者《篇名》)。
210
+ - **字号与字重**:标题 27 号 / 字重 500 / 字距 4(与默认版一致);诗句 28 号 / 字重 600 / 字距 6;出处 14 号 / 字距 3,比诗句小一号且对比不低。
211
+ - **位置**:左上标题 (86, 92);诗句 x=86、出处 x=88,三行基线 764 / 814 / 858,整体待在左下角、不碰圆角;**右下不放小字**。
212
+ - **出处不省**:朝代与作者写清,字号虽小但对比要够。
213
+
214
+ **诗词版的对比标准(比普通注脚更严)**:诗句是画面主文字之一,必须一眼看清。
215
+
216
+ - 诗句与出处都比普通注脚更深/更亮一档:亮底上压到接近墨色(如 `#1E2E24 / #1E3A20`),深底上提到接近纯白(如 `#FFFFFF / #FFF8E4`)。
217
+ - 诗句与出处的对比**不要拉开太多**:出处虽小,也必须在它的底色上看得清,不能发灰发淡。
218
+ - 仍按**文字所在区域**定色:诗句落在深色山影或雪地浅色区上时,以那块背景为准染深或染亮(宁可反向形成强对比,也不要顺着背景走)。
219
+ - 不用透明度、不加投影来“平衡关系”;对比不够就直接换色值或加字重。
220
+
221
+ ### 落款名:按使用者替换,不要照抄
222
+
223
+ 上面片段里的「名字」是占位符,**不是固定文案**——写死就等于把名字串给别人。每张开画前按此决定:
224
+
225
+ 1. **知道对方称呼时**:落款用对方的名字(如「晚安 · 小满」),跟随当前会话的使用者,不要沿用上一张或示例里的名字。
226
+ 2. **不确定时**:改用中性落款,只留情境词,如「晚安」「早安」「祝你睡个好觉」,宁可不写名字也不要猜错。
227
+ 3. **判断依据(按顺序取第一个可用的)**:当前会话里对方明确的称呼 → 记忆中的使用者姓名 → 中性落款。
228
+
229
+ - 只有认可原作 `references/moon_keeps_watch.svg` 里的「晚安 · 阿答」属于历史事实,保持原样不动;它是**示例来源**,不是**可复制模板**。
230
+ - 同一批多张图里,落款名要一致;换人做则整批一起换,不要一张一个名。
231
+ - 其余文案(标题、注脚)属于画风,保持克制中文短句即可,无需替换。
232
+
233
+ ## 动物主角配方(坐姿猫为例)
234
+
235
+ 1. 接触阴影椭圆 → 2. 身体大形(圆润梨形)→ 3. 条纹组(clip 到身体)→ 4. 肚皮浅色块 →
236
+ 5. 耳朵(先画,含内耳深色)→ 6. 头(盖住耳根)→ 7. 脸(闭眼两段弧、小三角鼻、两小钩嘴、腮红、2–4 根细胡须)→
237
+ 8. 前爪(圆头柱状,落在大形下缘)→ 9. 尾巴(最后画:粗圆头 stroke 绕出一侧,可与身体交叠出层次)
238
+
239
+ - 要点:圆头圆身;闭眼用上拱弧线;耳朵内色深一号。
240
+ - 变体:坐 / 趴 / 躺 / 回头 / 伸爪——改大形与四肢,**不要每张平移同一只猫**。
241
+
242
+ ## 开画前自查清单(过一遍再落盘,能省一轮返工)
243
+
244
+ **结构与画法**
245
+ - [ ] 月牙用 mask;不用天空色圆"假装"遮挡
246
+ - [ ] 窗 / 框内远景全部被 clip;外框线压在最上
247
+ - [ ] 耳先头后、身先脸后、尾巴最后并压住衔接
248
+ - [ ] 每个物件有影子 + 承托;灯落座、书贴桌
249
+ - [ ] 全幅圆角由 canvas clip 统一(含文字)
250
+ - [ ] 重复小元素(星 / 叶 / 毛簇)用 defs+use
251
+ - [ ] 不复用上一张的同一套坐标;题材与前一张不同类型,母题未超两次,构图骨架换过
252
+
253
+ **底线三条(明度 / 对比 / 可读性)**
254
+ - [ ] 整幅底色不是默认深色(成批时亮调 / 中间调占多数,无连续夜景)
255
+ - [ ] 整幅有一处明确的明暗落差:亮底有深色锚点(主体 / 影子 / 深色结构)或深底有大面积亮部
256
+ - [ ] 对比集中在主体、光源、影子等少数区域,其余保持柔和过渡
257
+ - [ ] 文字按所在区域选色(不是按整幅平均):亮底用深、暗底用亮,对比足够,不横跨明暗交界
258
+ - [ ] 小字(如有)在它的底色上能读清楚(最细的字对比要求最高)
259
+
260
+ **文字与静态底线**
261
+ - [ ] 文字对比足够、字体栈齐全;无动画 / 外链节点
262
+ - [ ] 落款名已按当前使用者替换(或改用中性落款),没有照抄示例里的名字
263
+ - [ ] 若用了诗词版:五处文字齐全——左上标题(27/500,自拟短句)+右上落款+左下诗句两行(28/600/字距6)+出处一行(14),右下无小字
264
+ - [ ] 诗词版的标题与诗句不重复:标题写自己的话,诗句引原典
265
+ - [ ] 诗词诗句与出处的对比足够:亮底近墨色、深底近纯白,出处没有发灰发淡
266
+
267
+ ## 交付前核对(结合 artkit 质检报告)
268
+
269
+ - 报告无 ✗:结构、圆角裁切(全部内容在框内)、引用、字体栈、静态底线。
270
+ - 调色清单与文案清单逐项核对:整幅用色协调、无错字、句子简短完整。
271
+ - 自查清单(含底线三条、题材轮换、诗词版)已勾完;最终交付 = 质检通过的最新版本,且为零动画原生 SVG。
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env python3
2
+ """静态 SVG 插画质检:结构校验 + 画风合规报告(秒级,直出 SVG,不转 PNG)。
3
+
4
+ 跨平台:macOS / Linux / Windows 都能跑,只用 Python 标准库(需 Python 3.9+)。
5
+
6
+ macOS / Linux python3 scripts/artkit.py 作品.svg
7
+ Windows py -3 scripts\\artkit.py 作品.svg
8
+
9
+ 批量质检(两个平台写法一致:目录和通配符都行,PowerShell 不必自己展开):
10
+ python3 scripts/artkit.py . # 当前目录下全部 .svg(递归)
11
+ python3 scripts/artkit.py "out/*.svg" # 通配符由脚本自己展开
12
+ py -3 scripts\\artkit.py 作品目录 --strict
13
+
14
+ - 结构:命名空间 / 尺寸 / viewBox / title·desc / role·aria / 唯一标识 /
15
+ 内部引用 / 禁用动态与外链 / 数值合法。
16
+ - 画风:全部内容位于圆角裁切内(画布圆角约定)/ 字体栈 / 调色清单 /
17
+ 文案清单 / 文案克制。
18
+ - 退出码:0 通过(可含提示);1 存在不合格项;--strict 时提示也算不合格。
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import argparse
23
+ import glob
24
+ import re
25
+ import sys
26
+ import xml.etree.ElementTree as ET
27
+ from collections import Counter
28
+ from pathlib import Path
29
+
30
+ if sys.version_info < (3, 9):
31
+ sys.exit(
32
+ "需要 Python 3.9 或更新版本(当前 %d.%d);"
33
+ "Windows 请用 py -3 运行,或到 python.org 安装新版。" % sys.version_info[:2]
34
+ )
35
+
36
+ # Windows 控制台默认是 GBK,直接 print 中文可能抛 UnicodeEncodeError → 强制 UTF-8 输出
37
+ if hasattr(sys.stdout, "reconfigure"):
38
+ try:
39
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
40
+ except (AttributeError, ValueError):
41
+ pass
42
+
43
+ SVG_NS = "http://www.w3.org/2000/svg"
44
+ BLOCKED = {
45
+ "script", "animate", "animatetransform", "animatemotion", "animatecolor",
46
+ "set", "discard", "foreignobject", "image", "iframe", "audio", "video", "a",
47
+ }
48
+ URL_RE = re.compile(r"url\(\s*(['\"]?)(.*?)\1\s*\)", re.I)
49
+ CSS_MOTION = re.compile(
50
+ r"@keyframes\b|@import\b|(?:^|[;{\s])(?:-\w+-)?(?:animation|transition)(?:-[\w-]+)?\s*:",
51
+ re.I,
52
+ )
53
+ SIZE_RE = re.compile(r"(?:\d+(?:\.\d+)?|\.\d+)(?:px)?")
54
+ NAN_RE = re.compile(r"\bNaN\b|\bInfinity\b", re.I)
55
+ CJK_RE = re.compile(r"[\u4e00-\u9fff]")
56
+ CJK_FACES = (
57
+ "pingfang sc", "noto sans cjk", "microsoft yahei", "heiti sc", "simhei",
58
+ "source han sans", "wenquanyi",
59
+ )
60
+ COUNT_ORDER = [
61
+ "g", "path", "circle", "ellipse", "rect", "line", "polygon", "polyline",
62
+ "text", "tspan", "use", "linearGradient", "radialGradient", "clipPath", "mask",
63
+ ]
64
+ CLIP_REF_RE = re.compile(r"url\(\s*#([^)\s]+)\s*\)")
65
+
66
+
67
+ def local(tag: str) -> str:
68
+ return tag.rsplit("}", 1)[-1]
69
+
70
+
71
+ def local_attr(key: str) -> str:
72
+ return key.rsplit("}", 1)[-1].lower()
73
+
74
+
75
+ def shorten(text: str, limit: int = 26) -> str:
76
+ return text if len(text) <= limit else text[:limit] + "…"
77
+
78
+
79
+ def inspect(source: Path) -> dict:
80
+ """质检一个 SVG;不合格时抛出 ValueError,通过时返回报告数据。"""
81
+ raw = source.read_text(encoding="utf-8")
82
+ if re.search(r"<!DOCTYPE|<!ENTITY|<\?xml-stylesheet", raw, re.I):
83
+ raise ValueError("不接受 DTD、实体声明或外部样式表。")
84
+ root = ET.fromstring(raw)
85
+ if root.tag != f"{{{SVG_NS}}}svg":
86
+ raise ValueError("根节点必须是含正确命名空间的 SVG。")
87
+ vb = [float(x) for x in re.split(r"[\s,]+", root.get("viewBox", "").strip()) if x]
88
+ if len(vb) != 4 or vb[2] <= 0 or vb[3] <= 0:
89
+ raise ValueError("viewBox 必须包含四个数,宽高须为正数。")
90
+ for attr in ("width", "height"):
91
+ value = root.get(attr, "")
92
+ if not SIZE_RE.fullmatch(value) or float(value.removesuffix("px")) <= 0:
93
+ raise ValueError(f"请显式填写正数 {attr}(数字或 px)。")
94
+ canvas_w = float(root.get("width").removesuffix("px"))
95
+ canvas_h = float(root.get("height").removesuffix("px"))
96
+ for tag in ("title", "desc"):
97
+ node = root.find(f"{{{SVG_NS}}}{tag}")
98
+ if node is None or not "".join(node.itertext()).strip():
99
+ raise ValueError(f"缺少非空的 {tag}。")
100
+ if root.get("role") != "img" or not root.get("aria-labelledby", "").strip():
101
+ raise ValueError("请填写 role=img 和 aria-labelledby。")
102
+
103
+ elements = list(root.iter())
104
+ ids = [e.get("id") for e in elements if e.get("id") is not None]
105
+ if any(not i for i in ids) or len(ids) != len(set(ids)):
106
+ raise ValueError("存在空标识或重复标识。")
107
+ refs = set(root.get("aria-labelledby", "").split())
108
+ colors = Counter()
109
+ for node in elements:
110
+ tag = local(node.tag).lower()
111
+ if tag in BLOCKED or not node.tag.startswith("{" + SVG_NS + "}"):
112
+ raise ValueError(f"不接受的节点:{tag}。")
113
+ for key, value in node.attrib.items():
114
+ name = local_attr(key)
115
+ if name.startswith("on") or name == "base":
116
+ raise ValueError(f"不接受事件处理器或基址属性:{name}。")
117
+ if NAN_RE.search(value):
118
+ raise ValueError(f"属性含非法数值(NaN/Infinity):{name}。")
119
+ if name == "href":
120
+ if not value.startswith("#") or len(value) < 2:
121
+ raise ValueError("href 只能使用非空的内部 #标识引用。")
122
+ refs.add(value[1:])
123
+ if name in {"aria-labelledby", "aria-describedby"}:
124
+ refs.update(value.split())
125
+ if name == "style" and CSS_MOTION.search(value):
126
+ raise ValueError("不接受 CSS 动画、过渡或导入规则。")
127
+ for _, target in URL_RE.findall(value):
128
+ if not target.startswith("#") or len(target) < 2:
129
+ raise ValueError("url() 只能使用内部 #标识引用。")
130
+ refs.add(target[1:])
131
+ if name in {"fill", "stroke", "stop-color"}:
132
+ plain = value.strip().lower()
133
+ if plain and not plain.startswith("url(") and plain not in {
134
+ "none", "currentcolor", "transparent", "inherit",
135
+ }:
136
+ colors[plain] += 1
137
+ if tag == "style":
138
+ css = "".join(node.itertext())
139
+ if CSS_MOTION.search(css):
140
+ raise ValueError("不接受 CSS 动画、过渡或导入规则。")
141
+ for _, target in URL_RE.findall(css):
142
+ if not target.startswith("#") or len(target) < 2:
143
+ raise ValueError("样式只能引用内部资源。")
144
+ refs.add(target[1:])
145
+ missing = refs - set(ids)
146
+ if missing:
147
+ raise ValueError("未定义的引用:" + "、".join(sorted(missing)))
148
+
149
+ # 画风约定:全部内容(含文字)位于圆角裁切分组内
150
+ rounded_clips = {}
151
+ for clip in root.iter(f"{{{SVG_NS}}}clipPath"):
152
+ for child in clip:
153
+ if local(child.tag) == "rect" and child.get("rx"):
154
+ try:
155
+ rx = float(child.get("rx"))
156
+ except ValueError:
157
+ continue
158
+ if rx >= 10:
159
+ rounded_clips[clip.get("id")] = (rx, child)
160
+ if not rounded_clips:
161
+ raise ValueError("缺少全幅圆角裁切:未找到含 rx≥10 矩形的 clipPath(画风约定:全幅统一圆角)。")
162
+ warns = []
163
+ frame_rx = None
164
+ for child in root:
165
+ tag = local(child.tag)
166
+ if tag in {"title", "desc", "defs", "metadata", "style"}:
167
+ continue
168
+ if tag == "g":
169
+ match = CLIP_REF_RE.fullmatch((child.get("clip-path") or "").strip())
170
+ target = match.group(1) if match else None
171
+ if target in rounded_clips:
172
+ rx, rect = rounded_clips[target]
173
+ if frame_rx is None:
174
+ frame_rx = rx
175
+ rw, rh = rect.get("width"), rect.get("height")
176
+ if rw and rh:
177
+ try:
178
+ if abs(float(rw) - canvas_w) > 0.5 or abs(float(rh) - canvas_h) > 0.5:
179
+ warns.append(
180
+ f"圆角裁切 {rw}×{rh} 与画布 {canvas_w:g}×{canvas_h:g} 不完全一致"
181
+ )
182
+ except ValueError:
183
+ pass
184
+ continue
185
+ raise ValueError(
186
+ f"最外层存在未纳入圆角裁切的 {tag}:全部内容(含文字)都应放在"
187
+ "引用圆角 clipPath 的 <g> 内。"
188
+ )
189
+ if frame_rx is None:
190
+ raise ValueError("找不到包住全部内容的圆角裁切分组。")
191
+
192
+ # 字体栈与文案
193
+ parents = {child: parent for parent in root.iter() for child in parent}
194
+ texts = []
195
+ for node in root.iter(f"{{{SVG_NS}}}text"):
196
+ content = "".join(node.itertext()).strip()
197
+ if content:
198
+ texts.append(content)
199
+ stack = node.get("font-family")
200
+ cursor = node
201
+ while stack is None and cursor in parents:
202
+ cursor = parents[cursor]
203
+ stack = cursor.get("font-family")
204
+ if not stack:
205
+ raise ValueError("有文字缺少 font-family(请在 <g> 或 <text> 上给出系统中文字体栈)。")
206
+ if CJK_RE.search(content) and not any(face in stack.lower() for face in CJK_FACES):
207
+ warns.append(f"字体栈可能缺少中文回退:「{shorten(content, 12)}」")
208
+ if len(texts) > 5:
209
+ warns.append(f"文案 {len(texts)} 处,注意克制(建议标题+一句注脚)")
210
+
211
+ counts = Counter(local(e.tag) for e in elements)
212
+ return {
213
+ "width": canvas_w,
214
+ "height": canvas_h,
215
+ "nodes": len(elements),
216
+ "ids": len(ids),
217
+ "refs": len(refs),
218
+ "rx": frame_rx,
219
+ "texts": texts,
220
+ "colors": colors,
221
+ "counts": counts,
222
+ "warns": warns,
223
+ }
224
+
225
+
226
+ def collect_sources(items: list[str]) -> list[Path]:
227
+ """把命令行参数展开成 SVG 文件列表:文件 / 目录 / 通配符都接受。
228
+
229
+ Windows 的 PowerShell 与 cmd 不会替命令展开 *.svg,所以通配符在这里自己展开,
230
+ 两个平台的命令写法才能真正一致。
231
+ """
232
+ found: list[Path] = []
233
+ for item in items:
234
+ text = str(item)
235
+ path = Path(text)
236
+ if path.is_dir():
237
+ found.extend(
238
+ sorted(
239
+ p
240
+ for p in path.rglob("*")
241
+ if p.is_file() and p.suffix.lower() == ".svg"
242
+ )
243
+ )
244
+ continue
245
+ if any(ch in text for ch in "*?["):
246
+ found.extend(
247
+ sorted(
248
+ Path(m)
249
+ for m in glob.glob(text, recursive=True)
250
+ if Path(m).is_file() and Path(m).suffix.lower() == ".svg"
251
+ )
252
+ )
253
+ continue
254
+ found.append(path)
255
+
256
+ unique: list[Path] = []
257
+ seen: set[str] = set()
258
+ for p in found:
259
+ try:
260
+ key = str(p.resolve())
261
+ except OSError:
262
+ key = str(p)
263
+ if key not in seen:
264
+ seen.add(key)
265
+ unique.append(p)
266
+ return unique
267
+
268
+
269
+ def main() -> int:
270
+ parser = argparse.ArgumentParser(
271
+ description="静态 SVG 质检:结构校验 + 画风合规报告(跨平台,纯标准库)",
272
+ epilog="参数可以是文件、目录或通配符;目录会递归查找全部 .svg。",
273
+ )
274
+ parser.add_argument("svgs", nargs="+", help="待质检的 SVG(文件 / 目录 / 通配符)")
275
+ parser.add_argument("--strict", action="store_true", help="提示也视为不合格")
276
+ args = parser.parse_args()
277
+
278
+ sources = collect_sources(args.svgs)
279
+ if not sources:
280
+ print("没有找到任何 .svg —— 检查一下路径,或改用目录 / 通配符批量指定。")
281
+ return 1
282
+
283
+ failed = 0
284
+ warned = 0
285
+ for index, svg in enumerate(sources, 1):
286
+ print(f"[{index}/{len(sources)}] {svg.name}")
287
+ if not svg.is_file():
288
+ print(f" ✗ 文件不存在或不是文件:{svg}")
289
+ failed += 1
290
+ continue
291
+ try:
292
+ report = inspect(svg)
293
+ except (OSError, ValueError, ET.ParseError) as exc:
294
+ print(f" ✗ 检查未通过:{exc}")
295
+ failed += 1
296
+ continue
297
+ counts = report["counts"]
298
+ parts = " · ".join(f"{tag} {counts[tag]}" for tag in COUNT_ORDER if counts.get(tag))
299
+ top = " · ".join(f"{color}×{n}" for color, n in report["colors"].most_common(10))
300
+ print(f" ✓ 结构:{report['width']:g}×{report['height']:g};title/desc/role 齐全;无动态与外链")
301
+ print(f" ✓ 引用:{report['ids']} 个标识 / {report['refs']} 处引用,全部有效")
302
+ print(f" ✓ 裁切:全部内容位于圆角裁切内(rx≈{report['rx']:g})")
303
+ print(f" ✓ 字体:{len(report['texts'])} 处文字字体栈完整")
304
+ print(f" 元素 {report['nodes']}|{parts}")
305
+ print(f" 调色 {len(report['colors'])} 种|{top}")
306
+ print(" 文案|" + ("|".join(shorten(t) for t in report["texts"]) or "无"))
307
+ if report["warns"]:
308
+ warned += 1
309
+ print(" ⚠ 提示|" + ";".join(report["warns"]))
310
+ else:
311
+ print(" 提示|无")
312
+ passed = len(sources) - failed
313
+ tail = ";存在提示" if warned and not failed else ""
314
+ print(f"总计:{passed}/{len(sources)} 通过质检{tail}。")
315
+ if failed or (args.strict and warned):
316
+ return 1
317
+ return 0
318
+
319
+
320
+ if __name__ == "__main__":
321
+ raise SystemExit(main())