Compare commits

...

12 Commits

Author SHA1 Message Date
Jim Liu 宝玉 c0162bb3af chore: release v1.26.1 2026-01-29 22:02:42 -06:00
Jim Liu 宝玉 e9342a16f8 fix(baoyu-xhs-images): remove notebook style, add backup rules for prompts and images 2026-01-29 22:01:47 -06:00
Jim Liu 宝玉 5cb86d8e51 feat(baoyu-slide-deck): add backup rules for source, prompt, and slide image files 2026-01-29 22:01:42 -06:00
Jim Liu 宝玉 66ed08f010 feat(baoyu-infographic): add backup rules for source, analysis, prompt, and image files 2026-01-29 22:01:22 -06:00
Jim Liu 宝玉 b179166d28 feat(baoyu-cover-image): add backup rules for source and prompt files 2026-01-29 22:01:17 -06:00
Jim Liu 宝玉 4805526649 feat(baoyu-comic): add backup rules for source, character sheet, prompts, and page images 2026-01-29 22:00:59 -06:00
Jim Liu 宝玉 89159bf86e feat(baoyu-article-illustrator): add backup rules for source, prompt, and image files 2026-01-29 22:00:37 -06:00
Jim Liu 宝玉 b45b5d2c6e chore: release v1.26.0 2026-01-29 20:59:45 -06:00
Jim Liu 宝玉 62866426c5 feat(baoyu-xhs-images): add notebook and study-notes styles with mindmap and quadrant layouts
- notebook: hand-drawn infographic style with watercolor rendering and Morandi palette
- study-notes: realistic handwritten photo style with blue pen, red annotations, yellow highlighter
- mindmap: center radial layout (4-8 branches)
- quadrant: four-quadrant / circular section layout
2026-01-29 20:59:24 -06:00
Jim Liu 宝玉 28a7db6129 chore: release v1.25.4 2026-01-29 17:17:58 -06:00
Jim Liu 宝玉 7c5e81fd15 chore: release v1.25.3 2026-01-28 22:31:47 -06:00
Jim Liu 宝玉 058d25d02c feat(baoyu-format-markdown): add content type detection and CJK punctuation handling 2026-01-28 22:31:25 -06:00
17 changed files with 542 additions and 51 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
}, },
"metadata": { "metadata": {
"description": "Skills shared by Baoyu for improving daily work efficiency", "description": "Skills shared by Baoyu for improving daily work efficiency",
"version": "1.25.2" "version": "1.26.1"
}, },
"plugins": [ "plugins": [
{ {
+27
View File
@@ -2,6 +2,33 @@
English | [中文](./CHANGELOG.zh.md) English | [中文](./CHANGELOG.zh.md)
## 1.26.1 - 2026-01-29
### Features
- `baoyu-article-illustrator`, `baoyu-comic`, `baoyu-cover-image`, `baoyu-infographic`, `baoyu-slide-deck`, `baoyu-xhs-images`: add backup rules for existing files—automatically renames source, prompt, and image files with timestamp suffix before overwriting.
### Fixes
- `baoyu-xhs-images`: remove `notebook` style (10 styles remaining).
## 1.26.0 - 2026-01-29
### Features
- `baoyu-xhs-images`: add `notebook` style (hand-drawn infographic with watercolor rendering and Morandi palette) and `study-notes` style (realistic handwritten photo aesthetic).
- `baoyu-xhs-images`: add `mindmap` (center radial) and `quadrant` (four-section grid) layouts.
## 1.25.4 - 2026-01-29
### Fixes
- `baoyu-markdown-to-html`: generate proper `<img>` tags with `data-local-path` attribute instead of text placeholders.
- `baoyu-post-to-wechat`: fix API publishing to read image paths from `data-local-path` attribute; fix title/cover extraction from corresponding `.md` frontmatter when publishing HTML files.
- `baoyu-post-to-wechat`: fix CLI argument parsing to handle unknown parameters gracefully; add `--summary` parameter support.
- `baoyu-post-to-wechat`: fix browser publishing to convert `<img>` tags back to text placeholders before paste.
## 1.25.3 - 2026-01-28
### Features
- `baoyu-format-markdown`: add content type detection with user confirmation for markdown files; add CJK punctuation handling to move paired punctuation outside emphasis markers.
## 1.25.2 - 2026-01-28 ## 1.25.2 - 2026-01-28
### Documentation ### Documentation
+27
View File
@@ -2,6 +2,33 @@
[English](./CHANGELOG.md) | 中文 [English](./CHANGELOG.md) | 中文
## 1.26.1 - 2026-01-29
### 新功能
- `baoyu-article-illustrator``baoyu-comic``baoyu-cover-image``baoyu-infographic``baoyu-slide-deck``baoyu-xhs-images`:新增文件备份规则,覆盖前自动将现有源文件、提示词和图片重命名为带时间戳后缀的备份文件。
### 修复
- `baoyu-xhs-images`:移除 `notebook` 风格(保留 10 种风格)。
## 1.26.0 - 2026-01-29
### 新功能
- `baoyu-xhs-images`:新增 `notebook` 风格(水彩渲染手绘信息图 + 莫兰迪配色)和 `study-notes` 风格(真实手写照片美学)。
- `baoyu-xhs-images`:新增 `mindmap`(中心发散式)和 `quadrant`(四象限)布局。
## 1.25.4 - 2026-01-29
### 修复
- `baoyu-markdown-to-html`:生成带 `data-local-path` 属性的 `<img>` 标签,而非纯文本占位符。
- `baoyu-post-to-wechat`:修复 API 发布时从 `data-local-path` 属性读取图片路径;修复发布 HTML 文件时从对应 `.md` 的 frontmatter 提取标题和封面图。
- `baoyu-post-to-wechat`:修复命令行参数解析,正确跳过未知参数;新增 `--summary` 参数支持。
- `baoyu-post-to-wechat`:修复浏览器发布模式,粘贴前将 `<img>` 标签转换回文本占位符。
## 1.25.3 - 2026-01-28
### 新功能
- `baoyu-format-markdown`:新增内容类型检测,对已有 markdown 格式的文件提供用户确认选项;新增 CJK 配对标点处理,将括号、引号等标点移出加粗标记外。
## 1.25.2 - 2026-01-28 ## 1.25.2 - 2026-01-28
### 文档 ### 文档
+6 -1
View File
@@ -78,6 +78,8 @@ Progress:
| File path | Ask user (1.2) | → 1.2 | | File path | Ask user (1.2) | → 1.2 |
| Pasted content | `illustrations/{topic-slug}/` | → 1.4 | | Pasted content | `illustrations/{topic-slug}/` | → 1.4 |
**Backup rule for pasted content**: If `source.md` exists in target directory, rename to `source-backup-YYYYMMDD-HHMMSS.md` before saving.
**1.2 Determine Output Directory** (file path input only) **1.2 Determine Output Directory** (file path input only)
Check `default_output_dir` in preferences: Check `default_output_dir` in preferences:
@@ -236,6 +238,7 @@ image_count: 4
**5.1 Create Prompts** **5.1 Create Prompts**
Follow [references/prompt-construction.md](references/prompt-construction.md). Save to `prompts/illustration-{slug}.md`. Follow [references/prompt-construction.md](references/prompt-construction.md). Save to `prompts/illustration-{slug}.md`.
- **Backup rule**: If prompt file exists, rename to `prompts/illustration-{slug}-backup-YYYYMMDD-HHMMSS.md`
**5.2 Select Generation Skill** **5.2 Select Generation Skill**
@@ -247,7 +250,9 @@ Add: `Include a subtle watermark "[content]" at [position].`
**5.4 Generate** **5.4 Generate**
1. Generate sequentially 1. For each illustration:
- **Backup rule**: If image file exists, rename to `NN-{type}-{slug}-backup-YYYYMMDD-HHMMSS.png`
- Generate image
2. After each: "Generated X/N" 2. After each: "Generated X/N"
3. On failure: retry once, then log and continue 3. On failure: retry once, then log and continue
+5
View File
@@ -194,6 +194,7 @@ Input → Preferences → Analyze → [Check Existing?] → [Confirm: Style + Re
**Character reference is MANDATORY for visual consistency.** **Character reference is MANDATORY for visual consistency.**
**7.1 Generate character sheet first**: **7.1 Generate character sheet first**:
- **Backup rule**: If `characters/characters.png` exists, rename to `characters/characters-backup-YYYYMMDD-HHMMSS.png`
```bash ```bash
# Use Reference Sheet Prompt from characters/characters.md # Use Reference Sheet Prompt from characters/characters.md
npx -y bun ${SKILL_DIR}/../baoyu-image-gen/scripts/main.ts \ npx -y bun ${SKILL_DIR}/../baoyu-image-gen/scripts/main.ts \
@@ -214,6 +215,10 @@ Compress to reduce token usage when used as reference image:
| Supports `--ref` | Pass `characters/characters.png` with EVERY page | | Supports `--ref` | Pass `characters/characters.png` with EVERY page |
| No `--ref` support | Prepend character descriptions to EVERY prompt file | | No `--ref` support | Prepend character descriptions to EVERY prompt file |
**Backup rules for page generation**:
- If prompt file exists: rename to `prompts/NN-{cover|page}-[slug]-backup-YYYYMMDD-HHMMSS.md`
- If image file exists: rename to `NN-{cover|page}-[slug]-backup-YYYYMMDD-HHMMSS.png`
```bash ```bash
# Example: ALWAYS include --ref for consistency # Example: ALWAYS include --ref for consistency
npx -y bun ${SKILL_DIR}/../baoyu-image-gen/scripts/main.ts \ npx -y bun ${SKILL_DIR}/../baoyu-image-gen/scripts/main.ts \
+9 -5
View File
@@ -84,6 +84,7 @@ Read source content, save it if needed, and perform deep analysis.
1. **Save source content** (if not already a file): 1. **Save source content** (if not already a file):
- If user provides a file path: use as-is - If user provides a file path: use as-is
- If user pastes content: save to `source.md` in target directory - If user pastes content: save to `source.md` in target directory
- **Backup rule**: If `source.md` exists, rename to `source-backup-YYYYMMDD-HHMMSS.md`
2. Read source content 2. Read source content
3. **Deep analysis** following `analysis-framework.md`: 3. **Deep analysis** following `analysis-framework.md`:
- Target audience identification - Target audience identification
@@ -329,6 +330,7 @@ Create image generation prompts for all pages.
1. Create prompt following art style + tone guidelines 1. Create prompt following art style + tone guidelines
2. Include character visual descriptions for consistency 2. Include character visual descriptions for consistency
3. Save to `prompts/NN-{cover|page}-[slug].md` 3. Save to `prompts/NN-{cover|page}-[slug].md`
- **Backup rule**: If prompt file exists, rename to `prompts/NN-{cover|page}-[slug]-backup-YYYYMMDD-HHMMSS.md`
**Prompt File Format**: **Prompt File Format**:
```markdown ```markdown
@@ -405,8 +407,9 @@ With confirmed prompts from Step 5/6:
### 7.1 Generate Character Reference Sheet (first) ### 7.1 Generate Character Reference Sheet (first)
1. Use Reference Sheet Prompt from `characters/characters.md` 1. Use Reference Sheet Prompt from `characters/characters.md`
2. Generate → `characters/characters.png` 2. **Backup rule**: If `characters/characters.png` exists, rename to `characters/characters-backup-YYYYMMDD-HHMMSS.png`
3. This ensures visual consistency for all subsequent pages 3. Generate → `characters/characters.png`
4. This ensures visual consistency for all subsequent pages
### 7.2 Generate Comic Pages ### 7.2 Generate Comic Pages
@@ -453,9 +456,10 @@ When skill does NOT support reference images, create combined prompt files:
**For each page (cover + pages)**: **For each page (cover + pages)**:
1. Read prompt from `prompts/NN-{cover|page}-[slug].md` 1. Read prompt from `prompts/NN-{cover|page}-[slug].md`
2. Generate image using Strategy A or B (based on skill capability) 2. **Backup rule**: If image file exists, rename to `NN-{cover|page}-[slug]-backup-YYYYMMDD-HHMMSS.png`
3. Save to `NN-{cover|page}-[slug].png` 3. Generate image using Strategy A or B (based on skill capability)
4. Report progress after each generation: "Generated X/N: [page title]" 4. Save to `NN-{cover|page}-[slug].png`
5. Report progress after each generation: "Generated X/N: [page title]"
**Session Management**: **Session Management**:
If image generation skill supports `--sessionId`: If image generation skill supports `--sessionId`:
+3
View File
@@ -209,6 +209,7 @@ Schema: [references/config/preferences-schema.md](references/config/preferences-
### Step 1: Analyze Content ### Step 1: Analyze Content
1. **Save source content** (if pasted, save to `source.md` in target directory; if file path, use as-is) 1. **Save source content** (if pasted, save to `source.md` in target directory; if file path, use as-is)
- **Backup rule**: If `source.md` exists, rename to `source-backup-YYYYMMDD-HHMMSS.md`
2. **Content analysis**: Extract topic, core message, tone, keywords; identify visual metaphors; detect content type 2. **Content analysis**: Extract topic, core message, tone, keywords; identify visual metaphors; detect content type
3. **Language detection**: Detect source language, note user's input language, compare with EXTEND.md preference 3. **Language detection**: Detect source language, note user's input language, compare with EXTEND.md preference
4. **Determine output directory** per File Structure rules. If no `default_output_dir` preference + file path input, include in Step 2 Q4 4. **Determine output directory** per File Structure rules. If no `default_output_dir` preference + file path input, include in Step 2 Q4
@@ -226,6 +227,8 @@ Validate all 5 dimensions + aspect ratio. Full confirmation flow: [references/wo
### Step 3: Create Prompt ### Step 3: Create Prompt
**Backup rule**: If `prompts/cover.md` exists, rename to `prompts/cover-backup-YYYYMMDD-HHMMSS.md`
Save to `prompts/cover.md`. Full template: [references/workflow/prompt-template.md](references/workflow/prompt-template.md) Save to `prompts/cover.md`. Full template: [references/workflow/prompt-template.md](references/workflow/prompt-template.md)
### Step 4: Generate Image ### Step 4: Generate Image
+53
View File
@@ -58,6 +58,55 @@ Claude performs content analysis and formatting (Steps 1-6), then runs the scrip
Read the user-specified markdown or plain text file. Read the user-specified markdown or plain text file.
### Step 1.5: Detect Content Type & Confirm
**Content Type Detection:**
| Indicator | Classification |
|-----------|----------------|
| Has `---` YAML frontmatter | Markdown |
| Has `#`, `##`, `###` headings | Markdown |
| Has `**bold**`, `*italic*` | Markdown |
| Has `- ` or `1. ` lists | Markdown |
| Has ``` code blocks | Markdown |
| Has `> ` blockquotes | Markdown |
| None of above | Plain text |
**Decision Flow:**
┌─────────────────┬────────────────────────────────────────────────┐
│ Content Type │ Action │
├─────────────────┼────────────────────────────────────────────────┤
│ Plain text │ Proceed to Step 2 (format to markdown) │
├─────────────────┼────────────────────────────────────────────────┤
│ Markdown │ Use AskUserQuestion to confirm optimization │
└─────────────────┴────────────────────────────────────────────────┘
**If Markdown detected, ask user:**
```
Detected existing markdown formatting. What would you like to do?
1. Optimize formatting (Recommended)
- Add/improve frontmatter, headings, bold, lists
- Run typography script (spacing, emphasis fixes)
- Output: {filename}-formatted.md
2. Keep original formatting
- Preserve existing markdown structure
- Run typography script (spacing, emphasis fixes)
- Output: {filename}-formatted.md
3. Typography fixes only
- Run typography script on original file in-place
- No copy created, modifies original file directly
```
**Based on user choice:**
- **Optimize**: Continue to Step 2-8 (full workflow)
- **Keep original**: Skip Steps 2-5, copy file → Step 6-8 (run script on copy)
- **Typography only**: Skip Steps 2-6, run Step 7 on original file directly
### Step 2: Analyze Content Structure ### Step 2: Analyze Content Structure
Identify: Identify:
@@ -138,6 +187,10 @@ Examples:
- `final.md``final-formatted.md` - `final.md``final-formatted.md`
- `draft-v1.md``draft-v1-formatted.md` - `draft-v1.md``draft-v1-formatted.md`
**If user chose "Keep original formatting" (from Step 1.5):**
- Copy original file to `{filename}-formatted.md` without modifications
- Proceed to Step 7 for typography fixes only
**Backup existing file:** **Backup existing file:**
If `{filename}-formatted.md` already exists, backup before overwriting: If `{filename}-formatted.md` already exists, backup before overwriting:
@@ -11,6 +11,21 @@ export const CJK_CHAR_RE = new RegExp(`[${CJK_SCRIPTS}]`, "u");
const PUNCT_OR_SYMBOL_RE = /[\p{P}\p{S}]/u; const PUNCT_OR_SYMBOL_RE = /[\p{P}\p{S}]/u;
const WORD_CHAR_RE = /[\p{L}\p{N}]/u; const WORD_CHAR_RE = /[\p{L}\p{N}]/u;
const CJK_PUNCT_PAIRS: Record<string, string> = {
"“": "”",
"": "",
"": "",
"": "",
"〖": "〗",
"〘": "〙",
"〚": "〛",
"「": "」",
"『": "』",
"〈": "〉",
"《": "》",
"【": "】",
};
function findInlineCodeRanges(text: string): Array<[number, number]> { function findInlineCodeRanges(text: string): Array<[number, number]> {
const ranges: Array<[number, number]> = []; const ranges: Array<[number, number]> = [];
let i = 0; let i = 0;
@@ -69,8 +84,120 @@ function isPunctuationOrSymbol(ch: string | undefined): boolean {
return !!ch && PUNCT_OR_SYMBOL_RE.test(ch); return !!ch && PUNCT_OR_SYMBOL_RE.test(ch);
} }
function fixCjkEmphasisSpacingInBlock(block: string): string { function isMatchingCjkPunct(open: string, close: string): boolean {
return CJK_PUNCT_PAIRS[open] === close;
}
function mapNonCodeSegments(
block: string,
mapper: (segment: string) => string
): string {
const codeRanges = findInlineCodeRanges(block); const codeRanges = findInlineCodeRanges(block);
if (codeRanges.length === 0) {
return mapper(block);
}
let result = "";
let lastIndex = 0;
for (const [start, end] of codeRanges) {
if (start > lastIndex) {
result += mapper(block.slice(lastIndex, start));
}
result += block.slice(start, end + 1);
lastIndex = end + 1;
}
if (lastIndex < block.length) {
result += mapper(block.slice(lastIndex));
}
return result;
}
function moveCjkPunctuationOutsideEmphasis(block: string): string {
return mapNonCodeSegments(block, (segment) => {
const delimiterPositions: number[] = [];
let cursor = 0;
while (cursor < segment.length - 1) {
if (
segment[cursor] === "*" &&
segment[cursor + 1] === "*" &&
segment[cursor - 1] !== "*" &&
segment[cursor + 2] !== "*" &&
!isEscaped(segment, cursor)
) {
delimiterPositions.push(cursor);
cursor += 2;
continue;
}
cursor += 1;
}
if (delimiterPositions.length < 2) return segment;
const stack: number[] = [];
const pairs: Array<{ open: number; close: number }> = [];
for (const pos of delimiterPositions) {
if (stack.length === 0) {
stack.push(pos);
} else {
const open = stack.pop() as number;
pairs.push({ open, close: pos });
}
}
const skip = new Set<number>();
const insertBefore = new Map<number, string>();
for (const pair of pairs) {
const openPunctPos = pair.open + 2;
const closePunctPos = pair.close - 1;
if (openPunctPos >= closePunctPos) continue;
const openPunct = segment[openPunctPos];
const closePunct = segment[closePunctPos];
if (!openPunct || !closePunct) continue;
if (!CJK_OPENING_PUNCT_RE.test(openPunct)) continue;
if (!CJK_CLOSING_PUNCT_RE.test(closePunct)) continue;
if (!isMatchingCjkPunct(openPunct, closePunct)) continue;
if (openPunctPos + 1 >= closePunctPos) continue;
const inner = segment.slice(openPunctPos + 1, closePunctPos);
if (inner.length === 0) continue;
skip.add(openPunctPos);
skip.add(closePunctPos);
insertBefore.set(pair.open, (insertBefore.get(pair.open) ?? "") + openPunct);
const afterClose = pair.close + 2;
insertBefore.set(
afterClose,
(insertBefore.get(afterClose) ?? "") + closePunct
);
}
if (skip.size === 0) return segment;
let result = "";
for (let idx = 0; idx < segment.length; idx += 1) {
const insert = insertBefore.get(idx);
if (insert) {
result += insert;
}
if (skip.has(idx)) {
continue;
}
result += segment[idx];
}
const tailInsert = insertBefore.get(segment.length);
if (tailInsert) {
result += tailInsert;
}
return result;
});
}
function fixCjkEmphasisSpacingInBlock(block: string): string {
const normalized = moveCjkPunctuationOutsideEmphasis(block);
const codeRanges = findInlineCodeRanges(normalized);
let rangeIndex = 0; let rangeIndex = 0;
const delimiters: Array<{ const delimiters: Array<{
@@ -79,7 +206,7 @@ function fixCjkEmphasisSpacingInBlock(block: string): string {
canClose: boolean; canClose: boolean;
}> = []; }> = [];
let cursor = 0; let cursor = 0;
while (cursor < block.length - 1) { while (cursor < normalized.length - 1) {
if (rangeIndex < codeRanges.length && cursor >= codeRanges[rangeIndex][0]) { if (rangeIndex < codeRanges.length && cursor >= codeRanges[rangeIndex][0]) {
if (cursor <= codeRanges[rangeIndex][1]) { if (cursor <= codeRanges[rangeIndex][1]) {
cursor = codeRanges[rangeIndex][1] + 1; cursor = codeRanges[rangeIndex][1] + 1;
@@ -90,14 +217,14 @@ function fixCjkEmphasisSpacingInBlock(block: string): string {
} }
if ( if (
block[cursor] === "*" && normalized[cursor] === "*" &&
block[cursor + 1] === "*" && normalized[cursor + 1] === "*" &&
block[cursor - 1] !== "*" && normalized[cursor - 1] !== "*" &&
block[cursor + 2] !== "*" && normalized[cursor + 2] !== "*" &&
!isEscaped(block, cursor) !isEscaped(normalized, cursor)
) { ) {
const before = block[cursor - 1]; const before = normalized[cursor - 1];
const after = block[cursor + 2]; const after = normalized[cursor + 2];
const beforeIsSpace = isWhitespaceChar(before); const beforeIsSpace = isWhitespaceChar(before);
const afterIsSpace = isWhitespaceChar(after); const afterIsSpace = isWhitespaceChar(after);
const beforeIsPunct = isPunctuationOrSymbol(before); const beforeIsPunct = isPunctuationOrSymbol(before);
@@ -140,12 +267,12 @@ function fixCjkEmphasisSpacingInBlock(block: string): string {
} }
} }
if (pairs.length === 0) return block; if (pairs.length === 0) return normalized;
const insertPositions = new Set<number>(); const insertPositions = new Set<number>();
for (const pair of pairs) { for (const pair of pairs) {
const insideLast = block[pair.close - 1]; const insideLast = normalized[pair.close - 1];
const afterClose = block[pair.close + 2]; const afterClose = normalized[pair.close + 2];
if (!afterClose) continue; if (!afterClose) continue;
if ( if (
CJK_CLOSING_PUNCT_RE.test(insideLast) && CJK_CLOSING_PUNCT_RE.test(insideLast) &&
@@ -155,16 +282,16 @@ function fixCjkEmphasisSpacingInBlock(block: string): string {
} }
} }
if (insertPositions.size === 0) return block; if (insertPositions.size === 0) return normalized;
let result = ""; let result = "";
for (let idx = 0; idx < block.length; idx += 1) { for (let idx = 0; idx < normalized.length; idx += 1) {
if (insertPositions.has(idx)) { if (insertPositions.has(idx)) {
result += " "; result += " ";
} }
result += block[idx]; result += normalized[idx];
} }
if (insertPositions.has(block.length)) { if (insertPositions.has(normalized.length)) {
result += " "; result += " ";
} }
return result; return result;
+8 -2
View File
@@ -153,10 +153,12 @@ Schema: `references/config/preferences-schema.md`
**1.2 Analyze Content → `analysis.md`** **1.2 Analyze Content → `analysis.md`**
1. Save source content (file path or paste → `source.md`) 1. Save source content (file path or paste → `source.md`)
- **Backup rule**: If `source.md` exists, rename to `source-backup-YYYYMMDD-HHMMSS.md`
2. Analyze: topic, data type, complexity, tone, audience 2. Analyze: topic, data type, complexity, tone, audience
3. Detect source language and user language 3. Detect source language and user language
4. Extract design instructions from user input 4. Extract design instructions from user input
5. Save analysis 5. Save analysis
- **Backup rule**: If `analysis.md` exists, rename to `analysis-backup-YYYYMMDD-HHMMSS.md`
See `references/analysis-framework.md` for detailed format. See `references/analysis-framework.md` for detailed format.
@@ -189,6 +191,8 @@ Present all options in single confirmation:
### Step 5: Generate Prompt → `prompts/infographic.md` ### Step 5: Generate Prompt → `prompts/infographic.md`
**Backup rule**: If `prompts/infographic.md` exists, rename to `prompts/infographic-backup-YYYYMMDD-HHMMSS.md`
Combine: Combine:
1. Layout definition from `references/layouts/<layout>.md` 1. Layout definition from `references/layouts/<layout>.md`
2. Style definition from `references/styles/<style>.md` 2. Style definition from `references/styles/<style>.md`
@@ -199,8 +203,10 @@ Combine:
### Step 6: Generate Image ### Step 6: Generate Image
1. Select available image generation skill (ask user if multiple) 1. Select available image generation skill (ask user if multiple)
2. Call with prompt file and output path 2. **Check for existing file**: Before generating, check if `infographic.png` exists
3. On failure, auto-retry once - If exists: Rename to `infographic-backup-YYYYMMDD-HHMMSS.png`
3. Call with prompt file and output path
4. On failure, auto-retry once
### Step 7: Output Summary ### Step 7: Output Summary
@@ -214,7 +214,6 @@ export async function convertMarkdown(markdownPath: string, options?: { title?:
} }
fs.copyFileSync(tempHtmlPath, finalHtmlPath); fs.copyFileSync(tempHtmlPath, finalHtmlPath);
console.error(`[markdown-to-html] HTML saved to: ${finalHtmlPath}`);
const contentImages: ImageInfo[] = []; const contentImages: ImageInfo[] = [];
for (const img of images) { for (const img of images) {
@@ -226,6 +225,15 @@ export async function convertMarkdown(markdownPath: string, options?: { title?:
}); });
} }
let htmlContent = fs.readFileSync(finalHtmlPath, 'utf-8');
for (const img of contentImages) {
const imgTag = `<img src="${img.placeholder}" data-local-path="${img.localPath}" style="display: block; width: 100%; margin: 1.5em auto;">`;
htmlContent = htmlContent.replace(img.placeholder, imgTag);
}
fs.writeFileSync(finalHtmlPath, htmlContent, 'utf-8');
console.error(`[markdown-to-html] HTML saved to: ${finalHtmlPath}`);
return { return {
title, title,
author, author,
@@ -177,7 +177,7 @@ async function uploadImagesInHtml(
accessToken: string, accessToken: string,
baseDir: string baseDir: string
): Promise<{ html: string; firstMediaId: string }> { ): Promise<{ html: string; firstMediaId: string }> {
const imgRegex = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi; const imgRegex = /<img[^>]*\ssrc=["']([^"']+)["'][^>]*>/gi;
const matches = [...html.matchAll(imgRegex)]; const matches = [...html.matchAll(imgRegex)];
if (matches.length === 0) { if (matches.length === 0) {
@@ -188,7 +188,7 @@ async function uploadImagesInHtml(
let updatedHtml = html; let updatedHtml = html;
for (const match of matches) { for (const match of matches) {
const [, src] = match; const [fullTag, src] = match;
if (!src) continue; if (!src) continue;
if (src.startsWith("https://mmbiz.qpic.cn")) { if (src.startsWith("https://mmbiz.qpic.cn")) {
@@ -198,15 +198,21 @@ async function uploadImagesInHtml(
continue; continue;
} }
console.error(`[wechat-api] Uploading image: ${src}`); const localPathMatch = fullTag.match(/data-local-path=["']([^"']+)["']/);
const imagePath = localPathMatch ? localPathMatch[1]! : src;
console.error(`[wechat-api] Uploading image: ${imagePath}`);
try { try {
const resp = await uploadImage(src, accessToken, baseDir); const resp = await uploadImage(imagePath, accessToken, baseDir);
updatedHtml = updatedHtml.replace(src, resp.url); const newTag = fullTag
.replace(/\ssrc=["'][^"']+["']/, ` src="${resp.url}"`)
.replace(/\sdata-local-path=["'][^"']+["']/, "");
updatedHtml = updatedHtml.replace(fullTag, newTag);
if (!firstMediaId) { if (!firstMediaId) {
firstMediaId = resp.media_id; firstMediaId = resp.media_id;
} }
} catch (err) { } catch (err) {
console.error(`[wechat-api] Failed to upload ${src}:`, err); console.error(`[wechat-api] Failed to upload ${imagePath}:`, err);
} }
} }
@@ -310,6 +316,7 @@ Arguments:
Options: Options:
--title <title> Override title --title <title> Override title
--summary <text> Article summary (for future use)
--theme <name> Theme name for markdown (default, grace, simple). Default: default --theme <name> Theme name for markdown (default, grace, simple). Default: default
--cover <path> Cover image path (local or URL) --cover <path> Cover image path (local or URL)
--dry-run Parse and render only, don't publish --dry-run Parse and render only, don't publish
@@ -337,6 +344,7 @@ interface CliArgs {
filePath: string; filePath: string;
isHtml: boolean; isHtml: boolean;
title?: string; title?: string;
summary?: string;
theme: string; theme: string;
cover?: string; cover?: string;
dryRun: boolean; dryRun: boolean;
@@ -358,12 +366,16 @@ function parseArgs(argv: string[]): CliArgs {
const arg = argv[i]!; const arg = argv[i]!;
if (arg === "--title" && argv[i + 1]) { if (arg === "--title" && argv[i + 1]) {
args.title = argv[++i]; args.title = argv[++i];
} else if (arg === "--summary" && argv[i + 1]) {
args.summary = argv[++i];
} else if (arg === "--theme" && argv[i + 1]) { } else if (arg === "--theme" && argv[i + 1]) {
args.theme = argv[++i]!; args.theme = argv[++i]!;
} else if (arg === "--cover" && argv[i + 1]) { } else if (arg === "--cover" && argv[i + 1]) {
args.cover = argv[++i]; args.cover = argv[++i];
} else if (arg === "--dry-run") { } else if (arg === "--dry-run") {
args.dryRun = true; args.dryRun = true;
} else if (arg.startsWith("--") && argv[i + 1] && !argv[i + 1]!.startsWith("-")) {
i++;
} else if (!arg.startsWith("-")) { } else if (!arg.startsWith("-")) {
args.filePath = arg; args.filePath = arg;
} }
@@ -405,6 +417,15 @@ async function main(): Promise<void> {
if (args.isHtml) { if (args.isHtml) {
htmlPath = filePath; htmlPath = filePath;
htmlContent = extractHtmlContent(htmlPath); htmlContent = extractHtmlContent(htmlPath);
const mdPath = filePath.replace(/\.html$/i, ".md");
if (fs.existsSync(mdPath)) {
const mdContent = fs.readFileSync(mdPath, "utf-8");
const parsed = parseFrontmatter(mdContent);
frontmatter = parsed.frontmatter;
if (!title && frontmatter.title) {
title = frontmatter.title;
}
}
if (!title) { if (!title) {
title = extractHtmlTitle(fs.readFileSync(htmlPath, "utf-8")); title = extractHtmlTitle(fs.readFileSync(htmlPath, "utf-8"));
} }
@@ -115,7 +115,7 @@ async function sendPaste(cdp?: CdpConnection, sessionId?: string): Promise<void>
} }
} }
async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string): Promise<void> { async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string, contentImages: ImageInfo[] = []): Promise<void> {
const absolutePath = path.isAbsolute(htmlFilePath) ? htmlFilePath : path.resolve(process.cwd(), htmlFilePath); const absolutePath = path.isAbsolute(htmlFilePath) ? htmlFilePath : path.resolve(process.cwd(), htmlFilePath);
const fileUrl = `file://${absolutePath}`; const fileUrl = `file://${absolutePath}`;
@@ -128,6 +128,28 @@ async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string): Pr
await cdp.send('Runtime.enable', {}, { sessionId }); await cdp.send('Runtime.enable', {}, { sessionId });
await sleep(2000); await sleep(2000);
if (contentImages.length > 0) {
console.log('[wechat] Replacing img tags with placeholders for browser paste...');
const replacements = contentImages.map(img => ({ placeholder: img.placeholder, localPath: img.localPath }));
await cdp.send<{ result: { value: unknown } }>('Runtime.evaluate', {
expression: `
(function() {
const replacements = ${JSON.stringify(replacements)};
for (const r of replacements) {
const imgs = document.querySelectorAll('img[src="' + r.placeholder + '"], img[data-local-path="' + r.localPath + '"]');
for (const img of imgs) {
const text = document.createTextNode(r.placeholder);
img.parentNode.replaceChild(text, img);
}
}
return true;
})()
`,
returnByValue: true,
}, { sessionId });
await sleep(500);
}
console.log('[wechat] Selecting #output content...'); console.log('[wechat] Selecting #output content...');
await cdp.send<{ result: { value: unknown } }>('Runtime.evaluate', { await cdp.send<{ result: { value: unknown } }>('Runtime.evaluate', {
expression: ` expression: `
@@ -176,7 +198,7 @@ async function parseMarkdownWithPlaceholders(markdownPath: string, theme?: strin
return JSON.parse(output); return JSON.parse(output);
} }
function parseHtmlMeta(htmlPath: string): { title: string; author: string; summary: string } { function parseHtmlMeta(htmlPath: string): { title: string; author: string; summary: string; contentImages: ImageInfo[] } {
const content = fs.readFileSync(htmlPath, 'utf-8'); const content = fs.readFileSync(htmlPath, 'utf-8');
let title = ''; let title = '';
@@ -203,7 +225,45 @@ function parseHtmlMeta(htmlPath: string): { title: string; author: string; summa
} }
} }
return { title, author, summary }; const mdPath = htmlPath.replace(/\.html$/i, '.md');
if (fs.existsSync(mdPath)) {
const mdContent = fs.readFileSync(mdPath, 'utf-8');
const fmMatch = mdContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (fmMatch) {
const lines = fmMatch[1]!.split('\n');
for (const line of lines) {
const colonIdx = line.indexOf(':');
if (colonIdx > 0) {
const key = line.slice(0, colonIdx).trim();
let value = line.slice(colonIdx + 1).trim();
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
if (key === 'title' && !title) title = value;
if (key === 'author' && !author) author = value;
if ((key === 'description' || key === 'summary') && !summary) summary = value;
}
}
}
}
const contentImages: ImageInfo[] = [];
const imgRegex = /<img[^>]*\ssrc=["']([^"']+)["'][^>]*>/gi;
const matches = [...content.matchAll(imgRegex)];
for (const match of matches) {
const [fullTag, src] = match;
if (!src || src.startsWith('http')) continue;
const localPathMatch = fullTag.match(/data-local-path=["']([^"']+)["']/);
if (localPathMatch) {
contentImages.push({
placeholder: src,
localPath: localPathMatch[1]!,
originalPath: src,
});
}
}
return { title, author, summary, contentImages };
} }
async function selectAndReplacePlaceholder(session: ChromeSession, placeholder: string): Promise<boolean> { async function selectAndReplacePlaceholder(session: ChromeSession, placeholder: string): Promise<boolean> {
@@ -273,9 +333,13 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
effectiveAuthor = effectiveAuthor || meta.author; effectiveAuthor = effectiveAuthor || meta.author;
effectiveSummary = effectiveSummary || meta.summary; effectiveSummary = effectiveSummary || meta.summary;
effectiveHtmlFile = htmlFile; effectiveHtmlFile = htmlFile;
if (meta.contentImages.length > 0) {
contentImages = meta.contentImages;
}
console.log(`[wechat] Title: ${effectiveTitle || '(empty)'}`); console.log(`[wechat] Title: ${effectiveTitle || '(empty)'}`);
console.log(`[wechat] Author: ${effectiveAuthor || '(empty)'}`); console.log(`[wechat] Author: ${effectiveAuthor || '(empty)'}`);
console.log(`[wechat] Summary: ${effectiveSummary || '(empty)'}`); console.log(`[wechat] Summary: ${effectiveSummary || '(empty)'}`);
console.log(`[wechat] Found ${contentImages.length} images to insert`);
} }
if (effectiveTitle && effectiveTitle.length > 64) throw new Error(`Title too long: ${effectiveTitle.length} chars (max 64)`); if (effectiveTitle && effectiveTitle.length > 64) throw new Error(`Title too long: ${effectiveTitle.length} chars (max 64)`);
@@ -416,7 +480,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
if (effectiveHtmlFile && fs.existsSync(effectiveHtmlFile)) { if (effectiveHtmlFile && fs.existsSync(effectiveHtmlFile)) {
console.log(`[wechat] Copying HTML content from: ${effectiveHtmlFile}`); console.log(`[wechat] Copying HTML content from: ${effectiveHtmlFile}`);
await copyHtmlFromBrowser(cdp, effectiveHtmlFile); await copyHtmlFromBrowser(cdp, effectiveHtmlFile, contentImages);
await sleep(500); await sleep(500);
console.log('[wechat] Pasting into editor...'); console.log('[wechat] Pasting into editor...');
await pasteFromClipboardInEditor(session); await pasteFromClipboardInEditor(session);
+5 -1
View File
@@ -226,6 +226,7 @@ Schema: `references/config/preferences-schema.md`
**1.2 Analyze Content** **1.2 Analyze Content**
1. Save source content (if pasted, save as `source.md`) 1. Save source content (if pasted, save as `source.md`)
- **Backup rule**: If `source.md` exists, rename to `source-backup-YYYYMMDD-HHMMSS.md`
2. Follow `references/analysis-framework.md` for content analysis 2. Follow `references/analysis-framework.md` for content analysis
3. Analyze content signals for style recommendations 3. Analyze content signals for style recommendations
4. Detect source language 4. Detect source language
@@ -483,6 +484,7 @@ options:
- Add slide-specific content - Add slide-specific content
- If `Layout:` specified, include layout guidance from `references/layouts.md` - If `Layout:` specified, include layout guidance from `references/layouts.md`
3. Save to `prompts/` directory 3. Save to `prompts/` directory
- **Backup rule**: If prompt file exists, rename to `prompts/NN-slide-{slug}-backup-YYYYMMDD-HHMMSS.md`
**After generation**: **After generation**:
- If `--prompts-only`, stop here and output prompt summary - If `--prompts-only`, stop here and output prompt summary
@@ -539,7 +541,9 @@ options:
**Standard flow**: **Standard flow**:
1. Select available image generation skill 1. Select available image generation skill
2. Generate session ID: `slides-{topic-slug}-{timestamp}` 2. Generate session ID: `slides-{topic-slug}-{timestamp}`
3. Generate each slide sequentially with same session ID 3. For each slide:
- **Backup rule**: If image file exists, rename to `NN-slide-{slug}-backup-YYYYMMDD-HHMMSS.png`
- Generate image sequentially with same session ID
4. Report progress: "Generated X/N" (in user's language) 4. Report progress: "Generated X/N" (in user's language)
5. Auto-retry once on failure before reporting error 5. Auto-retry once on failure before reporting error
+22 -14
View File
@@ -1,6 +1,6 @@
--- ---
name: baoyu-xhs-images name: baoyu-xhs-images
description: Generates Xiaohongshu (Little Red Book) infographic series with 9 visual styles and 6 layouts. Breaks content into 1-10 cartoon-style images optimized for XHS engagement. Use when user mentions "小红书图片", "XHS images", "RedNote infographics", "小红书种草", or wants social media infographics for Chinese platforms. description: Generates Xiaohongshu (Little Red Book) infographic series with 10 visual styles and 8 layouts. Breaks content into 1-10 cartoon-style images optimized for XHS engagement. Use when user mentions "小红书图片", "XHS images", "RedNote infographics", "小红书种草", or wants social media infographics for Chinese platforms.
--- ---
# Xiaohongshu Infographic Series Generator # Xiaohongshu Infographic Series Generator
@@ -42,8 +42,8 @@ Break down complex content into eye-catching infographic series for Xiaohongshu
| Dimension | Controls | Options | | Dimension | Controls | Options |
|-----------|----------|---------| |-----------|----------|---------|
| **Style** | Visual aesthetics: colors, lines, decorations | cute, fresh, warm, bold, minimal, retro, pop, notion, chalkboard | | **Style** | Visual aesthetics: colors, lines, decorations | cute, fresh, warm, bold, minimal, retro, pop, notion, chalkboard, study-notes |
| **Layout** | Information structure: density, arrangement | sparse, balanced, dense, list, comparison, flow | | **Layout** | Information structure: density, arrangement | sparse, balanced, dense, list, comparison, flow, mindmap, quadrant |
Style × Layout can be freely combined. Example: `--style notion --layout dense` creates an intellectual-looking knowledge card with high information density. Style × Layout can be freely combined. Example: `--style notion --layout dense` creates an intellectual-looking knowledge card with high information density.
@@ -60,6 +60,7 @@ Style × Layout can be freely combined. Example: `--style notion --layout dense`
| `pop` | Vibrant, energetic, eye-catching | | `pop` | Vibrant, energetic, eye-catching |
| `notion` | Minimalist hand-drawn line art, intellectual | | `notion` | Minimalist hand-drawn line art, intellectual |
| `chalkboard` | Colorful chalk on black board, educational | | `chalkboard` | Colorful chalk on black board, educational |
| `study-notes` | Realistic handwritten photo style, blue pen + red annotations + yellow highlighter |
Detailed style definitions: `references/presets/<style>.md` Detailed style definitions: `references/presets/<style>.md`
@@ -73,6 +74,8 @@ Detailed style definitions: `references/presets/<style>.md`
| `list` | Enumeration and ranking format (4-7 items) | | `list` | Enumeration and ranking format (4-7 items) |
| `comparison` | Side-by-side contrast layout | | `comparison` | Side-by-side contrast layout |
| `flow` | Process and timeline layout (3-6 steps) | | `flow` | Process and timeline layout (3-6 steps) |
| `mindmap` | Center radial mind map layout (4-8 branches) |
| `quadrant` | Four-quadrant / circular section layout |
Detailed layout definitions: `references/elements/canvas.md` Detailed layout definitions: `references/elements/canvas.md`
@@ -89,6 +92,7 @@ Detailed layout definitions: `references/elements/canvas.md`
| Fun, exciting, wow, amazing | `pop` | sparse/list | | Fun, exciting, wow, amazing | `pop` | sparse/list |
| Knowledge, concept, productivity, SaaS | `notion` | dense/list | | Knowledge, concept, productivity, SaaS | `notion` | dense/list |
| Education, tutorial, learning, teaching, classroom | `chalkboard` | balanced/dense | | Education, tutorial, learning, teaching, classroom | `chalkboard` | balanced/dense |
| Notes, handwritten, study guide, knowledge, realistic, photo | `study-notes` | dense/list/mindmap |
## Outline Strategies ## Outline Strategies
@@ -227,6 +231,7 @@ Read source content, save it if needed, and perform deep analysis.
1. **Save source content** (if not already a file): 1. **Save source content** (if not already a file):
- If user provides a file path: use as-is - If user provides a file path: use as-is
- If user pastes content: save to `source.md` in target directory - If user pastes content: save to `source.md` in target directory
- **Backup rule**: If `source.md` exists, rename to `source-backup-YYYYMMDD-HHMMSS.md`
2. Read source content 2. Read source content
3. **Deep analysis** following `references/workflows/analysis-framework.md`: 3. **Deep analysis** following `references/workflows/analysis-framework.md`:
- Content type classification (种草/干货/测评/教程/避坑...) - Content type classification (种草/干货/测评/教程/避坑...)
@@ -353,7 +358,9 @@ With confirmed outline + style + layout:
**For each image (cover + content + ending)**: **For each image (cover + content + ending)**:
1. Save prompt to `prompts/NN-{type}-[slug].md` (in user's preferred language) 1. Save prompt to `prompts/NN-{type}-[slug].md` (in user's preferred language)
- **Backup rule**: If prompt file exists, rename to `prompts/NN-{type}-[slug]-backup-YYYYMMDD-HHMMSS.md`
2. Generate image using confirmed style and layout 2. Generate image using confirmed style and layout
- **Backup rule**: If image file exists, rename to `NN-{type}-[slug]-backup-YYYYMMDD-HHMMSS.png`
3. Report progress after each generation 3. Report progress after each generation
**Watermark Application** (if enabled in preferences): **Watermark Application** (if enabled in preferences):
@@ -417,17 +424,18 @@ Files:
**Style × Layout Matrix** (✓✓ = highly recommended, ✓ = works well): **Style × Layout Matrix** (✓✓ = highly recommended, ✓ = works well):
| | sparse | balanced | dense | list | comparison | flow | | | sparse | balanced | dense | list | comparison | flow | mindmap | quadrant |
|---|:---:|:---:|:---:|:---:|:---:|:---:| |---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| cute | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓ | | cute | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓ | ✓ | ✓ |
| fresh | ✓✓ | ✓✓ | ✓ | ✓ | ✓ | ✓✓ | | fresh | ✓✓ | ✓✓ | ✓ | ✓ | ✓ | ✓✓ | ✓ | ✓ |
| warm | ✓✓ | ✓✓ | ✓ | ✓ | ✓✓ | ✓ | | warm | ✓✓ | ✓✓ | ✓ | ✓ | ✓✓ | ✓ | ✓ | ✓ |
| bold | ✓✓ | ✓ | ✓ | ✓✓ | ✓✓ | ✓ | | bold | ✓✓ | ✓ | ✓ | ✓✓ | ✓✓ | ✓ | ✓ | ✓✓ |
| minimal | ✓✓ | ✓✓ | ✓✓ | ✓ | ✓ | ✓ | | minimal | ✓✓ | ✓✓ | ✓✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| retro | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓ | | retro | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓ | ✓ | ✓ |
| pop | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓ | | pop | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓ | ✓ | ✓ |
| notion | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ | | notion | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ |
| chalkboard | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓ | ✓✓ | | chalkboard | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓ |
| study-notes | ✗ | ✓ | ✓✓ | ✓✓ | ✓ | ✓ | ✓✓ | ✓ |
## References ## References
@@ -51,6 +51,8 @@ Avoid placing critical content in these areas:
| list | Vertical enumeration | 4-7 | Rankings, checklists, step guides | | list | Vertical enumeration | 4-7 | Rankings, checklists, step guides |
| comparison | Left vs Right | 2 sections | Before/after, pros/cons | | comparison | Left vs Right | 2 sections | Before/after, pros/cons |
| flow | Connected nodes | 3-6 steps | Processes, timelines, workflows | | flow | Connected nodes | 3-6 steps | Processes, timelines, workflows |
| mindmap | Center radial | 4-8 branches | Concept maps, brainstorming, topic overview |
| quadrant | 4-section grid | 4 sections | SWOT analysis, priority matrix, classification |
## Layout by Position ## Layout by Position
@@ -106,3 +108,15 @@ For multi-element compositions:
- Directional flow (top→bottom or left→right) - Directional flow (top→bottom or left→right)
- Connected nodes with arrows - Connected nodes with arrows
- Clear progression indicators - Clear progression indicators
### Mindmap Layout
- Central topic node
- Radial branches outward
- Hierarchical sub-branches
- Organic curved connections
### Quadrant Layout
- 4-section grid (2×2)
- Clear axis labels
- Each quadrant with distinct content
- Optional circular variant for cycles
@@ -0,0 +1,115 @@
---
name: study-notes
category: realistic
---
# Study Notes Style
Realistic handwritten photo aesthetic - student notes style, dense and messy but readable.
## Element Combination
```yaml
canvas:
ratio: portrait-3-4
grid: single
image_effects:
cutout: none
stroke: none
filter: natural-photo
typography:
decorated: none
tags: none
direction: horizontal
decorations:
emphasis: circle-mark | underline | checkmark | cross | star-simple
background: lined-paper-white
doodles: arrows-simple | margin-notes | corrections | explanatory-diagrams
frames: none
```
## Color Palette (Three-Color Annotation System)
| Role | Colors | Hex |
|------|--------|-----|
| Primary | Blue ballpoint, Black ink | #1E3A5F, #1A1A1A |
| Highlights | Yellow highlighter | #FFFF00 (50% opacity) |
| Accents | Red pen (circles, underlines) | #CC0000 |
| Background | White lined paper | #FFFFFF |
## Visual Elements
- Realistic photo perspective: top-down view of study desk
- Hand holding blue ballpoint pen, actively underlining
- Extremely dense handwritten content, filling entire page
- Red pen annotations: circles, underlines, stars, boxes
- Yellow highlighter marking key terms
- Correction marks, cramped notes squeezed into margins
- Simple hand-drawn symbols: → * ✓ ✗ !
- Varying pen pressure creating lighter and darker strokes
## Typography
- Authentic student handwriting
- Messy but readable, clear structure maintained
- Varying font sizes (large titles, small body, tiny margin notes)
- CJK optimized
## Content Structure
Three-section layout:
### Top Section
- Core topic (circled multiple times in red)
- First section title + 3-4 key points
- Arrow connections, red underlines
### Middle Section
- Second section title (red pen box)
- Numbered steps ①②③
- Specific methods and supplementary notes
### Bottom Section
- Third section title (red star)
- Time points / key metrics
- Key quotes / core tips (tiny corner notes)
## Best Layout Pairings
| Layout | Compatibility | Use Case |
|--------|---------------|----------|
| sparse | ✗ | Not suitable - style requires dense content |
| balanced | ✓ | When content is lighter |
| dense | ✓✓ | Best fit - knowledge notes, summaries |
| list | ✓✓ | Step checklists, rankings |
| comparison | ✓ | Comparative analysis |
| flow | ✓ | Process flows |
| mindmap | ✓✓ | Mind map notes |
| quadrant | ✓ | Quadrant analysis |
## Best For
- Study guides, exam notes
- Knowledge organization, framework summaries
- Tutorial summaries, quick notes
- "Top student notes" style content
- Knowledge sharing requiring authentic feel
## Style Rules
### DO ✓
- Keep content extremely dense
- Use simple symbols (→ * ✓ ✗ !)
- Annotate key points with red pen
- Include correction marks
- Squeeze tiny notes into margins
### DON'T ✗
- Use complex emojis
- Leave too much whitespace
- Make neat, tidy layouts
- Add colorful decorations
- Include cartoon elements