From b39d2244cf93d2e39eb7e3dfc375b737b75ca14d Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Mon, 11 May 2026 05:18:18 -0400 Subject: [PATCH] docs: salvage focused stale PR contributions - add Vite and Redis pattern skills from closed stale PRs - add frontend-slides support assets - port skill-comply runner fixes and LLM prompt/provider regressions - harden agent frontmatter validation and sync catalog counts --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 10 +- AGENTS.md | 4 +- README.md | 6 +- README.zh-CN.md | 2 +- agents/a11y-architect.md | 1 - docs/zh-CN/AGENTS.md | 4 +- docs/zh-CN/README.md | 6 +- rules/web/hooks.md | 13 +- scripts/ci/validate-agents.js | 15 + skills/frontend-slides/animation-patterns.md | 122 +++++ skills/frontend-slides/html-template.md | 419 ++++++++++++++++ skills/frontend-slides/scripts/export-pdf.sh | 418 ++++++++++++++++ .../frontend-slides/scripts/extract-pptx.py | 96 ++++ skills/frontend-slides/viewport-base.css | 153 ++++++ skills/redis-patterns/SKILL.md | 403 ++++++++++++++++ skills/skill-comply/scripts/runner.py | 31 +- skills/skill-comply/tests/test_runner.py | 172 +++++++ skills/vite-patterns/SKILL.md | 449 ++++++++++++++++++ src/llm/prompt/__init__.py | 12 +- src/llm/prompt/builder.py | 29 +- src/llm/prompt/templates/__init__.py | 42 +- src/llm/providers/claude.py | 46 +- tests/ci/validators.test.js | 22 + tests/conftest.py | 6 + tests/test_builder.py | 47 +- tests/test_claude_provider.py | 111 +++++ tests/test_templates.py | 71 +++ 28 files changed, 2653 insertions(+), 59 deletions(-) create mode 100644 skills/frontend-slides/animation-patterns.md create mode 100644 skills/frontend-slides/html-template.md create mode 100755 skills/frontend-slides/scripts/export-pdf.sh create mode 100644 skills/frontend-slides/scripts/extract-pptx.py create mode 100644 skills/frontend-slides/viewport-base.css create mode 100644 skills/redis-patterns/SKILL.md create mode 100644 skills/skill-comply/tests/test_runner.py create mode 100644 skills/vite-patterns/SKILL.md create mode 100644 tests/test_claude_provider.py create mode 100644 tests/test_templates.py diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 7a8aebfe..ca7b1038 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -11,7 +11,7 @@ { "name": "ecc", "source": "./", - "description": "The most comprehensive Claude Code plugin — 50 agents, 185 skills, 68 legacy command shims, selective install profiles, and production-ready hooks for TDD, security scanning, code review, and continuous learning", + "description": "The most comprehensive Claude Code plugin — 50 agents, 187 skills, 68 legacy command shims, selective install profiles, and production-ready hooks for TDD, security scanning, code review, and continuous learning", "version": "2.0.0-rc.1", "author": { "name": "Affaan Mustafa", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index dee5080c..e24aae21 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "ecc", "version": "2.0.0-rc.1", - "description": "Battle-tested Claude Code plugin for engineering teams — 50 agents, 185 skills, 68 legacy command shims, production-ready hooks, and selective install workflows evolved through continuous real-world use", + "description": "Battle-tested Claude Code plugin for engineering teams — 50 agents, 187 skills, 68 legacy command shims, production-ready hooks, and selective install workflows evolved through continuous real-world use", "author": { "name": "Affaan Mustafa", "url": "https://x.com/affaanmustafa" @@ -23,6 +23,10 @@ "best-practices" ], "mcpServers": {}, - "skills": ["./skills/"], - "commands": ["./commands/"] + "skills": [ + "./skills/" + ], + "commands": [ + "./commands/" + ] } diff --git a/AGENTS.md b/AGENTS.md index 10feea33..d72e00aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Everything Claude Code (ECC) — Agent Instructions -This is a **production-ready AI coding plugin** providing 50 specialized agents, 185 skills, 68 commands, and automated hook workflows for software development. +This is a **production-ready AI coding plugin** providing 50 specialized agents, 187 skills, 68 commands, and automated hook workflows for software development. **Version:** 2.0.0-rc.1 @@ -146,7 +146,7 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat ``` agents/ — 50 specialized subagents -skills/ — 185 workflow skills and domain knowledge +skills/ — 187 workflow skills and domain knowledge commands/ — 68 slash commands hooks/ — Trigger-based automations rules/ — Always-follow guidelines (common + per-language) diff --git a/README.md b/README.md index 0c68f538..65607387 100644 --- a/README.md +++ b/README.md @@ -350,7 +350,7 @@ If you stacked methods, clean up in this order: /plugin list ecc@ecc ``` -**That's it!** You now have access to 50 agents, 185 skills, and 68 legacy command shims. +**That's it!** You now have access to 50 agents, 187 skills, and 68 legacy command shims. ### Dashboard GUI @@ -1338,7 +1338,7 @@ The configuration is automatically detected from `.opencode/opencode.json`. |---------|-------------|----------|--------| | Agents | PASS: 50 agents | PASS: 12 agents | **Claude Code leads** | | Commands | PASS: 68 commands | PASS: 31 commands | **Claude Code leads** | -| Skills | PASS: 185 skills | PASS: 37 skills | **Claude Code leads** | +| Skills | PASS: 187 skills | PASS: 37 skills | **Claude Code leads** | | Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** | | Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** | | MCP Servers | PASS: 14 servers | PASS: Full | **Full parity** | @@ -1443,7 +1443,7 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e |---------|------------|------------|-----------|----------| | **Agents** | 50 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 | | **Commands** | 68 | Shared | Instruction-based | 31 | -| **Skills** | 185 | Shared | 10 (native format) | 37 | +| **Skills** | 187 | Shared | 10 (native format) | 37 | | **Hook Events** | 8 types | 15 types | None yet | 11 types | | **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks | | **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions | diff --git a/README.zh-CN.md b/README.zh-CN.md index 4ff3a228..eca0a059 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -160,7 +160,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/" /plugin list ecc@ecc ``` -**完成!** 你现在可以使用 50 个代理、185 个技能和 68 个命令。 +**完成!** 你现在可以使用 50 个代理、187 个技能和 68 个命令。 ### multi-* 命令需要额外配置 diff --git a/agents/a11y-architect.md b/agents/a11y-architect.md index 1213b83a..531d43ff 100644 --- a/agents/a11y-architect.md +++ b/agents/a11y-architect.md @@ -3,7 +3,6 @@ name: a11y-architect description: Accessibility Architect specializing in WCAG 2.2 compliance for Web and Native platforms. Use PROACTIVELY when designing UI components, establishing design systems, or auditing code for inclusive user experiences. model: sonnet tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"] -model: opus --- You are a Senior Accessibility Architect. Your goal is to ensure that every digital product is Perceivable, Operable, Understandable, and Robust (POUR) for all users, including those with visual, auditory, motor, or cognitive disabilities. diff --git a/docs/zh-CN/AGENTS.md b/docs/zh-CN/AGENTS.md index a362437b..1c4db83d 100644 --- a/docs/zh-CN/AGENTS.md +++ b/docs/zh-CN/AGENTS.md @@ -1,6 +1,6 @@ # Everything Claude Code (ECC) — 智能体指令 -这是一个**生产就绪的 AI 编码插件**,提供 50 个专业代理、185 项技能、68 条命令以及自动化钩子工作流,用于软件开发。 +这是一个**生产就绪的 AI 编码插件**,提供 50 个专业代理、187 项技能、68 条命令以及自动化钩子工作流,用于软件开发。 **版本:** 2.0.0-rc.1 @@ -147,7 +147,7 @@ ``` agents/ — 50 个专业子代理 -skills/ — 185 个工作流技能和领域知识 +skills/ — 187 个工作流技能和领域知识 commands/ — 68 个斜杠命令 hooks/ — 基于触发的自动化 rules/ — 始终遵循的指导方针(通用 + 每种语言) diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md index 6f45cb18..7105c722 100644 --- a/docs/zh-CN/README.md +++ b/docs/zh-CN/README.md @@ -224,7 +224,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/" /plugin list ecc@ecc ``` -**搞定!** 你现在可以使用 50 个智能体、185 项技能和 68 个命令了。 +**搞定!** 你现在可以使用 50 个智能体、187 项技能和 68 个命令了。 *** @@ -1134,7 +1134,7 @@ opencode |---------|-------------|----------|--------| | 智能体 | PASS: 50 个 | PASS: 12 个 | **Claude Code 领先** | | 命令 | PASS: 68 个 | PASS: 31 个 | **Claude Code 领先** | -| 技能 | PASS: 185 项 | PASS: 37 项 | **Claude Code 领先** | +| 技能 | PASS: 187 项 | PASS: 37 项 | **Claude Code 领先** | | 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** | | 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** | | MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** | @@ -1242,7 +1242,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以 |---------|------------|------------|-----------|----------| | **智能体** | 50 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 | | **命令** | 68 | 共享 | 基于指令 | 31 | -| **技能** | 185 | 共享 | 10 (原生格式) | 37 | +| **技能** | 187 | 共享 | 10 (原生格式) | 37 | | **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 | | **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 | | **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 | diff --git a/rules/web/hooks.md b/rules/web/hooks.md index 22f8c277..eaab93ae 100644 --- a/rules/web/hooks.md +++ b/rules/web/hooks.md @@ -44,20 +44,29 @@ Equivalent local commands via `yarn prettier` or `npm exec prettier --` are fine ### Type Check +Use `--incremental` so re-runs reuse the previous `.tsbuildinfo` (1-3s on unchanged code instead of 30-60s every time). Wrap in `timeout` so a stuck tsc gets reaped by the OS instead of accumulating across edits — this prevents the multi-process buildup that happens when edits fire faster than tsc finishes. + ```json { "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", - "command": "pnpm tsc --noEmit --pretty false", - "description": "Type-check after frontend edits" + "command": "timeout 60 pnpm tsc --noEmit --pretty false --incremental --tsBuildInfoFile node_modules/.cache/tsc-hook.tsbuildinfo", + "description": "Type-check after frontend edits (incremental + timeout-capped)" } ] } } ``` +**Why both flags matter:** +- Without `--incremental`, every edit re-checks the entire program from scratch. On a real Next.js project this stacks fast: edits at 5-10s intervals + 30-60s tsc runs = N concurrent tsc processes. +- Without `timeout`, a tsc that hangs (transitive dep change, type-checker stuck on a recursive type) never exits and orphans when the parent shell does. +- `--tsBuildInfoFile` is required because `--noEmit` normally suppresses the buildinfo write; specifying the path explicitly keeps incremental working. + +If you're on Windows without GNU coreutils, swap `timeout 60` for a PowerShell wrapper or rely on a Stop/SessionEnd hook to sweep stale tsc processes. + ### CSS Lint ```json diff --git a/scripts/ci/validate-agents.js b/scripts/ci/validate-agents.js index 28a87506..e4220dfa 100644 --- a/scripts/ci/validate-agents.js +++ b/scripts/ci/validate-agents.js @@ -18,15 +18,25 @@ function extractFrontmatter(content) { if (!match) return null; const frontmatter = {}; + const duplicates = []; const lines = match[1].split(/\r?\n/); for (const line of lines) { + // Only top-level keys are unique. Indented YAML belongs to nested values. + if (/^\s/.test(line)) continue; const colonIdx = line.indexOf(':'); if (colonIdx > 0) { const key = line.slice(0, colonIdx).trim(); const value = line.slice(colonIdx + 1).trim(); + if (Object.prototype.hasOwnProperty.call(frontmatter, key)) { + duplicates.push(key); + } frontmatter[key] = value; } } + Object.defineProperty(frontmatter, '__duplicates__', { + value: duplicates, + enumerable: false, + }); return frontmatter; } @@ -57,6 +67,11 @@ function validateAgents() { continue; } + if (frontmatter.__duplicates__.length > 0) { + console.error(`ERROR: ${file} - Duplicate frontmatter keys: ${[...new Set(frontmatter.__duplicates__)].join(', ')}`); + hasErrors = true; + } + for (const field of REQUIRED_FIELDS) { if (!frontmatter[field] || (typeof frontmatter[field] === 'string' && !frontmatter[field].trim())) { console.error(`ERROR: ${file} - Missing required field: ${field}`); diff --git a/skills/frontend-slides/animation-patterns.md b/skills/frontend-slides/animation-patterns.md new file mode 100644 index 00000000..f2ab18ba --- /dev/null +++ b/skills/frontend-slides/animation-patterns.md @@ -0,0 +1,122 @@ +# Animation Patterns Reference + +Use this reference when generating presentations. Match animations to the intended feeling. + +## Effect-to-Feeling Guide + +| Feeling | Animations | Visual Cues | +|---------|-----------|-------------| +| **Dramatic / Cinematic** | Slow fade-ins (1-1.5s), large-scale transitions (0.9 to 1), parallax scrolling | Dark backgrounds, spotlight effects, full-bleed images | +| **Techy / Futuristic** | Neon glow (box-shadow), glitch/scramble text, grid reveals | Particle systems (canvas), grid patterns, monospace accents, cyan/magenta/electric blue | +| **Playful / Friendly** | Bouncy easing (spring physics), floating/bobbing | Rounded corners, pastel/bright colors, hand-drawn elements | +| **Professional / Corporate** | Subtle fast animations (200-300ms), clean slides | Navy/slate/charcoal, precise spacing, data visualization focus | +| **Calm / Minimal** | Very slow subtle motion, gentle fades | High whitespace, muted palette, serif typography, generous padding | +| **Editorial / Magazine** | Staggered text reveals, image-text interplay | Strong type hierarchy, pull quotes, grid-breaking layouts, serif headlines + sans body | + +## Entrance Animations + +```css +/* Fade + Slide Up (most versatile) */ +.reveal { + opacity: 0; + transform: translateY(30px); + transition: opacity 0.6s var(--ease-out-expo), + transform 0.6s var(--ease-out-expo); +} +.visible .reveal { + opacity: 1; + transform: translateY(0); +} + +/* Scale In */ +.reveal-scale { + opacity: 0; + transform: scale(0.9); + transition: opacity 0.6s, transform 0.6s var(--ease-out-expo); +} +.visible .reveal-scale { + opacity: 1; + transform: scale(1); +} + +/* Slide from Left */ +.reveal-left { + opacity: 0; + transform: translateX(-50px); + transition: opacity 0.6s, transform 0.6s var(--ease-out-expo); +} +.visible .reveal-left { + opacity: 1; + transform: translateX(0); +} + +/* Blur In */ +.reveal-blur { + opacity: 0; + filter: blur(10px); + transition: opacity 0.8s, filter 0.8s var(--ease-out-expo); +} +.visible .reveal-blur { + opacity: 1; + filter: blur(0); +} +``` + +## Background Effects + +```css +/* Gradient Mesh — layered radial gradients for depth */ +.gradient-bg { + background: + radial-gradient(ellipse at 20% 80%, rgba(120, 0, 255, 0.3) 0%, transparent 50%), + radial-gradient(ellipse at 80% 20%, rgba(0, 255, 200, 0.2) 0%, transparent 50%), + var(--bg-primary); +} + +/* Noise Texture — inline SVG for grain */ +.noise-bg { + background-image: url("data:image/svg+xml,..."); /* Inline SVG noise */ +} + +/* Grid Pattern — subtle structural lines */ +.grid-bg { + background-image: + linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px); + background-size: 50px 50px; +} +``` + +## Interactive Effects + +```javascript +/* 3D Tilt on Hover — adds depth to cards/panels */ +class TiltEffect { + constructor(element) { + this.element = element; + this.element.style.transformStyle = 'preserve-3d'; + this.element.style.perspective = '1000px'; + + this.element.addEventListener('mousemove', (e) => { + const rect = this.element.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width - 0.5; + const y = (e.clientY - rect.top) / rect.height - 0.5; + this.element.style.transform = `rotateY(${x * 10}deg) rotateX(${-y * 10}deg)`; + }); + + this.element.addEventListener('mouseleave', () => { + this.element.style.transform = 'rotateY(0) rotateX(0)'; + }); + } +} +``` + +## Troubleshooting + +| Problem | Fix | +|---------|-----| +| Fonts not loading | Check Fontshare/Google Fonts URL; ensure font names match in CSS | +| Animations not triggering | Verify Intersection Observer is running; check `.visible` class is being added | +| Scroll snap not working | Ensure `scroll-snap-type: y mandatory` on html; each slide needs `scroll-snap-align: start` | +| Mobile issues | Disable heavy effects at 768px breakpoint; test touch events; reduce particle count | +| Performance issues | Use `will-change` sparingly; prefer `transform`/`opacity` animations; throttle scroll handlers | diff --git a/skills/frontend-slides/html-template.md b/skills/frontend-slides/html-template.md new file mode 100644 index 00000000..7762478e --- /dev/null +++ b/skills/frontend-slides/html-template.md @@ -0,0 +1,419 @@ +# HTML Presentation Template + +Reference architecture for generating slide presentations. Every presentation follows this structure. + +## Base HTML Structure + +```html + + + + + + Presentation Title + + + + + + + + +
+ + + + + +
+

Presentation Title

+

Subtitle or author

+
+ +
+
+

Slide Title

+

Content...

+
+
+ + + + + + +``` + +## Required JavaScript Features + +Every presentation must include: + +1. **SlidePresentation Class** — Main controller with: + - Keyboard navigation (arrows, space, page up/down) + - Touch/swipe support + - Mouse wheel navigation + - Progress bar updates + - Navigation dots + +2. **Intersection Observer** — For scroll-triggered animations: + - Add `.visible` class when slides enter viewport + - Trigger CSS transitions efficiently + +3. **Optional Enhancements** (match to chosen style): + - Custom cursor with trail + - Particle system background (canvas) + - Parallax effects + - 3D tilt on hover + - Magnetic buttons + - Counter animations + +4. **Inline Editing** (only if user opted in during Phase 1 — skip entirely if they said No): + - Edit toggle button (hidden by default, revealed via hover hotzone or `E` key) + - Auto-save to localStorage + - Export/save file functionality + - See "Inline Editing Implementation" section below + +## Inline Editing Implementation (Opt-In Only) + +**If the user chose "No" for inline editing in Phase 1, do NOT generate any edit-related HTML, CSS, or JS.** + +**Do NOT use CSS `~` sibling selector for hover-based show/hide.** The CSS-only approach (`edit-hotzone:hover ~ .edit-toggle`) fails because `pointer-events: none` on the toggle button breaks the hover chain: user hovers hotzone -> button becomes visible -> mouse moves toward button -> leaves hotzone -> button disappears before click. + +**Required approach: JS-based hover with 400ms delay timeout.** + +HTML: + +```html +
+ +``` + +CSS (visibility controlled by JS classes only): + +```css +/* Do NOT use CSS ~ sibling selector for this! + pointer-events: none breaks the hover chain. + Must use JS with delay timeout. */ +.edit-hotzone { + position: fixed; + top: 0; + left: 0; + width: 80px; + height: 80px; + z-index: 10000; + cursor: pointer; +} +.edit-toggle { + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; + z-index: 10001; +} +.edit-toggle.show, +.edit-toggle.active { + opacity: 1; + pointer-events: auto; +} +``` + +JS (three interaction methods): + +```javascript +// 1. Click handler on the toggle button +document.getElementById("editToggle").addEventListener("click", () => { + editor.toggleEditMode(); +}); + +// 2. Hotzone hover with 400ms grace period +const hotzone = document.querySelector(".edit-hotzone"); +const editToggle = document.getElementById("editToggle"); +let hideTimeout = null; + +hotzone.addEventListener("mouseenter", () => { + clearTimeout(hideTimeout); + editToggle.classList.add("show"); +}); +hotzone.addEventListener("mouseleave", () => { + hideTimeout = setTimeout(() => { + if (!editor.isActive) editToggle.classList.remove("show"); + }, 400); +}); +editToggle.addEventListener("mouseenter", () => { + clearTimeout(hideTimeout); +}); +editToggle.addEventListener("mouseleave", () => { + hideTimeout = setTimeout(() => { + if (!editor.isActive) editToggle.classList.remove("show"); + }, 400); +}); + +// 3. Hotzone direct click +hotzone.addEventListener("click", () => { + editor.toggleEditMode(); +}); + +// 4. Keyboard shortcut (E key, skip when editing text) +document.addEventListener("keydown", (e) => { + if ( + (e.key === "e" || e.key === "E") && + !e.target.getAttribute("contenteditable") + ) { + editor.toggleEditMode(); + } +}); +``` + +**CRITICAL: `exportFile()` must strip edit state before capturing outerHTML.** + +When the user presses Ctrl+S in edit mode, `document.documentElement.outerHTML` captures the live DOM — +including `body.edit-active`, `contenteditable="true"` on every text element, and `.active`/`.show` classes on +the toggle button and banner. Anyone opening the saved file sees dashed outlines, a checkmark button, and an +edit banner, as if permanently stuck in edit mode. + +Always implement `exportFile()` like this: + +```javascript +exportFile() { + // Temporarily strip edit state so the saved file opens cleanly + const editableEls = Array.from(document.querySelectorAll('[contenteditable]')); + editableEls.forEach(el => el.removeAttribute('contenteditable')); + document.body.classList.remove('edit-active'); + + // Also strip UI classes from toggle button and banner + const editToggle = document.getElementById('editToggle'); + const editBanner = document.querySelector('.edit-banner'); + editToggle?.classList.remove('active', 'show'); + editBanner?.classList.remove('active', 'show'); + + const html = '\n' + document.documentElement.outerHTML; + + // Restore edit state so the user can keep editing + document.body.classList.add('edit-active'); + editableEls.forEach(el => el.setAttribute('contenteditable', 'true')); + editToggle?.classList.add('active'); + editBanner?.classList.add('active'); + + const blob = new Blob([html], { type: 'text/html' }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = 'presentation.html'; + a.click(); + URL.revokeObjectURL(a.href); +} +``` + +## Image Pipeline (Skip If No Images) + +If user chose "No images" in Phase 1, skip this entirely. If images were provided, process them before generating HTML. + +**Dependency:** `pip install Pillow` + +### Image Processing + +```python +from PIL import Image, ImageDraw + +# Circular crop (for logos on modern/clean styles) +def crop_circle(input_path, output_path): + img = Image.open(input_path).convert('RGBA') + w, h = img.size + size = min(w, h) + left, top = (w - size) // 2, (h - size) // 2 + img = img.crop((left, top, left + size, top + size)) + mask = Image.new('L', (size, size), 0) + ImageDraw.Draw(mask).ellipse([0, 0, size, size], fill=255) + img.putalpha(mask) + img.save(output_path, 'PNG') + +# Resize (for oversized images that inflate HTML) +def resize_max(input_path, output_path, max_dim=1200): + img = Image.open(input_path) + img.thumbnail((max_dim, max_dim), Image.LANCZOS) + img.save(output_path, quality=85) +``` + +| Situation | Operation | +| -------------------------------- | ----------------------------- | +| Square logo on rounded aesthetic | `crop_circle()` | +| Image > 1MB | `resize_max(max_dim=1200)` | +| Wrong aspect ratio | Manual crop with `img.crop()` | + +Save processed images with `_processed` suffix. Never overwrite originals. + +### Image Placement + +**Use direct file paths** (not base64) — presentations are viewed locally: + +```html + +Screenshot +``` + +```css +.slide-image { + max-width: 100%; + max-height: min(50vh, 400px); + object-fit: contain; + border-radius: 8px; +} +.slide-image.screenshot { + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} +.slide-image.logo { + max-height: min(30vh, 200px); +} +``` + +**Adapt border/shadow colors to match the chosen style's accent.** Never repeat the same image on multiple slides (except logos on title + closing). + +**Placement patterns:** Logo centered on title slide. Screenshots in two-column layouts with text. Full-bleed images as slide backgrounds with text overlay (use sparingly). + +--- + +## Code Quality + +**Comments:** Every section needs clear comments explaining what it does and how to modify it. + +**Accessibility:** + +- Semantic HTML (`
`, `