diff --git a/.opencode/skills/Media/Art/Examples/human-linear-form.png b/.opencode/skills/Media/Art/Examples/human-linear-form.png new file mode 100755 index 00000000..55bcd6b1 Binary files /dev/null and b/.opencode/skills/Media/Art/Examples/human-linear-form.png differ diff --git a/.opencode/skills/Media/Art/Examples/human-linear-style2.png b/.opencode/skills/Media/Art/Examples/human-linear-style2.png new file mode 100755 index 00000000..b19a630a Binary files /dev/null and b/.opencode/skills/Media/Art/Examples/human-linear-style2.png differ diff --git a/.opencode/skills/Media/Art/Examples/setting-line-style.png b/.opencode/skills/Media/Art/Examples/setting-line-style.png new file mode 100755 index 00000000..33e381fd Binary files /dev/null and b/.opencode/skills/Media/Art/Examples/setting-line-style.png differ diff --git a/.opencode/skills/Media/Art/Examples/setting-line-style2.png b/.opencode/skills/Media/Art/Examples/setting-line-style2.png new file mode 100755 index 00000000..c8ac0c4e Binary files /dev/null and b/.opencode/skills/Media/Art/Examples/setting-line-style2.png differ diff --git a/.opencode/skills/Media/Art/HeadshotExamples/Screenshot 2024-05-14 at 09.52.31.png b/.opencode/skills/Media/Art/HeadshotExamples/Screenshot 2024-05-14 at 09.52.31.png new file mode 100755 index 00000000..fbb3684c Binary files /dev/null and b/.opencode/skills/Media/Art/HeadshotExamples/Screenshot 2024-05-14 at 09.52.31.png differ diff --git a/.opencode/skills/Media/Art/HeadshotExamples/headshot-clean.png b/.opencode/skills/Media/Art/HeadshotExamples/headshot-clean.png new file mode 100755 index 00000000..d227fc8f Binary files /dev/null and b/.opencode/skills/Media/Art/HeadshotExamples/headshot-clean.png differ diff --git a/.opencode/skills/Media/Art/HeadshotExamples/headshot-hat-smiling.png b/.opencode/skills/Media/Art/HeadshotExamples/headshot-hat-smiling.png new file mode 100755 index 00000000..dd16ba57 Binary files /dev/null and b/.opencode/skills/Media/Art/HeadshotExamples/headshot-hat-smiling.png differ diff --git a/.opencode/skills/Media/Art/HeadshotExamples/headshot-nah.png b/.opencode/skills/Media/Art/HeadshotExamples/headshot-nah.png new file mode 100755 index 00000000..38218932 Binary files /dev/null and b/.opencode/skills/Media/Art/HeadshotExamples/headshot-nah.png differ diff --git a/.opencode/skills/Media/Art/HeadshotExamples/headshot-outside-smiling.png b/.opencode/skills/Media/Art/HeadshotExamples/headshot-outside-smiling.png new file mode 100755 index 00000000..666f890d Binary files /dev/null and b/.opencode/skills/Media/Art/HeadshotExamples/headshot-outside-smiling.png differ diff --git a/.opencode/skills/Media/Art/HeadshotExamples/headshot-pondering.png b/.opencode/skills/Media/Art/HeadshotExamples/headshot-pondering.png new file mode 100755 index 00000000..a28fe212 Binary files /dev/null and b/.opencode/skills/Media/Art/HeadshotExamples/headshot-pondering.png differ diff --git a/.opencode/skills/Media/Art/HeadshotExamples/headshot-smiling.png b/.opencode/skills/Media/Art/HeadshotExamples/headshot-smiling.png new file mode 100755 index 00000000..8153ecd8 Binary files /dev/null and b/.opencode/skills/Media/Art/HeadshotExamples/headshot-smiling.png differ diff --git a/.opencode/skills/Media/Art/HeadshotExamples/headshot-surprised-hat.png b/.opencode/skills/Media/Art/HeadshotExamples/headshot-surprised-hat.png new file mode 100755 index 00000000..41685596 Binary files /dev/null and b/.opencode/skills/Media/Art/HeadshotExamples/headshot-surprised-hat.png differ diff --git a/.opencode/skills/Media/Art/HeadshotExamples/headshot-walking-cap-smiling.png b/.opencode/skills/Media/Art/HeadshotExamples/headshot-walking-cap-smiling.png new file mode 100755 index 00000000..a8765117 Binary files /dev/null and b/.opencode/skills/Media/Art/HeadshotExamples/headshot-walking-cap-smiling.png differ diff --git a/.opencode/skills/Media/Art/HeadshotExamples/headshot-what-is-that.png b/.opencode/skills/Media/Art/HeadshotExamples/headshot-what-is-that.png new file mode 100755 index 00000000..5aee14aa Binary files /dev/null and b/.opencode/skills/Media/Art/HeadshotExamples/headshot-what-is-that.png differ diff --git a/.opencode/skills/Media/Art/HeadshotExamples/headshot-whatthehell.png b/.opencode/skills/Media/Art/HeadshotExamples/headshot-whatthehell.png new file mode 100755 index 00000000..76394e07 Binary files /dev/null and b/.opencode/skills/Media/Art/HeadshotExamples/headshot-whatthehell.png differ diff --git a/.opencode/skills/Media/Art/HeadshotExamples/headshot-yuk.png b/.opencode/skills/Media/Art/HeadshotExamples/headshot-yuk.png new file mode 100755 index 00000000..a87129b0 Binary files /dev/null and b/.opencode/skills/Media/Art/HeadshotExamples/headshot-yuk.png differ diff --git a/.opencode/skills/Media/Art/Lib/discord-bot.ts b/.opencode/skills/Media/Art/Lib/discord-bot.ts new file mode 100755 index 00000000..806db1d8 --- /dev/null +++ b/.opencode/skills/Media/Art/Lib/discord-bot.ts @@ -0,0 +1,275 @@ +/** + * discord-bot.ts - Discord Bot Client for Midjourney Integration + * + * Official Discord bot wrapper using discord.js for legitimate interaction + * with Midjourney bot. Handles connection, message sending, monitoring, + * and image downloads. + * + * @see ~/.opencode/skills/art/SKILL.md + */ + +import { + Client, + GatewayIntentBits, + Message, + TextChannel, + Partials +} from 'discord.js'; +import { writeFile } from 'node:fs/promises'; + +// ============================================================================ +// Constants +// ============================================================================ + +const MIDJOURNEY_BOT_ID = '936929561302675456'; // Official Midjourney bot ID + +// ============================================================================ +// Types +// ============================================================================ + +export interface DiscordBotConfig { + token: string; + channelId: string; +} + +export interface WaitForResponseOptions { + timeout: number; // in seconds + pollInterval?: number; // in milliseconds +} + +// ============================================================================ +// Discord Bot Client +// ============================================================================ + +export class DiscordBotClient { + private client: Client; + private config: DiscordBotConfig; + private connected: boolean = false; + + constructor(config: DiscordBotConfig) { + this.config = config; + + // Initialize Discord client with required intents + this.client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + ], + partials: [Partials.Message, Partials.Channel], + }); + } + + /** + * Connect to Discord + */ + async connect(): Promise { + if (this.connected) { + return; + } + + return new Promise((resolve, reject) => { + // Set up event handlers + this.client.once('ready', () => { + console.log(`✅ Discord bot connected as ${this.client.user?.tag}`); + this.connected = true; + resolve(); + }); + + this.client.on('error', (error) => { + console.error('❌ Discord client error:', error); + }); + + // Login with bot token + this.client.login(this.config.token).catch(reject); + }); + } + + /** + * Send a message to the specified channel + */ + async sendMessage(content: string): Promise { + if (!this.connected) { + throw new Error('Bot not connected. Call connect() first.'); + } + + const channel = await this.client.channels.fetch(this.config.channelId); + + if (!channel || !channel.isTextBased()) { + throw new Error(`Channel ${this.config.channelId} is not a text channel`); + } + + const message = await (channel as TextChannel).send(content); + console.log(`📤 Sent message: ${content}`); + + return message; + } + + /** + * Wait for Midjourney's response to a prompt + * + * Polls the channel for messages from Midjourney bot that reference + * our initial message. Returns when the response is complete (has image attachments). + */ + async waitForMidjourneyResponse( + initialMessageId: string, + options: WaitForResponseOptions + ): Promise { + const { timeout, pollInterval = 5000 } = options; + const startTime = Date.now(); + const timeoutMs = timeout * 1000; + + console.log(`⏳ Waiting for Midjourney response (timeout: ${timeout}s)...`); + + while (Date.now() - startTime < timeoutMs) { + // Fetch recent messages from channel + const channel = await this.client.channels.fetch(this.config.channelId); + + if (!channel || !channel.isTextBased()) { + throw new Error('Channel not found or not text-based'); + } + + const messages = await (channel as TextChannel).messages.fetch({ limit: 20 }); + + // Find Midjourney's response to our prompt + for (const [_, message] of messages) { + // Check if message is from Midjourney bot + if (message.author.id !== MIDJOURNEY_BOT_ID) { + continue; + } + + // Check if this message references our initial prompt + const referencesOurMessage = + message.reference?.messageId === initialMessageId || + message.interaction?.id === initialMessageId || + message.content.includes(initialMessageId); + + if (!referencesOurMessage) { + continue; + } + + // Check if generation is complete + if (this.isGenerationComplete(message)) { + console.log(`✅ Midjourney generation complete!`); + return message; + } else { + console.log(`⏳ Generation in progress... (${Math.floor((Date.now() - startTime) / 1000)}s)`); + } + } + + // Wait before next poll + await this.sleep(pollInterval); + } + + throw new Error(`Timeout waiting for Midjourney response after ${timeout}s`); + } + + /** + * Check if Midjourney generation is complete + * + * A complete generation has: + * - Image attachments + * - No "Waiting to start" or "%" progress indicators + */ + private isGenerationComplete(message: Message): boolean { + // Must have attachments (the generated image) + if (message.attachments.size === 0) { + return false; + } + + // Check for in-progress indicators + const content = message.content.toLowerCase(); + const inProgressIndicators = [ + 'waiting to start', + '(waiting)', + '(0%)', + '(1%)', + '(2%)', + '(3%)', + '(4%)', + '(5%)', + '(6%)', + '(7%)', + '(8%)', + '(9%)', + // Continue patterns for progress + '%)', + ]; + + for (const indicator of inProgressIndicators) { + if (content.includes(indicator)) { + return false; + } + } + + return true; + } + + /** + * Download image from URL to local path + */ + async downloadImage(url: string, outputPath: string): Promise { + console.log(`📥 Downloading image from ${url}...`); + + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`Failed to download image: ${response.statusText}`); + } + + const arrayBuffer = await response.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + + await writeFile(outputPath, buffer); + + console.log(`✅ Image saved to ${outputPath}`); + } + + /** + * Disconnect from Discord + */ + async disconnect(): Promise { + if (!this.connected) { + return; + } + + await this.client.destroy(); + this.connected = false; + console.log('👋 Discord bot disconnected'); + } + + /** + * Get the first image attachment URL from a message + */ + getImageUrl(message: Message): string | null { + if (message.attachments.size === 0) { + return null; + } + + // Get first attachment + const attachment = message.attachments.first(); + + if (!attachment) { + return null; + } + + // Verify it's an image + const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif']; + const isImage = imageExtensions.some(ext => + attachment.url.toLowerCase().includes(ext) + ); + + if (!isImage) { + return null; + } + + return attachment.url; + } + + /** + * Sleep utility + */ + private sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} diff --git a/.opencode/skills/Media/Art/Lib/midjourney-client.ts b/.opencode/skills/Media/Art/Lib/midjourney-client.ts new file mode 100755 index 00000000..15738f86 --- /dev/null +++ b/.opencode/skills/Media/Art/Lib/midjourney-client.ts @@ -0,0 +1,336 @@ +/** + * midjourney-client.ts - Midjourney Interaction Client + * + * High-level client for interacting with Midjourney bot through Discord. + * Handles prompt formatting, command submission, response parsing, + * and error detection. + * + * @see ~/.opencode/skills/art/SKILL.md + */ + +import { DiscordBotClient } from './discord-bot.js'; +import { Message } from 'discord.js'; + +// ============================================================================ +// Types +// ============================================================================ + +export interface MidjourneyOptions { + prompt: string; + aspectRatio?: string; + version?: string; + stylize?: number; + quality?: number; + chaos?: number; + weird?: number; + tile?: boolean; + timeout?: number; // in seconds +} + +export interface MidjourneyResult { + imageUrl: string; + prompt: string; + messageId: string; +} + +export type MidjourneyErrorType = + | 'content_policy' + | 'timeout' + | 'connection' + | 'invalid_params' + | 'generation_failed' + | 'no_image'; + +export class MidjourneyError extends Error { + constructor( + public type: MidjourneyErrorType, + message: string, + public originalPrompt?: string, + public suggestion?: string + ) { + super(message); + this.name = 'MidjourneyError'; + } +} + +// ============================================================================ +// Midjourney Client +// ============================================================================ + +export class MidjourneyClient { + private discordBot: DiscordBotClient; + + constructor(discordBot: DiscordBotClient) { + this.discordBot = discordBot; + } + + /** + * Generate image with Midjourney + * + * Submits prompt, waits for generation, and returns image URL + */ + async generateImage(options: MidjourneyOptions): Promise { + const { + prompt, + aspectRatio = '16:9', + version = '6.1', + stylize = 100, + quality = 1, + chaos, + weird, + tile = false, + timeout = 120, + } = options; + + // Format the Midjourney prompt + const formattedPrompt = this.formatPrompt({ + prompt, + aspectRatio, + version, + stylize, + quality, + chaos, + weird, + tile, + }); + + console.log(`🎨 Submitting to Midjourney: ${formattedPrompt}`); + + // Send the /imagine command + const initialMessage = await this.discordBot.sendMessage(`/imagine prompt: ${formattedPrompt}`); + + // Wait for Midjourney to complete generation + let responseMessage: Message; + try { + responseMessage = await this.discordBot.waitForMidjourneyResponse(initialMessage.id, { + timeout, + pollInterval: 5000, + }); + } catch (error) { + if (error instanceof Error && error.message.includes('Timeout')) { + throw new MidjourneyError( + 'timeout', + `Generation timed out after ${timeout}s. The image may still be processing in Discord.`, + formattedPrompt, + 'Try checking Discord manually or increasing the timeout value.' + ); + } + throw error; + } + + // Check for errors in response + this.detectErrors(responseMessage, formattedPrompt); + + // Extract image URL + const imageUrl = this.discordBot.getImageUrl(responseMessage); + + if (!imageUrl) { + throw new MidjourneyError( + 'no_image', + 'No image found in Midjourney response', + formattedPrompt, + 'The generation may have failed. Check Discord for error messages.' + ); + } + + return { + imageUrl, + prompt: formattedPrompt, + messageId: responseMessage.id, + }; + } + + /** + * Format Midjourney prompt with parameters + * + * Converts structured options into Midjourney command syntax + */ + private formatPrompt(options: { + prompt: string; + aspectRatio: string; + version: string; + stylize: number; + quality: number; + chaos?: number; + weird?: number; + tile: boolean; + }): string { + const { prompt, aspectRatio, version, stylize, quality, chaos, weird, tile } = options; + + let formattedPrompt = prompt; + + // Add aspect ratio + formattedPrompt += ` --ar ${aspectRatio}`; + + // Add version + formattedPrompt += ` --v ${version}`; + + // Add stylize (default is 100, only add if different) + if (stylize !== 100) { + formattedPrompt += ` --s ${stylize}`; + } + + // Add quality (default is 1, only add if different) + if (quality !== 1) { + formattedPrompt += ` --q ${quality}`; + } + + // Add optional parameters + if (chaos !== undefined) { + formattedPrompt += ` --chaos ${chaos}`; + } + + if (weird !== undefined) { + formattedPrompt += ` --weird ${weird}`; + } + + if (tile) { + formattedPrompt += ` --tile`; + } + + return formattedPrompt; + } + + /** + * Detect errors in Midjourney response + */ + private detectErrors(message: Message, originalPrompt: string): void { + const content = message.content.toLowerCase(); + + // Content policy violations + const contentPolicyIndicators = [ + 'banned prompt', + 'content policy', + 'violates our community standards', + 'inappropriate content', + 'against our terms', + ]; + + for (const indicator of contentPolicyIndicators) { + if (content.includes(indicator)) { + throw new MidjourneyError( + 'content_policy', + 'Prompt violates Midjourney content policy', + originalPrompt, + 'Try rephrasing your prompt to avoid potentially sensitive content.' + ); + } + } + + // Invalid parameters + const invalidParamIndicators = [ + 'invalid parameter', + 'unknown parameter', + 'invalid aspect ratio', + 'invalid version', + ]; + + for (const indicator of invalidParamIndicators) { + if (content.includes(indicator)) { + throw new MidjourneyError( + 'invalid_params', + 'Invalid Midjourney parameters', + originalPrompt, + 'Check your aspect ratio, version, and other parameter values.' + ); + } + } + + // Generation failures + const failureIndicators = [ + 'failed to generate', + 'generation failed', + 'error generating', + 'something went wrong', + ]; + + for (const indicator of failureIndicators) { + if (content.includes(indicator)) { + throw new MidjourneyError( + 'generation_failed', + 'Midjourney generation failed', + originalPrompt, + 'Try again or check Discord for more details.' + ); + } + } + } + + /** + * Parse Midjourney response to extract metadata + */ + parseResponse(message: Message): { + prompt: string; + parameters: Record; + } { + const content = message.content; + + // Extract prompt (usually before the first --) + const promptMatch = content.match(/^(.+?)(?:\s+--|\s*$)/); + const prompt = promptMatch ? promptMatch[1].trim() : content; + + // Extract parameters + const parameters: Record = {}; + const paramRegex = /--(\w+)\s+([^\s-]+)/g; + let match; + + while ((match = paramRegex.exec(content)) !== null) { + parameters[match[1]] = match[2]; + } + + return { prompt, parameters }; + } + + /** + * Validate Midjourney options before submission + */ + static validateOptions(options: MidjourneyOptions): void { + // Validate aspect ratio + const validAspectRatios = [ + '1:1', '16:9', '9:16', '2:3', '3:2', '4:5', '5:4', '7:4', '4:7', + '21:9', '9:21', '3:4', '4:3' + ]; + + if (options.aspectRatio && !validAspectRatios.includes(options.aspectRatio)) { + throw new Error( + `Invalid aspect ratio: ${options.aspectRatio}. Valid ratios: ${validAspectRatios.join(', ')}` + ); + } + + // Validate version + const validVersions = ['6.1', '6', '5.2', '5.1', '5', 'niji', 'niji 6']; + + if (options.version && !validVersions.includes(options.version)) { + throw new Error( + `Invalid version: ${options.version}. Valid versions: ${validVersions.join(', ')}` + ); + } + + // Validate stylize (0-1000) + if (options.stylize !== undefined && (options.stylize < 0 || options.stylize > 1000)) { + throw new Error('Stylize must be between 0 and 1000'); + } + + // Validate quality + const validQualities = [0.25, 0.5, 1, 2]; + + if (options.quality !== undefined && !validQualities.includes(options.quality)) { + throw new Error('Quality must be 0.25, 0.5, 1, or 2'); + } + + // Validate chaos (0-100) + if (options.chaos !== undefined && (options.chaos < 0 || options.chaos > 100)) { + throw new Error('Chaos must be between 0 and 100'); + } + + // Validate weird (0-3000) + if (options.weird !== undefined && (options.weird < 0 || options.weird > 3000)) { + throw new Error('Weird must be between 0 and 3000'); + } + + // Validate timeout + if (options.timeout !== undefined && options.timeout < 30) { + throw new Error('Timeout must be at least 30 seconds'); + } + } +} diff --git a/.opencode/skills/Media/Art/SKILL.md b/.opencode/skills/Media/Art/SKILL.md new file mode 100755 index 00000000..cde154b9 --- /dev/null +++ b/.opencode/skills/Media/Art/SKILL.md @@ -0,0 +1,199 @@ +--- +name: Art +description: Visual content system. USE WHEN art, illustrations, diagrams, visualizations, mermaid, flowchart. +--- + +# Art Skill + +Complete visual content system for creating illustrations, diagrams, and visual content. + +## Customization + +**Before executing, check for user customizations at:** +`~/.opencode/skills/CORE/USER/SKILLCUSTOMIZATIONS/Art/` + +If this directory exists, load and apply: +- `PREFERENCES.md` - Aesthetic preferences, default model, output location +- `CharacterSpecs.md` - Character design specifications +- `SceneConstruction.md` - Scene composition guidelines + +These override default behavior. If the directory does not exist, proceed with skill defaults. + +## 🚨🚨🚨 MANDATORY: Output to Downloads First 🚨🚨🚨 + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚠️ ALL GENERATED IMAGES GO TO ~/Downloads/ FIRST ⚠️ +⚠️ NEVER output directly to project directories ⚠️ +⚠️ User MUST preview in Finder/Preview before use ⚠️ +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +**This applies to ALL workflows in this skill.** + +## Voice Notification + +**When executing a workflow, do BOTH:** + +1. **Send voice notification**: + ```bash + curl -s -X POST http://localhost:8888/notify \ + -H "Content-Type: application/json" \ + -d '{"message": "Running the WORKFLOWNAME workflow from the Art skill"}' \ + > /dev/null 2>&1 & + ``` + +2. **Output text notification**: + ``` + Running the **WorkflowName** workflow from the **Art** skill... + ``` + +--- + +## Workflow Routing + +Route to the appropriate workflow based on the request. + + - Blog header or editorial illustration → `Workflows/Essay.md` + - D3.js interactive chart or dashboard → `Workflows/D3Dashboards.md` + - Visualization or unsure which format → `Workflows/Visualize.md` + - Mermaid flowchart or sequence diagram → `Workflows/Mermaid.md` + - Technical or architecture diagram → `Workflows/TechnicalDiagrams.md` + - Taxonomy or classification grid → `Workflows/Taxonomies.md` + - Timeline or chronological progression → `Workflows/Timelines.md` + - Framework or 2x2 matrix → `Workflows/Frameworks.md` + - Comparison or X vs Y → `Workflows/Comparisons.md` + - Annotated screenshot → `Workflows/AnnotatedScreenshots.md` + - Recipe card or step-by-step → `Workflows/RecipeCards.md` + - Aphorism or quote card → `Workflows/Aphorisms.md` + - Conceptual map or territory → `Workflows/Maps.md` + - Stat card or big number visual → `Workflows/Stats.md` + - Comic or sequential panels → `Workflows/Comics.md` + - YouTube thumbnail (with existing assets) → `Workflows/YouTubeThumbnail.md` + - Ad-hoc YouTube thumbnail (generate from content) → `Workflows/AdHocYouTubeThumbnail.md` + - PAI pack icon → `Workflows/CreatePAIPackIcon.md` + +--- + +## Core Aesthetic + +**Default:** Production-quality concept art style appropriate for editorial and technical content. + +**User customization** defines specific aesthetic preferences including: +- Visual style and influences +- Line treatment and rendering approach +- Color palette and wash technique +- Character design specifications +- Scene composition rules + +**Load from:** `~/.opencode/skills/CORE/USER/SKILLCUSTOMIZATIONS/Art/PREFERENCES.md` + +--- + +## Reference Images + +**User customization** may include reference images for consistent style. + +Check `~/.opencode/skills/CORE/USER/SKILLCUSTOMIZATIONS/Art/PREFERENCES.md` for: +- Reference image locations +- Style examples by use case +- Character and scene reference guidance + +**Usage:** Before generating images, load relevant user-provided references to match their preferred style. + +--- + +## Image Generation + +**Default model:** Check user customization at `SKILLCUSTOMIZATIONS/Art/PREFERENCES.md` +**Fallback:** nano-banana-pro (Gemini 3 Pro) + +### 🚨 CRITICAL: Always Output to Downloads First + +**ALL generated images MUST go to `~/Downloads/` first for preview and selection.** + +Never output directly to a project's `public/images/` directory. User needs to review images in Preview before they're used. + +**Workflow:** +1. Generate to `~/Downloads/[descriptive-name].png` +2. User reviews in Preview +3. If approved, THEN copy to final destination (e.g., `cms/public/images/`) +4. Create WebP and thumbnail versions at final destination + +```bash +# CORRECT - Output to Downloads for preview +bun run ~/.opencode/skills/Media/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[PROMPT]" \ + --size 2K \ + --aspect-ratio 1:1 \ + --thumbnail \ + --output ~/Downloads/blog-header-concept.png + +# After approval, copy to final location +cp ~/Downloads/blog-header-concept.png ~/Projects/Website/cms/public/images/ +cp ~/Downloads/blog-header-concept-thumb.png ~/Projects/Website/cms/public/images/ +``` + +### Multiple Reference Images (Character/Style Consistency) + +For improved character or style consistency, use multiple `--reference-image` flags: + +```bash +# Multiple reference images for better likeness +bun run ~/.opencode/skills/Media/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "Person from references at a party..." \ + --reference-image face1.jpg \ + --reference-image face2.jpg \ + --reference-image face3.jpg \ + --size 2K \ + --aspect-ratio 16:9 \ + --output ~/Downloads/character-scene.png +``` + +**API Limits (Gemini):** +- Up to 5 human reference images +- Up to 6 object reference images +- Maximum 14 total reference images per request + +**API keys in:** `${PAI_DIR}/.env` + +## Examples + +**Example 1: Blog header image** +``` +User: "create a header for my AI agents post" +→ Invokes ESSAY workflow +→ Generates charcoal sketch prompt +→ Creates image with architectural aesthetic +→ Saves to ~/Downloads/ for preview +→ After approval, copies to public/images/ +``` + +**Example 2: Technical architecture diagram** +``` +User: "make a diagram showing the SPQA pattern" +→ Invokes TECHNICALDIAGRAMS workflow +→ Creates structured architecture visual +→ Outputs PNG with consistent styling +``` + +**Example 3: Comparison visualization** +``` +User: "visualize humans vs AI decision-making" +→ Invokes COMPARISONS workflow +→ Creates side-by-side visual +→ Charcoal sketch with labeled elements +``` + +**Example 4: PAI pack icon** +``` +User: "create icon for the skill system pack" +→ Invokes CREATEPAIPACKICON workflow +→ Reads workflow from Workflows/CreatePAIPackIcon.md +→ Generates 1K image with --remove-bg for transparency +→ Resizes to 256x256 RGBA PNG +→ Outputs to ~/Downloads/ for preview +→ After approval, copies to ~/Projects/PAI/Packs/icons/ +``` diff --git a/.opencode/skills/Media/Art/ThumbnailExamples/AudioEssay.png b/.opencode/skills/Media/Art/ThumbnailExamples/AudioEssay.png new file mode 100755 index 00000000..0ab640f1 Binary files /dev/null and b/.opencode/skills/Media/Art/ThumbnailExamples/AudioEssay.png differ diff --git a/.opencode/skills/Media/Art/ThumbnailExamples/InterviewVideo.png b/.opencode/skills/Media/Art/ThumbnailExamples/InterviewVideo.png new file mode 100755 index 00000000..aa08f962 Binary files /dev/null and b/.opencode/skills/Media/Art/ThumbnailExamples/InterviewVideo.png differ diff --git a/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo1.png b/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo1.png new file mode 100755 index 00000000..36c201b8 Binary files /dev/null and b/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo1.png differ diff --git a/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo2.png b/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo2.png new file mode 100755 index 00000000..1918c0de Binary files /dev/null and b/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo2.png differ diff --git a/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo3.png b/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo3.png new file mode 100755 index 00000000..843387f3 Binary files /dev/null and b/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo3.png differ diff --git a/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo4.png b/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo4.png new file mode 100755 index 00000000..2cbba4ac Binary files /dev/null and b/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo4.png differ diff --git a/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo5.png b/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo5.png new file mode 100755 index 00000000..d0455400 Binary files /dev/null and b/.opencode/skills/Media/Art/ThumbnailExamples/RegularVideo5.png differ diff --git a/.opencode/skills/Media/Art/Tools/ComposeThumbnail.ts b/.opencode/skills/Media/Art/Tools/ComposeThumbnail.ts new file mode 100755 index 00000000..cd0c24d8 --- /dev/null +++ b/.opencode/skills/Media/Art/Tools/ComposeThumbnail.ts @@ -0,0 +1,539 @@ +#!/usr/bin/env bun + +/** + * ComposeThumbnail - YouTube Thumbnail Composition CLI + * + * Composites background, headshot, and text into a YouTube thumbnail. + * Uses ImageMagick for all composition operations. + * + * Features: + * - Dynamic headshot positioning (left, center, right) + * - Solid black backdrop boxes behind text for readability + * - Full-height headshot that dominates the frame + * - Colored border (Tokyo Night purple default) + */ + +import { spawn } from "node:child_process"; +import { existsSync, unlinkSync } from "node:fs"; +import { resolve, dirname } from "node:path"; + +// ============================================================================ +// Types +// ============================================================================ + +interface CLIArgs { + background: string; + headshot: string; + title: string; + subtitle: string; + output: string; + titleColor?: string; + subtitleColor?: string; + borderColor?: string; + font?: string; + headshotPosition?: "left" | "center" | "right"; +} + +// ============================================================================ +// Configuration +// ============================================================================ + +const DEFAULTS = { + titleColor: "#7dcfff", // Tokyo Night cyan - VIBRANT by default + subtitleColor: "#FFFFFF", // White text for contrast + borderColor: "#bb9af7", // Tokyo Night Vivid Purple + font: "Helvetica-Bold", // System font that actually exists + headshotPosition: "left" as const, + output: `${process.env.HOME}/Downloads/yt-thumbnail-${Date.now()}.png`, +}; + +const LAYOUT = { + width: 1280, + height: 720, + borderWidth: 16, + // TEXT - BILLBOARD STYLE (large, bold, dominant) + titleSize: 100, // DOMINANT - fills the space + subtitleSize: 50, // Proportionally sized, still readable + titleStroke: 4, // Bold outline for visibility + subtitleStroke: 3, // Visible outline + textPadding: 6, + textBoxPadding: 20, + // Safe zones - headshot and text never overlap + headshotMaxWidth: 0.40, // Headshot takes max 40% width + textZoneWidth: 0.55, // Text zone is 55% width (FILLS the space) + textZoneGap: 0.05, // 5% gap between zones +}; + +// Color presets for text (Tokyo Night palette + extras) +const COLOR_PRESETS: Record = { + white: "#FFFFFF", + cyan: "#7dcfff", + purple: "#bb9af7", + blue: "#7aa2f7", + magenta: "#ff007c", + yellow: "#e0af68", + green: "#9ece6a", + orange: "#ff9e64", + red: "#f7768e", +}; + +function resolveColor(color: string): string { + // If it's a preset name, return the hex value + const preset = COLOR_PRESETS[color.toLowerCase()]; + if (preset) return preset; + // Otherwise assume it's already a hex color + return color; +} + +// ============================================================================ +// Error Handling +// ============================================================================ + +class CLIError extends Error { + constructor(message: string, public exitCode: number = 1) { + super(message); + this.name = "CLIError"; + } +} + +// ============================================================================ +// Helpers +// ============================================================================ + +function printHelp(): void { + console.log(` +ComposeThumbnail - YouTube Thumbnail Composition CLI + +USAGE: + bun ~/.opencode/skills/Art/Tools/ComposeThumbnail.ts [OPTIONS] + +REQUIRED: + --background Background image (dramatic tech art) + --headshot Headshot image (transparent background) + --title Title text (max 6 words, auto-capitalized) + --subtitle Subtitle text (max 12 words, auto-capitalized) + +OPTIONAL: + --output Output path (default: ~/Downloads/yt-thumbnail-{timestamp}.png) + --position Headshot position: left, center, right (default: left) + --font Font name (default: Helvetica-Bold) + --title-color Title color (default: #FFFFFF) + --subtitle-color Subtitle color (default: #FFFFFF) + --border-color Border color (default: #bb9af7 Tokyo Night Purple) + --help, -h Show this help message + +EXAMPLE: + bun ~/.opencode/skills/Art/Tools/ComposeThumbnail.ts \\ + --background ~/Downloads/tech-background.png \\ + --headshot ~/Downloads/headshot-nobg.png \\ + --title "AI AGENTS KILLING SOFTWARE" \\ + --subtitle "WHY TRADITIONAL DEVELOPMENT IS DEAD" \\ + --position left \\ + --output ~/Downloads/thumbnail.png + +LAYOUT: + Canvas: 1280x720 px + Border: 16px colored border (Tokyo Night purple) + Headshot: Full height inside border, positioned left/center/right + Text: White text with minimal black backdrop boxes +`); +} + +function parseArgs(args: string[]): CLIArgs { + const result: Partial = {}; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + const next = args[i + 1]; + + switch (arg) { + case "--help": + case "-h": + printHelp(); + process.exit(0); + case "--background": + result.background = next; + i++; + break; + case "--headshot": + result.headshot = next; + i++; + break; + case "--title": + result.title = next; + i++; + break; + case "--subtitle": + result.subtitle = next; + i++; + break; + case "--output": + result.output = next; + i++; + break; + case "--position": + if (next === "left" || next === "center" || next === "right") { + result.headshotPosition = next; + } + i++; + break; + case "--title-color": + result.titleColor = next; + i++; + break; + case "--subtitle-color": + result.subtitleColor = next; + i++; + break; + case "--border-color": + result.borderColor = next; + i++; + break; + case "--font": + result.font = next; + i++; + break; + } + } + + // Validate required args + if (!result.background) throw new CLIError("--background is required"); + if (!result.headshot) throw new CLIError("--headshot is required"); + if (!result.title) throw new CLIError("--title is required"); + if (!result.subtitle) throw new CLIError("--subtitle is required"); + + // Validate files exist + if (!existsSync(result.background)) { + throw new CLIError(`Background file not found: ${result.background}`); + } + if (!existsSync(result.headshot)) { + throw new CLIError(`Headshot file not found: ${result.headshot}`); + } + + return { + background: resolve(result.background), + headshot: resolve(result.headshot), + title: result.title.toUpperCase(), + subtitle: result.subtitle.toUpperCase(), + output: result.output ? resolve(result.output) : DEFAULTS.output, + titleColor: result.titleColor || DEFAULTS.titleColor, + subtitleColor: result.subtitleColor || DEFAULTS.subtitleColor, + borderColor: result.borderColor || DEFAULTS.borderColor, + font: result.font || DEFAULTS.font, + headshotPosition: result.headshotPosition || DEFAULTS.headshotPosition, + }; +} + +async function runCommand(cmd: string, args: string[]): Promise { + return new Promise((resolve, reject) => { + const proc = spawn(cmd, args); + let stdout = ""; + let stderr = ""; + + proc.stdout.on("data", (data) => (stdout += data.toString())); + proc.stderr.on("data", (data) => (stderr += data.toString())); + + proc.on("close", (code) => { + if (code === 0) { + resolve(stdout); + } else { + reject(new CLIError(`Command failed: ${cmd} ${args.join(" ")}\n${stderr}`, code || 1)); + } + }); + }); +} + +// ============================================================================ +// Headshot Processing +// ============================================================================ + +/** + * Crop headshot to FACE ONLY - removes shoulders/body and zooms into face. + * This ensures the face dominates the frame without clipped body parts. + */ +async function cropToFaceOnly(headshotPath: string, outputPath: string): Promise { + // Get original dimensions + const dimensions = await runCommand("magick", [ + "identify", "-format", "%wx%h", headshotPath + ]); + const [width, height] = dimensions.trim().split("x").map(Number); + + // Crop bottom 25% (removes shoulders/body) and zoom 135% into face + await runCommand("magick", [ + headshotPath, + "-gravity", "north", // Anchor to top (face area) + "-crop", `100%x75%+0+0`, // Crop bottom 25% (shoulders/body) + "+repage", + "-resize", "135%", // Zoom into face + "-gravity", "center", + "-extent", `${width}x${height}`, // Restore original dimensions + outputPath, + ]); +} + +// ============================================================================ +// Main Composition +// ============================================================================ + +async function composeThumbnail(args: CLIArgs): Promise { + const outputDir = dirname(args.output); + const timestamp = Date.now(); + + // Intermediate files + const resizedBg = `${outputDir}/.yt-bg-${timestamp}.png`; + const croppedHeadshot = `${outputDir}/.yt-cropped-${timestamp}.png`; + const withHeadshot = `${outputDir}/.yt-headshot-${timestamp}.png`; + const withText = `${outputDir}/.yt-text-${timestamp}.png`; + + const intermediates = [resizedBg, croppedHeadshot, withHeadshot, withText]; + + try { + console.log("🎨 Composing YouTube thumbnail..."); + + // Step 1: Resize background to exact dimensions + console.log(" 📐 Resizing background to 1280x720..."); + await runCommand("magick", [ + args.background, + "-resize", `${LAYOUT.width}x${LAYOUT.height}^`, + "-gravity", "center", + "-extent", `${LAYOUT.width}x${LAYOUT.height}`, + resizedBg, + ]); + + // Step 2: Crop headshot to FACE ONLY (remove shoulders/body) + console.log(` ✂️ Cropping headshot to face only...`); + await cropToFaceOnly(args.headshot, croppedHeadshot); + + // Step 3: Composite headshot based on position + console.log(` 👤 Adding headshot (${args.headshotPosition})...`); + + // Calculate headshot height - FULL HEIGHT inside border + // Face should fill ~95% of vertical space inside the border + const headshotHeight = LAYOUT.height - (LAYOUT.borderWidth * 2); // 688px + + // Determine gravity and offset based on position + let gravity: string; + let geometryOffset: string; + + switch (args.headshotPosition) { + case "left": + gravity = "west"; + geometryOffset = "+20+0"; + break; + case "center": + gravity = "center"; + geometryOffset = "+0+0"; + break; + case "right": + gravity = "east"; + geometryOffset = "+20+0"; + break; + default: + gravity = "west"; + geometryOffset = "+20+0"; + } + + await runCommand("magick", [ + resizedBg, + "(", + croppedHeadshot, // Use cropped headshot (face only) + "-resize", `x${headshotHeight}`, + ")", + "-gravity", gravity, + "-geometry", geometryOffset, + "-composite", + withHeadshot, + ]); + + // Step 4: Add text with stroke outline + console.log(" 📝 Adding text with stroke outlines..."); + + // For left/right positions: create combined text block, center in available region + // For center position: separate title (top) and subtitle (bottom) + + // Resolve colors (support preset names like "cyan" or hex like "#ff007c") + const titleColorResolved = resolveColor(args.titleColor!); + const subtitleColorResolved = resolveColor(args.subtitleColor!); + + if (args.headshotPosition === "center") { + // CENTER: Title at top, subtitle at bottom (outside headshot zone) + const titleWithStroke = `${outputDir}/.yt-title-${timestamp}.png`; + const subtitleWithStroke = `${outputDir}/.yt-subtitle-${timestamp}.png`; + intermediates.push(titleWithStroke, subtitleWithStroke); + + // Create title (WIDE canvas for 100pt BILLBOARD text) + await runCommand("magick", [ + "-size", "1400x200", + "xc:transparent", + "-font", args.font!, + "-pointsize", String(LAYOUT.titleSize), + "-gravity", "center", + "-stroke", "#000000", "-strokewidth", String(LAYOUT.titleStroke), "-fill", "none", + "-annotate", "+0+0", args.title, + "-stroke", "none", "-fill", titleColorResolved, + "-annotate", "+0+0", args.title, + "-trim", "+repage", + titleWithStroke, + ]); + + // Create subtitle (WIDE canvas for 50pt BILLBOARD text) + await runCommand("magick", [ + "-size", "1400x120", + "xc:transparent", + "-font", args.font!, + "-pointsize", String(LAYOUT.subtitleSize), + "-gravity", "center", + "-stroke", "#000000", "-strokewidth", String(LAYOUT.subtitleStroke), "-fill", "none", + "-annotate", "+0+0", args.subtitle, + "-stroke", "none", "-fill", subtitleColorResolved, + "-annotate", "+0+0", args.subtitle, + "-trim", "+repage", + subtitleWithStroke, + ]); + + // Composite title at top (inside border, above headshot zone) + const withTitle = `${outputDir}/.yt-with-title-${timestamp}.png`; + intermediates.push(withTitle); + await runCommand("magick", [ + withHeadshot, + titleWithStroke, + "-gravity", "north", + "-geometry", "+0+25", + "-composite", + withTitle, + ]); + + // Composite subtitle at bottom (inside border, below headshot zone) + await runCommand("magick", [ + withTitle, + subtitleWithStroke, + "-gravity", "south", + "-geometry", "+0+25", + "-composite", + withText, + ]); + + } else { + // LEFT or RIGHT: Create text in safe zone (NEVER overlap headshot) + + // Calculate text zone center (opposite side from headshot) + // Position at 62% or 38% of canvas width with generous margins + // Account for 16px border + compression during final resize + const textZoneCenter = args.headshotPosition === "left" + ? Math.round(LAYOUT.width * 0.62) // 794px - text on right with generous margin + : Math.round(LAYOUT.width * 0.38); // 486px - text on left with generous margin + + // Create title and subtitle with BILLBOARD sizing + const titleImg = `${outputDir}/.yt-title-${timestamp}.png`; + const subtitleImg = `${outputDir}/.yt-subtitle-${timestamp}.png`; + intermediates.push(titleImg, subtitleImg); + + // Create title - 100pt BILLBOARD text on WIDE canvas (prevents cutoff) + await runCommand("magick", [ + "-size", "1400x200", + "xc:transparent", + "-font", args.font!, + "-gravity", "center", + "-pointsize", String(LAYOUT.titleSize), + "-stroke", "#000000", "-strokewidth", String(LAYOUT.titleStroke), "-fill", "none", + "-annotate", "+0+0", args.title, + "-stroke", "none", "-fill", titleColorResolved, + "-annotate", "+0+0", args.title, + "-trim", "+repage", + titleImg, + ]); + + // Create subtitle - 50pt BILLBOARD text on WIDE canvas (prevents cutoff) + await runCommand("magick", [ + "-size", "1400x120", + "xc:transparent", + "-font", args.font!, + "-gravity", "center", + "-pointsize", String(LAYOUT.subtitleSize), + "-stroke", "#000000", "-strokewidth", String(LAYOUT.subtitleStroke), "-fill", "none", + "-annotate", "+0+0", args.subtitle, + "-stroke", "none", "-fill", subtitleColorResolved, + "-annotate", "+0+0", args.subtitle, + "-trim", "+repage", + subtitleImg, + ]); + + // Position text at center of text zone using absolute coordinates + // Composite title above center + const withTitle = `${outputDir}/.yt-with-title-${timestamp}.png`; + intermediates.push(withTitle); + + // Calculate absolute X position for centering in text zone + // We'll use page geometry to position at exact coordinates + // Larger fonts need more vertical spread + const titleY = Math.round(LAYOUT.height / 2) - 80; // Above center (for 100pt) + const subtitleY = Math.round(LAYOUT.height / 2) + 50; // Below center (for 50pt) + + await runCommand("magick", [ + withHeadshot, + titleImg, + "-gravity", "north", + "-geometry", `+${textZoneCenter - LAYOUT.width/2}+${titleY}`, + "-composite", + withTitle, + ]); + + // Composite subtitle below title + await runCommand("magick", [ + withTitle, + subtitleImg, + "-gravity", "north", + "-geometry", `+${textZoneCenter - LAYOUT.width/2}+${subtitleY}`, + "-composite", + withText, + ]); + } + + // Step 5: Add colored border + console.log(" 🖼️ Adding border..."); + await runCommand("magick", [ + withText, + "-bordercolor", args.borderColor!, + "-border", String(LAYOUT.borderWidth), + "-resize", `${LAYOUT.width}x${LAYOUT.height}!`, + args.output, + ]); + + console.log(`✅ Thumbnail saved to ${args.output}`); + + // Verify dimensions + const identify = await runCommand("magick", ["identify", "-format", "%wx%h", args.output]); + console.log(` 📏 Dimensions: ${identify.trim()}`); + + } finally { + // Cleanup intermediate files + for (const file of intermediates) { + try { + if (existsSync(file)) { + unlinkSync(file); + } + } catch { + // Ignore cleanup errors + } + } + } +} + +// ============================================================================ +// Main +// ============================================================================ + +async function main(): Promise { + try { + const args = parseArgs(process.argv.slice(2)); + await composeThumbnail(args); + } catch (error) { + if (error instanceof CLIError) { + console.error(`❌ Error: ${error.message}`); + process.exit(error.exitCode); + } + throw error; + } +} + +main(); diff --git a/.opencode/skills/Media/Art/Tools/Generate.ts b/.opencode/skills/Media/Art/Tools/Generate.ts new file mode 100755 index 00000000..7e8c8142 --- /dev/null +++ b/.opencode/skills/Media/Art/Tools/Generate.ts @@ -0,0 +1,724 @@ +#!/usr/bin/env bun + +/** + * generate - UL Image Generation CLI + * + * Generate Unsupervised Learning branded images using Flux 1.1 Pro, Nano Banana, Nano Banana Pro, or GPT-image-1. + * Follows llcli pattern for deterministic, composable CLI design. + * + * Usage: + * generate --model nano-banana-pro --prompt "..." --size 16:9 --output /tmp/image.png + * + * @see ~/.opencode/skills/art/README.md + */ + +import Replicate from "replicate"; +import OpenAI from "openai"; +import { GoogleGenAI } from "@google/genai"; +import { writeFile, readFile } from "node:fs/promises"; +import { extname, resolve } from "node:path"; + +// ============================================================================ +// Environment Loading +// ============================================================================ + +/** + * Load environment variables from ${PAI_DIR}/.env + * This ensures API keys are available regardless of how the CLI is invoked + */ +async function loadEnv(): Promise { + const paiDir = process.env.PAI_DIR || resolve(process.env.HOME!, '.opencode'); + const envPath = resolve(paiDir, '.env'); + try { + const envContent = await readFile(envPath, 'utf-8'); + for (const line of envContent.split('\n')) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + const eqIndex = trimmed.indexOf('='); + if (eqIndex === -1) continue; + const key = trimmed.slice(0, eqIndex).trim(); + let value = trimmed.slice(eqIndex + 1).trim(); + // Remove surrounding quotes if present + if ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1); + } + // Only set if not already defined (allow overrides from shell) + if (!process.env[key]) { + process.env[key] = value; + } + } + } catch (error) { + // Silently continue if .env doesn't exist - rely on shell env vars + } +} + +// ============================================================================ +// Types +// ============================================================================ + +type Model = "flux" | "nano-banana" | "nano-banana-pro" | "gpt-image-1"; +type ReplicateSize = "1:1" | "16:9" | "3:2" | "2:3" | "3:4" | "4:3" | "4:5" | "5:4" | "9:16" | "21:9"; +type OpenAISize = "1024x1024" | "1536x1024" | "1024x1536"; +type GeminiSize = "1K" | "2K" | "4K"; +type Size = ReplicateSize | OpenAISize | GeminiSize; + +interface CLIArgs { + model: Model; + prompt: string; + size: Size; + output: string; + creativeVariations?: number; + aspectRatio?: ReplicateSize; // For Gemini models + transparent?: boolean; // Enable transparent background + referenceImages?: string[]; // Reference image paths (Nano Banana Pro only) - up to 14 total + removeBg?: boolean; // Remove background after generation using remove.bg API + addBg?: string; // Add background color (hex) to transparent image + thumbnail?: boolean; // Generate additional thumbnail with #EAE9DF background for social previews +} + +// ============================================================================ +// Configuration +// ============================================================================ + +const DEFAULTS = { + model: "flux" as Model, + size: "16:9" as Size, + output: `${process.env.HOME}/Downloads/ul-image.png`, +}; + +const REPLICATE_SIZES: ReplicateSize[] = ["1:1", "16:9", "3:2", "2:3", "3:4", "4:3", "4:5", "5:4", "9:16", "21:9"]; +const OPENAI_SIZES: OpenAISize[] = ["1024x1024", "1536x1024", "1024x1536"]; +const GEMINI_SIZES: GeminiSize[] = ["1K", "2K", "4K"]; + +// Aspect ratio mapping for Gemini (used with image size like 2K) +const GEMINI_ASPECT_RATIOS: ReplicateSize[] = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]; + +// ============================================================================ +// Error Handling +// ============================================================================ + +class CLIError extends Error { + constructor(message: string, public exitCode: number = 1) { + super(message); + this.name = "CLIError"; + } +} + +function handleError(error: unknown): never { + if (error instanceof CLIError) { + console.error(`❌ Error: ${error.message}`); + process.exit(error.exitCode); + } + + if (error instanceof Error) { + console.error(`❌ Unexpected error: ${error.message}`); + console.error(error.stack); + process.exit(1); + } + + console.error(`❌ Unknown error:`, error); + process.exit(1); +} + +// ============================================================================ +// Help Text +// ============================================================================ + +// PAI directory for documentation paths +const PAI_DIR = process.env.PAI_DIR || `${process.env.HOME}/.opencode`; + +function showHelp(): void { + console.log(` +generate - UL Image Generation CLI + +Generate Unsupervised Learning branded images using Flux 1.1 Pro, Nano Banana, or GPT-image-1. + +USAGE: + generate --model --prompt "" [OPTIONS] + +REQUIRED: + --model Model to use: flux, nano-banana, nano-banana-pro, gpt-image-1 + --prompt Image generation prompt (quote if contains spaces) + +OPTIONS: + --size Image size/aspect ratio (default: 16:9) + Replicate (flux, nano-banana): 1:1, 16:9, 3:2, 2:3, 3:4, 4:3, 4:5, 5:4, 9:16, 21:9 + OpenAI (gpt-image-1): 1024x1024, 1536x1024, 1024x1536 + Gemini (nano-banana-pro): 1K, 2K, 4K (resolution); aspect ratio inferred from context or defaults to 16:9 + --aspect-ratio Aspect ratio for Gemini nano-banana-pro (default: 16:9) + Options: 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 + --output Output file path (default: /tmp/ul-image.png) + --reference-image Reference image for style/character consistency (Nano Banana Pro only) + Can specify MULTIPLE times for improved consistency + Accepts: PNG, JPEG, WebP images + API Limits: Up to 5 human refs, 6 object refs, 14 total max + --transparent Enable transparent background (adds transparency instructions to prompt) + Note: Not all models support transparency natively; may require post-processing + --remove-bg Remove background after generation using remove.bg API + Creates true transparency by removing the generated background + --add-bg Add background color to a transparent image (e.g., "#EAE9DF") + Useful for creating thumbnails/social previews from transparent images + --thumbnail Generate BOTH transparent AND thumbnail versions for blog headers + Creates: output.png (transparent) + output-thumb.png (#EAE9DF background) + Automatically enables --remove-bg + --creative-variations Generate N variations (appends -v1, -v2, etc. to output filename) + Use with the be-creative skill for true prompt diversity + CLI mode: generates N images with same prompt (tests model variability) + --help, -h Show this help message + +EXAMPLES: + # Generate blog header with Nano Banana Pro (16:9, 2K quality) + generate --model nano-banana-pro --prompt "Abstract UL illustration..." --size 2K --aspect-ratio 16:9 + + # Generate high-res 4K image with Nano Banana Pro + generate --model nano-banana-pro --prompt "Editorial cover..." --size 4K --aspect-ratio 3:2 + + # Generate blog header with original Nano Banana (16:9) + generate --model nano-banana --prompt "Abstract UL illustration..." --size 16:9 + + # Generate square image with Flux + generate --model flux --prompt "Minimal geometric art..." --size 1:1 --output /tmp/header.png + + # Generate portrait with GPT-image-1 + generate --model gpt-image-1 --prompt "Editorial cover..." --size 1024x1536 + + # Generate 3 creative variations (for testing model variability) + generate --model gpt-image-1 --prompt "..." --creative-variations 3 --output /tmp/essay.png + # Outputs: /tmp/essay-v1.png, /tmp/essay-v2.png, /tmp/essay-v3.png + + # Single reference image for style guidance (Nano Banana Pro only) + generate --model nano-banana-pro --prompt "Tokyo Night themed illustration..." \\ + --reference-image /tmp/style-reference.png --size 2K --aspect-ratio 16:9 + + # MULTIPLE reference images for character consistency (Nano Banana Pro only) + generate --model nano-banana-pro --prompt "Person from references at a party..." \\ + --reference-image face1.jpg --reference-image face2.jpg --reference-image face3.jpg \\ + --size 2K --aspect-ratio 16:9 + +NOTE: For true creative diversity with different prompts, use the creative workflow which +integrates the be-creative skill. CLI creative mode generates multiple images with the SAME prompt. + +MULTI-REFERENCE LIMITS (Gemini API): + - Up to 5 human reference images for character consistency + - Up to 6 object reference images + - Maximum 14 total reference images per request + +ENVIRONMENT VARIABLES: + REPLICATE_API_TOKEN Required for flux and nano-banana models + OPENAI_API_KEY Required for gpt-image-1 model + GOOGLE_API_KEY Required for nano-banana-pro model + REMOVEBG_API_KEY Required for --remove-bg flag + +ERROR CODES: + 0 Success + 1 General error (invalid arguments, API error, file write error) + +MORE INFO: + Documentation: ${PAI_DIR}/skills/Art/README.md + Source: ${PAI_DIR}/skills/Art/Tools/Generate.ts +`); + process.exit(0); +} + +// ============================================================================ +// Argument Parsing +// ============================================================================ + +function parseArgs(argv: string[]): CLIArgs { + const args = argv.slice(2); + + // Check for help flag + if (args.includes("--help") || args.includes("-h") || args.length === 0) { + showHelp(); + } + + const parsed: Partial = { + model: DEFAULTS.model, + size: DEFAULTS.size, + output: DEFAULTS.output, + }; + + // Collect reference images into array + const referenceImages: string[] = []; + + // Parse arguments + for (let i = 0; i < args.length; i++) { + const flag = args[i]; + + if (!flag.startsWith("--")) { + throw new CLIError(`Invalid flag: ${flag}. Flags must start with --`); + } + + const key = flag.slice(2); + + // Handle boolean flags (no value) + if (key === "transparent") { + parsed.transparent = true; + continue; + } + if (key === "remove-bg") { + parsed.removeBg = true; + continue; + } + if (key === "thumbnail") { + parsed.thumbnail = true; + parsed.removeBg = true; // Thumbnail mode requires remove-bg + continue; + } + + // Handle flags with values + const value = args[i + 1]; + if (!value || value.startsWith("--")) { + throw new CLIError(`Missing value for flag: ${flag}`); + } + + switch (key) { + case "model": + if (value !== "flux" && value !== "nano-banana" && value !== "nano-banana-pro" && value !== "gpt-image-1") { + throw new CLIError(`Invalid model: ${value}. Must be: flux, nano-banana, nano-banana-pro, or gpt-image-1`); + } + parsed.model = value; + i++; // Skip next arg (value) + break; + case "prompt": + parsed.prompt = value; + i++; // Skip next arg (value) + break; + case "size": + parsed.size = value as Size; + i++; // Skip next arg (value) + break; + case "aspect-ratio": + parsed.aspectRatio = value as ReplicateSize; + i++; // Skip next arg (value) + break; + case "output": + parsed.output = value; + i++; // Skip next arg (value) + break; + case "reference-image": + // Collect multiple reference images into array + referenceImages.push(value); + i++; // Skip next arg (value) + break; + case "creative-variations": + const variations = parseInt(value, 10); + if (isNaN(variations) || variations < 1 || variations > 10) { + throw new CLIError(`Invalid creative-variations: ${value}. Must be 1-10`); + } + parsed.creativeVariations = variations; + i++; // Skip next arg (value) + break; + case "add-bg": + // Validate hex color format + if (!/^#[0-9A-Fa-f]{6}$/.test(value)) { + throw new CLIError(`Invalid hex color: ${value}. Must be in format #RRGGBB (e.g., #EAE9DF)`); + } + parsed.addBg = value; + i++; // Skip next arg (value) + break; + default: + throw new CLIError(`Unknown flag: ${flag}`); + } + } + + // Assign collected reference images if any + if (referenceImages.length > 0) { + parsed.referenceImages = referenceImages; + } + + // Validate required arguments + if (!parsed.prompt) { + throw new CLIError("Missing required argument: --prompt"); + } + + if (!parsed.model) { + throw new CLIError("Missing required argument: --model"); + } + + // Validate reference-image is only used with nano-banana-pro + if (parsed.referenceImages && parsed.referenceImages.length > 0 && parsed.model !== "nano-banana-pro") { + throw new CLIError("--reference-image is only supported with --model nano-banana-pro"); + } + + // Validate reference image count (API limits: 5 human, 6 object, 14 total max) + if (parsed.referenceImages && parsed.referenceImages.length > 14) { + throw new CLIError(`Too many reference images: ${parsed.referenceImages.length}. Maximum is 14 total (5 human, 6 object)`); + } + + // Validate size based on model + if (parsed.model === "gpt-image-1") { + if (!OPENAI_SIZES.includes(parsed.size as OpenAISize)) { + throw new CLIError(`Invalid size for gpt-image-1: ${parsed.size}. Must be: ${OPENAI_SIZES.join(", ")}`); + } + } else if (parsed.model === "nano-banana-pro") { + if (!GEMINI_SIZES.includes(parsed.size as GeminiSize)) { + throw new CLIError(`Invalid size for nano-banana-pro: ${parsed.size}. Must be: ${GEMINI_SIZES.join(", ")}`); + } + // Validate aspect ratio if provided + if (parsed.aspectRatio && !GEMINI_ASPECT_RATIOS.includes(parsed.aspectRatio)) { + throw new CLIError(`Invalid aspect-ratio for nano-banana-pro: ${parsed.aspectRatio}. Must be: ${GEMINI_ASPECT_RATIOS.join(", ")}`); + } + // Default to 16:9 if not specified + if (!parsed.aspectRatio) { + parsed.aspectRatio = "16:9"; + } + } else { + if (!REPLICATE_SIZES.includes(parsed.size as ReplicateSize)) { + throw new CLIError(`Invalid size for ${parsed.model}: ${parsed.size}. Must be: ${REPLICATE_SIZES.join(", ")}`); + } + } + + return parsed as CLIArgs; +} + +// ============================================================================ +// Prompt Enhancement +// ============================================================================ + +function enhancePromptForTransparency(prompt: string): string { + const transparencyPrefix = "CRITICAL: Transparent background (PNG with alpha channel) - NO background color, pure transparency. Object floating in transparent space. "; + return transparencyPrefix + prompt; +} + +// ============================================================================ +// Background Removal +// ============================================================================ + +import { exec } from "node:child_process"; +import { promisify } from "node:util"; + +const execAsync = promisify(exec); + +// ============================================================================ +// Background Operations +// ============================================================================ + +/** + * Add a solid background color to a transparent PNG image + * Uses ImageMagick to composite the transparent image onto a colored background + */ +async function addBackgroundColor(inputPath: string, outputPath: string, hexColor: string): Promise { + console.log(`🎨 Adding background color ${hexColor} to image...`); + + // Use ImageMagick to composite the transparent image onto a colored background + // -background sets the fill color, -flatten composites onto that background + const command = `magick "${inputPath}" -background "${hexColor}" -flatten "${outputPath}"`; + + try { + await execAsync(command); + console.log(`✅ Thumbnail saved to ${outputPath}`); + } catch (error) { + throw new CLIError(`Failed to add background color: ${error instanceof Error ? error.message : String(error)}`); + } +} + +async function removeBackground(imagePath: string): Promise { + const apiKey = process.env.REMOVEBG_API_KEY; // pragma: allowlist secret + if (!apiKey) { + throw new CLIError("Missing environment variable: REMOVEBG_API_KEY"); + } + + console.log("🔲 Removing background with remove.bg API..."); + + const imageBuffer = await readFile(imagePath); + const formData = new FormData(); + formData.append("image_file", new Blob([imageBuffer]), "image.png"); + formData.append("size", "auto"); + + const response = await fetch("https://api.remove.bg/v1.0/removebg", { + method: "POST", + headers: { + "X-Api-Key": apiKey, + }, + body: formData, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new CLIError(`remove.bg API error: ${response.status} - ${errorText}`); + } + + const resultBuffer = Buffer.from(await response.arrayBuffer()); + await writeFile(imagePath, resultBuffer); + console.log("✅ Background removed successfully"); +} + +// ============================================================================ +// Image Generation +// ============================================================================ + +async function generateWithFlux(prompt: string, size: ReplicateSize, output: string): Promise { + const token = process.env.REPLICATE_API_TOKEN; // pragma: allowlist secret + if (!token) { + throw new CLIError("Missing environment variable: REPLICATE_API_TOKEN"); + } + + const replicate = new Replicate({ auth: token }); + + console.log("🎨 Generating with Flux 1.1 Pro..."); + + const result = await replicate.run("black-forest-labs/flux-1.1-pro", { + input: { + prompt, + aspect_ratio: size, + output_format: "png", + output_quality: 95, + prompt_upsampling: false, + }, + }); + + await writeFile(output, result); + console.log(`✅ Image saved to ${output}`); +} + +async function generateWithNanoBanana(prompt: string, size: ReplicateSize, output: string): Promise { + const token = process.env.REPLICATE_API_TOKEN; // pragma: allowlist secret + if (!token) { + throw new CLIError("Missing environment variable: REPLICATE_API_TOKEN"); + } + + const replicate = new Replicate({ auth: token }); + + console.log("🍌 Generating with Nano Banana..."); + + const result = await replicate.run("google/nano-banana", { + input: { + prompt, + aspect_ratio: size, + output_format: "png", + }, + }); + + await writeFile(output, result); + console.log(`✅ Image saved to ${output}`); +} + +async function generateWithGPTImage(prompt: string, size: OpenAISize, output: string): Promise { + const apiKey = process.env.OPENAI_API_KEY; // pragma: allowlist secret + if (!apiKey) { + throw new CLIError("Missing environment variable: OPENAI_API_KEY"); + } + + const openai = new OpenAI({ apiKey }); + + console.log("🤖 Generating with GPT-image-1..."); + + const response = await openai.images.generate({ + model: "gpt-image-1", + prompt, + size, + n: 1, + }); + + const imageData = response.data[0].b64_json; + if (!imageData) { + throw new CLIError("No image data returned from OpenAI API"); + } + + const imageBuffer = Buffer.from(imageData, "base64"); + await writeFile(output, imageBuffer); + console.log(`✅ Image saved to ${output}`); +} + +async function generateWithNanoBananaPro( + prompt: string, + size: GeminiSize, + aspectRatio: ReplicateSize, + output: string, + referenceImages?: string[] +): Promise { + const apiKey = process.env.GOOGLE_API_KEY; // pragma: allowlist secret + if (!apiKey) { + throw new CLIError("Missing environment variable: GOOGLE_API_KEY"); + } + + const ai = new GoogleGenAI({ apiKey }); + + if (referenceImages && referenceImages.length > 0) { + console.log(`🍌✨ Generating with Nano Banana Pro (Gemini 3 Pro) at ${size} ${aspectRatio} with ${referenceImages.length} reference image(s)...`); + } else { + console.log(`🍌✨ Generating with Nano Banana Pro (Gemini 3 Pro) at ${size} ${aspectRatio}...`); + } + + // Prepare content parts + const parts: Array<{ text?: string; inlineData?: { mimeType: string; data: string } }> = []; + + // Add all reference images if provided + if (referenceImages && referenceImages.length > 0) { + for (const referenceImage of referenceImages) { + // Read image file + const imageBuffer = await readFile(referenceImage); + const imageBase64 = imageBuffer.toString("base64"); + + // Determine MIME type from extension + const ext = extname(referenceImage).toLowerCase(); + let mimeType: string; + switch (ext) { + case ".png": + mimeType = "image/png"; + break; + case ".jpg": + case ".jpeg": + mimeType = "image/jpeg"; + break; + case ".webp": + mimeType = "image/webp"; + break; + default: + throw new CLIError(`Unsupported image format: ${ext}. Supported: .png, .jpg, .jpeg, .webp`); + } + + parts.push({ + inlineData: { + mimeType, + data: imageBase64, + }, + }); + } + } + + // Add text prompt + parts.push({ text: prompt }); + + const response = await ai.models.generateContent({ + model: "gemini-3-pro-image-preview", + contents: [{ parts }], + config: { + responseModalities: ["TEXT", "IMAGE"], + imageConfig: { + aspectRatio: aspectRatio, + imageSize: size, + }, + }, + }); + + // Extract image data from response + let imageData: string | undefined; + + if (response.candidates && response.candidates.length > 0) { + const parts = response.candidates[0].content.parts; + for (const part of parts) { + // Check if this part contains inline image data + if (part.inlineData && part.inlineData.data) { + imageData = part.inlineData.data; + break; + } + } + } + + if (!imageData) { + throw new CLIError("No image data returned from Gemini API"); + } + + const imageBuffer = Buffer.from(imageData, "base64"); + await writeFile(output, imageBuffer); + console.log(`✅ Image saved to ${output}`); +} + +// ============================================================================ +// Main +// ============================================================================ + +async function main(): Promise { + try { + // Load API keys from ${PAI_DIR}/.env + await loadEnv(); + + const args = parseArgs(process.argv); + + // Enhance prompt for transparency if requested + const finalPrompt = args.transparent + ? enhancePromptForTransparency(args.prompt) + : args.prompt; + + if (args.transparent) { + console.log("🔲 Transparent background mode enabled"); + console.log("💡 Note: Not all models support transparency natively; may require post-processing\n"); + } + + // Handle creative variations mode + if (args.creativeVariations && args.creativeVariations > 1) { + console.log(`🎨 Creative Mode: Generating ${args.creativeVariations} variations...`); + console.log(`💡 Note: CLI mode uses same prompt for all variations (tests model variability)`); + console.log(` For true creative diversity, use the creative workflow with be-creative skill\n`); + + const basePath = args.output.replace(/\.png$/, ""); + const promises: Promise[] = []; + + for (let i = 1; i <= args.creativeVariations; i++) { + const varOutput = `${basePath}-v${i}.png`; + console.log(`Variation ${i}/${args.creativeVariations}: ${varOutput}`); + + if (args.model === "flux") { + promises.push(generateWithFlux(finalPrompt, args.size as ReplicateSize, varOutput)); + } else if (args.model === "nano-banana") { + promises.push(generateWithNanoBanana(finalPrompt, args.size as ReplicateSize, varOutput)); + } else if (args.model === "nano-banana-pro") { + promises.push( + generateWithNanoBananaPro( + finalPrompt, + args.size as GeminiSize, + args.aspectRatio!, + varOutput, + args.referenceImages + ) + ); + } else if (args.model === "gpt-image-1") { + promises.push(generateWithGPTImage(finalPrompt, args.size as OpenAISize, varOutput)); + } + } + + await Promise.all(promises); + console.log(`\n✅ Generated ${args.creativeVariations} variations`); + return; + } + + // Standard single image generation + if (args.model === "flux") { + await generateWithFlux(finalPrompt, args.size as ReplicateSize, args.output); + } else if (args.model === "nano-banana") { + await generateWithNanoBanana(finalPrompt, args.size as ReplicateSize, args.output); + } else if (args.model === "nano-banana-pro") { + await generateWithNanoBananaPro( + finalPrompt, + args.size as GeminiSize, + args.aspectRatio!, + args.output, + args.referenceImages + ); + } else if (args.model === "gpt-image-1") { + await generateWithGPTImage(finalPrompt, args.size as OpenAISize, args.output); + } + + // Remove background if requested + if (args.removeBg) { + await removeBackground(args.output); + } + + // Add background color if requested (standalone mode) + if (args.addBg && !args.thumbnail) { + // For standalone --add-bg, modify the image in place + const tempPath = args.output.replace(/\.png$/, "-temp.png"); + await addBackgroundColor(args.output, tempPath, args.addBg); + // Replace original with the one with background + const { rename } = await import("node:fs/promises"); + await rename(tempPath, args.output); + } + + // Generate thumbnail with background color if requested (blog header mode) + if (args.thumbnail) { + const thumbPath = args.output.replace(/\.png$/, "-thumb.png"); + const THUMBNAIL_BG_COLOR = "#EAE9DF"; // UL brand background color for social previews + await addBackgroundColor(args.output, thumbPath, THUMBNAIL_BG_COLOR); + console.log(`\n📸 Blog header mode: Created both versions`); + console.log(` Transparent: ${args.output}`); + console.log(` Thumbnail: ${thumbPath}`); + } + } catch (error) { + handleError(error); + } +} + +main(); diff --git a/.opencode/skills/Media/Art/Tools/GenerateMidjourneyImage.ts b/.opencode/skills/Media/Art/Tools/GenerateMidjourneyImage.ts new file mode 100755 index 00000000..6e4cfcb4 --- /dev/null +++ b/.opencode/skills/Media/Art/Tools/GenerateMidjourneyImage.ts @@ -0,0 +1,415 @@ +#!/usr/bin/env bun + +/** + * generate-midjourney-image - Midjourney Image Generation CLI + * + * Generate images using Midjourney via Discord bot integration. + * Follows llcli pattern for deterministic, composable CLI design. + * + * Usage: + * generate-midjourney-image --prompt "..." --aspect-ratio 16:9 --output /tmp/image.png + * + * @see ~/.opencode/skills/art/SKILL.md + */ + +import { readFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { DiscordBotClient } from "../Lib/discord-bot.js"; +import { MidjourneyClient, MidjourneyError } from "../Lib/midjourney-client.js"; + +// ============================================================================ +// Environment Loading +// ============================================================================ + +/** + * Load environment variables from ${PAI_DIR}/.env + * This ensures API keys are available regardless of how the CLI is invoked + */ +async function loadEnv(): Promise { + const paiDir = process.env.PAI_DIR || resolve(process.env.HOME!, ".opencode"); + const envPath = resolve(paiDir, ".env"); + try { + const envContent = await readFile(envPath, "utf-8"); + for (const line of envContent.split("\n")) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + const eqIndex = trimmed.indexOf("="); + if (eqIndex === -1) continue; + const key = trimmed.slice(0, eqIndex).trim(); + let value = trimmed.slice(eqIndex + 1).trim(); + // Remove surrounding quotes if present + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + // Only set if not already defined (allow overrides from shell) + if (!process.env[key]) { + process.env[key] = value; + } + } + } catch (_error) { + // Silently continue if .env doesn't exist - rely on shell env vars + } +} + +// ============================================================================ +// Types +// ============================================================================ + +interface CLIArgs { + prompt: string; + aspectRatio: string; + version: string; + stylize: number; + quality: number; + chaos?: number; + weird?: number; + tile: boolean; + output: string; + timeout: number; +} + +// ============================================================================ +// Configuration +// ============================================================================ + +const DEFAULTS = { + aspectRatio: "16:9", + version: process.env.MIDJOURNEY_DEFAULT_VERSION || "6.1", + stylize: parseInt(process.env.MIDJOURNEY_DEFAULT_STYLIZE || "100", 10), + quality: parseInt(process.env.MIDJOURNEY_DEFAULT_QUALITY || "1", 10), + tile: false, + output: "/tmp/midjourney-image.png", + timeout: 120, +}; + +// ============================================================================ +// Error Handling +// ============================================================================ + +class CLIError extends Error { + constructor( + message: string, + public exitCode: number = 1, + ) { + super(message); + this.name = "CLIError"; + } +} + +function handleError(error: unknown): never { + if (error instanceof MidjourneyError) { + console.error(`\n❌ Midjourney Error: ${error.message}`); + console.error(` Type: ${error.type}`); + if (error.originalPrompt) { + console.error(` Prompt: ${error.originalPrompt}`); + } + if (error.suggestion) { + console.error(` Suggestion: ${error.suggestion}`); + } + process.exit(1); + } + + if (error instanceof CLIError) { + console.error(`❌ Error: ${error.message}`); + process.exit(error.exitCode); + } + + if (error instanceof Error) { + console.error(`❌ Unexpected error: ${error.message}`); + console.error(error.stack); + process.exit(1); + } + + console.error(`❌ Unknown error:`, error); + process.exit(1); +} + +// ============================================================================ +// Help Text +// ============================================================================ + +function showHelp(): void { + console.log(` +generate-midjourney-image - Midjourney Image Generation CLI + +Generate images using Midjourney via Discord bot integration. + +USAGE: + generate-midjourney-image --prompt "" [OPTIONS] + +REQUIRED: + --prompt Image generation prompt (quote if contains spaces) + +OPTIONS: + --aspect-ratio Aspect ratio (default: 16:9) + Valid: 1:1, 16:9, 9:16, 2:3, 3:2, 4:5, 5:4, 7:4, 4:7, 21:9, 9:21, 3:4, 4:3 + --version Midjourney version (default: ${DEFAULTS.version}) + Valid: 6.1, 6, 5.2, 5.1, 5, niji, niji 6 + --stylize Stylization 0-1000 (default: ${DEFAULTS.stylize}) + --quality Quality: 0.25, 0.5, 1, 2 (default: ${DEFAULTS.quality}) + --chaos Chaos 0-100 (optional) + --weird Weird 0-3000 (optional) + --tile Enable tiling mode (default: false) + --output Output file path (default: ${DEFAULTS.output}) + --timeout Max wait time (default: ${DEFAULTS.timeout}) + +ENVIRONMENT VARIABLES: + DISCORD_BOT_TOKEN Discord bot token (required) + MIDJOURNEY_CHANNEL_ID Channel ID for Midjourney (required) + MIDJOURNEY_DEFAULT_VERSION Default Midjourney version + MIDJOURNEY_DEFAULT_QUALITY Default quality setting + MIDJOURNEY_DEFAULT_STYLIZE Default stylize setting + +EXAMPLES: + # Standard blog header + generate-midjourney-image \\ + --prompt "abstract flowing data streams, minimal shapes, Tokyo Night colors" \\ + --aspect-ratio 16:9 \\ + --output /tmp/header.png + + # High quality square image + generate-midjourney-image \\ + --prompt "geometric network visualization, abstract tech concept" \\ + --aspect-ratio 1:1 \\ + --quality 2 \\ + --output /tmp/square.png + + # Creative with high stylization + generate-midjourney-image \\ + --prompt "flowing organic shapes, data visualization" \\ + --stylize 500 \\ + --weird 1000 +`); +} + +// ============================================================================ +// Argument Parsing +// ============================================================================ + +function parseArgs(args: string[]): CLIArgs { + const result: Partial = { + aspectRatio: DEFAULTS.aspectRatio, + version: DEFAULTS.version, + stylize: DEFAULTS.stylize, + quality: DEFAULTS.quality, + tile: DEFAULTS.tile, + output: DEFAULTS.output, + timeout: DEFAULTS.timeout, + }; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + switch (arg) { + case "--help": + case "-h": + showHelp(); + process.exit(0); + break; + + case "--prompt": + if (i + 1 >= args.length) + throw new CLIError("Missing value for --prompt"); + result.prompt = args[++i]; + break; + + case "--aspect-ratio": + case "--ar": + if (i + 1 >= args.length) + throw new CLIError("Missing value for --aspect-ratio"); + result.aspectRatio = args[++i]; + break; + + case "--version": + case "-v": + if (i + 1 >= args.length) + throw new CLIError("Missing value for --version"); + result.version = args[++i]; + break; + + case "--stylize": + case "-s": { + if (i + 1 >= args.length) + throw new CLIError("Missing value for --stylize"); + const stylizeVal = parseInt(args[++i], 10); + if (Number.isNaN(stylizeVal)) + throw new CLIError("Invalid number for --stylize"); + result.stylize = stylizeVal; + break; + } + + case "--quality": + case "-q": { + if (i + 1 >= args.length) + throw new CLIError("Missing value for --quality"); + const qualityVal = parseFloat(args[++i]); + if (Number.isNaN(qualityVal)) + throw new CLIError("Invalid number for --quality"); + result.quality = qualityVal; + break; + } + + case "--chaos": { + if (i + 1 >= args.length) + throw new CLIError("Missing value for --chaos"); + const chaosVal = parseInt(args[++i], 10); + if (Number.isNaN(chaosVal)) + throw new CLIError("Invalid number for --chaos"); + result.chaos = chaosVal; + break; + } + + case "--weird": { + if (i + 1 >= args.length) + throw new CLIError("Missing value for --weird"); + const weirdVal = parseInt(args[++i], 10); + if (Number.isNaN(weirdVal)) + throw new CLIError("Invalid number for --weird"); + result.weird = weirdVal; + break; + } + + case "--tile": + result.tile = true; + break; + + case "--output": + case "-o": + if (i + 1 >= args.length) + throw new CLIError("Missing value for --output"); + result.output = args[++i]; + break; + + case "--timeout": { + if (i + 1 >= args.length) + throw new CLIError("Missing value for --timeout"); + const timeoutVal = parseInt(args[++i], 10); + if (Number.isNaN(timeoutVal)) + throw new CLIError("Invalid number for --timeout"); + result.timeout = timeoutVal; + break; + } + + default: + throw new CLIError(`Unknown argument: ${arg}`); + } + } + + // Validate required args + if (!result.prompt) { + throw new CLIError("Missing required argument: --prompt"); + } + + return result as CLIArgs; +} + +// ============================================================================ +// Main +// ============================================================================ + +async function main() { + try { + // Load API keys from ${PAI_DIR}/.env + await loadEnv(); + + // Parse arguments + const args = parseArgs(process.argv.slice(2)); + + // Validate environment variables + const botToken = process.env.DISCORD_BOT_TOKEN; // pragma: allowlist secret + const channelId = process.env.MIDJOURNEY_CHANNEL_ID; + + if (!botToken) { + throw new CLIError( + "Missing DISCORD_BOT_TOKEN environment variable. Add it to ${PAI_DIR}/.env", + ); + } + + if (!channelId) { + throw new CLIError( + "Missing MIDJOURNEY_CHANNEL_ID environment variable. Add it to ${PAI_DIR}/.env", + ); + } + + // Validate Midjourney options + MidjourneyClient.validateOptions({ + prompt: args.prompt, + aspectRatio: args.aspectRatio, + version: args.version, + stylize: args.stylize, + quality: args.quality, + chaos: args.chaos, + weird: args.weird, + tile: args.tile, + timeout: args.timeout, + }); + + console.log("🤖 Midjourney Image Generation"); + console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + console.log(`Prompt: ${args.prompt}`); + console.log(`Aspect Ratio: ${args.aspectRatio}`); + console.log(`Version: ${args.version}`); + console.log(`Stylize: ${args.stylize}`); + console.log(`Quality: ${args.quality}`); + if (args.chaos) console.log(`Chaos: ${args.chaos}`); + if (args.weird) console.log(`Weird: ${args.weird}`); + if (args.tile) console.log(`Tile: enabled`); + console.log(`Output: ${args.output}`); + console.log(`Timeout: ${args.timeout}s`); + console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"); + + // Initialize Discord bot + const discordBot = new DiscordBotClient({ + token: botToken, + channelId: channelId, + }); + + // Initialize Midjourney client + const midjourneyClient = new MidjourneyClient(discordBot); + + try { + // Connect to Discord + await discordBot.connect(); + + // Generate image + const result = await midjourneyClient.generateImage({ + prompt: args.prompt, + aspectRatio: args.aspectRatio, + version: args.version, + stylize: args.stylize, + quality: args.quality, + chaos: args.chaos, + weird: args.weird, + tile: args.tile, + timeout: args.timeout, + }); + + // Download image + await discordBot.downloadImage(result.imageUrl, args.output); + + console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + console.log("✅ Success!"); + console.log(` Image URL: ${result.imageUrl}`); + console.log(` Saved to: ${args.output}`); + console.log(` Message ID: ${result.messageId}`); + console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"); + + // Disconnect + await discordBot.disconnect(); + + process.exit(0); + } catch (error) { + // Ensure we disconnect even on error + await discordBot.disconnect(); + throw error; + } + } catch (error) { + handleError(error); + } +} + +// Run +main(); diff --git a/.opencode/skills/Media/Art/Tools/GeneratePrompt.ts b/.opencode/skills/Media/Art/Tools/GeneratePrompt.ts new file mode 100755 index 00000000..eff25340 --- /dev/null +++ b/.opencode/skills/Media/Art/Tools/GeneratePrompt.ts @@ -0,0 +1,451 @@ +#!/usr/bin/env bun + +/** + * UL Abstract Illustration Prompt Generator + * + * ⚠️ DEPRECATED - THIS TOOL USES OLD CHARACTER-BASED SYSTEM + * ⚠️ NEEDS COMPLETE REWRITE FOR ABSTRACT SHAPES/IMPRESSIONS ONLY + * ⚠️ DO NOT USE UNTIL UPDATED + * + * This tool needs to be rewritten to generate prompts using: + * - Abstract shapes and forms (NO characters) + * - Visual motifs (networks, flows, structures, horizons) + * - Composition approaches (centered, horizon, flow, opposition, layered) + * + * Usage (when updated): + * bun run generate-prompt.ts --input essay.md --type essay-illustration + * bun run generate-prompt.ts --input essay.md --type blog-header --format json + */ + +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +// ============================================================================ +// Types +// ============================================================================ + +type CompositionType = "observation" | "horizon" | "dialogue" | "workshop" | "aura"; +type CharacterFocus = "maya" | "kai" | "both"; +type TokyoNightColor = + | "Electric Blue" + | "Vivid Purple" + | "Bright Cyan" + | "Neon Green" + | "Warm Yellow" + | "Soft Magenta"; +type Human3Motif = "agents" | "networks" | "aura" | "substrates" | "horizons" | "ts_stacks"; +type BackgroundType = "sepia" | "dark_tokyo_night"; +type OutputFormat = "text" | "json"; + +interface PromptConfig { + essayTheme: string; + characterFocus: CharacterFocus; + compositionType: CompositionType; + emotionalToneDescription: string; + coreObjectDescription: string; + overallMood: string; + accentColors: TokyoNightColor[]; + human3Motifs: Human3Motif[]; + backgroundType: BackgroundType; + imageType: "essay-illustration" | "blog-header"; +} + +interface PromptOutput { + essay_theme: string; + character_focus: CharacterFocus; + composition_type: CompositionType; + emotional_tone_description: string; + core_object_description: string; + overall_mood: string; + accent_colors: string[]; + human3_motifs: string[]; + image_prompt: string; + suggested_filename?: string; +} + +// ============================================================================ +// Constants +// ============================================================================ + +// Note: This tool is deprecated and needs rewriting for abstract shapes/impressions + +const COLOR_HEX_MAP: Record = { + "Electric Blue": "#7aa2f7", + "Vivid Purple": "#bb9af7", + "Bright Cyan": "#7dcfff", + "Neon Green": "#9ece6a", + "Warm Yellow": "#e0af68", + "Soft Magenta": "#ff007c", +}; + +const CHARACTER_DESCRIPTIONS = { + maya: `Maya is a young, highly curious girl with a round head, simple short hair, and big round glasses (her signature feature). She has a stick-figure body with thin limbs and a slightly oversized head, with minimal facial features (dots for eyes, simple line for mouth when needed).`, + kai: `Kai is a young boy with a slightly oval head, a soft messy hair tuft on top (his signature feature), and NO glasses. He wears a simple t-shirt and shorts or pants. He has a stick-figure body with thin limbs and a slightly oversized head, with minimal facial features.`, + both: `Two recurring child characters: Maya and Kai. Maya is a young, highly curious girl with a round head, simple short hair, and big round glasses. Kai is a young boy with a slightly oval head, a soft messy hair tuft, and a simple t-shirt and shorts or pants. Both have stick-figure bodies with thin limbs and slightly oversized heads, with minimal facial features.`, +}; + +// ============================================================================ +// Helpers +// ============================================================================ + +function parseArgs(): { + input: string; + type: "essay-illustration" | "blog-header"; + format: OutputFormat; + composition?: CompositionType; + character?: CharacterFocus; + colors?: string; + motifs?: string; +} { + const args = process.argv.slice(2); + const parsed: any = { + type: "essay-illustration", + format: "text", + }; + + for (let i = 0; i < args.length; i += 2) { + const key = args[i].replace(/^--/, ""); + const value = args[i + 1]; + parsed[key] = value; + } + + return parsed; +} + +function readEssayContent(path: string): string { + try { + return readFileSync(path, "utf-8"); + } catch (error) { + console.error(`Error reading essay file: ${path}`); + throw error; + } +} + +function analyzeContent(essayContent: string): { + theme: string; + tone: string; + metaphors: string[]; + concepts: string[]; +} { + // Simple content analysis + // In a production version, this could use more sophisticated NLP or LLM analysis + + const lines = essayContent.split("\n"); + const firstParagraph = lines.slice(0, 5).join(" "); + + // Extract title (first # line) + const titleLine = lines.find((line) => line.startsWith("# ")); + const theme = titleLine?.replace(/^# /, "") || "essay topic"; + + // Simple tone detection based on keywords + const contentLower = essayContent.toLowerCase(); + let tone = "analytical curiosity"; + + if (contentLower.includes("future") || contentLower.includes("possibility")) { + tone = "cautious wonder about future possibilities"; + } else if (contentLower.includes("build") || contentLower.includes("create")) { + tone = "collaborative optimism"; + } else if (contentLower.includes("problem") || contentLower.includes("challenge")) { + tone = "analytical focus on challenges and opportunities"; + } + + return { + theme, + tone, + metaphors: [], // Could be extracted with more sophisticated analysis + concepts: [], // Could be extracted with more sophisticated analysis + }; +} + +function selectComposition( + essayContent: string, + override?: CompositionType +): CompositionType { + if (override) return override; + + const contentLower = essayContent.toLowerCase(); + + if (contentLower.includes("future") || contentLower.includes("horizon")) { + return "horizon"; + } else if (contentLower.includes("together") || contentLower.includes("collaborate")) { + return "dialogue"; + } else if (contentLower.includes("build") || contentLower.includes("create")) { + return "workshop"; + } else if (contentLower.includes("personal") || contentLower.includes("context")) { + return "aura"; + } + + return "observation"; // Default +} + +function selectCharacter( + compositionType: CompositionType, + override?: CharacterFocus +): CharacterFocus { + if (override) return override; + + if (compositionType === "dialogue") return "both"; + if (compositionType === "workshop") return "kai"; + if (compositionType === "observation") return "maya"; + + return "both"; // Default +} + +function selectColors( + essayContent: string, + override?: string +): TokyoNightColor[] { + if (override) { + return override.split(",").map((c) => c.trim() as TokyoNightColor); + } + + const contentLower = essayContent.toLowerCase(); + + if (contentLower.includes("security") || contentLower.includes("privacy")) { + return ["Vivid Purple"]; + } else if (contentLower.includes("tool") || contentLower.includes("productivity")) { + return ["Bright Cyan"]; + } else if (contentLower.includes("human") || contentLower.includes("growth")) { + return ["Neon Green"]; + } + + return ["Electric Blue"]; // Default for AI/tech +} + +function selectMotifs( + essayContent: string, + override?: string +): Human3Motif[] { + if (override) { + return override.split(",").map((m) => m.trim() as Human3Motif); + } + + const motifs: Human3Motif[] = []; + const contentLower = essayContent.toLowerCase(); + + if (contentLower.includes("agent") || contentLower.includes("ai")) { + motifs.push("agents"); + } + if (contentLower.includes("network") || contentLower.includes("connect")) { + motifs.push("networks"); + } + if (contentLower.includes("future") || contentLower.includes("horizon")) { + motifs.push("horizons"); + } + if (contentLower.includes("personal") || contentLower.includes("context")) { + motifs.push("aura"); + } + + return motifs.slice(0, 2); // Max 2 motifs +} + +function buildVisualMetaphor( + essayContent: string, + compositionType: CompositionType, + characterFocus: CharacterFocus +): string { + // This is a simplified version. In production, this would use more sophisticated + // content analysis to generate specific visual metaphors from essay content. + + const analysis = analyzeContent(essayContent); + + let metaphor = ""; + + switch (compositionType) { + case "observation": + metaphor = `${characterFocus === "maya" ? "Maya" : "Kai"} positioned in the left quarter of the frame, small and observing with ${analysis.tone}, looking at a large visual element on the right that represents the core concept of ${analysis.theme}`; + break; + case "horizon": + metaphor = `${characterFocus === "both" ? "Maya and Kai" : characterFocus === "maya" ? "Maya" : "Kai"} in the foreground, facing a wide distant horizon filled with tiny elements representing future possibilities related to ${analysis.theme}`; + break; + case "dialogue": + metaphor = `Maya and Kai positioned with space between them, interacting with a shared element or concept in the center, representing different perspectives on ${analysis.theme}`; + break; + case "workshop": + metaphor = `${characterFocus === "kai" ? "Kai" : "Both Maya and Kai"} actively building or creating, with elements spreading horizontally showing the process of making something related to ${analysis.theme}`; + break; + case "aura": + metaphor = `${characterFocus === "maya" ? "Maya" : characterFocus === "kai" ? "Kai" : "The character"} surrounded by a soft, translucent aura bubble containing tiny symbolic icons representing aspects of ${analysis.theme}`; + break; + } + + return metaphor; +} + +function buildMotifsDescription(motifs: Human3Motif[]): string { + if (motifs.length === 0) return ""; + + const descriptions: string[] = []; + + for (const motif of motifs) { + switch (motif) { + case "agents": + descriptions.push("tiny cute pill-shaped agent robots"); + break; + case "networks": + descriptions.push("thin network lines connecting small nodes"); + break; + case "aura": + descriptions.push( + "soft aura bubbles around people or robots with tiny symbolic icons like hearts, book-shapes, leaves, or stars (icons must be purely visual and contain no letters or numbers)" + ); + break; + case "substrates": + descriptions.push("horizontal platform layers suggesting infrastructure"); + break; + case "horizons": + descriptions.push("distant horizon line filled with tiny silhouettes"); + break; + case "ts_stacks": + descriptions.push("stacks of thin blank rectangular sheets"); + break; + } + } + + return `Optionally include Human 3.0 motifs that fit the essay: ${descriptions.join(", ")}.`; +} + +// ============================================================================ +// Prompt Generation +// ============================================================================ + +function generatePrompt(config: PromptConfig): string { + const { + essayTheme, + characterFocus, + compositionType, + emotionalToneDescription, + coreObjectDescription, + overallMood, + accentColors, + human3Motifs, + backgroundType, + imageType, + } = config; + + // Build color description + const colorDescriptions = accentColors + .map((color) => `${color} ${COLOR_HEX_MAP[color]}`) + .join(" and "); + + // Build character descriptions + const characterDesc = CHARACTER_DESCRIPTIONS[characterFocus]; + + // Build motifs description + const motifsDesc = buildMotifsDescription(human3Motifs); + + // Background description + const backgroundDesc = + backgroundType === "sepia" + ? "Soft sepia-toned paper background with lots of empty space." + : "Dark gradient background transitioning from #1a1b26 to #24283b."; + + // Base prompt + let prompt = `Minimal Tokyo Night–inspired illustration for ${imageType === "blog-header" ? "an Unsupervised Learning blog post" : "an essay"} about ${essayTheme}. + +${backgroundDesc} Thin, slightly imperfect deep navy linework and flat color fills only, no shading. Tokyo Night–inspired accent color${accentColors.length > 1 ? "s" : ""} ${colorDescriptions} used sparingly. + +${characterDesc} + +Show ${characterFocus === "both" ? "Maya and Kai" : characterFocus} in a ${compositionType} scene${imageType === "blog-header" ? " optimized for horizontal 16:9 composition" : ""}. ${emotionalToneDescription.charAt(0).toUpperCase() + emotionalToneDescription.slice(1)}, interacting with ${coreObjectDescription}. + +${motifsDesc} + +The overall mood should be ${overallMood}. No text, no letters, no numbers, and no labels anywhere in the image.`; + + // Add blog header specifications if applicable + if (imageType === "blog-header") { + prompt += ` + +=== BLOG HEADER SPECIFICATIONS === + +Output format: PNG, 1536x1024 (16:9 landscape for blog header) +Horizontal composition optimized for wide format +Primary focus in upper two-thirds of frame +Maximum quality settings (95% quality) +Editorial cover image quality like The Atlantic or New Yorker or New York Times`; + } + + return prompt; +} + +// ============================================================================ +// Main +// ============================================================================ + +function main() { + const args = parseArgs(); + + if (!args.input) { + console.error("Usage: bun run generate-prompt.ts --input [options]"); + console.error("\nOptions:"); + console.error(" --type essay-illustration | blog-header (default: essay-illustration)"); + console.error(" --format text | json (default: text)"); + console.error(" --composition observation | horizon | dialogue | workshop | aura"); + console.error(" --character maya | kai | both"); + console.error(' --colors "Electric Blue,Neon Green" (comma-separated)'); + console.error(' --motifs "agents,networks" (comma-separated)'); + process.exit(1); + } + + // Read essay content + const essayContent = readEssayContent(args.input); + + // Analyze content + const analysis = analyzeContent(essayContent); + + // Select visual elements + const compositionType = selectComposition(essayContent, args.composition); + const characterFocus = selectCharacter(compositionType, args.character); + const accentColors = selectColors(essayContent, args.colors); + const human3Motifs = selectMotifs(essayContent, args.motifs); + + // Build visual metaphor + const coreObjectDescription = buildVisualMetaphor( + essayContent, + compositionType, + characterFocus + ); + + // Build config + const config: PromptConfig = { + essayTheme: analysis.theme, + characterFocus, + compositionType, + emotionalToneDescription: `They are ${analysis.tone}`, + coreObjectDescription, + overallMood: analysis.tone.split(" ").slice(0, 2).join(" "), // Simplified mood + accentColors, + human3Motifs, + backgroundType: "sepia", + imageType: args.type, + }; + + // Generate prompt + const imagePrompt = generatePrompt(config); + + // Output + if (args.format === "json") { + const output: PromptOutput = { + essay_theme: analysis.theme, + character_focus: characterFocus, + composition_type: compositionType, + emotional_tone_description: config.emotionalToneDescription, + core_object_description: coreObjectDescription, + overall_mood: config.overallMood, + accent_colors: accentColors, + human3_motifs: human3Motifs, + image_prompt: imagePrompt, + suggested_filename: analysis.theme + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-|-$/g, "") + ".png", + }; + + console.log(JSON.stringify(output, null, 2)); + } else { + console.log(imagePrompt); + } +} + +main(); diff --git a/.opencode/skills/Media/Art/Workflows/AdHocYouTubeThumbnail.md b/.opencode/skills/Media/Art/Workflows/AdHocYouTubeThumbnail.md new file mode 100755 index 00000000..add01efb --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/AdHocYouTubeThumbnail.md @@ -0,0 +1,342 @@ +# Ad-hoc YouTube Thumbnail Workflow + +Generate complete YouTube thumbnails from content input with dramatic tech backgrounds and AI-generated headshots. + +## Explicit Criteria + +### 1. Dynamic Headshot - FACE ONLY +- **Fresh AI-generated** each time using Nano Banana Pro with reference images +- Reference image is used for likeness, NOT the actual headshot +- **Position is dynamic**: left, center, or right (based on content/preference) +- **🚨 FACE ONLY**: Forehead to chin, ear to ear - **NO shoulders, NO neck, NO body** +- Face fills 95% of the image area (ComposeThumbnail auto-crops) +- **Transparent background**: Must run RemoveBg after generation +- **🚨 MUST VARY** between thumbnails (see Variation Requirements below) + +### 2. Dramatic Tech Background +- **Style**: Futuristic, sci-fi aesthetic (hexagonal circuits, glowing edges, 3D depth) +- **Colors**: Dark with cyan/blue/purple neon accents (Tokyo Night palette) +- **No text, no people** in the background - pure abstract tech art +- **Examples**: Blade Runner, Tron, circuit board patterns with glow + +### 3. Text - BILLBOARD STYLE +- **Title**: Up to 6 words, CAPITALIZED, **CYAN** by default (vibrant, not white) +- **Subtitle**: Up to 12 words, CAPITALIZED, white text +- **🚨 TEXT FILLS THE SPACE** - takes up most of available area opposite headshot +- **BOLD STROKE OUTLINE** (4px title, 3px subtitle) - visible at 320px thumbnail size +- **Visually centered** in safe zone (NEVER overlaps headshot) +- **Grouped as a unit** - title and subtitle together as text block + +### 4. Colored Border +- **Tokyo Night purple** (#bb9af7) default +- **16px width** around entire thumbnail +- Creates professional framing + +--- + +## Output Specifications + +| Element | Value | +|---------|-------| +| Canvas | 1280x720 px | +| Border | **16px** #bb9af7 (Tokyo Night purple) | +| Headshot | **FACE ONLY** (~688px height), auto-cropped (no shoulders/body) | +| Title | **100pt** Helvetica-Bold, **4px** black stroke outline | +| Subtitle | **50pt** Helvetica-Bold, **3px** black stroke outline | +| Title color | **CYAN (#7dcfff) by default** - NEVER plain white | +| Subtitle color | White (#FFFFFF) for contrast | +| Text position | **FILLS** safe zone opposite headshot (NEVER overlaps) | +| Background | Dramatic futuristic tech art | +| Fresh headshot | **MANDATORY** - generate new WITH VARIATION each time | +| 320x180 test | **MANDATORY** - must be readable at YouTube grid size | + +### Text Color Presets (--title-color, --subtitle-color) + +| Name | Hex | Use | +|------|-----|-----| +| cyan | #7dcfff | **DEFAULT** - Tech, futuristic | +| white | #FFFFFF | High contrast (subtitle default) | +| purple | #bb9af7 | Matches border | +| blue | #7aa2f7 | Professional | +| magenta | #ff007c | Bold, attention | +| yellow | #e0af68 | Warning, highlight | +| green | #9ece6a | Success, growth | +| orange | #ff9e64 | Energy, urgency | +| red | #f7768e | Alert, danger | + +--- + +## Step 1: Content Analysis + +**Extract title and subtitle from input content.** + +### Input Types +- Script or article text +- URL (fetch and analyze) +- Topic description +- Video outline + +### Extraction Prompt + +```text +Analyze this content and extract: + +1. TITLE (max 6 words): The attention-grabbing hook +2. SUBTITLE (max 12 words): The value promise or context + +Guidelines: +- Use power words: "SECRET", "HIDDEN", "REAL", "TRUTH", "WHY", "HOW" +- Create curiosity gaps +- Be specific over generic +- Make a bold claim or promise + +Content: [INPUT] +``` + +--- + +## Step 2: Background Generation + +**Generate dramatic futuristic tech background.** + +### Background Prompt Template + +```text +Dramatic futuristic technology background. Dark hexagonal circuit board pattern +with glowing cyan/blue neon edge lighting. 3D depth perspective. Metallic dark +grey hexagons with embedded circuit patterns. Glowing cyan (#7dcfff) and purple +(#bb9af7) edge highlights. Deep shadows, high contrast. Sci-fi aesthetic like +Blade Runner or Tron. Abstract technology, no text, no people. Dark moody +atmosphere with electric blue glow accents. + +Topic context: [EXTRACTED TOPIC] +``` + +### Generate Command + +```bash +TIMESTAMP=$(date +%Y%m%d-%H%M%S) +bun run ~/.opencode/skills/Media/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[BACKGROUND PROMPT]" \ + --size 2K \ + --aspect-ratio 16:9 \ + --output ~/Downloads/yt-bg-${TIMESTAMP}.png +``` + +--- + +## Step 3: Headshot Generation + +**🚨 MANDATORY: Generate a FRESH, VARIED, FACE-ONLY headshot EVERY time.** + +```text +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚠️ FACE ONLY: Forehead to chin, ear to ear ⚠️ +⚠️ NO shoulders, NO neck, NO body visible ⚠️ +⚠️ If shoulders/body visible → REGENERATE IMMEDIATELY ⚠️ +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +### Headshot Variation Requirements + +**For each thumbnail, RANDOMLY select ONE from each category:** + +**Angle:** +- Straight-on, looking directly at camera +- Slight 3/4 turn, face angled 15 degrees to the right +- Head tilted slightly to the right + +**Expression:** +- Confident, authoritative +- Contemplative, thoughtful intensity +- Focused, direct engagement + +**Lighting:** +- Soft diffused key light +- Dramatic side lighting with shadow +- Rembrandt lighting pattern + +### Base Headshot Requirements (always include) +- **🚨 FACE ONLY** - forehead to chin, ear to ear +- **NO shoulders, NO neck, NO body** - face fills entire frame +- Pure black background (for easy removal) +- Face fills 95% of image area +- Load facial features from user customizations at `SKILLCUSTOMIZATIONS/Art/CharacterSpecs.md` + +### Example FACE-ONLY Prompts + +**Variation A (confident, straight-on):** +```text +Extreme close-up of the subject's FACE ONLY. Frame shows forehead to chin, ear to ear. +Absolutely NO shoulders, NO neck, NO body visible. Face fills entire image. +Confident, authoritative expression - NOT smiling. Looking directly at camera. +Pure black background. [FACIAL_FEATURES from CharacterSpecs.md]. +Soft diffused key lighting. Ultra-tight crop on face only. +``` + +**Variation B (contemplative, 3/4 angle):** +```text +Extreme close-up of the subject's FACE ONLY. Frame shows forehead to chin, ear to ear. +Absolutely NO shoulders, NO neck, NO body visible. Face fills entire image. +Contemplative, thoughtful expression with subtle intensity - NOT smiling. +Face turned 15 degrees to the right, slight 3/4 angle. +Pure black background. [FACIAL_FEATURES from CharacterSpecs.md]. +Dramatic side lighting creating depth. Ultra-tight crop on face only. +``` + +**Variation C (focused, head tilt):** +```text +Extreme close-up of the subject's FACE ONLY. Frame shows forehead to chin, ear to ear. +Absolutely NO shoulders, NO neck, NO body visible. Face fills entire image. +Focused, direct engagement expression - NOT smiling. Head tilted slightly. +Pure black background. [FACIAL_FEATURES from CharacterSpecs.md]. +Rembrandt lighting pattern. Looking at camera. Ultra-tight crop on face only. +``` + +### Generate Command + +**NOTE: The Headshot.ts tool is NOT IMPLEMENTED.** Use your preferred AI image generation tool (e.g., Nano Banana Pro, Midjourney, or similar) with the face-only prompts above. + +```bash +# Generate face-only headshot using your preferred AI image generation service +# Examples: Nano Banana Pro, Midjourney, DALL-E 3, etc. +# Key requirement: Follow the "FACE ONLY" prompts in the variations above +# Output should be saved as: ~/Downloads/yt-headshot-${TIMESTAMP}.png +``` + +**Note:** Image generation must produce FACE-ONLY output (forehead to chin, ear to ear, no shoulders/body). ComposeThumbnail will also auto-crop to remove any remaining body parts. + +### Remove Background + +```bash +bun ~/.opencode/skills/CORE/Tools/RemoveBg.ts ~/Downloads/yt-headshot-${TIMESTAMP}.png +``` + +--- + +## Step 4: Composition + +**Composite all elements using ComposeThumbnail tool.** + +### Compose Command + +```bash +bun ~/.opencode/skills/Media/Art/Tools/ComposeThumbnail.ts \ + --background ~/Downloads/yt-bg-${TIMESTAMP}.png \ + --headshot ~/Downloads/yt-headshot-${TIMESTAMP}.png \ + --title "[TITLE]" \ + --subtitle "[SUBTITLE]" \ + --title-color [cyan|purple|magenta|white|etc] \ + --position [left|center|right] \ + --output ~/Downloads/yt-thumbnail-${TIMESTAMP}.png +``` + +### Position Logic +- **left**: Headshot on left, text centered on right half +- **center**: Headshot centered, title at top, subtitle at bottom +- **right**: Headshot on right, text centered on left half + +### Text Positioning (automatic) +- For left/right: Text block (title + subtitle) centered vertically in opposite half +- For center: Title at top edge, subtitle at bottom edge +- Text uses black stroke outline for readability (no black boxes) + +--- + +## Step 5: Quality Validation + +**🚨 MANDATORY: ALL checks must pass before presenting to the user.** + +```text +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚠️ THE 320x180 TEST IS MANDATORY ⚠️ +⚠️ If text isn't readable at thumbnail size → FAIL ⚠️ +⚠️ If it looks like ass at any size → FAIL ⚠️ +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +### Quality Gates (ALL MUST PASS) + +| # | Check | Pass Criteria | +|---|-------|---------------| +| 1 | Dimensions | Exactly 1280x720 | +| 2 | **FACE-ONLY headshot** | NO shoulders, NO neck, NO body visible | +| 3 | Face fills frame | Face is 90%+ of headshot area | +| 4 | Text fills space | Title is large, bold, FILLS the text zone | +| 5 | Text color | **CYAN or vibrant** - NOT plain white | +| 6 | Stroke visible | 4px title / 3px subtitle - visible at 320px | +| 7 | No overlap | Text entirely in its safe zone | +| 8 | Variation | Visibly different from previous generation | +| 9 | **320x180 readability** | **Text readable at YouTube grid size** | +| 10 | Overall | Professional, billboard-quality appearance | + +### Validation Commands + +```bash +# 1. Verify dimensions +magick identify -format "%wx%h" ~/Downloads/yt-thumbnail-${TIMESTAMP}.png +# Expected: 1280x720 + +# 2. Open for visual inspection at full size +open ~/Downloads/yt-thumbnail-${TIMESTAMP}.png +# Confirm: Face only (no body), text fills space, cyan color visible + +# 3. 🚨 MANDATORY: Test at YouTube thumbnail size +magick ~/Downloads/yt-thumbnail-${TIMESTAMP}.png -resize 320x180 /tmp/yt-preview.png +open /tmp/yt-preview.png +# Confirm: Title READABLE, face RECOGNIZABLE, colors POP +# If you can't read the title at 320x180 → FAIL +``` + +### Failure Response + +**If ANY check fails:** +1. **DO NOT present to the user** +2. Identify the specific failure +3. Fix the issue: + - Body visible → Regenerate headshot with FACE-ONLY prompt + - Text too small → Already fixed (100pt/50pt) + - Text not visible at 320x180 → Check color/stroke + - Text overlapping → Check positioning +4. Re-run composition +5. Re-verify ALL checks including 320x180 test +6. **Only present when ALL checks pass** + +### Quality Standards +- **Thumbnail is a BILLBOARD** - text must dominate, face must dominate +- **DO NOT present output that looks broken, garbled, or unprofessional** +- **Iterate until it matches ALL criteria** +- **If it looks like ass, fix it before showing the user** +- **The 320x180 test is the ultimate validation** - that's what YouTube shows + +--- + +## Quick Reference + +### Tokyo Night Colors +```text +Purple (border): #bb9af7 +Cyan (accents): #7dcfff +Blue (accents): #7aa2f7 +Dark base: #1a1b26 +``` + +### Workflow Summary +```text +1. ANALYZE content → Extract TITLE + SUBTITLE +2. GENERATE background → Dramatic tech art (Nano Banana Pro) +3. GENERATE headshot → FACE-ONLY (1:1 aspect), WITH VARIATION + RemoveBg +4. COMPOSE → ComposeThumbnail.ts (auto-crops body, cyan text, 100pt title) +5. VALIDATE → ALL gates including 320x180 readability test +``` + +### Philosophy +**The thumbnail is a BILLBOARD, not a document.** +- FACE dominates one side +- TEXT FILLS the other side +- Must be readable at 320x180 +- Every generation is visibly different + +### Output Location +All outputs: `~/Downloads/yt-thumbnail-${TIMESTAMP}.png` diff --git a/.opencode/skills/Media/Art/Workflows/AnnotatedScreenshots.md b/.opencode/skills/Media/Art/Workflows/AnnotatedScreenshots.md new file mode 100755 index 00000000..f3490091 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/AnnotatedScreenshots.md @@ -0,0 +1,353 @@ +# Annotated Screenshots Workflow + +**Real screenshots with hand-drawn editorial annotations, arrows, and highlights using UL aesthetic.** + +Creates **ANNOTATED SCREENSHOTS** — actual UI screenshots or code snippets with hand-drawn purple/teal commentary overlays. + +--- + +## Purpose + +Annotated screenshots combine real artifacts (UI, code, data) with hand-drawn editorial commentary. This **hybrid real + illustrated** approach adds voice and insights directly onto actual examples. + +**Use this workflow for:** +- Product reviews with annotated screenshots +- Technical tutorials pointing out UI elements +- UX critiques with visual commentary +- Code reviews with illustrated notes +- "THIS IS THE PROBLEM" arrows and callouts + +--- + +## Visual Aesthetic: Real + Hand-Drawn Overlay + +**Think:** Screenshot with hand-drawn arrows, circles, and annotations in editorial voice + +### Core Characteristics +1. **Real foundation** — Actual screenshot or code snippet (not illustrated) +2. **Hand-drawn overlay** — Arrows, circles, highlights, callouts in editorial style +3. **Typography mix** — Real UI text + hand-lettered annotations +4. **Color accents** — Purple/teal for annotations against real screenshot +5. **Editorial voice** — Annotations sound like smart commentary +6. **Editorial style** — Maintains UL imperfect, gestural linework for overlays +7. **Functional clarity** — Annotations enhance understanding, not just decoration + +--- + +## Color System for Annotated Screenshots + +### Real Screenshot Layer +``` +Original colors preserved (screenshot remains unmodified) +OR +Slightly desaturated/faded to make annotations pop +``` + +### Annotation Overlay +``` +Deep Purple #4A148C — Primary annotations (important callouts) +Deep Teal #00796B — Secondary annotations (supporting notes) +Black #000000 — Arrows, circles, underlines +Charcoal #2D2D2D — Annotation text (when not purple/teal) +``` + +### Strategy +- Screenshot slightly faded/grayed (80% opacity) to let annotations stand out +- Purple for critical annotations ("THIS IS THE ISSUE") +- Teal for helpful context ("here's how it works") +- Black for structural annotations (arrows, circles, boxes) + +--- + +## 🚨 MANDATORY WORKFLOW STEPS + +### Step 1: Prepare Screenshot + +**Get the base image:** + +1. **Capture screenshot:** + - Take actual screenshot of UI, code, website, etc. + - Crop to relevant area + - Ensure text is readable + +2. **Process screenshot:** + - Optionally desaturate slightly (makes overlays pop) + - Resize if needed for clarity + - Save as base image + +**Output:** +``` +SCREENSHOT SOURCE: [Path to screenshot file] +SUBJECT: [What the screenshot shows] +KEY AREAS TO ANNOTATE: +- Area 1: [Description] — [What to call out] +- Area 2: [Description] — [What to call out] +... +``` + +--- + +### Step 2: Plan Annotations + +**Identify what to mark:** + +1. **What are you calling attention to?** + - Problem areas + - Good examples + - Workflow steps + - Hidden features + +2. **What type of annotation for each?** + - Arrow pointing to element + - Circle/box highlighting region + - Underline or bracket + - Callout with note + +3. **What's the commentary?** + - "*this is the problem*" + - "*should be here instead*" + - "*genius design*" + - "*completely missed the point*" + +**Output:** +``` +ANNOTATIONS TO ADD: + +1. [Area/Element]: + - Type: [Arrow / Circle / Box / Underline] + - Color: [Purple / Teal / Black] + - Text: "[Your commentary]" + - Position: [Where on screenshot] + +2. [Area/Element]: + - Type: [Annotation type] + - Color: [Color choice] + - Text: "[Commentary]" + - Position: [Location] + +... + +EMPHASIS: +- Purple (critical): [Which annotations] +- Teal (helpful): [Which annotations] +``` + +--- + +### Step 3: Construct Prompt + +**Note:** This workflow is different - you're adding overlays to an existing image. You may need to: +- Upload screenshot as reference image +- Generate hand-drawn annotation layer separately +- Composite in image editor + +OR + +- Use prompt to describe "screenshot with annotations" if model can render both + +### Prompt Template (If Generating Combined Image) + +``` +Real UI screenshot with hand-drawn editorial annotations overlay. + +STYLE: Actual screenshot with imperfect hand-drawn arrows, circles, and notes on top + +SCREENSHOT BASE: +- [Describe the screenshot content, e.g.: "ChatGPT interface showing conversation"] +- Slightly desaturated/faded (80% opacity) to let annotations stand out +- All original text and UI elements clearly visible + +ANNOTATION OVERLAY STYLE: +- Hand-drawn arrows, circles, underlines in editorial style +- Variable stroke weight, wobbly imperfect lines +- Gestural quality (not polished vectors) +- Hand-lettered annotation text + +TYPOGRAPHY FOR ANNOTATIONS (Advocate Italic): +- Font: Advocate condensed italic (hand-lettered style) +- Size: Readable against screenshot +- Color: Purple #4A148C or Teal #00796B for emphasis +- Style: Editorial voice — casual, direct, insightful + +ANNOTATIONS TO ADD: +[List each annotation, e.g.:] + +1. PURPLE ARROW pointing to [UI element]: + - Hand-drawn wobbly arrow in Purple (#4A148C) + - Text annotation: "*THIS IS THE PROBLEM*" + - Thick stroke, clear pointing direction + - Position: [Location on screenshot] + +2. TEAL CIRCLE around [UI area]: + - Hand-drawn imperfect circle in Teal (#00796B) + - Text annotation: "*notice this pattern*" + - Slightly wobbly outline + - Position: [Area to highlight] + +3. BLACK UNDERLINE beneath [text]: + - Hand-drawn wavy underline in Black (#000000) + - Emphasizes existing screenshot text + - No additional annotation needed + +4. PURPLE CALLOUT box: + - Hand-drawn box with arrow pointing to [element] + - Text: "*should have been here instead*" + - Purple (#4A148C) box outline and text + - Position: [Near relevant UI element] + +[etc. for all annotations] + +COLOR USAGE: +- Screenshot: Original colors (or slightly desaturated) +- Purple (#4A148C): Critical annotations, "this is wrong" callouts +- Teal (#00796B): Helpful context, "here's why" explanations +- Black (#000000): Structural annotations (arrows, circles, underlines) +- Charcoal (#2D2D2D): General annotation text when not emphasized + +CRITICAL REQUIREMENTS: +- Screenshot remains readable and recognizable +- Hand-drawn annotations clearly overlay (not integrated into UI) +- Annotations enhance understanding, point out insights +- Variable stroke weight, imperfect human-drawn quality +- Editorial voice in text ("*this*", not formal descriptions) +- Strategic color (not every annotation needs color) +- No gradients on annotations + +Optional: Sign small in bottom corner in charcoal (#2D2D2D). +``` + +### Alternative: Composite Workflow + +If generating combined image is difficult: + +1. **Generate annotation layer separately:** + - Transparent background + - Only arrows, circles, text annotations + - Match screenshot dimensions + +2. **Composite in image editor:** + - Layer screenshot (bottom) + - Layer annotations (top) + - Adjust annotation opacity if needed + +--- + +### Step 4: Determine Aspect Ratio + +**Match screenshot aspect ratio:** +- Screenshot is 16:9 → Use 16:9 +- Screenshot is vertical phone UI → Use 9:16 +- Screenshot is square → Use 1:1 +- Screenshot is wide desktop → Use 21:9 + +**Preserve original screenshot proportions** + +--- + +### Step 5: Execute Generation + +**Option A: Generate combined (if model supports):** +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --reference-image /path/to/screenshot.png \ + --prompt "[ANNOTATION PROMPT]" \ + --size 2K \ + --aspect-ratio [match screenshot] \ + --output /path/to/annotated.png +``` + +**Option B: Generate annotation layer, then composite manually** + +**Immediately Open:** +```bash +open /path/to/annotated.png +``` + +--- + +### Step 6: Validation (MANDATORY) + +#### Must Have +- [ ] **Screenshot readable** — Original content clearly visible +- [ ] **Annotations clear** — Arrows/circles/text obviously hand-drawn overlays +- [ ] **Editorial voice** — Annotations sound like smart commentary +- [ ] **Strategic pointing** — Annotations highlight key insights, not random decoration +- [ ] **Color emphasis** — Purple on critical, teal on helpful +- [ ] **Hand-drawn quality** — Wobbly arrows, imperfect circles, gestural +- [ ] **Functional value** — Annotations actually enhance understanding + +#### Must NOT Have +- [ ] Unreadable screenshot +- [ ] Polished digital annotation look +- [ ] Generic corporate callouts ("Feature A") +- [ ] Too many annotations (cluttered) +- [ ] Formal voice (should be casual, direct) +- [ ] Perfect straight arrows or circles + +#### If Validation Fails + +| Problem | Fix | +|---------|-----| +| Screenshot too dark | Lighten/desaturate screenshot layer, increase annotation contrast | +| Annotations too polished | Emphasize "hand-drawn wobbly arrows, imperfect circles, gestural sketch" | +| Voice too formal | Rewrite annotations in casual voice: "*this right here*" | +| Can't tell what's being pointed out | Larger/bolder arrows, clearer pointing direction | +| Too cluttered | Reduce annotations to 3-5 key insights only | +| Looks corporate | Reference "editorial annotation style, smart person's markup, hand-drawn notes" | + +--- + +## Example Use Cases + +### Example 1: ChatGPT UI Critique +- **Screenshot:** ChatGPT conversation interface +- **Annotations:** + - Purple arrow: "*this prompt engineering is bad*" + - Teal circle: "*notice how it avoided the question*" + - Black underline: Emphasizing problematic output +- **Aspect:** 16:9 + +### Example 2: Code Review +- **Screenshot:** Python code snippet +- **Annotations:** + - Purple box: "*bottleneck right here*" + - Teal arrow: "*clever use of list comprehension*" + - Black circle: Highlighting security issue +- **Aspect:** 1:1 (code block) + +### Example 3: UX Flow Breakdown +- **Screenshot:** Mobile app workflow (multiple screens) +- **Annotations:** + - Numbered purple arrows showing flow + - Teal notes on each step: "*where users drop off*" + - Black boxes highlighting UI elements +- **Aspect:** 9:16 (vertical phone layout) + +--- + +## Quick Reference + +**Annotated Screenshot Formula:** +``` +1. Prepare screenshot (capture, crop, optionally desaturate) +2. Plan annotations (what to mark, commentary, colors) +3. Construct prompt OR composite manually +4. Match screenshot aspect ratio +5. Generate/composite annotations +6. Validate for clarity and voice +``` + +**Color Strategy:** +- Screenshot: Original colors (or slightly faded) +- Purple: Critical annotations +- Teal: Helpful context +- Black: Structural marks + +**Voice:** +- Casual, direct, editorial commentary +- "*this is the issue*" not "Area A shows problem" + +--- + +**The workflow: Prepare → Plan → Annotate → Generate → Validate → Complete** diff --git a/.opencode/skills/Media/Art/Workflows/Aphorisms.md b/.opencode/skills/Media/Art/Workflows/Aphorisms.md new file mode 100755 index 00000000..3fb42be0 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/Aphorisms.md @@ -0,0 +1,335 @@ +# Visual Aphorisms & Quote Cards Workflow + +**Aphorisms as shareable visual quote cards using editorial aesthetic.** + +Creates **VISUAL APHORISM CARDS** — insights and quotes as shareable square images with massive typography and minimal hand-drawn accents. + +--- + +## Purpose + +Visual aphorism cards turn memorable one-liners into shareable social media content. These are **typographic statements with personality** — the quote IS the visual, with subtle editorial accents. + +**Use this workflow for:** +- Social media quote cards (LinkedIn, Instagram, X) +- Newsletter pull quotes +- Aphorisms as standalone images +- Thought leadership visuals +- "HUMANS NEED ENTROPY" style statements +- Memorable insights amplified visually + +--- + +## Visual Aesthetic: Typography as Hero + +**Think:** Giant bold typography with subtle hand-drawn accent, not full illustration + +### Core Characteristics +1. **Typography dominant** — The quote IS the visual (80-90% of image) +2. **Massive Advocate** — All-caps bold lettering fills the frame +3. **Minimal illustration** — Small subtle accent element (not full scene) +4. **Square format** — 1:1 for social media +5. **High contrast** — Black text on light, or white text on dark +6. **Hand-lettered quality** — Imperfect typography, not digital font +7. **Editorial voice** — Punchy, memorable, thought-provoking + +--- + +## Color System for Aphorisms + +### Typography +``` +Black #000000 — Primary text (most common) +OR +Deep Purple #4A148C — Full text in brand color (alternative) +OR +White #FFFFFF — Text on dark background (high contrast) +``` + +### Accent Element +``` +Deep Purple #4A148C — Small accent illustration +Deep Teal #00796B — Alternative accent color +``` + +### Background +``` +Light Cream #F5E6D3 — Warm neutral (most common) +OR +White #FFFFFF — Clean modern +OR +Black #000000 — Dark dramatic (white text) +OR +Deep Purple #4A148C — Bold brand (white text) +``` + +### Color Strategy +- **High contrast typography** — Text must be immediately readable +- **Minimal color** — Quote + small accent, not busy +- **Brand presence** — Purple somewhere (text OR accent OR background) + +--- + +## 🚨 MANDATORY WORKFLOW STEPS + +### Step 1: Select Aphorism + +**Choose the quote:** + +1. **What's the aphorism?** + - The exact quote + - Must be punchy and memorable + - Ideal length: 2-8 words (fits large on card) + +2. **What's the insight?** + - What makes this quote powerful + - Why is it shareable + +3. **What tiny visual accent supports it?** + - NOT a full illustration + - Small simple element reinforcing the idea + - Examples: scatter dots for entropy, em dash for typography quote + +**Output:** +``` +APHORISM: "[Quote in all-caps]" +LENGTH: [X words] + +INSIGHT: [Why this quote resonates] + +ACCENT ELEMENT: [Tiny illustration, e.g.:] +- "scatter of dots" for entropy +- "em dash symbol" for typography topic +- "lightning bolt" for insight moment +- "simple line drawing" reinforcing concept +``` + +--- + +### Step 2: Design Typography Layout + +**Plan the visual:** + +1. **Typography arrangement:** + - All one line (short quote) + - Multiple lines (longer quote) + - Stacked words (vertical emphasis) + - Asymmetric layout (dynamic placement) + +2. **Size and weight:** + - How large can text go while remaining readable + - Line breaks for rhythm and emphasis + - Word hierarchy (which words largest) + +3. **Accent placement:** + - Where does small illustration go + - How does it complement (not compete with) text + - Size: 5-10% of image area + +**Output:** +``` +TYPOGRAPHY LAYOUT: +[Describe arrangement, e.g.:] +- "HUMANS NEED" on first line +- "ENTROPY" on second line (larger) +- All-caps Advocate style, massive bold letters +- Fills 80% of image area +- Hand-lettered imperfection + +ACCENT ELEMENT: +- Small scatter of dots (entropy visual) +- Purple (#4A148C) colored +- Position: Bottom right corner +- Size: ~8% of image +- Does NOT compete with text + +COLOR SCHEME: +- Text: [Black / Purple / White] +- Background: [Cream / White / Black / Purple] +- Accent: [Purple / Teal] +- Signature: Charcoal (optional) +``` + +--- + +### Step 3: Construct Prompt + +### Prompt Template + +``` +Typographic quote card in editorial hand-lettered style. + +STYLE REFERENCE: Bold typography poster, quote card, hand-lettered aphorism + +BACKGROUND: [Light Cream #F5E6D3 / White #FFFFFF / Black #000000 / Purple #4A148C] — flat, solid + +AESTHETIC: +- Typography as the primary visual (dominates composition) +- Hand-lettered Advocate style (imperfect, gestural, bold) +- Massive scale lettering (fills 80-90% of frame) +- Minimal accent illustration (subtle, not competing) +- High contrast for readability +- Square 1:1 format + +QUOTE CARD STRUCTURE: + +TYPOGRAPHY (Advocate Block Display - MASSIVE): +"[APHORISM TEXT IN ALL-CAPS]" + +- Font: Advocate style extra bold, hand-lettered, all-caps +- Size: MASSIVE — fills most of image area +- Layout: [Single line / Multi-line / Stacked words] +- Line breaks: [Where breaks occur for rhythm] + Line 1: "[FIRST PART]" + Line 2: "[SECOND PART]" (optionally larger) +- Color: [Black #000000 / Purple #4A148C / White #FFFFFF] +- Style: Hand-lettered with imperfections (not perfect digital font) +- Variable letter sizing for emphasis +- Letters should have character and personality + +ACCENT ILLUSTRATION (Minimal): +- [Small simple element, e.g., "scattered dots", "small em dash", "lightning bolt"] +- Hand-drawn, simple, editorial style +- Position: [Bottom right / Top left / etc. — does NOT interfere with text] +- Size: 5-10% of image area +- Color: [Purple #4A148C / Teal #00796B] +- Style: Imperfect sketch quality, matches text aesthetic +- Purpose: Subtle visual reinforcement, NOT competing focal point + +COLOR USAGE: +- Background: [Color choice] — flat solid fill +- Typography: [Color choice] — high contrast with background +- Accent element: [Purple or Teal] +- Signature: Charcoal (#2D2D2D) small in corner (optional) + +CRITICAL REQUIREMENTS: +- Typography is HERO (quote fills 80-90% of frame) +- Hand-lettered quality (wobbly lines, imperfect character shapes) +- NOT a digital font — should feel hand-drawn +- Accent illustration MINIMAL (does not distract from quote) +- High contrast readability (text must pop from background) +- Square 1:1 aspect ratio +- No gradients, flat colors only +- Shareable social media quality + +Optional: Sign small in bottom right corner in charcoal (#2D2D2D). +``` + +--- + +### Step 4: Determine Aspect Ratio + +**Always 1:1 (square)** — Optimized for social media (Instagram, LinkedIn, X) + +--- + +### Step 5: Execute Generation + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR PROMPT]" \ + --size 2K \ + --aspect-ratio 1:1 \ + --output /path/to/aphorism.png +``` + +**Model Recommendation:** nano-banana-pro (best text rendering) or flux (stylistic variety) + +**Immediately Open:** +```bash +open /path/to/aphorism.png +``` + +--- + +### Step 6: Validation (MANDATORY) + +#### Must Have +- [ ] **Quote readable** — Instantly legible even at thumbnail size +- [ ] **Typography dominant** — Quote is 80-90% of visual +- [ ] **Hand-lettered** — Imperfect, gestural quality (not digital font) +- [ ] **High contrast** — Text pops from background +- [ ] **Minimal accent** — Small element supports, doesn't compete +- [ ] **Shareable** — Works as social media post +- [ ] **Brand presence** — Purple visible somewhere (text/accent/background) + +#### Must NOT Have +- [ ] Perfect digital font (should be hand-lettered) +- [ ] Busy background or complex illustration +- [ ] Low contrast (can't read text easily) +- [ ] Accent element competing with quote +- [ ] Tiny text (must be readable at thumbnail) +- [ ] Gradients or shadows + +#### If Validation Fails + +| Problem | Fix | +|---------|-----| +| Text too small | "MASSIVE hand-lettered typography filling 85% of frame" | +| Looks like digital font | "Hand-drawn Advocate letters, imperfect wobbly strokes, gestural quality" | +| Accent too busy | "MINIMAL accent: small simple [element], 8% of image, subtle" | +| Can't read thumbnail | Increase text size, stronger contrast, simplify layout | +| No brand presence | "Purple (#4A148C) on [accent element / text / background]" | +| Too complex | "Typography IS the visual — quote dominant, minimal everything else" | + +--- + +## Example Use Cases + +### Example 1: "HUMANS NEED ENTROPY" +- **Typography:** Two lines, "ENTROPY" larger +- **Accent:** Small scatter of purple dots (bottom right) +- **Background:** Light cream +- **Text:** Black +- **Use:** LinkedIn post, newsletter pull quote + +### Example 2: "THE EM DASH IS PERFECT" +- **Typography:** Stacked words, "EM DASH" emphasized +- **Accent:** Small purple em dash symbol +- **Background:** White +- **Text:** Black +- **Use:** X post about typography + +### Example 3: "AI COPIES HUMAN CREATIVITY" +- **Typography:** Three lines, "AI" and "CREATIVITY" larger +- **Accent:** Tiny robot hand + human hand (purple, minimal) +- **Background:** Black +- **Text:** White (high contrast) +- **Use:** Instagram thought leadership post + +### Example 4: "SECURITY IS A FEELING" +- **Typography:** Two lines +- **Accent:** Small purple shield with heart +- **Background:** Purple #4A148C +- **Text:** White +- **Use:** Bold brand statement + +--- + +## Quick Reference + +**Aphorism Card Formula:** +``` +1. Select aphorism (punchy quote, 2-8 words ideal) +2. Design typography layout (arrangement, emphasis, size) +3. Choose minimal accent element (5-10% of image) +4. Construct prompt with massive typography +5. Always use 1:1 square aspect ratio +6. Generate with nano-banana-pro +7. Validate for readability and shareability +``` + +**Color Strategy:** +- High contrast: Black text on cream, or white text on black/purple +- Brand presence: Purple somewhere in composition +- Minimal palette: Quote + accent + background = 3 colors max + +**Key Principle:** +- **Typography IS the visual** — Everything else is subtle support +- Shareable, memorable, instantly readable +- Your voice amplified visually + +--- + +**The workflow: Select → Design → Construct → Generate → Validate → Complete** diff --git a/.opencode/skills/Media/Art/Workflows/Comics.md b/.opencode/skills/Media/Art/Workflows/Comics.md new file mode 100755 index 00000000..3bcaa421 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/Comics.md @@ -0,0 +1,424 @@ +# Hand-Drawn Comics Workflow + +**Comic strips in UL editorial illustration style, NOT cartoonish.** + +Creates **EDITORIAL COMICS** — 3-4 panel storytelling with sophisticated hand-drawn aesthetic, maintaining UL flat color and black linework. + +--- + +## Purpose + +Editorial comics use sequential panels to explain concepts, tell stories, or illustrate scenarios. These are **sophisticated comics** — not cutesy or cartoonish, but thoughtful illustrated narratives with editorial style. + +**Use this workflow for:** +- Explaining complex concepts through narrative +- "AGI arrives" scenario panels +- Before/during/after sequences +- Illustrated thought experiments +- Multi-step processes shown visually +- Storytelling with editorial sophistication + +--- + +## Visual Aesthetic: Sophisticated Sequential Art + +**Think:** New Yorker cartoon style, not Sunday funnies + +### Core Characteristics +1. **Multi-panel** — 3-4 panels telling sequential story +2. **Editorial style** — Maintains UL flat color, black linework aesthetic +3. **Simplified figures** — Characters stylized, not realistic or cutesy +4. **Hand-drawn** — Imperfect linework, gestural quality +5. **Narrative flow** — Panels build on each other to make a point +6. **Minimal dialogue** — Text supports, doesn't dominate +7. **Sophisticated humor/insight** — Smart, not silly + +--- + +## Color System for Comics + +### Structure +``` +Black #000000 — All linework, panel borders, character outlines +``` + +### Character/Element Accents +``` +Deep Purple #4A148C — Key character or important element +Deep Teal #00796B — Secondary character or contrast element +Charcoal #2D2D2D — Dialogue text, captions +``` + +### Background +``` +Light Cream #F5E6D3 — Panel backgrounds +OR +White #FFFFFF — Clean backgrounds +OR +Varied per panel — Different cream/light tones for panel differentiation +``` + +### Color Strategy +- Characters primarily black linework +- Purple accent on protagonist or key element +- Teal on secondary character if needed +- Backgrounds light and simple (no busy scenes) +- Dialogue in charcoal for readability + +--- + +## 🚨 MANDATORY WORKFLOW STEPS + +### Step 1: Define Comic Narrative + +**Plan the story:** + +1. **What's the concept/scenario?** + - What are you explaining or illustrating + - The arc or transformation to show + +2. **How many panels?** + - 3 panels (setup → action → result) + - 4 panels (setup → complication → action → result) + +3. **What happens in each panel?** + - Panel 1: [Scene/action] + - Panel 2: [Scene/action] + - Panel 3: [Scene/action] + - Panel 4: [Scene/action] (if using 4) + +4. **What's the punchline/insight?** + - Final panel delivers the point + - What makes this memorable + +**Output:** +``` +COMIC CONCEPT: [What you're illustrating] +PANELS: [3 or 4] + +NARRATIVE ARC: +Panel 1: [Setup - what's the initial state] +Panel 2: [Action/Complication - what changes] +Panel 3: [Escalation or Result] +Panel 4: [Punchline/Insight - the point] (if using 4) + +DIALOGUE (Minimal): +Panel 1: "[Optional brief text]" +Panel 2: "[Optional brief text]" +Panel 3: "[Optional brief text]" +Panel 4: "[Punchline or insight]" + +KEY CHARACTERS: +- [Character/Element 1]: [Description, purple accent] +- [Character/Element 2]: [Description, teal accent if needed] +``` + +--- + +### Step 2: Design Panel Layout + +**Plan the comic structure:** + +1. **Panel arrangement:** + - Horizontal strip (3-4 panels left to right) + - Vertical strip (3-4 panels top to bottom) + - Grid (2x2 for 4 panels) + +2. **Panel size:** + - Equal sized panels (classic) + - Varied sizes for emphasis + - Final panel larger (punchline emphasis) + +3. **Panel content:** + - What's illustrated in each panel + - Character positions and actions + - Background elements (minimal) + +**Output:** +``` +PANEL LAYOUT: [Horizontal strip / Vertical strip / Grid] + +PANEL STRUCTURE: +- Panel 1: [Same size / Smaller / Larger] + * Content: [What's shown] + * Characters: [Positions] + * Background: [Minimal elements] + +- Panel 2: [Size] + * Content: [What's shown] + * Characters: [Positions] + * Background: [Elements] + +- Panel 3: [Size] + * Content: [What's shown] + * Characters: [Positions] + * Background: [Elements] + +- Panel 4: [Size - often larger for punchline] + * Content: [What's shown] + * Characters: [Positions] + * Background: [Elements] + +COLOR CODING: +- Main character/element: Purple (#4A148C) accents +- Secondary: Teal (#00796B) accents (if needed) +- Backgrounds: Light cream or white, simple +``` + +--- + +### Step 3: Construct Prompt + +### Prompt Template + +``` +Hand-drawn editorial comic strip in New Yorker style. + +STYLE REFERENCE: New Yorker cartoon, editorial illustration comic, sophisticated sequential art + +BACKGROUND: Light Cream (#F5E6D3) OR varied light tones per panel + +AESTHETIC: +- Hand-drawn editorial style (NOT cartoonish or cute) +- Flat color, black linework, UL palette +- Simplified but sophisticated character design +- Variable stroke weight (thicker for outlines, thinner for details) +- Gestural imperfect linework +- Minimal backgrounds (not busy scenes) +- Smart humor or insight, not silly + +COMIC STRUCTURE: [3-panel / 4-panel] [horizontal strip / vertical strip / grid] + +PANEL LAYOUT: +- [Number] panels arranged [horizontally left-to-right / vertically / grid 2x2] +- Each panel has black border (hand-drawn, slightly wobbly) +- Panel sizes: [Equal / Varied - specify which panels larger] + +TYPOGRAPHY FOR DIALOGUE (Advocate Condensed): +- Minimal text, supports visual narrative +- Font: Advocate condensed +- Size: Small readable +- Color: Charcoal (#2D2D2D) +- Style: Hand-lettered in speech bubbles or captions + +COMIC NARRATIVE: "[Overall concept being illustrated]" + +PANEL 1 - [SETUP]: +Scene: [Describe what's happening] +Characters: [Who's present, what they're doing] +- Main character: Simplified figure with Purple (#4A148C) accent on [element] +- Hand-drawn black linework, imperfect +Background: Light cream, minimal [optional elements] +Dialogue: "[Brief text]" OR no text +Represents: [Initial state] + +PANEL 2 - [ACTION/COMPLICATION]: +Scene: [What changes or happens] +Characters: [Actions, positions] +- Main character: [Reacting or acting] +- [Optional secondary character]: Teal (#00796B) accent +Background: [Minimal elements] +Dialogue: "[Brief text]" OR no text +Represents: [The change] + +PANEL 3 - [ESCALATION/RESULT]: +Scene: [Situation develops] +Characters: [New positions or states] +- Main character: [Further development] +Background: [Minimal] +Dialogue: "[Brief text]" OR no text +Represents: [Progression] + +PANEL 4 - [PUNCHLINE/INSIGHT]: (if using 4 panels) +Scene: [Final state or revelation] +Characters: [Final positions] +- Main character: [Conclusion state] +- Often larger panel for emphasis +Background: [Simple or empty for focus] +Dialogue: "[Punchline or insight text]" +Represents: [The point being made] + +CHARACTER DESIGN - PLANEFORM AESTHETIC (CRITICAL): +- All figures constructed from ANGULAR PLANES (like architectural paper models) +- NO round forms, NO smooth curves, NO circles on bodies +- Adult proportions (1:7 head-to-body ratio), elongated and dignified +- NO cute proportions (big heads, stubby limbs) +- Faces are MINIMAL geometric blocks — NOT detailed, NOT cute, NO big eyes +- Emotion through GESTURE and SILHOUETTE only +- Russian Constructivist influence: El Lissitzky, Oskar Schlemmer, Saul Bass +- Hand-drawn gestural quality with angular construction +- Consistent character across panels (same angular vocabulary) +- Editorial sophistication — NOT cartoonish, NOT children's book style +- If robots present: same angular planes as humans, differentiated by teal accents + +VISUAL CONTINUITY: +- Same character recognizable across all panels +- Consistent hand-drawn style throughout +- Background simplicity maintained in all panels +- Color accents (purple/teal) consistent + +COLOR USAGE: +- Black (#000000) for all linework, panel borders, character outlines +- Deep Purple (#4A148C) accent on main character or key element +- Deep Teal (#00796B) accent on secondary character (if present) +- Charcoal (#2D2D2D) for all dialogue and captions +- Light Cream (#F5E6D3) OR White (#FFFFFF) panel backgrounds +- Minimal flat color fills, mostly linework + +CRITICAL REQUIREMENTS: +- Hand-drawn editorial style (NOT cartoonish, NOT clip-art) +- Simplified but sophisticated character design +- Clear narrative flow across panels +- Minimal dialogue (visual storytelling prioritized) +- Strategic purple/teal accents (not overwhelming color) +- No gradients, flat colors only +- Maintains UL aesthetic (black linework, flat color, imperfect) +- Smart insight or humor (sophisticated, not silly) + +Optional: Sign small in bottom right corner of final panel in charcoal (#2D2D2D). +``` + +--- + +### Step 4: Determine Aspect Ratio + +| Comic Layout | Aspect Ratio | Reasoning | +|--------------|--------------|-----------| +| 3-panel horizontal | 16:9 or 21:9 | Wide strip format | +| 4-panel horizontal | 21:9 | Extra wide for 4 panels | +| 3-panel vertical | 9:16 | Tall strip | +| 4-panel grid (2x2) | 1:1 | Square balanced | +| Variable | 4:3 | Flexible proportions | + +**Default: 16:9 (horizontal)** — Classic comic strip format + +--- + +### Step 5: Execute Generation + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR PROMPT]" \ + --size 2K \ + --aspect-ratio 16:9 \ + --output /path/to/comic.png +``` + +**Model Recommendation:** nano-banana-pro or flux (both handle sequential panels well) + +**Immediately Open:** +```bash +open /path/to/comic.png +``` + +--- + +### Step 6: Validation (MANDATORY) + +#### Must Have +- [ ] **Clear panel structure** — Panels obviously sequential +- [ ] **Editorial aesthetic** — Sophisticated, not cartoonish +- [ ] **Narrative flow** — Story/concept clear across panels +- [ ] **Character consistency** — Same character recognizable in all panels +- [ ] **Hand-drawn quality** — Imperfect linework, gestural +- [ ] **Minimal backgrounds** — Simple, not busy +- [ ] **Smart insight** — Punchline or point lands effectively +- [ ] **UL aesthetic maintained** — Flat color, black linework + +#### Character Validation (Planeform Aesthetic) +- [ ] **Angular construction** — Bodies built from planes, NOT round forms +- [ ] **Adult proportions** — Elongated (1:7), NOT stubby/cute (1:3) +- [ ] **Minimal faces** — Geometric blocks, NOT detailed cute faces +- [ ] **Gesture expression** — Emotion through posture, NOT facial features +- [ ] **NOT cartoonish** — Sophisticated editorial, NOT children's book style +- [ ] **Constructivist influence** — El Lissitzky, Schlemmer aesthetic visible + +#### Must NOT Have +- [ ] Cartoonish or cutesy style +- [ ] Round forms or smooth curves on figures +- [ ] Big heads, stubby proportions +- [ ] Detailed facial features or big eyes +- [ ] Realistic detailed illustration +- [ ] Busy complex backgrounds +- [ ] Too much dialogue (should be visual) +- [ ] Inconsistent character design across panels +- [ ] Gradients or shadows +- [ ] Silly humor (should be sophisticated) +- [ ] Generic AI illustration style + +#### If Validation Fails + +| Problem | Fix | +|---------|-----| +| Too cartoonish | "Sophisticated editorial style, New Yorker cartoon aesthetic, NOT cartoonish" | +| Can't follow story | Clarify narrative arc: "Panel 1 setup → Panel 2 complication → Panel 3 result" | +| Characters inconsistent | "Same simplified character across all panels, consistent design" | +| Too complex | "Minimal backgrounds, simple scenes, focus on key action" | +| Too much text | "Visual storytelling prioritized, minimal dialogue, brief text" | +| Looks corporate | Reference "hand-drawn editorial comic, imperfect wobbly lines, gestural quality" | + +**Character-Specific Failures:** + +| Problem | Fix | +|---------|-----| +| **Characters too round/cute** | "Figures built from ANGULAR PLANES ONLY. NO round forms. Constructivist angular construction like El Lissitzky, Oskar Schlemmer." | +| **Cartoon proportions** | "Adult proportions (1:7 head-to-body). Elongated dignified figures. NO big heads, NO stubby limbs." | +| **Too much facial detail** | "Faces are MINIMAL geometric blocks. NO detailed features, NO big eyes. Emotion through GESTURE only." | +| **Generic AI illustration** | "Bauhaus figure studies. Russian Constructivism. Architectural magazine illustration. NOT children's book." | + +--- + +## Example Use Cases + +### Example 1: "AGI Arrives" (4 panels) +- **Panel 1:** Person at desk, normal work +- **Panel 2:** AGI announcement (computer screen glowing) +- **Panel 3:** Person staring, processing +- **Panel 4:** Person still at desk: "...so what do I do now?" +- **Layout:** Horizontal 21:9 +- **Character:** Purple accent on person + +### Example 2: "Security Theater vs Real Security" (3 panels) +- **Panel 1:** Fancy lock on cardboard door (theater) +- **Panel 2:** Simple lock on solid door (real) +- **Panel 3:** Thief easily bypassing fancy lock, stopped by simple door +- **Layout:** Horizontal 16:9 +- **Accents:** Purple on real security, teal on theater + +### Example 3: "Junior vs Senior Engineer" (4 panels grid) +- **Panel 1 (top-left):** Junior with complex spaghetti code +- **Panel 2 (top-right):** Senior with simple elegant line +- **Panel 3 (bottom-left):** Both present to boss +- **Panel 4 (bottom-right):** Boss confused by junior's complexity, nodding at senior's simplicity +- **Layout:** Grid 1:1 +- **Accents:** Purple on senior, teal on junior + +--- + +## Quick Reference + +**Editorial Comic Formula:** +``` +1. Define narrative (concept, panels, arc, insight) +2. Design layout (arrangement, panel sizes, content) +3. Construct prompt with sequential structure +4. Choose aspect ratio for panel layout +5. Generate with nano-banana-pro +6. Validate for flow and sophistication +``` + +**Color Strategy:** +- Characters: Black linework + purple/teal accents +- Backgrounds: Simple light cream/white +- Dialogue: Charcoal +- Panels: Black borders + +**Key Principle:** +- **Sophisticated, not silly** — New Yorker style, editorial intelligence +- **Visual storytelling** — Minimal dialogue, panels tell the story +- **UL aesthetic** — Flat color, hand-drawn, imperfect + +--- + +**The workflow: Define → Design → Construct → Generate → Validate → Complete** diff --git a/.opencode/skills/Media/Art/Workflows/Comparisons.md b/.opencode/skills/Media/Art/Workflows/Comparisons.md new file mode 100755 index 00000000..cfbe49f2 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/Comparisons.md @@ -0,0 +1,371 @@ +# Illustrated Dichotomies & Comparisons Workflow + +**Hand-drawn side-by-side visual comparisons using UL aesthetic.** + +Creates **VISUAL COMPARISONS** — "X vs Y" split compositions, before/after transformations, and illustrated contrasts with editorial style. + +--- + +## Purpose + +Illustrated comparisons show two contrasting concepts, states, or approaches side-by-side. These are **visual dichotomies** that make differences immediately obvious through illustrated metaphor. + +**Use this workflow for:** +- "X vs Y" comparisons +- Before/After transformations +- This/That contrasts +- Junior vs Senior behaviors +- Old way vs New way +- Opposite approaches + +--- + +## Visual Aesthetic: Split Screen Editorial + +**Think:** Magazine spread showing contrast, split composition with personality + +### Core Characteristics +1. **Split composition** — Clear left/right or top/bottom division +2. **Mirror structure** — Parallel visual elements showing contrast +3. **Hand-drawn** — Both sides maintain editorial imperfect linework +4. **Color differentiation** — Purple for one side, teal for other (or both black) +5. **Immediate contrast** — Differences obvious at a glance +6. **Editorial style** — Flat colors, black linework, UL aesthetic +7. **Balanced layout** — Equal visual weight to both sides + +### Character Requirements (When figures present) + +**If comparison includes human or robot figures, MUST apply Planeform aesthetic:** +- Figures built from ANGULAR PLANES (no round forms) +- Adult proportions (1:7), NOT cute/stubby +- Faces are minimal geometric blocks +- Emotion through gesture/silhouette +- Constructivist/Bauhaus influence +- NOT cartoonish (sophisticated editorial) + +--- + +## Color System for Comparisons + +### Split Differentiation +``` +Left/Top Side: Purple #4A148C accents +Right/Bottom Side: Teal #00796B accents +OR +Both sides: Black with strategic purple/teal highlights +``` + +### Structure +``` +Black #000000 — Dividing line, all linework on both sides +Charcoal #2D2D2D — All text and labels +``` + +### Background +``` +White #FFFFFF or Light Cream #F5E6D3 on both sides +OR +Left: Light Purple tint, Right: Light Teal tint (very subtle) +``` + +### Color Strategy +- Option 1: Purple accents left, Teal accents right (clear differentiation) +- Option 2: Both black linework, purple on "preferred" side +- Dividing line always black +- Maintain flat aesthetic, no gradients + +--- + +## 🚨 MANDATORY WORKFLOW STEPS + +### Step 1: Define Comparison + +**Identify what you're contrasting:** + +1. **What are the two sides?** + - Side A: [Concept / State / Approach] + - Side B: [Concept / State / Approach] + +2. **What's the key difference?** + - [What fundamentally distinguishes them] + +3. **What visual metaphors show the contrast?** + - Side A metaphor: [Physical object/scene] + - Side B metaphor: [Contrasting object/scene] + +4. **Is one side "better" or are they equal alternatives?** + - Better: [Which side to highlight in purple] + - Equal: [Use balanced color or both in black] + +**Output:** +``` +COMPARISON: [Side A] vs [Side B] + +CORE CONTRAST: [What's fundamentally different] + +VISUAL METAPHORS: +- Side A: [Metaphor showing this approach/state] +- Side B: [Contrasting metaphor] + +VALUE JUDGMENT: +- [Neutral comparison] OR [Side X is preferred] + +COLOR STRATEGY: +- [Purple left / Teal right] OR [Purple on preferred, black on alternative] +``` + +--- + +### Step 2: Design Split Layout + +**Plan the visual structure:** + +1. **Split orientation:** + - Vertical split (left/right) — Classic comparison + - Horizontal split (top/bottom) — Before/after flow + - Diagonal split — More dynamic + +2. **Mirror elements:** + - What visual elements repeat on both sides + - How metaphors contrast (same structure, different details) + - Balance of visual weight + +3. **Dividing line:** + - Strong black line separating sides + - Soft visual separation + - No line (color/metaphor creates division) + +**Output:** +``` +SPLIT ORIENTATION: [Vertical left/right / Horizontal top/bottom] + +LAYOUT STRUCTURE: +Left/Top: [Side A] +- Metaphor: [What to illustrate] +- Key elements: [Specific visual details] +- Color: [Purple accents / Black only] + +Right/Bottom: [Side B] +- Metaphor: [Contrasting illustration] +- Key elements: [Specific visual details] +- Color: [Teal accents / Black only] + +DIVIDING LINE: +- [Strong black vertical/horizontal line] OR [Soft separation] OR [No line] + +MIRROR ELEMENTS: +- [What appears on both sides for parallel structure] +``` + +--- + +### Step 3: Construct Prompt + +### Prompt Template + +``` +Hand-drawn split composition comparing two contrasting concepts in editorial style. + +STYLE REFERENCE: Magazine comparison spread, split-screen editorial illustration, before/after visual + +BACKGROUND: [White #FFFFFF OR Light Cream #F5E6D3] — clean, flat, both sides + +AESTHETIC: +- Split composition with [vertical/horizontal] division +- Hand-drawn black linework on both sides (imperfect, gestural) +- Mirror structure showing parallel concepts with visual contrast +- Editorial flat color with strategic purple/teal differentiation +- Variable stroke weight, organic lines + +SPLIT ORIENTATION: [Vertical left-to-right / Horizontal top-to-bottom] + +COMPOSITION STRUCTURE: +- Clear [vertical/horizontal] division creating two equal sections +- [Black dividing line] OR [Visual separation through composition] +- Left/Top: [Side A name] +- Right/Bottom: [Side B name] + +TYPOGRAPHY SYSTEM (3-TIER): + +TIER 1 - COMPARISON TITLE (Advocate Block Display): +- "[SIDE A] VS [SIDE B]" — Large at top +- Font: Advocate style, extra bold, hand-lettered, all-caps +- Size: 3x larger than body text +- Color: Black #000000 +- Position: Top center above split +- Example: "JUNIOR ENGINEER VS SENIOR ENGINEER" + +TIER 2 - SIDE LABELS (Concourse Sans): +- Left/Top: "[Side A]" +- Right/Bottom: "[Side B]" +- Font: Concourse geometric sans-serif +- Size: Medium readable +- Color: Charcoal #2D2D2D +- Position: Headers for each side + +TIER 3 - ANNOTATIONS (Advocate Condensed Italic): +- Key characteristics: "*overthinks*" vs "*simplifies*" +- Font: Advocate condensed italic +- Size: 60% of Tier 2 +- Color: Matches side color (Purple left, Teal right) +- Position: Within each side's visual + +LEFT/TOP SIDE - [SIDE A]: +Visual metaphor: [Describe the illustration, e.g.:] +- [Metaphor showing Side A characteristic] +- Hand-drawn with [imperfect lines, gestural quality] +- Color: Purple (#4A148C) accents on [specific elements] +- Black (#000000) primary linework +- Represents: [What this side embodies] + +RIGHT/BOTTOM SIDE - [SIDE B]: +Visual metaphor: [Contrasting illustration, e.g.:] +- [Metaphor showing Side B characteristic] +- Hand-drawn matching style to left side +- Color: Teal (#00796B) accents on [specific elements] +- Black (#000000) primary linework +- Represents: [What this side embodies] + +[OR if one side is preferred:] +- Preferred side: Purple (#4A148C) accents +- Alternative side: Black only (or subtle Teal) + +DIVIDING LINE: +- [Strong black vertical/horizontal line down center] OR +- [Soft visual separation through composition and color] + +COLOR USAGE: +- Black (#000000) for all linework on both sides and dividing line +- Left side: Purple (#4A148C) accents on [elements] +- Right side: Teal (#00796B) accents on [elements] +- Charcoal (#2D2D2D) for all label text +- OR: Purple on preferred side only, black on alternative + +CRITICAL REQUIREMENTS: +- Hand-drawn editorial style on BOTH sides (consistent aesthetic) +- Clear visual contrast between sides (metaphors show difference) +- Mirror structure (parallel elements contrasted) +- Strategic color differentiation (purple vs teal, or purple on better side) +- No gradients, flat colors only +- Immediate visual understanding of the difference +- Equal visual weight to both sides (balanced composition) + +Optional: Sign small in bottom right corner in charcoal (#2D2D2D). +``` + +--- + +### Step 4: Determine Aspect Ratio + +| Split Type | Aspect Ratio | Reasoning | +|------------|--------------|-----------| +| Vertical split (left/right) | 16:9 or 21:9 | Wide for side-by-side | +| Horizontal split (top/bottom) | 9:16 or 1:1 | Vertical or square for stacking | +| Square balanced | 1:1 | Symmetric comparison | +| Social media | 1:1 | Instagram/LinkedIn friendly | + +**Default: 16:9 (horizontal)** — Classic side-by-side comparison + +--- + +### Step 5: Execute Generation + +```bash +bun run ~/.opencode/skills/Media/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR PROMPT]" \ + --size 2K \ + --aspect-ratio 16:9 \ + --output /path/to/comparison.png +``` + +**Model Recommendation:** nano-banana-pro or flux (both work well for split compositions) + +**Immediately Open:** +```bash +open /path/to/comparison.png +``` + +--- + +### Step 6: Validation (MANDATORY) + +#### Must Have +- [ ] **Clear split** — Obvious division between two sides +- [ ] **Visual contrast** — Metaphors clearly show the difference +- [ ] **Balanced composition** — Equal visual weight to both sides +- [ ] **Readable labels** — Side names and annotations legible +- [ ] **Color differentiation** — Purple/teal (or purple/black) distinguishes sides +- [ ] **Hand-drawn** — Both sides maintain editorial aesthetic +- [ ] **Immediate understanding** — Difference obvious at a glance + +#### Must NOT Have +- [ ] Unbalanced sides (one dominates) +- [ ] Unclear which is which +- [ ] Corporate comparison chart look +- [ ] Gradients or photorealistic elements +- [ ] Cluttered or confusing visuals +- [ ] Missing dividing line or separation + +#### If Validation Fails + +| Problem | Fix | +|---------|-----| +| Sides unclear | "Strong black dividing line down center, clear LEFT: vs RIGHT: labels" | +| Not balanced | "Equal visual weight, mirror structure, parallel composition both sides" | +| Contrast weak | "Stronger metaphor contrast: [Side A metaphor] vs [Side B opposite metaphor]" | +| Too complex | Simplify each side to single clear metaphor | +| Colors confusing | "Purple accents left side only, Teal accents right side only" | +| Looks corporate | Reference "editorial split composition, hand-drawn contrast illustration" | + +--- + +## Example Use Cases + +### Example 1: "Junior Engineer vs Senior Engineer" +- **Split:** Vertical left/right +- **Left (Junior):** Complex spaghetti code (purple tangle) +- **Right (Senior):** Simple elegant solution (teal straight line) +- **Color:** Purple left, Teal right +- **Aspect:** 16:9 + +### Example 2: "Before AI vs After AI" +- **Split:** Horizontal top/bottom +- **Top (Before):** Manual tedious work (person with paper pile) +- **Bottom (After):** Automated flow (person directing AI) +- **Color:** Purple on "After" (preferred state) +- **Aspect:** 9:16 + +### Example 3: "Security Theater vs Real Security" +- **Split:** Vertical left/right +- **Left (Theater):** Fancy locks on cardboard door +- **Right (Real):** Simple but solid construction +- **Color:** Purple right (effective), black left (ineffective) +- **Aspect:** 16:9 + +--- + +## Quick Reference + +**Comparison Formula:** +``` +1. Define comparison (sides, contrast, metaphors) +2. Design split layout (orientation, mirror elements, colors) +3. Construct prompt with split structure +4. Choose aspect ratio for split type +5. Generate with nano-banana-pro +6. Validate for clarity and balance +``` + +**Color Strategy:** +- Balanced comparison: Purple left, Teal right +- Value judgment: Purple on better side, black on other +- Neutral: Both black with subtle purple/teal accents + +**Key Principle:** +- Difference should be immediately obvious +- Visual metaphors do the talking, minimal text needed + +--- + +**The workflow: Define → Design → Construct → Generate → Validate → Complete** diff --git a/.opencode/skills/Media/Art/Workflows/CreatePAIPackIcon.md b/.opencode/skills/Media/Art/Workflows/CreatePAIPackIcon.md new file mode 100755 index 00000000..9d6ddb9c --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/CreatePAIPackIcon.md @@ -0,0 +1,207 @@ +# PAI Pack Icon Workflow + +**Generate 256x256 transparent PNG icons for PAI packs.** + +--- + +## Purpose + +Create consistent, professional icons for PAI packs following the established visual identity. + +**Use for:** New pack icons, icon refreshes, icon regeneration. + +--- + +## Visual Specifications + +### Required Specs + +| Spec | Value | +|------|-------| +| **Dimensions** | 256x256 pixels | +| **Format** | PNG with transparency | +| **Background** | ACTUAL transparent (not checkerboard) | +| **Primary Color** | Electric blue #4a90d9 | +| **Accent Color** | Purple #8b5cf6 (10-15% max) | +| **Style** | Simple, flat, readable at 64x64 | + +### Color Palette + +``` +Background: Transparent (actual transparency, not pattern) +Primary: Electric Blue #4a90d9 (dominant color) +Accent: Purple #8b5cf6 (sparingly, 10-15% of design) +Optional Dark: Dark #0a0a0f (for contrast elements if needed) +``` + +### Design Rules + +1. **Simple geometry** - Icon must be readable at 64x64 pixels +2. **Conceptual** - Represent the pack's core function visually +3. **Consistent style** - Match existing PAI pack icons +4. **No text** - Icons should work without labels +5. **Centered** - Icon should be centered in the 256x256 canvas + +--- + +## Workflow Steps + +### Step 1: Understand Pack Purpose + +Before generating, understand: +- What does this pack do? +- What visual metaphor represents it? +- How should it relate to other pack icons? + +**Good icon concepts:** +- `pai-hook-system` → Hook shape, event trigger +- `pai-core-install` → Download/install arrow +- `pai-skill-system` → Brain/routing/capability +- `pai-agent-system` → Robot/assistant figure +- `pai-voice-system` → Sound wave/speaker + +### Step 2: Construct Prompt + +Build a prompt that specifies: +1. The visual concept +2. The style (simple flat icon) +3. The color palette +4. The size requirements + +**Prompt template:** +``` +[VISUAL CONCEPT representing {pack function}], simple flat icon design, 256x256 pixels. +COLOR PALETTE: Primary electric blue (#4a90d9), Accent purple (#8b5cf6) sparingly. +STYLE: Modern flat icon, simple enough to read at 64x64, no text, centered. +BACKGROUND: Dark (#0a0a0f) - will be removed for transparency. +``` + +### Step 3: Generate Icon + +**Command:** +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR_PROMPT]" \ + --size 1K \ + --aspect-ratio 1:1 \ + --remove-bg \ + --output ~/Projects/PAI/Packs/icons/[PACK_NAME].png +``` + +**Flags explained:** +- `--model nano-banana-pro` - Best quality for icons +- `--size 1K` - Small file, fast generation +- `--aspect-ratio 1:1` - Square for icons +- `--remove-bg` - Creates actual transparency + +### Step 4: Verify Output + +Check the generated icon: +```bash +# Verify file exists and size +ls -la ~/Projects/PAI/Packs/icons/[PACK_NAME].png + +# Check dimensions (requires imagemagick) +file ~/Projects/PAI/Packs/icons/[PACK_NAME].png +``` + +**Verification checklist:** +- [ ] File exists at correct location +- [ ] PNG format +- [ ] Approximately 256x256 dimensions +- [ ] Has transparency (no solid background) +- [ ] Uses blue/purple palette +- [ ] Readable at small size + +--- + +## Examples + +### Example 1: Hook System Pack + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "A stylized hook or fishing hook shape representing event hooks in software, simple flat icon design, 256x256 pixels. COLOR PALETTE: Primary electric blue (#4a90d9), Accent purple (#8b5cf6) sparingly. STYLE: Modern flat icon, simple enough to read at 64x64, no text, centered. BACKGROUND: Dark (#0a0a0f)." \ + --size 1K \ + --aspect-ratio 1:1 \ + --remove-bg \ + --output ~/Projects/PAI/Packs/icons/pai-hook-system.png +``` + +### Example 2: Core Install Pack + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "A download arrow pointing into a foundation/base structure representing core installation, simple flat icon design, 256x256 pixels. COLOR PALETTE: Primary electric blue (#4a90d9), Accent purple (#8b5cf6) sparingly. STYLE: Modern flat icon, simple enough to read at 64x64, no text, centered. BACKGROUND: Dark (#0a0a0f)." \ + --size 1K \ + --aspect-ratio 1:1 \ + --remove-bg \ + --output ~/Projects/PAI/Packs/icons/pai-core-install.png +``` + +### Example 3: Memory System Pack + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "A brain with memory/data flowing in and out representing an AI memory system, simple flat icon design, 256x256 pixels. COLOR PALETTE: Primary electric blue (#4a90d9), Accent purple (#8b5cf6) sparingly. STYLE: Modern flat icon, simple enough to read at 64x64, no text, centered. BACKGROUND: Dark (#0a0a0f)." \ + --size 1K \ + --aspect-ratio 1:1 \ + --remove-bg \ + --output ~/Projects/PAI/Packs/icons/pai-memory-system.png +``` + +--- + +## Output Location + +All PAI pack icons go to: +``` +~/Projects/PAI/Packs/icons/[PACK_NAME].png +``` + +**Naming convention:** Match the pack directory name exactly. +- Pack: `Packs/pai-hook-system/` +- Icon: `Packs/icons/pai-hook-system.png` + +--- + +## Regeneration + +If an icon needs to be regenerated: + +1. Delete the old icon +2. Run the generate command with updated prompt +3. Verify the new icon +4. Update README if icon changed significantly + +--- + +## Validation Checklist + +Before marking icon complete: + +- [ ] **Exists** at `~/Projects/PAI/Packs/icons/[PACK_NAME].png` +- [ ] **Format** is PNG with transparency +- [ ] **Size** approximately 256x256 +- [ ] **Colors** use blue primary, purple accent +- [ ] **Readable** at 64x64 size +- [ ] **Conceptual** - represents pack function +- [ ] **Consistent** - matches other PAI icons in style + +--- + +## Related Workflows + +> **Note:** The PAI skill workflows referenced below are planned but not yet implemented. + +- `~/.opencode/skills/PAI/Workflows/CreatePack.md` - *(NOT IMPLEMENTED)* Would use this for icon generation +- `~/.opencode/skills/PAI/Workflows/ValidatePack.md` - *(NOT IMPLEMENTED)* Would validate icon exists +- `~/.opencode/skills/PAI/Workflows/PAIIntegrityCheck.md` - *(NOT IMPLEMENTED)* Would check all icons + +--- + +**Last Updated:** 2026-01-10 diff --git a/.opencode/skills/Media/Art/Workflows/D3Dashboards.md b/.opencode/skills/Media/Art/Workflows/D3Dashboards.md new file mode 100755 index 00000000..a311d7a6 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/D3Dashboards.md @@ -0,0 +1,382 @@ +# D3.js Interactive Dashboards Workflow + +**Interactive data visualizations and dashboards using D3.js.** + +--- + +## Purpose + +Creates sophisticated, interactive data visualizations using D3.js for dashboards, reports, and data analysis. + +**Use for:** +- TELOS consulting dashboards (project dependencies, constraint analysis) +- Blog post data visualizations (statistics, trends, relationships) +- Network diagrams (system architecture, organizational relationships) +- Interactive reports and presentations + +**This is NOT for:** +- Static diagrams → Use TechnicalDiagrams or Mermaid workflows +- Editorial illustrations → Use Essay workflow +- Simple infographics → Use other visualization workflows + +--- + +## Supported Visualization Types + +### Charts & Graphs +- **Bar Charts** - Comparisons, rankings, distributions +- **Line Charts** - Trends over time, performance metrics +- **Scatter Plots** - Correlations, clusters, outliers +- **Area Charts** - Cumulative values, stacked comparisons +- **Pie/Donut Charts** - Proportions, percentages + +### Network & Relationships +- **Force-Directed Graphs** - Project dependencies, team relationships +- **Tree Diagrams** - Hierarchies, organizational structures +- **Chord Diagrams** - Entity relationships, data flow +- **Sankey Diagrams** - Flow visualization, process mapping + +### Advanced +- **Heatmaps** - Intensity, density, correlation matrices +- **Geographic Maps** - Location data, regional analysis +- **Timeline Visualizations** - Project milestones, historical data +- **Custom Dashboards** - Multi-chart compositions + +--- + +## Color Palette (PAI Standard) + +**Primary Colors:** +``` +Deep Purple: #4A148C - Brand accent +Deep Teal: #00796B - Secondary accent +Charcoal: #2D2D2D - Text and lines +``` + +**Data Visualization Colors:** +- Sequential scales for continuous data: `d3.interpolateViridis`, `d3.interpolatePlasma` +- Categorical scales for discrete data: `d3.schemeCategory10`, `d3.schemeSet3` +- Maintain accessibility with sufficient contrast + +**Typography:** +- System fonts: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto` +- Label sizes: 12px for axes, 14px for titles +- Consistent spacing and alignment + +--- + +## Implementation Approach + +### Standard Workflow + +```javascript +function createVisualization(data, config) { + // 1. Setup SVG container + const svg = d3.select('#chart'); + svg.selectAll("*").remove(); // Clear previous render + + // 2. Define dimensions with margins + const width = 800, height = 400; + const margin = { top: 20, right: 30, bottom: 40, left: 50 }; + const innerWidth = width - margin.left - margin.right; + const innerHeight = height - margin.top - margin.bottom; + + // 3. Create scales + const xScale = d3.scaleLinear() + .domain([0, d3.max(data, d => d.value)]) + .range([0, innerWidth]); + + // 4. Create axes + const xAxis = d3.axisBottom(xScale); + + // 5. Bind data and create elements + const g = svg.append('g') + .attr('transform', `translate(${margin.left},${margin.top})`); + + // 6. Add interactive features + g.selectAll('circle') + .data(data) + .join('circle') + .attr('cx', d => xScale(d.value)) + .attr('cy', height / 2) + .attr('r', 5) + .on('mouseover', showTooltip) + .on('mouseout', hideTooltip); +} +``` + +### Integration Patterns + +**Direct DOM Manipulation (Recommended):** +- D3 selects and imperatively manipulates DOM elements +- Works in any JavaScript context +- Full control over rendering + +**Declarative Rendering:** +- D3 calculates scales and layouts +- Framework renders via templating +- Suitable for simpler visualizations + +--- + +## Interactive Features + +### Tooltips + +```javascript +const tooltip = d3.select('body').append('div') + .attr('class', 'tooltip') + .style('opacity', 0); + +function showTooltip(event, d) { + tooltip.transition() + .duration(200) + .style('opacity', .9); + tooltip.html(`Value: ${d.value}`) + .style('left', (event.pageX + 10) + 'px') + .style('top', (event.pageY - 28) + 'px'); +} +``` + +### Zoom & Pan + +```javascript +const zoom = d3.zoom() + .scaleExtent([0.5, 5]) + .on('zoom', (event) => { + g.attr('transform', event.transform); + }); + +svg.call(zoom); +``` + +### Transitions & Animations + +```javascript +circles.transition() + .duration(750) + .delay((d, i) => i * 50) + .attr('r', d => radiusScale(d.value)) + .style('fill', d => colorScale(d.category)) + .ease(d3.easeBounceOut); +``` + +### Responsive Design + +```javascript +// Handle container resizing +const resizeObserver = new ResizeObserver(entries => { + const { width, height } = entries[0].contentRect; + redrawVisualization(width, height); +}); + +resizeObserver.observe(document.querySelector('#chart-container')); +``` + +--- + +## TELOS Dashboard Patterns + +### Project Dependency Network + +```javascript +// Force-directed graph for project dependencies +const simulation = d3.forceSimulation(nodes) + .force('link', d3.forceLink(links).id(d => d.id)) + .force('charge', d3.forceManyBody().strength(-100)) + .force('center', d3.forceCenter(width / 2, height / 2)); + +// Visualize blockers as red nodes +nodes.forEach(node => { + node.color = node.isBlocker ? '#D32F2F' : '#4A148C'; +}); +``` + +### Constraint Theory Visualization + +```javascript +// Bottleneck analysis with bar chart +const constraints = [ + { name: 'Resource A', impact: 85, isBottleneck: true }, + { name: 'Resource B', impact: 45, isBottleneck: false }, + // ... +]; + +// Highlight bottlenecks in contrasting color +bars.attr('fill', d => d.isBottleneck ? '#D32F2F' : '#00796B'); +``` + +### Progress Dashboard + +```javascript +// Multi-metric dashboard +const metrics = { + currentCustomers: 243, + targetCustomers: 2000, + growthRate: 0.15, + blockers: 3 +}; + +// Create gauge chart for progress +const progress = (metrics.currentCustomers / metrics.targetCustomers) * 100; +createGaugeChart(progress); +``` + +--- + +## Best Practices + +### Data Validation +```javascript +// Always validate and clean data first +const cleanData = data.filter(d => + d.value !== null && + d.value !== undefined && + !isNaN(d.value) +); +``` + +### Performance Optimization +- **<1000 elements**: Use SVG (optimal) +- **1000-10,000 elements**: Consider canvas rendering +- **>10,000 elements**: Implement virtual scrolling or aggregation + +### Accessibility +```javascript +// Add ARIA labels and semantic markup +svg.attr('role', 'img') + .attr('aria-label', 'Bar chart showing project metrics'); + +// Add keyboard navigation +circles.attr('tabindex', 0) + .on('keypress', handleKeyPress); +``` + +### Error Handling +```javascript +// Graceful error handling +try { + const svg = d3.select('#chart'); + if (svg.empty()) { + throw new Error('Chart container not found'); + } + + if (!Array.isArray(data) || data.length === 0) { + throw new Error('Invalid or empty data'); + } + + renderVisualization(data); +} catch (error) { + console.error('Visualization error:', error); + showErrorMessage('Unable to render chart. Please check your data.'); +} +``` + +--- + +## Output Formats + +### HTML Artifact +- Complete standalone HTML file +- Embedded D3.js library (CDN or inline) +- Responsive container +- Interactive controls + +### Code Snippet +- Reusable JavaScript function +- Configurable parameters +- Documentation comments + +### Dashboard Page +- Multi-chart layout +- Coordinated interactions +- Shared data filtering +- Export/download functionality + +--- + +## Quick Start Examples + +### Bar Chart +```javascript +// Simple bar chart +const data = [12, 5, 6, 6, 9, 10]; + +d3.select('#chart') + .selectAll('div') + .data(data) + .join('div') + .style('width', d => `${d * 10}px`) + .style('height', '20px') + .style('background', '#4A148C') + .text(d => d); +``` + +### Network Diagram +```javascript +// Project dependency network +const nodes = [ + { id: 'A', label: 'API' }, + { id: 'B', label: 'Database' }, + { id: 'C', label: 'Frontend' } +]; + +const links = [ + { source: 'A', target: 'B' }, + { source: 'C', target: 'A' } +]; + +createForceDirectedGraph(nodes, links); +``` + +--- + +## D3.js Resources + +**Core Concepts:** +- Selections: `d3.select()`, `d3.selectAll()` +- Data binding: `.data()`, `.join()` +- Scales: `d3.scaleLinear()`, `d3.scaleBand()`, `d3.scaleOrdinal()` +- Axes: `d3.axisBottom()`, `d3.axisLeft()` +- Shapes: `d3.line()`, `d3.arc()`, `d3.area()` + +**Layout Algorithms:** +- Force simulation: `d3.forceSimulation()` +- Hierarchies: `d3.hierarchy()`, `d3.tree()` +- Chord: `d3.chord()` +- Sankey: `d3.sankey()` + +**Official Documentation:** +- https://d3js.org/ +- https://observablehq.com/@d3/gallery + +--- + +## Execution + +1. Gather data requirements and determine visualization type +2. Choose appropriate chart/graph pattern +3. Set up HTML structure with D3.js +4. Implement visualization with standard color palette +5. Add interactivity (tooltips, zoom, transitions) +6. Validate accessibility and responsiveness +7. Output as HTML artifact or code snippet + +--- + +## Validation + +**Must have:** +- [ ] Clean, professional appearance +- [ ] Standard color palette applied +- [ ] Interactive features working +- [ ] Responsive to container size +- [ ] Accessible (ARIA labels, keyboard nav) +- [ ] Data validation in place +- [ ] Error handling for edge cases + +**Must NOT have:** +- [ ] Generic color schemes +- [ ] Static-only presentation when interactivity makes sense +- [ ] Missing axis labels or legends +- [ ] Overflow or cropped elements diff --git a/.opencode/skills/Media/Art/Workflows/EmbossedLogoWallpaper.md b/.opencode/skills/Media/Art/Workflows/EmbossedLogoWallpaper.md new file mode 100755 index 00000000..cafe3717 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/EmbossedLogoWallpaper.md @@ -0,0 +1,281 @@ +# Embossed Logo Wallpaper + +**Generate sophisticated wallpapers with logo physically embossed into the design.** + +## Voice Notification + +```bash +curl -s -X POST http://localhost:8888/notify \ + -H "Content-Type: application/json" \ + -d '{"message": "Running the EmbossedLogoWallpaper workflow in the Art skill to create wallpapers"}' \ + > /dev/null 2>&1 & +``` + +Running **EmbossedLogoWallpaper** in **Art**... + +--- + +Creates wallpapers where the UL logo is integrated as an embossed texture within the visual content — not overlaid, not floating in empty space. + +--- + +## Purpose + +Generate wallpapers that: +- Integrate the logo as a physical embossed element within the design +- Use the UL color palette (blue/purple/cyan only) +- Match sophisticated reference wallpapers in quality +- Position logo small in bottom left, surrounded by visual content + +--- + +## Prerequisites + +**Logo Source:** `~/Projects/Logos/ul-blue.png` +**Style References:** `~/Projects/Wallpaper/` (blue-purple-circuits.png, circuit-board.png) +**Output Directory:** `~/Projects/Wallpaper/` + +--- + +## Critical Lessons Learned (Validation Checklist) + +### ❌ COMMON FAILURES TO AVOID + +**1. Wrong Logo Treatment** +- ❌ Literal text "UL" or "Unsupervised Learning" instead of the logo shape +- ❌ Logo overlaid/floating instead of embossed into surface +- ❌ Logo placed in empty/blank area instead of integrated into design +- ❌ Logo too large and prominent +- ❌ Logo glowing or different color than surroundings +- ✅ CORRECT: Logo shape from reference image, embossed as texture, small, within visual content + +**2. Wrong Colors** +- ❌ Matrix green (#00ff41) +- ❌ Pink/magenta neon +- ❌ Bright saturated neons +- ❌ Any colors outside the UL palette +- ✅ CORRECT: Blue (#4a90d9 or muted #3a6a9a), Purple (#8b5cf6 or muted #6b4c96), Cyan (#06b6d4 or muted #4a9a9a) + +**3. Wrong Style** +- ❌ Simple, cartoony, flat vector art +- ❌ Too bright, loud, gaudy +- ❌ Clean lines without texture or depth +- ✅ CORRECT: Sophisticated, photorealistic or stylized with depth, muted/subdued, dense detail + +**4. Wrong Composition** +- ❌ Logo in empty/blank corner +- ❌ Visual content clustered in center with empty edges +- ❌ Logo too prominent/centered +- ✅ CORRECT: Visual content fills entire canvas, logo small in bottom left WITHIN the design + +**5. Missing Reference Images** +- ❌ Not using ul-blue.png as reference for logo shape +- ❌ Not checking existing wallpapers for quality benchmark +- ✅ CORRECT: Always use --reference-image with the logo file + +--- + +## Workflow Steps + +### Step 1: Gather Requirements + +Ask about: +1. **Style direction** — Photorealistic circuit, cyberpunk/hacker, abstract, etc. +2. **Tone** — Bright and energetic OR muted and subdued +3. **Output name** — Filename (kebab-case) + +### Step 2: Load References + +```bash +# Verify logo exists +ls ~/Projects/Logos/ul-blue.png + +# View style reference wallpapers +open ~/Projects/Wallpaper/circuit-board.png +open ~/Projects/Wallpaper/blue-purple-circuits.png +``` + +**Study reference wallpapers for:** +- Level of visual sophistication and detail +- Color palette application +- Depth and atmospheric effects +- Texture and material quality + +### Step 3: Construct Prompt + +**Required prompt sections:** + +``` +1. AESTHETIC - Define the visual style (cyberpunk, circuit, etc.) + +2. VISUAL COMPLEXITY - Specify density, layers, detail level + +3. TONE - Muted/subdued OR bright (usually muted is better) + +4. COLOR PALETTE (STRICT): + - Deep black base (#0a0a0f) + - Blue (#4a90d9 or muted #3a6a9a) + - Purple (#8b5cf6 or muted #6b4c96) + - Cyan (#06b6d4 or muted #4a9a9a) + - NO GREEN, NO PINK, NO OTHER COLORS + +5. LOGO INTEGRATION (CRITICAL): + - Connected-nodes logo from reference + - EMBOSSED into surface (raised/pressed texture) + - Position: bottom left WITHIN the visual content + - Size: 3-5% of image width (SMALL) + - Same materials/colors as surroundings + - Slight luminosity difference only + - NOT overlaid, NOT floating, NOT glowing + - Must be surrounded by design elements + +6. COMPOSITION: + - Visual content fills ENTIRE canvas + - NO empty corners or blank areas + - Logo area has visual content WITH logo embossed into it + +7. CRITICAL reminders: + - Logo integrated INTO design + - Logo SMALL and SUBTLE + - Entire image has visual content + - Correct color palette only +``` + +### Step 4: Generate + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[CONSTRUCTED_PROMPT]" \ + --size 4K \ + --aspect-ratio 16:9 \ + --reference-image ~/Projects/Logos/ul-blue.png \ + --output ~/Projects/Wallpaper/.png +``` + +### Step 5: Validate (CRITICAL) + +Open the generated image and check EVERY item: + +```bash +open -a "Dia" ~/Projects/Wallpaper/.png +``` + +**Validation Checklist:** + +| Check | Pass/Fail | +|-------|-----------| +| Logo is the correct shape (connected nodes), not text | | +| Logo is EMBOSSED (texture), not overlaid or floating | | +| Logo is in bottom left corner | | +| Logo is SMALL (3-5% width) | | +| Logo is WITHIN visual content, not in empty space | | +| Logo is same color palette as surroundings | | +| Colors are ONLY blue/purple/cyan (no green, pink, etc.) | | +| Style is sophisticated, not cartoony or simple | | +| Tone matches request (muted if requested) | | +| Visual content fills entire canvas (no blank areas) | | +| Quality matches reference wallpapers | | +| Image dimensions are 4K+ (5504×3072 or similar) | | + +**If ANY check fails → regenerate with adjusted prompt** + +### Step 6: Iterate if Needed + +Common fixes: +- Logo in wrong place → Emphasize "WITHIN the visual content" and "NO empty corners" +- Logo too big → Specify exact percentage "3-5% of image width" +- Wrong colors → List exact hex codes and explicitly say "NO GREEN, NO PINK" +- Too bright → Add "MUTED, SUBDUED, desaturated" +- Too simple → Describe sophistication level, reference existing wallpapers + +### Step 7: Save and Apply + +```bash +# Verify saved +ls -la ~/Projects/Wallpaper/.png + +# Apply to Kitty + macOS +k -w +``` + +--- + +## Example Prompt (Muted Cyberpunk) + +``` +Cyberpunk hacker wallpaper, 16:9 4K resolution. SUBDUED AND MUTED. + +AESTHETIC: +- Dense layers of data streams and neural network architecture +- Sophisticated cyberpunk atmosphere - Ghost in the Shell / Lain +- Japanese anime styling - mature, serious, detailed + +VISUAL COMPLEXITY: +- Thousands of tiny particles and data points +- Overlapping translucent layers of circuit geometry +- Dense but organized chaos throughout THE ENTIRE IMAGE +- Visual content should extend to ALL edges including bottom left +- NO empty or blank areas anywhere + +TONE (SUBDUED AND HUMBLE): +- MUTED colors - desaturated, not neon bright +- DARK overall - near-black dominates +- Subtle glows instead of bright neon +- Quiet sophistication, not loud +- Moody and atmospheric + +COLOR PALETTE (MUTED UL BRAND): +- Deep black void dominates (#0a0a0f) +- Desaturated blue (#3a6a9a) - muted +- Muted purple (#6b4c96) - subtle +- Soft cyan (#4a9a9a) - hints only + +LOGO INTEGRATION (CRITICAL): +- The connected-nodes logo (from reference) must be EMBOSSED INTO the visual content +- Position: bottom left, but WITHIN the circuit/data design, not in empty space +- The logo should be part of the circuit architecture - traces flow through it +- SMALL - about 3-5% of image width +- Same visual treatment as surrounding elements - muted, subtle +- Embossed texture - slight depth/luminosity difference only +- Should look like it was manufactured into the circuit board +- NOT floating in empty space - surrounded by and integrated with the design + +COMPOSITION: +- Visual activity and detail must cover the ENTIRE canvas +- Bottom left corner has circuit detail WITH the logo embossed into it +- No blank corners or empty zones +- Uniform density of visual interest + +CRITICAL: +- Logo MUST be integrated INTO the design, not placed in empty space +- Logo must be SMALL and SUBTLE +- Entire image should have visual content - no blank areas +- Subdued, muted, sophisticated +``` + +--- + +## Quick Reference + +| Parameter | Value | +|-----------|-------| +| Model | nano-banana-pro | +| Size | 4K | +| Aspect Ratio | 16:9 | +| Logo Reference | ~/Projects/Logos/ul-blue.png | +| Output Directory | ~/Projects/Wallpaper/ | +| Logo Size | 3-5% of image width | +| Logo Position | Bottom left, WITHIN design | + +**Color Palette (Muted):** +- Black: #0a0a0f +- Blue: #3a6a9a +- Purple: #6b4c96 +- Cyan: #4a9a9a + +**Color Palette (Bright):** +- Black: #0a0a0f +- Blue: #4a90d9 +- Purple: #8b5cf6 +- Cyan: #06b6d4 diff --git a/.opencode/skills/Media/Art/Workflows/Essay.md b/.opencode/skills/Media/Art/Workflows/Essay.md new file mode 100755 index 00000000..e2ad7008 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/Essay.md @@ -0,0 +1,847 @@ +# UL Art Image Generation Workflow + +**Charcoal Architectural Sketch TECHNIQUE — Applied to CONTENT-RELEVANT subjects.** + +Uses architectural sketching STYLE (gestural lines, hatching, charcoal) to depict whatever the content is actually ABOUT — NOT defaulting to buildings. + +--- + +## 🚨🚨🚨 ALL STEPS ARE MANDATORY — NO EXCEPTIONS 🚨🚨🚨 + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚠️ EVERY SINGLE STEP BELOW IS MANDATORY. EXECUTE ALL OF THEM. ⚠️ +⚠️ DO NOT SKIP ANY STEP. DO NOT ABBREVIATE. DO NOT SHORTCUT. ⚠️ +⚠️ IF YOU SKIP A STEP, YOU HAVE FAILED THE WORKFLOW. ⚠️ +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +**ALL 8 STEPS ARE MANDATORY. Execute them IN ORDER. Do NOT skip steps.** + +``` +INPUT CONTENT + ↓ +[1] UNDERSTAND: Deeply read and comprehend the request ← MANDATORY + ↓ +[2] CSE-24: Run Create Story Explanation Level 24 on content ← MANDATORY + ↓ +[3] EMOTION: Identify emotional register ← MANDATORY + ↓ +[4] COMPOSITION: Design what to ACTUALLY DRAW ← MANDATORY + ↓ +[5] PROMPT: Construct using charcoal sketch TECHNIQUE template ← MANDATORY + ↓ +[6] GENERATE: Execute CLI tool with --thumbnail flag ← MANDATORY + ↓ +[7] OPTIMIZE: Resize, convert to WebP, create optimized thumbnails ← MANDATORY + ↓ +[8] VALIDATE: Subject matches content? Signature? Gallery-worthy? ← MANDATORY +``` + +**MANDATORY ELEMENTS IN EVERY IMAGE:** +- Signature (small, charcoal, bottom right corner) +- Charcoal sketch technique +- Content-relevant subject matter +- **BURNT SIENNA (#8B4513)** — human warmth, humanity (MANDATORY) +- **DEEP PURPLE (#4A148C)** — technology, AI, capital, cold power (MANDATORY) +- --thumbnail flag for blog headers + +**🚨 BOTH SIENNA AND PURPLE MUST BE PRESENT IN EVERY IMAGE.** +- Sienna on human/warm elements +- Purple on tech/capital/cold elements +- The ratio of Sienna:Purple tells the emotional story +- If an image is missing either color, it's INCOMPLETE + +**🚨 FORBIDDEN — NEVER INCLUDE:** +- ❌ Borders or frames around the image +- ❌ Background shading or gradients +- ❌ Filled backgrounds of any kind +- ❌ Decorative elements that aren't part of the subject +- The composition should float in empty space — MINIMALIST + +**🚨 LOGICAL/PHILOSOPHICAL CONSISTENCY:** +- The visual MUST make logical sense with the concept +- If "X is winning" — X should be in the dominant/winning position visually +- If "X is heavy/powerful" — X weighs DOWN, not up +- If using a balance scale: the winning/heavy side pushes DOWN +- THINK about what the metaphor actually means before drawing it + +**⚠️ KNOWN ISSUE: Background removal may remove the signature.** +If the signature is missing after generation, you must add it manually or regenerate with the signature more integrated into the composition (not isolated in corner with empty space). + +--- + +## Step 1: Deeply Understand the Request — MANDATORY + +**Before doing ANYTHING, deeply read and understand:** + +1. **What is the content?** Read the full blog post, essay, or input material +2. **What is it ABOUT?** Not surface-level — the actual core concept/argument +3. **What are the key concrete elements?** Nouns, metaphors, imagery FROM the content +4. **What should NOT be drawn?** Architecture, buildings, vast spaces — UNLESS the content is about those +5. **Did the user provide GUIDANCE?** If the user gave direction about what to focus on, what the image should convey, or what angle to take — THIS TAKES PRIORITY over your own interpretation + +**🚨 USER GUIDANCE TAKES PRIORITY:** +If the user provides specific direction like: +- "Focus on the tension between X and Y" +- "The image should show Z losing" +- "Emphasize the human impact" +- Any other compositional or thematic guidance + +**USE THAT GUIDANCE** as the primary input for composition design. The CSE-24 supports the user's direction — it doesn't override it. + +**Output:** Clear understanding of the content's core subject matter + any user-provided guidance. + +--- + +## Step 2: Run Create Story Explanation Level 24 — MANDATORY + +**Extract the FULL narrative arc to understand the emotional core.** + +**🚨 ACTUALLY EXECUTE THIS COMMAND — DO NOT SKIP:** + +``` +Invoke the StoryExplanation Skill with: "Create a 24-item story explanation for this content" +``` + +Or use the slash command: +``` +/cse [paste the content or URL] +``` + +**What CSE-24 gives you:** +- The complete narrative arc: setup, tension, transformation, resolution +- Key metaphors and imagery from the piece +- The emotional journey +- What the piece is REALLY about +- The "wow" factor and significance + +**DO NOT PROCEED TO STEP 3 UNTIL YOU HAVE:** +1. Actually run the CSE command +2. Read and understood the 24-item output +3. Identified the key metaphors and emotional beats + +**Output:** 24-item story explanation revealing the emotional and conceptual core. + +--- + +## Step 3: Identify Emotional Register — MANDATORY + +**Read the aesthetic file and select the appropriate emotional vocabulary.** + +```bash +Read ~/.opencode/skills/Art/SKILL.md +``` + +**Match the contVent to one of these emotional registers:** + +| Register | When to Use | +|----------|-------------| +| **DREAD / FEAR** | AI takeover, existential risk, loss of control | +| **HOPE / POSSIBILITY** | Human potential, growth, positive futures | +| **CONTEMPLATION** | Philosophy, meaning, deep questions | +| **URGENCY / WARNING** | Security threats, calls to action | +| **WONDER / DISCOVERY** | Breakthroughs, encountering the vast | +| **DETERMINATION / EFFORT** | Overcoming obstacles, "gym" work | +| **MELANCHOLY / LOSS** | Endings, what's lost to progress | +| **CONNECTION / KINDNESS** | Human bonds, community | + +**Output:** Selected emotional register with specific vocabulary from the aesthetic file. + +These are just examples. It can be really anything which you will get from the Create Story Explanation Run. + +--- + +## Step 4: Design Composition — MANDATORY + +**🚨 CRITICAL: Design what to ACTUALLY DRAW based on the CONTENT — NOT defaulting to architecture.** + +### The Core Question + +**What is this content ABOUT, and what visual would represent THAT?** + +**🚨 IF USER PROVIDED GUIDANCE — START THERE:** +If the user gave direction in Step 1 (e.g., "focus on the tension between labor and capital", "show labor losing"), use that as your PRIMARY composition direction. The CSE-24 output SUPPORTS this direction — it doesn't replace it. + +Use the content from the create-story-explanation run to compose this. + +- Architecture is the TECHNIQUE (how to draw), NOT the required subject +- Only draw buildings/spaces if the content is about those things +- Draw what the content is actually about using architectural sketch style +- **User guidance shapes WHAT to draw; CSE-24 helps you understand the emotional core** + +### Composition Design Questions + +**🚨 STEP 4A: IDENTIFY THE PROBLEM (MOST CRITICAL)** + +Before designing anything, extract from the CSE-24 output: + +1. **What is the PROBLEM the essay addresses?** + - What's WRONG with the current state? + - What unfairness, mistake, or confusion exists? + - What are people doing wrong that this essay corrects? + - **The art should SHOW THIS PROBLEM visually** + +2. **What TYPE of problem is it?** + + Identify the problem archetype from the CSE output: + + | Problem Type | Description | Visual Metaphor | + |--------------|-------------|-----------------| + | **SORTING/CLASSIFICATION** | Need to categorize things into the right buckets | Scattered items + empty labeled bins | + | **COMMUNICATION** | Can't express ideas clearly, talking past each other | Tangled speech, broken telephone | + | **DOUBLE STANDARD** | Same thing judged differently based on source | Tilted scales, unfair judges | + | **MISDIRECTION** | Focusing on wrong thing, missing the real issue | Looking left while danger is right | + | **OVERWHELM** | Too much to process, can't see clearly | Flood of items, buried figure | + | **MISSING FRAMEWORK** | No structure to organize thinking | Chaos vs. empty scaffolding | + | **FALSE DICHOTOMY** | Forced choice that ignores better options | Two doors, hidden third path | + | **COMPLEXITY** | Simple thing made unnecessarily complicated | Tangled vs. straight path | + | **BLINDSPOT** | Can't see obvious thing right in front | Figure ignoring elephant | + + **🚨 THE PROBLEM TYPE SHAPES THE VISUAL METAPHOR.** + - SORTING problem → show the sorting challenge (scattered items, categories) + - COMMUNICATION problem → show the breakdown (garbled speech, confusion) + - DOUBLE STANDARD → show the unfairness (tilted scales, biased judge) + + **Examples with problem types:** + - ATHI framework → Problem TYPE: SORTING — "When you have a threat, which category does it belong to?" + - AI judgment essay → Problem TYPE: DOUBLE STANDARD — "Same output judged differently based on source" + - Security theater → Problem TYPE: MISDIRECTION — "Focus on visible but ineffective measures" + - Meaning essay → Problem TYPE: MISDIRECTION — "Chasing status instead of purpose" + + **THE ART SHOULD MAKE THE PROBLEM TYPE VISIBLE AT A GLANCE.** + Someone seeing the image should immediately understand WHAT KIND of problem this is. + +3. **What are the CONCRETE SUBJECTS in the content?** + - Extract specific nouns, metaphors, imagery FROM the content + - "Bowling pins" → draw bowling pins + - "Hands juggling" → draw hands juggling + - "Balance between capital and labor" → draw a balance/scale metaphor + - **The visual should match the content's core concept** + +4. **What VISUAL METAPHOR represents the PROBLEM?** + - What image would make someone say "Oh, I see what's wrong"? + - If the piece uses a metaphor USE THAT + - If no metaphor, what scene captures the problematic situation? + - **Show the unfairness, the mistake, the confusion** + +5. **Should there be FIGURES showing the problem?** + - Judges applying double standards + - People ignoring obvious issues + - Actors making the mistake the essay critiques + - The dynamic that needs to change + +6. **What is the EMOTIONAL treatment?** + - The emotion should match the PROBLEM being shown + - Unfairness → show the contrast, the tipped scale + - Confusion → show the misdirection, the wrong focus + - Loss → show what's fading, being ignored + +7. **What is the COMPOSITION?** + - Centered, minimalist, breathing space + - Arrange to make the PROBLEM OBVIOUS + - The viewer should "get it" immediately + - NOT busy, NOT cluttered + +### Composition Design Template + +``` +THE PROBLEM (from CSE-24 — MOST CRITICAL): +[What's WRONG with the current state that this essay addresses?] +[The unfairness, mistake, or confusion the essay critiques] +[This is what the art should SHOW] + +SUBJECT (WHAT TO DRAW — showing the problem): +[The actual visual subject that makes the PROBLEM visible] +[Key elements from the content's metaphors/imagery] + +VISUAL METAPHOR: +[The core image that represents the PROBLEM] +[What would make someone say "Oh, I see what's wrong"?] + +FIGURE TREATMENT (if applicable): +[Type of figures, their roles in showing the problem] +[Who is judging unfairly? Who is being judged? Who is making the mistake?] + +EMOTIONAL REGISTER: +[From Step 3] + +COMPOSITION: +[Arrangement that makes the PROBLEM OBVIOUS] +[The viewer should "get it" immediately] + +COLOR APPROACH: +[Warm:Cool ratio, which colors where] +``` + +**Output:** A specific composition design that makes the essay's PROBLEM VISIBLE at a glance. + +--- + +## Step 5: Construct the Prompt — MANDATORY + +**Use deep thinking to construct the final prompt using the charcoal sketch TECHNIQUE template.** + +### Prompt Template + +``` +Sophisticated charcoal sketch using architectural rendering TECHNIQUE. + +THE PROBLEM THIS ESSAY ADDRESSES (from Step 4 — drives the entire composition): +[What's WRONG with the current state that this essay critiques?] +[The art should make this problem VISIBLE AT A GLANCE] + +SUBJECT (WHAT TO DRAW — showing the problem): +[The actual visual subject that makes the PROBLEM visible] +[NOT defaulting to architecture — draw what makes the problem clear] + +EMOTIONAL REGISTER: [From Step 3] + +TECHNIQUE — GESTURAL ARCHITECTURAL SKETCH STYLE: +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚨 Architecture is the TECHNIQUE, not the required subject 🚨 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +- GESTURAL — quick, confident, energetic marks +- OVERLAPPING LINES — multiple strokes suggesting form +- HATCHING — cross-hatching creates depth and tone +- Loose charcoal/graphite pencil strokes throughout +- Variable line weight, some lines trailing off +- NOT clean vectors, NOT smooth +- Like Paul Rudolph, Lebbeus Woods sketches + +LINEWORK (applies to ALL subjects): +- [Specific line quality from emotional vocabulary] +- Visible hatching and gestural marks +- UNIFIED sketch quality across all elements + +HUMAN FIGURES (if present) — GESTURAL ABSTRACTED: +- MULTIPLE OVERLAPPING LINES suggesting the form +- Quick, confident, ENERGETIC gestural marks +- HATCHING and cross-hatching to create tone/depth +- 20-40 overlapping strokes creating the form +- Form EMERGES from accumulated linework +- Abstracted but with PRESENCE and WEIGHT +- FACES via simple charcoal marks (dark strokes for eyes, line for mouth) +- Burnt Sienna (#8B4513) WASH accent + +HANDS (if present) — GESTURAL: +- Same overlapping line technique +- Form suggested through accumulated marks +- Sienna wash accent for human warmth + +OBJECTS (if present) — GESTURAL SUGGESTED FORMS: +- Objects implied through hatching and gestural strokes +- Same energetic sketch quality +- Recognizable forms through accumulated lines +- NOT flat symbols — sketched with depth + +COMPOSITION — FULL FRAME IS MANDATORY: +- 🚨 SUBJECTS MUST FILL THE ENTIRE FRAME — edge to edge horizontally and vertically +- Subjects should nearly TOUCH the edges of the image +- NO large empty margins on any side +- If there's 20%+ empty space on any edge, the composition is WRONG +- MINIMALIST means few elements, NOT small elements with lots of empty space +- Subjects LARGE and DOMINANT — filling the available space + +COLOR — CHARCOAL DOMINANT, COLORS AS ACCENTS ONLY: +- CHARCOAL AND GRAY DOMINANT — 70-80% of image +- Colors INTEGRATED INTO forms — not splattered or applied on top +- Colors are the ESSENCE of elements (purple = cold capital, sienna = human warmth) +- Every bit of color belongs to a form — no random color floating in space + +Optional: Sign small in bottom right corner in charcoal. +NO other text. +``` + +### Prompt Quality Check + +Before generating, verify: +- [ ] **PROBLEM IS VISIBLE** — someone could understand what's wrong just from the image +- [ ] **Concrete subjects present** — nouns from title/content appear visually (not abstracted) +- [ ] Emotional register explicitly stated +- [ ] Figure treatment shows the problematic dynamic (if applicable) +- [ ] Light source and meaning specified +- [ ] Warm:cool ratio matches emotion +- [ ] "Charcoal sketch", "gestural", "hatching" explicitly stated +- [ ] Artist reference appropriate to emotion +- [ ] SPECIFIC to this content (couldn't be about something else) +- [ ] **Title test** — could someone guess the title from the image alone? + +**Output:** A complete prompt ready for generation. + +--- + +## Step 6: Execute the Generation — MANDATORY + +### Intent-to-Flag Mapping + +**Interpret user request and select appropriate flags:** + +#### Model Selection + +| User Says | Flag | When to Use | +|-----------|------|-------------| +| "fast", "quick", "draft" | `--model nano-banana` | Faster iteration, slightly lower quality | +| (default), "best", "high quality" | `--model nano-banana-pro` | Best quality + text rendering (recommended) | +| "flux", "stylistic variety" | `--model flux` | Different aesthetic, stylistic variety | + +#### Size Selection + +| User Says | Flag | Resolution | +|-----------|------|------------| +| "thumbnail", "small" | `--size 1K` | Quick previews | +| (default), "standard" | `--size 2K` | Standard blog headers | +| "high res", "large", "print" | `--size 4K` | Maximum resolution | + +#### Aspect Ratio + +| User Says | Flag | Use Case | +|-----------|------|----------| +| "square" | `--aspect-ratio 1:1` | Default for blog headers | +| "wide", "landscape", "banner" | `--aspect-ratio 16:9` | Wide banners | +| "portrait", "vertical" | `--aspect-ratio 9:16` | Vertical content | +| "ultrawide" | `--aspect-ratio 21:9` | Cinematic banners | + +#### Post-Processing + +| User Says | Flag | Effect | +|-----------|------|--------| +| "blog header" (default) | `--thumbnail` | Creates transparent + thumb versions | +| "transparent only" | `--remove-bg` | Just removes background | +| "with reference", "style like" | `--reference-image ` | Uses reference for style guidance | +| "variations", "options" | `--creative-variations 3` | Generates multiple versions | + +### Default Model: nano-banana-pro + +### 🚨 CRITICAL: Always Output to Downloads First + +**ALL images go to `~/Downloads/` for preview before final placement.** + +```bash +# ALWAYS output to Downloads first for user to review in Preview +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR PROMPT]" \ + --size 2K \ + --aspect-ratio 1:1 \ + --thumbnail \ + --output ~/Downloads/[descriptive-name].png + +# After user approves, THEN copy to final destination: +cp ~/Downloads/[name].png ~/Projects/Website/cms/public/images/ +cp ~/Downloads/[name]-thumb.png ~/Projects/Website/cms/public/images/ +``` + +### Construct Command Based on Intent + +Based on user's request and the mapping tables above, construct the CLI command: + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model [SELECTED_MODEL from table] \ + --prompt "[PROMPT from Step 5]" \ + --size [SELECTED_SIZE] \ + --aspect-ratio [SELECTED_RATIO] \ + [--thumbnail if blog header] \ + [--reference-image PATH if style reference provided] \ + [--creative-variations N if variations requested] \ + --output [OUTPUT_PATH] +``` + +### 🚨 MANDATORY: Blog Header Images → Use `--thumbnail` + +**ALL blog header images MUST use the `--thumbnail` flag.** + +The `--thumbnail` flag generates TWO versions: +1. `output.png` — Transparent background (for compositing over website backgrounds) +2. `output-thumb.png` — With `#EAE9DF` background (for thumbnails, social previews, OpenGraph) + +```bash +# Example: Generates both header.png AND header-thumb.png +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR PROMPT]" \ + --size 2K \ + --aspect-ratio 1:1 \ + --thumbnail \ + --output ~/Website/cms/public/images/my-header.png +``` + +**Why two versions?** +- **Transparent (`output.png`):** For the blog post inline image — composites beautifully over website background +- **Thumbnail (`output-thumb.png`):** For `thumbnail:` frontmatter field — visible in social previews, RSS readers, and anywhere that doesn't composite transparency + +### 🚨 CRITICAL: Blog Post Frontmatter Must Use `-thumb` Version + +**ALWAYS reference the `-thumb` file in the blog post's `thumbnail:` frontmatter field:** + +```yaml +# ✅ CORRECT - Use the -thumb version with sepia background +thumbnail: https://example.com/images/my-header-thumb.png + +# ❌ WRONG - Transparent version shows white background on social media +thumbnail: https://example.com/images/my-header.png +``` + +**The inline image in the post body uses the transparent version:** +```markdown +[![Description](/images/my-header.png)](/images/my-header.png) +``` + +**Summary:** +| File | Background | Use For | +|------|------------|---------| +| `output.png` | Transparent | Inline blog image (composites over page background) | +| `output-thumb.png` | Sepia #EAE9DF | `thumbnail:` frontmatter, social previews, OpenGraph | + +### Alternative: Standalone Background Removal + +For non-blog images that only need transparency, or to remove backgrounds after generation: + +```bash +# Use the Images Skill for background removal +bun ~/.opencode/skills/CORE/Tools/RemoveBg.ts /path/to/output.png + +# Or batch process multiple images +bun ~/.opencode/skills/CORE/Tools/RemoveBg.ts image1.png image2.png image3.png +``` + +### 🚨 COMPOSITION: USE FULL FRAME, MINIMALIST, NO BACKGROUNDS + +**SUBJECTS FILL THE FRAME. FEW ELEMENTS. NO FILLED BACKGROUNDS.** + +**ALWAYS include in prompt:** +- "USE FULL FRAME — subjects fill horizontal and vertical space" +- "Subjects LARGE and DOMINANT in the composition" +- "MINIMALIST — few elements, each intentional" +- "NO filled-in backgrounds — composition floats in empty space" +- "Clean, uncluttered — gallery-worthy simplicity" + +**Common failures:** +- ❌ WRONG: Subjects too small, too much empty space around them +- ❌ WRONG: Busy backgrounds with lots of detail +- ❌ WRONG: Filled-in architectural environments surrounding subject +- ❌ WRONG: Cluttered compositions with competing elements + +**The fix:** +- ✅ RIGHT: Subjects LARGE, filling the frame +- ✅ RIGHT: Few elements, each intentional — gallery aesthetic +- ✅ RIGHT: No background fill — subjects float in white/transparent space +- ✅ RIGHT: Full use of horizontal and vertical dimensions + +### Alternative Models + +| Model | Command | When to Use | +|-------|---------|-------------| +| **flux** | `--model flux --size 1:1 --remove-bg` | Maximum quality, more detail | +| **gpt-image-1** | `--model gpt-image-1 --size 1024x1024 --remove-bg` | Different interpretation | + +### Immediately Open + +```bash +open /path/to/output.png +``` + +--- + +## Step 7: Optimize Images (MANDATORY) + +**🚨 CRITICAL: This step happens AFTER generation and background removal, BEFORE validation.** + +### Why This Step Matters + +Generated images at 2K resolution (2048x2048) are 6-8MB each - far too large for web use. Optimization reduces file sizes by 90-95% while maintaining visual quality, ensuring fast page loads and better user experience. + +### Optimization Process + +**For ALL blog header images, automatically execute these commands:** + +```bash +# 1. Resize main image from 2K (2048x2048) to 1K (1024x1024) for web display +magick "~/Downloads/[name].png" -resize 1024x1024 "~/Downloads/[name]-1024.png" + +# 2. Convert resized image to WebP format (main display version) +cwebp -q 75 "~/Downloads/[name]-1024.png" -o "~/Downloads/[name].webp" + +# 3. Create optimized PNG thumbnail for social media (512x512) +magick "~/Downloads/[name]-thumb.png" -resize 512x512 -quality 80 "~/Downloads/[name]-thumb-optimized.png" + +# 4. Clean up temporary resized PNG +rm "~/Downloads/[name]-1024.png" + +# 5. Check final file sizes +ls -lh ~/Downloads/[name].webp ~/Downloads/[name]-thumb-optimized.png +``` + +**Expected Results:** +- Main WebP image: ~150-500KB (from ~7.5MB PNG) +- Optimized thumbnail: ~300-600KB (from ~6.8MB PNG) +- 90-95% total file size reduction + +### File Usage Matrix + +After optimization, you'll have these files: + +| File | Format | Size | Use For | +|------|--------|------|---------| +| `[name].png` | PNG | ~7.5MB | Archive/backup (original with transparency) | +| `[name].webp` | WebP | ~400KB | **Inline blog display** (reference this in post body) | +| `[name]-thumb.png` | PNG | ~6.8MB | Archive/backup (original with sepia background) | +| `[name]-thumb-optimized.png` | PNG | ~500KB | **Social media thumbnails** (reference this in `thumbnail:` frontmatter) | + +### Blog Post References + +**After optimization, update the blog post to use optimized versions:** + +```markdown +--- +thumbnail: https://example.com/images/[name]-thumb-optimized.png +--- + +[![Alt text](/images/[name].webp)](/images/[name].webp) +``` + +**🚨 CRITICAL: Use `.webp` for inline images and `-thumb-optimized.png` for thumbnails.** + +### Quality Settings Explained + +- **WebP quality 75**: Aggressive compression with minimal visible quality loss. Perfect for web display of charcoal sketches where slight compression artifacts are invisible. +- **Thumbnail quality 80**: Standard optimization for PNG social previews. Balances file size with quality for platforms that don't support WebP. +- **Resize to 1024x1024**: Optimal for web display. Higher resolutions provide no visual benefit on typical displays but significantly increase file sizes. + +### Error Handling + +**If WebP is over 500KB:** +```bash +# Lower quality further +cwebp -q 65 "~/Downloads/[name]-1024.png" -o "~/Downloads/[name].webp" +``` + +**If thumbnail is over 600KB:** +```bash +# Resize smaller or lower quality +magick "[name]-thumb.png" -resize 400x400 -quality 75 "[name]-thumb-optimized.png" +``` + +**If magick command not found:** +```bash +# Install ImageMagick +brew install imagemagick +``` + +**If cwebp command not found:** +```bash +# Install WebP tools +brew install webp +``` + +### Integration Notes + +- **This step is AUTOMATIC** - do not ask the user if optimization should be done +- **Happens in ~/Downloads/** before files are copied to final destination +- **Original high-res files are preserved** as archives +- **Validation (Step 8) checks the optimized files**, not the originals + +--- + +## Step 8: Validation (MANDATORY) + +**🚨 CRITICAL: This step is MANDATORY. Regenerate if validation fails.** + +### 🚨🚨🚨 ACTUALLY LOOK AT THE IMAGE AND THINK 🚨🚨🚨 + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚠️ DO NOT JUST CHECK BOXES. ACTUALLY ANALYZE THE IMAGE. ⚠️ +⚠️ LOOK AT IT. THINK ABOUT IT. ASK: DOES THIS MAKE SENSE? ⚠️ +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +### Open and Inspect + +```bash +open /path/to/generated-image.png +``` + +### 🧠 CRITICAL ANALYSIS (DO THIS FIRST — BEFORE THE CHECKLIST) + +**STOP. Look at the image. Answer these questions honestly:** + +**0. SIGNATURE CHECK:** +- Is signature present in the BOTTOM RIGHT CORNER of the image? (if included) +- Not bottom center. Not near the subject. BOTTOM RIGHT CORNER. +- Is the signature correctly rendered? (no literal prompt text) +- If missing, wrong location, or wrong text → REGENERATE + +**0.5. PROMPT LITERAL INTERPRETATION CHECK:** +- Did the model take prompt instructions literally? (e.g., writing literal prompt text instead of a signature) +- Are there any instruction words visible in the image that shouldn't be? +- Did labels come out as intended? (e.g., "A T H I" not "Actor Technique Harm Impact" spelled out) +- If prompt instructions appear as text in image → REGENERATE with clearer wording + +**1. PHYSICAL REALITY CHECK:** +- Do objects obey physics? (heavy things fall DOWN, scales tip toward heavy side) +- If there's a scale: TRACE THE BEAM WITH YOUR EYES + - Find the fulcrum (center pivot) + - Which end of the beam is LOWER? That's the heavy side. + - The heavy/winning side's end of the beam points DOWN toward the ground + - The light/losing side's end of the beam points UP toward the sky +- If there's gravity: do things fall in the right direction? +- Are proportions reasonable? +- Would this scene make physical sense in the real world? + +**2. LOGICAL CONSISTENCY CHECK:** +- Does the visual metaphor match the concept? +- If "X is winning" — is X visually dominant/powerful? +- If "X is losing" — is X diminished/fading/rising (on a scale)? +- Does cause match effect in the image? + +**3. PHILOSOPHICAL ALIGNMENT CHECK:** +- Does the image represent the MEANING of the content? +- Would the user look at this and say "yes, that captures it"? +- Is the emotional register correct? +- Does the image argue the same point as the content? + +**🚨 IF ANY OF THESE FAIL — STOP AND REGENERATE. DO NOT PROCEED.** + +**Example failures:** +- ❌ Signature missing or not in bottom right corner (if signature was requested) +- ❌ Scale shows heavy side's beam going UP (physically impossible — heavy pulls DOWN) +- ❌ "Capital winning" but capital looks small/weak +- ❌ "Labor losing" but labor looks strong/dominant +- ❌ Objects floating when they should fall +- ❌ Visual contradicts the conceptual argument + +### Validation Checklist + +**🚨 MANDATORY ELEMENTS (if ANY are missing, REGENERATE):** +- [ ] **SIGNATURE PRESENT** — signed small in charcoal, bottom right corner (if requested) +- [ ] **PROBLEM TYPE VISIBLE** — the problem type (sorting, double standard, etc.) is immediately obvious +- [ ] **Subject matches CONTENT** — drew what the piece is ABOUT, not defaulted to architecture +- [ ] **Concrete subjects visible** — key nouns/metaphors from content actually appear +- [ ] **Title test passes** — someone could guess the topic from the image alone +- [ ] **Labels readable** — if there are labels (like A, T, H, I), they are clearly visible and correct +- [ ] **NOT defaulting to buildings/spaces** — unless content is actually about architecture +- [ ] **CSE-24 insights captured** — the visual represents the narrative arc discovered in Step 2 +- [ ] **User guidance incorporated** — if the user gave direction, it's reflected in the image +- [ ] **Background removed** — transparent background, or re-run background removal if it failed + +**TECHNIQUE (all required):** +- [ ] Charcoal sketch quality — visible strokes, hatching, gestural marks +- [ ] NOT clean vectors or cartoony +- [ ] Gestural overlapping lines suggesting form +- [ ] Gallery-worthy sophistication + +**FIGURE STYLE (if figures present):** +- [ ] **GESTURAL ABSTRACTION** — multiple overlapping lines suggesting form +- [ ] **ENERGETIC LINEWORK** — quick, confident, scratchy strokes +- [ ] **HATCHING creates depth** — cross-hatching for tone and shadow +- [ ] **20-40 overlapping strokes** per figure — form emerges from accumulated marks +- [ ] **Figures have PRESENCE** — abstracted but with weight and dimension +- [ ] **Faces have EMOTION** — via charcoal marks (dark strokes for eyes, line for mouth, head tilt) +- [ ] Human = organic flowing gestural marks + sienna wash +- [ ] Robot = angular rigid gestural marks + purple wash +- [ ] Looks like Paul Rudolph / Lebbeus Woods architectural sketches + +**COLOR (all required — BOTH SIENNA AND PURPLE MANDATORY):** +- [ ] **CHARCOAL/GRAY DOMINANT** — 70-85% of image +- [ ] **BURNT SIENNA (#8B4513) PRESENT** — on human/warm elements (MANDATORY) +- [ ] **DEEP PURPLE (#4A148C) PRESENT** — on tech/capital/cold elements (MANDATORY) +- [ ] Colors as washes/accents, not solid fills +- [ ] Sienna:Purple ratio matches emotional story + +**EMOTION (all required):** +- [ ] Emotional register clear — matches Step 2 selection +- [ ] Architecture reinforces the feeling +- [ ] Figure treatment (if present) supports the mood +- [ ] Light placement serves the narrative +- [ ] Overall atmosphere matches intended emotion + +**COMPOSITION (all required):** +- [ ] **FULL FRAME** — subjects nearly touch all edges, NO large empty margins +- [ ] **SUBJECTS LARGE** — dominant, filling the available space +- [ ] **NO BACKGROUND FILL** — floats in empty/transparent space (but subjects are LARGE) +- [ ] **KAI SIGNATURE** — small cursive charcoal in BOTTOM RIGHT CORNER +- [ ] **MARGIN CHECK** — is there more than 20% empty space on any edge? If yes, REGENERATE + +**QUALITY (all required):** +- [ ] Could hang in a gallery next to Piranesi +- [ ] Could be concept art for a Villeneuve film +- [ ] Distinctive — NOT generic AI illustration +- [ ] Sophisticated — rewards closer looking +- [ ] **Transparent background** — used `--remove-bg` flag + +### If Validation Fails + +**Common failures and fixes:** + +| Problem | Fix | +|---------|-----| +| **Subjects too SMALL** | 🚨 Add "LARGE SUBJECTS that FILL THE FRAME", "minimal empty space around subjects" | +| **Too much empty space** | 🚨 Add "minimal empty space around subjects", "subjects FILL THE FRAME" | +| **Background dominates** | 🚨 Add "subjects are DOMINANT focus", "subjects LARGE" | +| **Setting not recognizable** | Add "SETTING: [location]" with "2-3 KEY OBJECTS that establish location" — gym needs weights/bench visible | +| **Figures look like CARTOONS** | 🚨 Add "GESTURAL ABSTRACTION", "like Paul Rudolph sketches", "Lebbeus Woods figure studies", "OVERLAPPING LINES" | +| **Lines are SINGLE/CLEAN** | 🚨 Add "MULTIPLE OVERLAPPING LINES", "20-40 strokes per figure", "hatching for depth", "energetic gestural marks" | +| **Figures are FLAT** | 🚨 Add "HATCHING creates depth", "figures have PRESENCE and WEIGHT", "form emerges from accumulated marks" | +| **No emotion on faces** | Add "dark charcoal strokes for eyes area", "line for mouth angle", "head TILT conveys emotion", "SUGGESTED expression" | +| **Too illustrated/rendered** | Add "GESTURAL SKETCH quality", "quick energetic marks", "like architectural concept sketches" | +| **Objects too detailed** | Add "objects implied through hatching", "same sketch quality as figures", "suggested forms" | +| Wrong emotion | Adjust POSTURE and LINE QUALITY — leaning = relaxed, rigid = tense, dense hatching = weight | +| Colors too solid | Emphasize "atmospheric washes", "tints over charcoal", "not solid fills" | +| Generic AI look | Add "Paul Rudolph", "Lebbeus Woods", "architectural concept sketches" references | + +**Regeneration Process:** +1. Identify failed criteria +2. Update prompt with specific fixes +3. Regenerate +4. Re-validate +5. Repeat until ALL criteria pass + +--- + +## Quick Reference + +### The Workflow in Brief + +``` +1. UNDERSTAND → Deeply read and comprehend the content +2. CSE-24 → Run Create Story Explanation (24 items) to extract narrative arc +3. EMOTION → Match to register in ~/.opencode/skills/CORE/aesthetic.md +4. COMPOSITION → Design what to DRAW (content-relevant, NOT defaulting to architecture) +5. PROMPT → Build using charcoal sketch TECHNIQUE template +6. GENERATE → Execute with nano-banana-pro + --thumbnail flag +7. OPTIMIZE → Resize to 1024, convert to WebP, create optimized thumbnails +8. VALIDATE → Subject matches content? Technique correct? Gallery-worthy? +``` + +### Emotional Quick-Select + +| Content About... | Register | Warm:Cool | Visual Treatment | +|------------------|----------|-----------|------------------| +| AI danger | Dread | 20:80 | Heavy, dense, oppressive linework | +| Human potential | Hope | 80:20 | Light, ascending, open | +| Philosophy | Contemplation | 50:50 | Balanced, still, thoughtful | +| Security threats | Urgency | 60:40 | Fractured, dynamic, tense | +| Discoveries | Wonder | 40:60 | Revelatory, light breaking through | +| Building skills | Determination | 70:30 | Strong, grounded, effort-showing | +| What's lost | Melancholy | 40:60 | Fading, dissolving, trailing off | +| Community | Connection | 90:10 | Warm, intimate, multiple figures | + +### The UL Look Checklist + +Before submitting any image: +- ✅ **Subject matches CONTENT** — drew what the piece is ABOUT (not defaulting to architecture) +- ✅ **CSE-24 was run** — actually executed the story explanation command +- ✅ **Concrete subjects visible** — key nouns/metaphors from content appear +- ✅ Charcoal sketch TECHNIQUE — gestural, atmospheric, hatching +- ✅ Emotional register — clear and intentional +- ✅ Color washes — warm/cool ratio tells the story +- ✅ Gallery-worthy — sophisticated, not generic AI +- ✅ **--thumbnail flag used** — both transparent and sepia versions generated +- ✅ **OPTIMIZATION COMPLETED** — resized to 1024, converted to WebP, optimized thumbnails created +- ✅ Signature — small charcoal bottom right (optional) + +--- + +**The workflow: UNDERSTAND → CSE-24 → EMOTION → COMPOSITION → PROMPT → GENERATE (--thumbnail) → OPTIMIZE → VALIDATE → Complete** diff --git a/.opencode/skills/Media/Art/Workflows/Frameworks.md b/.opencode/skills/Media/Art/Workflows/Frameworks.md new file mode 100755 index 00000000..a6a10db4 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/Frameworks.md @@ -0,0 +1,362 @@ +# Visual Mental Models & Frameworks Workflow + +**Hand-drawn frameworks, mental models, and conceptual diagrams using UL aesthetic.** + +Creates **VISUAL FRAMEWORKS** — signature mental models illustrated as memorable diagrams with editorial hand-drawn style. + +--- + +## Purpose + +Visual frameworks illustrate mental models, thinking frameworks, and conceptual relationships. These are **signature frameworks** made visual — 2x2 matrices, Venn diagrams, conceptual maps with personality and editorial style. + +**Use this workflow for:** +- 2x2 matrices and quadrant models +- Venn diagrams with editorial flair +- Conceptual relationship maps +- "The [Your Name] Framework for X" +- Mental models and thinking tools +- Decision frameworks + +--- + +## Visual Aesthetic: Structured Concepts with Editorial Style + +**Think:** Smart conceptual diagram, but hand-drawn and visually interesting + +### Core Characteristics +1. **Clear structure** — Framework shape is recognizable (2x2, Venn, pyramid, etc.) +2. **Hand-drawn organic** — Imperfect lines, wobbly circles, human touch +3. **Editorial aesthetic** — Flat colors, black linework, UL palette +4. **Labels integrated** — Typography part of visual design +5. **Conceptual clarity** — Framework immediately understandable +6. **Memorable visual** — Becomes THE reference image for this framework +7. **Thoughtful color** — Strategic use of purple/teal to show relationships + +--- + +## Color System for Frameworks + +### Structure +``` +Black #000000 — All framework structure (axes, circles, boxes) +``` + +### Concept Differentiation +``` +Deep Purple #4A148C — Concept area 1 or optimal quadrant +Deep Teal #00796B — Concept area 2 or contrast quadrant +Charcoal #2D2D2D — All text and labels +``` + +### Background +``` +White #FFFFFF or Light Cream #F5E6D3 +``` + +### Color Strategy +- Framework lines/structure in black +- Purple for "ideal" or primary concept +- Teal for "secondary" or contrast concept +- Subtle fills or accents, not solid color blocks + +--- + +## 🚨 MANDATORY WORKFLOW STEPS + +### Step 1: Define Framework Structure + +**Identify the mental model:** + +1. **What framework type?** + - 2x2 matrix (four quadrants) + - Venn diagram (overlapping circles) + - Pyramid/hierarchy + - Spectrum/continuum + - Triangle (three-way balance) + - Other conceptual shape + +2. **What are the dimensions/concepts?** + - For 2x2: X-axis concept, Y-axis concept + - For Venn: Circle 1 concept, Circle 2 concept, overlap meaning + - For pyramid: Levels from bottom to top + +3. **What are the quadrants/areas/zones?** + - Name and describe each region + - Which is "optimal" or most important? + +**Output:** +``` +FRAMEWORK TYPE: [2x2 Matrix / Venn Diagram / Pyramid / etc.] + +FRAMEWORK NAME: "The [Your Name] Framework for [Topic]" + +DIMENSIONS: +- X-axis: [Concept] (Low → High) +- Y-axis: [Concept] (Low → High) + +QUADRANTS/AREAS: +1. [Name]: [Description] — [Color if highlighted] +2. [Name]: [Description] — [Color if highlighted] +3. [Name]: [Description] +4. [Name]: [Description] + +OPTIMAL ZONE: [Which quadrant/area is ideal] +``` + +--- + +### Step 2: Design Framework Visual + +**Plan the visual representation:** + +1. **Framework geometry:** + - How large is each element + - Proportions and spacing + - Symmetry or intentional asymmetry + +2. **Labeling strategy:** + - Where axis labels go + - Where quadrant names go + - Additional annotations + +3. **Color assignment:** + - Which quadrant gets purple (optimal) + - Which gets teal (contrast or secondary) + - Rest remain black/white + +**Output:** +``` +VISUAL STRUCTURE: +[Describe the framework shape, e.g.:] +- Two intersecting axes forming four quadrants +- X-axis labeled [left] to [right] +- Y-axis labeled [bottom] to [top] +- Each quadrant labeled with concept name + +COLOR CODING: +- Top-right quadrant (optimal): Purple #4A148C accent +- Bottom-left quadrant (contrast): Teal #00796B accent +- Other quadrants: Black structure only + +TYPOGRAPHY PLACEMENT: +- Title (Tier 1): Top center +- Axis labels (Tier 2): Along axes +- Quadrant labels (Tier 2): Inside each quadrant +- Annotations (Tier 3): Strategic notes on key quadrants +``` + +--- + +### Step 3: Construct Prompt + +### Prompt Template + +``` +Hand-drawn conceptual framework diagram in editorial style. + +STYLE REFERENCE: Mental model illustration, conceptual diagram with personality, smart person's framework sketch + +BACKGROUND: [White #FFFFFF OR Light Cream #F5E6D3] — clean, flat + +AESTHETIC: +- Hand-drawn framework structure (wobbly lines, organic shapes) +- Variable stroke weight (axes thicker, details thinner) +- Imperfect but intentional geometry (circles not perfect, axes slightly wavy) +- Editorial flat color with strategic purple/teal accents +- Clear conceptual structure with human touch + +FRAMEWORK TYPE: [2x2 Matrix / Venn Diagram / Pyramid / Spectrum / etc.] + +FRAMEWORK STRUCTURE: +[Describe the specific framework geometry, e.g.:] +- Two hand-drawn perpendicular axes (black) forming cross +- X-axis: [Low concept] on left → [High concept] on right +- Y-axis: [Low concept] on bottom → [High concept] on top +- Four quadrants created by intersection + +TYPOGRAPHY SYSTEM (3-TIER): + +TIER 1 - FRAMEWORK TITLE (Advocate Block Display): +- "[FRAMEWORK NAME IN ALL-CAPS]" +- Font: Advocate style, extra bold, hand-lettered, all-caps +- Size: 3x larger than body text +- Color: Black #000000 +- Position: Top center +- Example: "THE SECURITY VS CONVENIENCE FRAMEWORK" + +TIER 2 - LABELS & CONCEPTS (Concourse Sans): +- Axis labels: "[X-axis concept]", "[Y-axis concept]" +- Quadrant names: "[Quadrant 1]", "[Quadrant 2]", etc. +- Font: Concourse geometric sans-serif +- Size: Medium readable +- Color: Charcoal #2D2D2D +- Position: Along axes and inside quadrants + +TIER 3 - ANNOTATIONS (Advocate Condensed Italic): +- Insight notes: "*optimal zone*", "*avoid this quadrant*" +- Font: Advocate condensed italic +- Size: 60% of Tier 2 +- Color: Purple #4A148C or Teal #00796B for emphasis +- Position: Near relevant quadrants/areas + +QUADRANTS/AREAS TO SHOW: +[List each region with description, e.g.:] + +TOP-RIGHT QUADRANT: +- Label: "[Name]" +- Description: [What this represents] +- Color: Purple (#4A148C) subtle accent/highlight — OPTIMAL ZONE +- Annotation: "*ideal state*" in purple italic + +TOP-LEFT QUADRANT: +- Label: "[Name]" +- Description: [What this represents] +- Color: Black structure only + +BOTTOM-RIGHT QUADRANT: +- Label: "[Name]" +- Description: [What this represents] +- Color: Teal (#00796B) subtle accent — CONTRAST ZONE + +BOTTOM-LEFT QUADRANT: +- Label: "[Name]" +- Description: [What this represents] +- Color: Black structure only + +[Adjust based on framework type - Venn would describe circles, pyramid would describe levels, etc.] + +COLOR USAGE: +- Black (#000000) for all framework structure (axes, circles, lines) +- Deep Purple (#4A148C) for [optimal zone] — subtle fill or accent +- Deep Teal (#00796B) for [contrast zone] — subtle accent +- Charcoal (#2D2D2D) for all text except emphasized annotations + +CRITICAL REQUIREMENTS: +- Hand-drawn imperfect geometry (NOT digital precision) +- Framework structure immediately recognizable +- Clear labels in 3-tier typography hierarchy +- Strategic color on 1-2 key zones only (subtle, not solid fills) +- No gradients, flat colors only +- Editorial illustration aesthetic maintained +- Conceptually clear and memorable + +Optional: Sign small in bottom right corner in charcoal (#2D2D2D). +``` + +--- + +### Step 4: Determine Aspect Ratio + +| Framework Type | Aspect Ratio | Reasoning | +|----------------|--------------|-----------| +| 2x2 Matrix | 1:1 | Square for balanced quadrants | +| Venn Diagram | 1:1 | Square for circular symmetry | +| Pyramid | 1:1 or 4:3 | Vertical emphasis | +| Horizontal spectrum | 16:9 | Wide for left-right continuum | +| Triangle | 1:1 | Balanced for three concepts | + +**Default: 1:1 (square)** — Works for most framework types + +--- + +### Step 5: Execute Generation + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR PROMPT]" \ + --size 2K \ + --aspect-ratio 1:1 \ + --output /path/to/framework.png +``` + +**Model Recommendation:** nano-banana-pro (best text rendering for labels) + +**Immediately Open:** +```bash +open /path/to/framework.png +``` + +--- + +### Step 6: Validation (MANDATORY) + +#### Must Have +- [ ] **Framework structure clear** — 2x2 / Venn / Pyramid immediately recognizable +- [ ] **Readable labels** — All text legible in 3-tier hierarchy +- [ ] **Hand-drawn aesthetic** — Imperfect lines, organic shapes, human quality +- [ ] **Strategic color** — Purple on optimal zone, teal on contrast, not everywhere +- [ ] **Conceptually memorable** — This becomes THE reference image for framework +- [ ] **Editorial style** — Maintains UL flat color, black linework aesthetic + +#### Must NOT Have +- [ ] Perfect digital geometry (too clean) +- [ ] Illegible or cluttered text +- [ ] Color overload (solid fills everywhere) +- [ ] Confusing structure (can't identify framework type) +- [ ] Corporate/boring diagram look +- [ ] Gradients or shadows + +#### If Validation Fails + +| Problem | Fix | +|---------|-----| +| Too precise/digital | "Hand-drawn wobbly axes, organic imperfect circles, human sketch quality" | +| Text unreadable | Increase label sizes, simplify annotations | +| Over-colored | "Subtle purple accent on optimal zone only, rest black structure" | +| Confusing structure | Simplify framework, stronger geometry cues | +| Looks corporate | Reference "editorial conceptual illustration, Saul Steinberg style" | +| Not memorable | Add strategic annotation showing the insight: "*this is the sweet spot*" | + +--- + +## Example Use Cases + +### Example 1: "Security vs Convenience Framework" +- **Type:** 2x2 matrix +- **Axes:** Security (low → high), Convenience (low → high) +- **Quadrants:** Vulnerable, Fortress, Balanced (purple), Abandoned +- **Color:** Purple on "Balanced" optimal quadrant +- **Aspect:** 1:1 + +### Example 2: "Human 3.0 Capability Venn" +- **Type:** Venn diagram (3 circles) +- **Circles:** Human abilities, AI capabilities, Tools +- **Overlap:** Where magic happens (purple) +- **Color:** Purple on center overlap +- **Aspect:** 1:1 + +### Example 3: "Threat Modeling Pyramid" +- **Type:** Pyramid (4 levels) +- **Levels:** Assets (bottom) → Threats → Vulnerabilities → Mitigations (top) +- **Color:** Purple on top level (actions), teal on bottom (foundation) +- **Aspect:** 4:3 + +--- + +## Quick Reference + +**Framework Formula:** +``` +1. Define framework structure (type, dimensions, quadrants) +2. Design visual (geometry, labeling, color assignment) +3. Construct prompt with clear structure +4. Choose square aspect ratio (usually 1:1) +5. Generate with nano-banana-pro +6. Validate for clarity and memorability +``` + +**Color Strategy:** +- Framework structure: Black +- Optimal zone: Purple (subtle accent) +- Contrast zone: Teal (subtle accent) +- Text: Charcoal (except emphasized annotations) + +**Key Principle:** +- This becomes THE reference image people remember for this framework +- Must be conceptually clear AND visually distinctive + +--- + +**The workflow: Define → Design → Construct → Generate → Validate → Complete** diff --git a/.opencode/skills/Media/Art/Workflows/Maps.md b/.opencode/skills/Media/Art/Workflows/Maps.md new file mode 100755 index 00000000..63f56b65 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/Maps.md @@ -0,0 +1,415 @@ +# Conceptual Maps & Landscapes Workflow + +**Hand-drawn conceptual maps showing idea territories and domain landscapes using UL aesthetic.** + +Creates **CONCEPTUAL MAPS** — illustrated maps of idea territories, not geographic locations, with editorial hand-drawn style. + +--- + +## Purpose + +Conceptual maps visualize abstract territories, domains, and relationships as illustrated landscapes. These are **metaphorical maps** showing where ideas, concepts, or domains exist in relation to each other. + +**Use this workflow for:** +- "The Landscape of AI Safety" +- "Map of Cybersecurity Domains" +- "Territory of Human Capabilities" +- Domain overviews showing relationships +- Conceptual geography of a field +- Orientation guides for complex topics + +--- + +## Visual Aesthetic: Illustrated Cartography + +**Think:** Hand-drawn fantasy map, but for conceptual territories + +### Core Characteristics +1. **Map structure** — Islands, continents, rivers, mountains representing ideas +2. **Cartographic elements** — Borders, labels, landmarks +3. **Hand-drawn** — Imperfect coastlines, wobbly borders, human quality +4. **Metaphorical geography** — Physical features represent conceptual relationships +5. **Labeled territories** — Clear naming of domains/concepts +6. **Editorial style** — Flat colors, black linework, UL aesthetic +7. **Navigable** — Helps understand the "lay of the land" in a field + +--- + +## Color System for Conceptual Maps + +### Land/Territory +``` +Black #000000 — All coastlines, borders, terrain features +Light fills — Very subtle cream/beige for different territories +``` + +### Domain Differentiation +``` +Deep Purple #4A148C — Primary domain or "optimal" territory +Deep Teal #00796B — Secondary domain or adjacent territory +Charcoal #2D2D2D — All labels and annotations +``` + +### Background (Water/Space) +``` +Light Cream #F5E6D3 — Warm neutral "ocean" +OR +White #FFFFFF — Clean empty space around territories +``` + +### Color Strategy +- Coastlines/borders all black linework +- Territories subtly differentiated by color (purple, teal, or just labeled) +- Background as "ocean" or empty space +- Text all charcoal for readability + +--- + +## 🚨 MANDATORY WORKFLOW STEPS + +### Step 1: Define Conceptual Geography + +**Identify the territories:** + +1. **What domain are you mapping?** + - Subject area (e.g., AI capabilities, security landscape) + - What are the major territories/concepts + +2. **What are the territories?** + - List 4-8 major domains/concepts + - How do they relate spatially + +3. **What metaphors represent relationships?** + - Close territories = related concepts + - Ocean between = very different domains + - Mountains = barriers or challenges + - Rivers = connections or flows + - Bridges = integrations + +**Output:** +``` +MAP SUBJECT: "The [Domain] Landscape" + +TERRITORIES (Major Domains): +1. [Territory name] — [What it represents] +2. [Territory name] — [What it represents] +3. [Territory name] — [What it represents] +... + +SPATIAL RELATIONSHIPS: +- [Territory A] and [Territory B]: Adjacent (related concepts) +- [Territory C]: Island (isolated domain) +- Ocean between [X] and [Y]: Very different domains +- Mountain range along [Z]: Barrier/challenge + +METAPHORICAL FEATURES: +- Rivers: [What they represent, e.g., "data flows"] +- Mountains: [What they represent, e.g., "technical barriers"] +- Bridges: [What they represent, e.g., "integration points"] +``` + +--- + +### Step 2: Design Map Layout + +**Plan the cartography:** + +1. **Map orientation:** + - North-up traditional + - Centered around key territory + - Horizontal spread + - Vertical layers + +2. **Territory shapes:** + - Continents (large connected domains) + - Islands (isolated concepts) + - Archipelagos (related cluster) + - Peninsulas (partially connected) + +3. **Features to include:** + - Coastlines/borders + - Landmarks (mountains, rivers) + - Labels for territories + - Legend or compass rose + +**Output:** +``` +MAP ORIENTATION: [North-up / Centered / Horizontal] + +TERRITORY LAYOUT: +[Describe the spatial arrangement, e.g.:] +- Center: Large continent of [Primary Domain] (purple) +- East: Island of [Domain 2] (teal) +- West: Peninsula of [Domain 3] connected to center +- North: Mountain range representing [Barrier] +- South: Ocean of [Unknown Territory] + +FEATURES: +- Coastlines: All hand-drawn, wobbly, imperfect +- Rivers: [Number] rivers showing [connections] +- Mountains: Along [borders] representing [challenges] +- Bridges: Connecting [Territory A] to [Territory B] + +LABELS: +- Territory names inside borders (Tier 2) +- Feature labels for mountains, rivers (Tier 3) +- Map title at top (Tier 1) + +COLOR CODING: +- [Primary territory]: Purple (#4A148C) subtle fill +- [Secondary territory]: Teal (#00796B) subtle fill +- Other territories: Light cream or white +- All borders/coastlines: Black +``` + +--- + +### Step 3: Construct Prompt + +### Prompt Template + +``` +Hand-drawn conceptual map in editorial cartography style. + +STYLE REFERENCE: Fantasy map, hand-drawn cartography, illustrated territory map + +BACKGROUND: [Light Cream #F5E6D3 / White #FFFFFF] — represents ocean/empty space + +AESTHETIC: +- Hand-drawn map (wobbly coastlines, imperfect borders) +- Cartographic elements (territories, features, labels) +- Variable stroke weight (coastlines thicker, details thinner) +- Editorial flat color with strategic purple/teal territories +- Sketch quality, not polished digital map + +MAP SUBJECT: "[The X Landscape]" or "[Map of Y Domains]" + +CARTOGRAPHIC STRUCTURE: +[Describe the overall map, e.g.:] +- Central large landmass: [Primary Domain] +- Surrounding islands and territories representing related concepts +- Ocean/empty space between distant domains +- Physical features (mountains, rivers, bridges) showing relationships + +TYPOGRAPHY SYSTEM (3-TIER): + +TIER 1 - MAP HEADER & SUBTITLE (Valkyrie Two-Part System): +Header (Main Title): +- "[Header Text]" — Left-justified at top +- Font: Valkyrie serif italic (elegant, sophisticated) +- Size: Large - 3-4x body text (prominent, commanding attention) +- Style: Italicized, sentence case or title case (NOT all-caps) +- Color: Black #000000 (or Purple #4A148C for emphasis) +- Position: Top-left with margin +- Example: "The Landscape of Artificial Intelligence" + +Subtitle (Clarifying Detail): +- "[Subtitle Text]" — Below header +- Font: Valkyrie serif regular (warm, readable) +- Size: Small - 1-1.5x body text (noticeably smaller than header, supportive) +- Style: Regular (NOT italicized), sentence case (first letter capitalized, rest lowercase except proper nouns) +- Color: Black #000000 or Charcoal #2D2D2D +- Position: Small gap below header, aligned left +- Example: "Domains, Territories, and Frontiers" + +TIER 2 - TERRITORY NAMES (Concourse Sans): +- Territory labels: "[Domain 1]", "[Domain 2]", etc. +- Font: Concourse geometric sans-serif +- Size: Medium readable +- Color: Charcoal #2D2D2D +- Position: Inside territory borders or nearby + +TIER 3 - FEATURE LABELS (Advocate Condensed Italic): +- Geographic features: "*mountains of complexity*", "*river of data*" +- Font: Advocate condensed italic +- Size: 60% of Tier 2 +- Color: Charcoal #2D2D2D +- Position: Near relevant features + +TERRITORIES TO MAP: +[List each territory with details, e.g.:] + +CENTRAL TERRITORY: [Primary Domain Name] +- Shape: Large irregular continent in center of map +- Coastline: Hand-drawn wobbly black line +- Fill: Subtle Purple (#4A148C) tint (very light, not solid) +- Features: [Mountains / Rivers / Landmarks within] +- Label: "[Domain name]" in Concourse sans +- Represents: [Core concept] + +EASTERN TERRITORY: [Secondary Domain Name] +- Shape: Medium island separated by ocean from center +- Coastline: Hand-drawn irregular outline +- Fill: Subtle Teal (#00796B) tint +- Features: [Specific landmarks] +- Label: "[Domain name]" +- Represents: [Related but distinct concept] + +WESTERN TERRITORY: [Domain Name] +- Shape: Peninsula connected to central continent +- Coastline: Wobbly black line +- Fill: Light cream or white (minimal color) +- Connection: Narrow land bridge to center +- Label: "[Domain name]" +- Represents: [Partially connected concept] + +[Continue for all territories...] + +GEOGRAPHIC FEATURES: +- Mountain range: Hand-drawn peaks along [border/territory] + - Represents: [Barrier or challenge] + - Color: Black (#000000) linework + - Label: "*mountains of [X]*" in italic + +- Rivers: Wobbly flowing lines connecting territories + - Represents: [Connections or data flows] + - Color: Black (#000000) or Teal (#00796B) + - Label: "*river of [Y]*" + +- Bridges: Small illustrated bridges connecting islands/territories + - Represents: [Integration points] + - Color: Black (#000000) + +- Compass rose: Small decorative compass in corner (optional) + - Hand-drawn, simple + - Purple (#4A148C) north arrow + +OCEAN/EMPTY SPACE: +- Background: Light cream or white +- Represents: Unknown territory or very different domains +- Blank areas between distant territories + +COLOR USAGE: +- Black (#000000) for ALL coastlines, borders, geographic features +- Deep Purple (#4A148C) subtle fill on [primary territory] +- Deep Teal (#00796B) subtle fill on [secondary territory] +- Light cream/white fills on other territories +- Charcoal (#2D2D2D) for all text labels + +CRITICAL REQUIREMENTS: +- Hand-drawn cartographic style (wobbly lines, imperfect shapes) +- Clear territory labels in 3-tier typography +- Physical features represent conceptual relationships +- Strategic color on 1-2 key territories (subtle fills, not solid) +- No gradients, flat colors only +- Navigable and understandable as conceptual geography +- Editorial illustration aesthetic maintained + +Optional: Sign small in bottom corner in charcoal (#2D2D2D). +``` + +--- + +### Step 4: Determine Aspect Ratio + +| Map Type | Aspect Ratio | Reasoning | +|----------|--------------|-----------| +| Wide horizontal landscape | 16:9 or 21:9 | Spread-out territories | +| Balanced map | 1:1 | Symmetric territory distribution | +| Vertical territories | 9:16 or 4:3 | Stacked or layered domains | +| Poster map | 4:3 | Classic map proportions | + +**Default: 16:9 (horizontal)** — Classic map orientation + +--- + +### Step 5: Execute Generation + +```bash +bun run ~/.opencode/skills/Media/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR PROMPT]" \ + --size 2K \ + --aspect-ratio 16:9 \ + --output /path/to/conceptual-map.png +``` + +**Model Recommendation:** nano-banana-pro (best for territory labels) or flux (stylistic variety) + +**Immediately Open:** +```bash +open /path/to/conceptual-map.png +``` + +--- + +### Step 6: Validation (MANDATORY) + +#### Must Have +- [ ] **Map structure clear** — Territories, coastlines, borders obvious +- [ ] **Conceptual geography** — Physical features represent ideas +- [ ] **Readable labels** — Territory names and feature labels legible +- [ ] **Hand-drawn** — Wobbly coastlines, imperfect borders, human quality +- [ ] **Strategic color** — Purple/teal on key territories (subtle fills) +- [ ] **Navigable** — Helps understand relationships between concepts +- [ ] **Editorial aesthetic** — Maintains UL flat color, black linework + +#### Must NOT Have +- [ ] Perfect digital map (too clean) +- [ ] Realistic geographic features +- [ ] Illegible territory names +- [ ] Color chaos (too many territory colors) +- [ ] Confusing metaphors (features don't represent concepts clearly) +- [ ] Gradients or shadows + +#### If Validation Fails + +| Problem | Fix | +|---------|-----| +| Too precise/digital | "Hand-drawn wobbly coastlines, imperfect irregular borders, sketch quality" | +| Unclear metaphors | Strengthen feature descriptions: "mountains represent [specific barrier]" | +| Labels unreadable | Increase label sizes, clearer placement inside territories | +| Too complex | Reduce to 4-6 major territories, simplify features | +| Looks like real map | "Conceptual geography, metaphorical territories, abstract cartography" | +| Missing relationships | Add rivers/bridges showing connections: "river connecting [A] to [B]" | + +--- + +## Example Use Cases + +### Example 1: "The AI Capabilities Landscape" +- **Territories:** Reasoning (center, purple), Creativity (island, teal), Perception (west peninsula), Action (east island) +- **Features:** Mountains of complexity, Rivers of data, Bridges of integration +- **Aspect:** 16:9 + +### Example 2: "Cybersecurity Domain Map" +- **Territories:** Offensive (red island), Defensive (center continent), Governance (north peninsula), Human Layer (south) +- **Features:** Ocean of unknown threats, Mountain range of technical barriers +- **Aspect:** 16:9 + +### Example 3: "The TELOS Territory" +- **Territories:** Questions (center), Context (surrounding), Blockers (west mountains), Constraints (north), Solutions (east coast) +- **Features:** Rivers connecting domains, Bridges from problems to solutions +- **Aspect:** 1:1 + +--- + +## Quick Reference + +**Conceptual Map Formula:** +``` +1. Define conceptual geography (territories, relationships, metaphors) +2. Design map layout (orientation, shapes, features) +3. Construct prompt with cartographic structure +4. Choose aspect ratio for map type +5. Generate with nano-banana-pro +6. Validate for clarity and navigability +``` + +**Color Strategy:** +- Coastlines/borders: Black +- Primary territory: Purple (subtle fill) +- Secondary territory: Teal (subtle fill) +- Other territories: Light/white +- Labels: Charcoal + +**Metaphor Key:** +- Physical proximity = conceptual relationship +- Ocean = very different domains +- Mountains = barriers/challenges +- Rivers = connections/flows +- Bridges = integration points + +--- + +**The workflow: Define → Design → Construct → Generate → Validate → Complete** diff --git a/.opencode/skills/Media/Art/Workflows/Mermaid.md b/.opencode/skills/Media/Art/Workflows/Mermaid.md new file mode 100755 index 00000000..5e4aac7b --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/Mermaid.md @@ -0,0 +1,896 @@ +# Mermaid-Style Technical Diagrams with Excalidraw Aesthetic + +**Hand-drawn technical diagrams combining Mermaid structure with Excalidraw sketchy aesthetic and UL color scheme.** + +Creates **EXCALIDRAW-STYLE MERMAID DIAGRAMS** — flowcharts, sequence diagrams, state machines, and other technical diagrams with whiteboard hand-drawn feel, derived from content via story explanation. + +--- + +## Purpose + +The Mermaid workflow creates structured technical diagrams (like Mermaid.js generates) but with a **hand-drawn whiteboard aesthetic** (like Excalidraw) while maintaining **UL editorial color scheme**. Unlike generic technical diagrams, these follow specific diagram grammar (flowcharts, sequences, states, etc.) and are derived from content analysis, not hand-specified. + +**Use this workflow for:** +- Flowcharts showing decision logic and process flows +- Sequence diagrams showing interactions over time +- State diagrams showing state transitions +- Class diagrams showing object relationships +- ER diagrams showing data models +- Git graphs showing branching/merging +- Any diagram where Mermaid structure + hand-drawn aesthetic is ideal + +**This is NOT for:** +- Freeform architecture diagrams (use technical-diagrams.md) +- Abstract conceptual metaphors (use editorial-illustration.md) +- Data visualizations (use visualize.md) + +--- + +## Mermaid Diagram Types Supported + +### 1. Flowcharts +**When:** Decision trees, algorithmic logic, process flows with conditions +``` +Start → Decision? → [Yes] → Action → End + → [No] → Different Action → End +``` + +### 2. Sequence Diagrams +**When:** Interactions between entities/actors over time +``` +User → API: Request +API → Database: Query +Database → API: Results +API → User: Response +``` + +### 3. State Diagrams +**When:** State machines, status transitions, lifecycle flows +``` +[Idle] → (trigger) → [Processing] → (complete) → [Done] + → (error) → [Failed] +``` + +### 4. Class Diagrams +**When:** Object relationships, inheritance, composition +``` +User ──has many──> Posts +User ──belongs to──> Organization +Post ──has many──> Comments +``` + +### 5. Entity Relationship Diagrams +**When:** Database schemas, data models, table relationships +``` +Customer ||──o{ Order : places +Order ||──o{ LineItem : contains +Product ||──o{ LineItem : ordered_in +``` + +### 6. Gantt Charts +**When:** Project timelines, task dependencies, schedules +``` +Task 1: Jan 1 - Jan 15 +Task 2: Jan 10 - Jan 30 (depends on Task 1) +Task 3: Jan 20 - Feb 10 +``` + +### 7. Git Graphs +**When:** Branching strategies, merge flows, version control +``` +main ──> feature branch ──> merged back ──> main + └──> hotfix ──────────> merged ──────> main +``` + +--- + +## Excalidraw Aesthetic Principles + +**Think:** Whiteboard sketch, not polished Visio diagram + +### Visual Characteristics +1. **Wobbly boxes** — Rectangles with rough, hand-drawn edges (not perfect) +2. **Sketchy arrows** — Arrows with slight wobble, not ruler-straight +3. **Rough edges** — Everything has organic imperfection +4. **Hand-lettered text** — Labels look handwritten, not typed +5. **Whiteboard feel** — Looks like someone drew this on a whiteboard +6. **Variable line weight** — Heavier for boxes, lighter for arrows +7. **Crossing-out style** — Double-line or rough crossing for connections + +### What This Looks Like +- Diamond decision boxes with wobbly edges +- Arrows that curve slightly even when "straight" +- Rectangles that aren't quite rectangular +- Text that's imperfectly aligned +- Circles that are slightly oval +- Lines that overlap at connections with organic joins + +### AVOID +- Perfect geometric shapes +- Ruler-straight arrows +- Digital precision +- Smooth curves (too polished) +- Perfect alignment +- Vector crispness + +--- + +## Color System for Mermaid Diagrams + +### Structure +``` +Black #000000 — All primary linework (boxes, arrows, decision diamonds) +``` + +### Flow Emphasis +``` +Deep Purple #4A148C — Critical path, main flow, important states +Deep Teal #00796B — Alternative paths, secondary flows, supporting states +Charcoal #2D2D2D — All text labels and annotations +``` + +### Background +``` +Light Cream #F5E6D3 — Whiteboard/sketch paper feel +OR +White #FFFFFF — Clean background +``` + +### Color Strategy for Diagram Types + +**Flowcharts:** +- Main path boxes: Purple outlines +- Alternative branches: Teal outlines +- Decision diamonds: Black outlines +- All connecting arrows: Black + +**Sequence Diagrams:** +- Critical actor/entity: Purple box +- Secondary actors: Teal boxes +- All messages/arrows: Black +- Activation boxes: Purple fills (subtle) + +**State Diagrams:** +- Active/important states: Purple +- Transition states: Teal +- Terminal states: Black +- Arrows: Black with labels + +**Class/ER Diagrams:** +- Key entities: Purple boxes +- Related entities: Teal boxes +- Relationships: Black arrows with labels +- Inheritance: Black with different arrow style + +--- + +## 🚨 MANDATORY WORKFLOW STEPS + +### Step 1: Run Story Explanation on Content (MANDATORY) + +**🎯 CRITICAL: You MUST use /cse (Create Story Explanation) with 24-item length.** + +This extracts the full narrative arc and identifies the STRUCTURE that needs to be diagrammed. + +```bash +/cse [content or URL] +``` + +The 24-item output reveals: +- Process flows and sequences +- Decision points and conditions +- State transitions and triggers +- Entity relationships and interactions +- Temporal ordering and dependencies + +**Do NOT skip this step. Do NOT manually derive diagram structure without running /cse first.** + +**Output from CSE Analysis:** +``` +24-ITEM STORY EXPLANATION: +1. [Item 1] +2. [Item 2] +... +24. [Item 24] + +STRUCTURAL ELEMENTS IDENTIFIED: +- Processes: [List of distinct processes/actions] +- Decisions: [List of decision points with conditions] +- States: [List of distinct states] +- Entities: [List of actors/objects/components] +- Flows: [List of connections and sequences] +- Conditions: [List of triggers and transitions] +``` + +--- + +### Step 2: Determine Optimal Mermaid Diagram Type + +**Based on CSE analysis, identify the best diagram type:** + +#### Decision Framework + +**Choose FLOWCHART when:** +- Content describes process with decision points +- "If/then/else" logic is present +- Multiple paths based on conditions +- Algorithm or procedure being explained +- Clear start and end points + +**Choose SEQUENCE DIAGRAM when:** +- Content describes interactions between entities over time +- Request/response patterns present +- Multiple actors communicating +- Temporal ordering is critical +- API calls, messaging, or protocols + +**Choose STATE DIAGRAM when:** +- Content describes states and transitions +- Status changes are central +- Lifecycle or workflow states +- Event-driven transitions +- System can be in discrete states + +**Choose CLASS/ER DIAGRAM when:** +- Content describes relationships between objects/entities +- Data structures or models +- Inheritance or composition patterns +- Database schemas +- Object hierarchies + +**Choose GANTT CHART when:** +- Content describes project timeline +- Task dependencies and schedules +- Milestones and deadlines +- Parallel and sequential tasks + +**Choose GIT GRAPH when:** +- Content describes version control workflow +- Branching strategies +- Merge patterns +- Release flows + +**Multiple diagram types possible?** +- Choose the PRIMARY type that captures the main structure +- Can note that alternative representations exist +- Focus on the most illuminating visualization + +**Output from Type Selection:** +``` +DIAGRAM TYPE: [Flowchart / Sequence / State / Class / ER / Gantt / Git Graph] + +RATIONALE: [Why this type best represents the content] + +ALTERNATIVE TYPES CONSIDERED: [If any, and why not chosen] +``` + +--- + +### Step 3: Extract Diagram Structure from CSE + +**Map the 24-item story explanation to diagram components:** + +#### For Flowcharts +Identify: +- **Start node:** Where does the process begin? +- **Process nodes:** What actions happen? (rectangles) +- **Decision nodes:** What choices are made? (diamonds) +- **End nodes:** Where does it terminate? (rounded rectangles) +- **Flows:** How do nodes connect? (arrows with labels) + +#### For Sequence Diagrams +Identify: +- **Actors/Entities:** Who/what participates? (boxes at top) +- **Messages:** What communications occur? (arrows between lifelines) +- **Temporal order:** What sequence? (top to bottom) +- **Activations:** When are entities active? (vertical bars) + +#### For State Diagrams +Identify: +- **States:** What are the distinct states? (rounded boxes) +- **Initial state:** Where does it start? (filled circle) +- **Final state:** Where does it end? (double circle) +- **Transitions:** What triggers state changes? (arrows with conditions) +- **Events:** What causes transitions? + +#### For Class/ER Diagrams +Identify: +- **Entities/Classes:** What objects exist? (boxes) +- **Attributes:** What properties? (inside boxes) +- **Relationships:** How do they relate? (arrows with cardinality) +- **Inheritance:** What hierarchies? (special arrows) + +**Output from Structure Extraction:** +``` +DIAGRAM COMPONENTS: + +[For Flowchart Example:] +NODES: +- Start: [Label] +- Process 1: [Action description] (rectangle, purple) +- Decision 1: [Question] (diamond, black) +- Process 2a: [Action if yes] (rectangle, purple) +- Process 2b: [Action if no] (rectangle, teal) +- End: [Terminal state] (rounded, black) + +FLOWS: +- Start → Process 1: (black arrow) +- Process 1 → Decision 1: (black arrow) +- Decision 1 → Process 2a: "Yes" (black arrow) +- Decision 1 → Process 2b: "No" (black arrow) +- Process 2a → End: (black arrow) +- Process 2b → End: (black arrow) + +CRITICAL PATH: [Start → Process 1 → Decision 1 → Process 2a → End] +(This path highlighted with purple boxes) +``` + +--- + +### Step 4: Design Excalidraw-Style Layout + +**Plan the whiteboard sketch aesthetic:** + +#### A. Spatial Arrangement +- **Flowcharts:** Top-to-bottom or left-to-right flow +- **Sequence diagrams:** Actors across top, interactions descending +- **State diagrams:** Circular or network layout +- **Class diagrams:** Hierarchical tree or interconnected network +- **ER diagrams:** Entities spread out with relationships between +- **Gantt:** Horizontal timeline with tasks stacked vertically +- **Git graph:** Branching tree structure + +#### B. Hand-Drawn Styling +Each node type gets Excalidraw treatment: + +**Rectangles (Process boxes):** +``` +Instead of: ┌──────────┐ +This: ╱──────────╲ (wobbly, not perfect) + │ Process │ (slightly tilted) + ╲──────────╱ (organic edges) +``` + +**Diamonds (Decisions):** +``` +Instead of: ◇ +This: ◊ (wobbly, asymmetric, hand-drawn diamond) +``` + +**Arrows:** +``` +Instead of: ────────→ +This: ∼∼∼∼∼∼∼→ (slightly wavy, organic curve) +``` + +**Text:** +``` +Instead of: Arial 12pt +This: Hand-lettered appearance, slight slant, imperfect +``` + +#### C. Visual Hierarchy +- **Primary path/flow:** Purple boxes, thicker lines +- **Secondary paths:** Teal boxes, standard lines +- **Structure/framework:** Black lines and shapes +- **Labels/text:** Charcoal, hand-lettered style + +**Output from Layout Design:** +``` +SPATIAL LAYOUT: [Top-to-bottom flow / Left-to-right / Circular / etc.] + +EXCALIDRAW STYLING NOTES: +- All boxes: Wobbly rectangles, slightly tilted +- Arrows: Gentle curves even when "straight" +- Diamonds: Asymmetric, hand-drawn feel +- Circles: Slightly oval, imperfect +- Text: Hand-lettered, natural slant + +NODE POSITIONING: +[Describe relative positions, e.g.:] +- Start node: Top center +- Process 1: Below start, slightly left +- Decision 1: Below process 1, centered +- Process 2a: Bottom left (Yes branch) +- Process 2b: Bottom right (No branch) +- End nodes: Bottom (two endpoints merge) + +CONNECTION PATHS: +[Describe arrow routes with organic curves] +``` + +--- + +### Step 5: Construct Comprehensive Prompt + +**Build the generation prompt with Excalidraw + Mermaid + UL aesthetic:** + +### Prompt Template + +``` +Hand-drawn Mermaid [DIAGRAM TYPE] in Excalidraw whiteboard sketch style. + +STYLE REFERENCE: Excalidraw whiteboard diagram, hand-drawn flowchart, sketchy technical diagram + +BACKGROUND: [Light Cream #F5E6D3 / White #FFFFFF] — whiteboard/sketch paper feel + +AESTHETIC: +- Excalidraw hand-drawn style (wobbly, sketchy, organic) +- Whiteboard sketch quality (looks hand-drawn, not digital) +- Rough edges on all shapes (rectangles not perfect, circles slightly oval) +- Sketchy arrows (gentle curves, slight wobble, not ruler-straight) +- Hand-lettered text labels (imperfect alignment, natural slant) +- Variable line weight (boxes thicker, arrows medium, details thinner) +- Organic connections (lines join naturally, small overlaps at nodes) +- NO digital precision, NO perfect geometry, NO smooth vectors + +DIAGRAM TYPE: [Flowchart / Sequence Diagram / State Diagram / etc.] + +OVERALL STRUCTURE: +[Describe the complete diagram flow, e.g.:] +- [DIAGRAM TYPE] showing [what it represents] +- Layout: [Top-to-bottom / Left-to-right / etc.] +- [Number] main nodes/states/entities +- Critical path highlighted in purple +- Alternative paths in teal + +TYPOGRAPHY SYSTEM (4-FONT HIERARCHY): + +TIER 1 - DIAGRAM HEADER & SUBTITLE (Valkyrie Two-Part System): +Header (Main Title): +- "[Header Text]" +- Font: Valkyrie serif italic (elegant, sophisticated) +- Size: Large - 3-4x body text (prominent, commanding attention) (refined, not overwhelming) +- Style: Italicized, sentence case or title case (NOT all-caps) +- Color: Black #000000 (or Purple #4A148C for emphasis) +- Position: Top-left with margin +- Example: "User Authentication Flow" + +Subtitle (Clarifying Detail): +- "[Subtitle Text]" +- Font: Valkyrie serif regular (warm, readable) +- Size: Small - 1-1.5x body text (noticeably smaller than header, supportive) +- Style: Regular (NOT italicized), sentence case (first letter capitalized, rest lowercase except proper nouns) +- Color: Black #000000 or Charcoal #2D2D2D +- Position: Small gap below header, aligned left +- Example: "Security Validation Process" + +TIER 2 - NODE LABELS & DESCRIPTIONS (Concourse T3 + Valkyrie): +Technical Node Labels — Concourse T3: +- Labels inside boxes/nodes for technical identifiers +- Font: Concourse T3 geometric sans, functional, precise +- Size: Medium readable +- Color: Charcoal #2D2D2D +- Style: Hand-drawn interpretation, slightly imperfect +- Examples: "Auth Service", "Database", "API Gateway" + +Human/Process Descriptions — Valkyrie serif: +- Process descriptions, human-readable actions +- Font: Valkyrie serif, warm, narrative +- Size: Medium (same as Concourse T3) +- Color: Charcoal #2D2D2D +- Style: Natural, readable, explanatory +- Examples: "Validate credentials", "Check permissions", "Send confirmation" + +TIER 3 - EDGE LABELS & ANNOTATIONS (Advocate Italic + Valkyrie): +Edge Labels/Conditions — Advocate Condensed (or Valkyrie): +- Labels on arrows/connections, conditions +- Font: Advocate condensed (voice) or Valkyrie (neutral) +- Size: 60% of Tier 2 +- Color: Charcoal #2D2D2D +- Style: Hand-written notes along arrows +- Examples: "Yes", "No", "timeout", "success", "error" + +Insights/Commentary — Advocate Italic: +- Critical observations, editorial voice +- Font: Advocate condensed italic +- Size: 60% of Tier 2 +- Color: Purple #4A148C or Teal #00796B +- Examples: "*this is where it breaks*", "*performance bottleneck*" + +DIAGRAM COMPONENTS (Excalidraw Style): + +[LIST EACH NODE/COMPONENT:] + +NODE 1: [Type - e.g., START NODE] +- Shape: [Rounded rectangle / Circle / etc.] +- Label: "[Label text]" +- Style: Wobbly hand-drawn edges, slightly asymmetric +- Color: Black (#000000) outline, no fill OR subtle cream fill +- Size: [Relative size] +- Position: [Location in layout] + +NODE 2: [Type - e.g., PROCESS BOX] +- Shape: Rectangle with rough edges +- Label: "[Action description]" +- Style: Wobbly lines, slightly tilted, hand-drawn imperfection +- Color: Purple (#4A148C) outline — CRITICAL PATH +- Fill: Light cream or transparent +- Size: [Relative size] +- Position: [Below Node 1] + +NODE 3: [Type - e.g., DECISION DIAMOND] +- Shape: Diamond/rhombus with wobbly edges +- Label: "[Question?]" +- Style: Hand-drawn, asymmetric diamond, organic edges +- Color: Black (#000000) outline +- Fill: Transparent or very light cream +- Size: [Relative size] +- Position: [Below Node 2, centered] + +NODE 4: [Type - e.g., PROCESS BOX - ALTERNATIVE PATH] +- Shape: Rectangle with rough edges +- Label: "[Alternative action]" +- Style: Wobbly lines, slightly tilted +- Color: Teal (#00796B) outline — SECONDARY PATH +- Fill: Light cream or transparent +- Size: [Relative size] +- Position: [To the side, alternative branch] + +NODE 5: [Type - e.g., END NODE] +- Shape: Rounded rectangle or double circle +- Label: "[Terminal state]" +- Style: Hand-drawn, organic curves +- Color: Black (#000000) outline +- Fill: Subtle fill or transparent +- Size: [Relative size] +- Position: [Bottom of diagram] + +[Continue for all nodes...] + +CONNECTIONS (Sketchy Arrows): + +ARROW 1: [Node A] → [Node B] +- Style: Sketchy hand-drawn arrow, slight curve even if "straight" +- Path: [Describe route, e.g., "curves gently from Node 1 down to Node 2"] +- Color: Black (#000000) +- Label: [Optional label text, e.g., "process" or condition] +- Arrowhead: Hand-drawn triangle, slightly asymmetric + +ARROW 2: [Node C] → [Node D] +- Style: Sketchy arrow with organic wobble +- Path: [Describe route] +- Color: Black (#000000) +- Label: "[Yes]" in small hand-written style +- Arrowhead: Rough triangle + +[Continue for all arrows/connections...] + +SPECIAL ELEMENTS (if applicable): + +[For Sequence Diagrams:] +- Actor boxes: Hand-drawn rectangles at top +- Lifelines: Dashed vertical lines (hand-drawn, wobbly) +- Activation boxes: Rectangles on lifelines (purple for key) +- Messages: Arrows between lifelines with labels + +[For State Diagrams:] +- Initial state: Filled circle (hand-drawn) +- Final state: Double circle (wobbly concentric circles) +- State boxes: Rounded rectangles with rough edges +- Transition arrows: Curved arrows with condition labels + +[For Class/ER Diagrams:] +- Class boxes: Three-section rectangles (wobbly dividers) +- Relationship lines: Different arrow styles for different relationships +- Cardinality labels: Hand-written "1", "*", "0..1", etc. + +COLOR USAGE (Strategic, UL Palette): +- Black (#000000): All primary structure (most boxes, all arrows) +- Deep Purple (#4A148C): Critical path nodes, main flow, key entities (10-20% of nodes) +- Deep Teal (#00796B): Alternative paths, secondary entities (5-10% of nodes) +- Charcoal (#2D2D2D): All text labels (node labels, arrow labels) +- Background: Light Cream (#F5E6D3) OR White (#FFFFFF) + +CRITICAL REQUIREMENTS: +- Excalidraw hand-drawn aesthetic (wobbly, sketchy, organic) +- Mermaid diagram structure ([chosen type] grammar) +- UL color scheme (purple for critical, teal for secondary, black structure) +- 3-tier typography (title, node labels, edge labels) +- Whiteboard sketch feel (not polished, not digital) +- All shapes imperfect (rectangles wobbly, circles oval, arrows curved) +- Variable line weight (thicker boxes, medium arrows, thin details) +- Hand-lettered text (natural slant, imperfect alignment) +- Strategic color (not everything colored, mostly black structure) +- Readable and clear despite sketch style +- Follows [Mermaid diagram type] conventions + +VALIDATION CHECKPOINTS: +- Does it look hand-drawn on a whiteboard (not digital)? +- Are all geometric shapes imperfect (wobbly edges)? +- Is the diagram type structure clear (flowchart/sequence/state/etc.)? +- Can you follow the flow/logic/sequence easily? +- Is the critical path obvious (purple highlights)? +- Are labels readable despite hand-lettered style? +- Does it maintain UL aesthetic (flat colors, no gradients)? + +Optional: Sign small in bottom right corner in charcoal (#2D2D2D). +``` + +--- + +### Step 6: Determine Aspect Ratio + +**Based on diagram type and complexity:** + +| Diagram Type | Typical Aspect Ratio | Reasoning | +|--------------|---------------------|-----------| +| Flowchart (vertical) | 9:16 or 4:3 | Top-to-bottom flow | +| Flowchart (horizontal) | 16:9 or 21:9 | Left-to-right flow | +| Sequence diagram | 16:9 | Actors across, time down | +| State diagram | 1:1 | Circular/network layout | +| Class diagram | 1:1 or 4:3 | Tree or network | +| ER diagram | 16:9 or 1:1 | Entity spread | +| Gantt chart | 16:9 or 21:9 | Timeline horizontal | +| Git graph | 16:9 | Branching horizontal | + +**Default: 16:9** — Works for most diagram types + +--- + +### Step 7: Generate with Nano Banana Pro + +**Execute with optimal model for text-heavy diagrams:** + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR COMPREHENSIVE PROMPT]" \ + --size 2K \ + --aspect-ratio [chosen ratio] \ + --output /path/to/mermaid-diagram.png +``` + +**Why Nano Banana Pro:** +- Best text rendering (critical for labels on nodes and arrows) +- Handles complex multi-element compositions +- Can render hand-drawn aesthetic while maintaining readability +- Excellent for technical diagrams with lots of labels + +**Background rules based on use case:** +``` +ONE-OFF / QUICK PREVIEW: Keep white background (#FFFFFF) +GOING INTO BLOG/WEBSITE: Remove background for transparency +``` + +**For blog/website use** — use the **Images skill** for background removal: + +```bash +bun ~/.opencode/skills/CORE/Tools/RemoveBg.ts /path/to/mermaid-diagram.png +``` + +**Immediately open:** +```bash +open /path/to/mermaid-diagram.png +``` + +--- + +### Step 8: Comprehensive Validation (MANDATORY) + +**Validate across all dimensions:** + +#### Diagram Correctness +- [ ] **Structure accurate:** Diagram follows [type] conventions +- [ ] **Logic clear:** Flow/sequence/states make sense +- [ ] **Complete:** All elements from CSE represented +- [ ] **Connections correct:** Arrows point to right places +- [ ] **Labels accurate:** Node and edge labels match content + +#### Excalidraw Aesthetic +- [ ] **Hand-drawn feel:** Looks sketched on whiteboard +- [ ] **Wobbly shapes:** No perfect rectangles/circles +- [ ] **Sketchy arrows:** Organic curves, not ruler-straight +- [ ] **Imperfect text:** Hand-lettered, natural slant +- [ ] **Variable line weight:** Thicker boxes, thinner details +- [ ] **Organic joins:** Connections look natural + +#### UL Editorial Style +- [ ] **Color strategic:** Purple on critical (10-20%), teal on secondary (5-10%) +- [ ] **Black dominant:** Most structure in black +- [ ] **Typography hierarchy:** 3 tiers clear +- [ ] **No gradients:** Flat colors maintained +- [ ] **Signature:** Present in corner (optional) + +#### Readability & Clarity +- [ ] **Labels readable:** All text legible despite hand-drawn style +- [ ] **Flow obvious:** Can follow the diagram easily +- [ ] **Critical path clear:** Purple highlights guide eye +- [ ] **Not cluttered:** Spacing adequate, not cramped +- [ ] **Scale works:** Readable at thumbnail and full-size + +#### If Validation Fails + +**Common issues and fixes:** + +| Problem | Diagnosis | Fix | +|---------|-----------|-----| +| **Too polished/digital** | Missing Excalidraw aesthetic | Emphasize: "Wobbly rectangles, sketchy arrows, hand-drawn on whiteboard, imperfect shapes" | +| **Perfect geometry** | Shapes too clean | "All rectangles with rough edges, circles slightly oval, organic imperfection throughout" | +| **Can't follow flow** | Unclear structure | Strengthen arrow directions, add labels, clarify critical path with purple | +| **Labels unreadable** | Text too sketchy or small | Increase label size, simplify hand-lettering: "Readable hand-lettered style" | +| **Wrong diagram type** | Doesn't match content | Return to Step 2, reconsider diagram type based on CSE | +| **Missing Mermaid structure** | Doesn't follow conventions | Add proper diagram grammar: decision diamonds for flowcharts, lifelines for sequence, etc. | +| **Color overload** | Too much purple/teal | Limit: "Purple on 2-3 critical nodes only, teal on 1-2 secondary, rest black" | +| **Looks generic** | Missing UL or Excalidraw style | Combine both: "Excalidraw wobbly whiteboard sketch + UL purple/teal strategic accents" | + +**Regeneration Process:** +1. Identify specific validation failures +2. Update prompt with targeted fixes from table +3. Regenerate with refined prompt +4. Re-validate against all checkpoints +5. Repeat until ALL validation criteria pass + +**CRITICAL: Do not declare completion until validation passes.** + +--- + +## Diagram Type Deep Dives + +### Flowchart Specifics + +**Node Types:** +- **Start/End:** Rounded rectangles (wobbly ovals) +- **Process:** Rectangles with rough edges +- **Decision:** Diamonds (asymmetric, hand-drawn) +- **Input/Output:** Parallelograms (tilted, wobbly) +- **Predefined Process:** Rectangles with double side lines + +**Flow Rules:** +- Always flows one direction (typically top-down or left-right) +- Arrows never cross if avoidable +- Decision diamonds have exactly 2 exits (Yes/No or True/False) +- Loops back with curved arrows + +**Color Strategy:** +- Purple: Main success/happy path +- Teal: Error handling or alternative paths +- Black: All decision nodes and structure + +--- + +### Sequence Diagram Specifics + +**Components:** +- **Actors/Entities:** Boxes at top (wobbly rectangles) +- **Lifelines:** Vertical dashed lines (hand-drawn, imperfect) +- **Messages:** Horizontal arrows between lifelines +- **Activations:** Vertical bars on lifelines (when entity is active) +- **Return messages:** Dashed arrows going back + +**Temporal Flow:** +- Always top to bottom (time flows down) +- Left to right is actor/entity ordering +- Synchronous: Solid arrow +- Asynchronous: Open arrow +- Return: Dashed arrow + +**Color Strategy:** +- Purple: Critical actor/main entity +- Teal: Secondary actors +- Black: All messages/arrows +- Purple fill: Activation bars for critical entity + +--- + +### State Diagram Specifics + +**Components:** +- **States:** Rounded rectangles (wobbly) +- **Initial state:** Filled circle (hand-drawn blob) +- **Final state:** Double circle (concentric wobbly circles) +- **Transitions:** Arrows with event labels +- **Conditions:** Guards in brackets on arrows + +**State Rules:** +- Each state is distinct and named +- Transitions show event/condition +- Initial state has only outgoing arrows +- Final state has only incoming arrows + +**Color Strategy:** +- Purple: Active/current/important states +- Teal: Intermediate states +- Black: Terminal and error states +- All transitions: Black arrows + +--- + +### Class/ER Diagram Specifics + +**Components:** +- **Classes/Entities:** Three-section boxes (name, attributes, methods) +- **Relationships:** Arrows with labels +- **Cardinality:** 1, *, 0..1, 1..* on relationship lines +- **Inheritance:** Triangle arrow pointing to parent +- **Composition:** Diamond on containing class + +**Relationship Types:** +- Association: Plain arrow +- Inheritance: Arrow with triangle head +- Composition: Arrow with filled diamond +- Aggregation: Arrow with open diamond + +**Color Strategy:** +- Purple: Core/important entities +- Teal: Related entities +- Black: All relationship lines +- Charcoal: All attribute/method text + +--- + +## Example Scenarios + +### Example 1: Flowchart for Authentication Flow +**Content:** Blog post about user authentication process +**CSE Result:** 24-item story showing login attempt → credential check → success/failure paths +**Diagram Type:** Flowchart +**Structure:** Start → Enter Credentials → Valid? → [Yes] → Generate Token → Success + → [No] → Retry Limit? → [Yes] → Lock Account + → [No] → Return to Enter +**Color:** Purple on success path, Teal on error handling +**Aspect:** 9:16 vertical + +### Example 2: Sequence Diagram for API Call +**Content:** Technical article about microservices communication +**CSE Result:** 24-item story showing User → API Gateway → Auth Service → Database → Response chain +**Diagram Type:** Sequence Diagram +**Structure:** 4 actors (User, Gateway, Auth, DB) with message arrows showing request/response flow +**Color:** Purple on Gateway (critical), Teal on Auth (secondary) +**Aspect:** 16:9 horizontal + +### Example 3: State Diagram for Order Lifecycle +**Content:** E-commerce order processing explanation +**CSE Result:** 24-item story showing order states: Pending → Processing → Shipped → Delivered (with error states) +**Diagram Type:** State Diagram +**Structure:** Initial → Pending → Processing → Shipped → Delivered → Final + → (error) → Cancelled +**Color:** Purple on happy path states, Teal on processing, Black on cancelled +**Aspect:** 1:1 square + +### Example 4: ER Diagram for Database Schema +**Content:** Data modeling article about blog platform +**CSE Result:** 24-item story revealing entities: Users, Posts, Comments, Categories with relationships +**Diagram Type:** Entity Relationship Diagram +**Structure:** User 1──* Post, Post 1──* Comment, Post *──* Category (many-to-many) +**Color:** Purple on User and Post (core), Teal on Comment and Category +**Aspect:** 16:9 horizontal + +--- + +## Quick Reference + +### When to Use Mermaid Workflow +- Content has inherent diagram structure (flow, sequence, states) +- Need structured technical diagram (not freeform architecture) +- Want hand-drawn whiteboard aesthetic (Excalidraw style) +- Deriving diagram from content analysis (not manually specified) + +### Mermaid vs Technical Diagrams +- **Mermaid:** Structured diagram types (flowchart, sequence, etc.), Excalidraw sketchy aesthetic +- **Technical:** Freeform architecture diagrams, cleaner hand-drawn style + +### Process Summary +``` +1. Run /cse (24-item story explanation) ← MANDATORY +2. Determine diagram type (flowchart, sequence, state, etc.) +3. Extract structure from CSE (nodes, edges, flows) +4. Design Excalidraw layout (wobbly, sketchy, whiteboard) +5. Construct comprehensive prompt +6. Choose aspect ratio (based on diagram type) +7. Generate with nano-banana-pro +8. Validate thoroughly (structure + aesthetic + UL + readability) +``` + +### Core Principles +1. **CSE-driven:** Always derive from content analysis, never manually specify +2. **Mermaid grammar:** Follow proper diagram type conventions +3. **Excalidraw aesthetic:** Hand-drawn whiteboard sketch feel +4. **UL color scheme:** Strategic purple/teal, black structure +5. **Readable imperfection:** Sketchy but clear + +--- + +**The workflow: /cse → Diagram Type → Structure → Excalidraw Design → Prompt → Generate → Validate → Complete** + +**The synthesis: Mermaid structure + Excalidraw aesthetic + UL editorial style = Technical diagrams that feel like smart sketches on a whiteboard.** diff --git a/.opencode/skills/Media/Art/Workflows/RecipeCards.md b/.opencode/skills/Media/Art/Workflows/RecipeCards.md new file mode 100755 index 00000000..80bab948 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/RecipeCards.md @@ -0,0 +1,377 @@ +# Process Recipe Cards Workflow + +**Step-by-step visual recipes for processes and methodologies using UL aesthetic.** + +Creates **PROCESS RECIPE CARDS** — numbered steps with small illustrations for each action, combining procedural clarity with editorial style. + +--- + +## Purpose + +Process recipe cards present methodologies, workflows, and step-by-step processes as visual recipes. These **illustrated how-to guides** make complex processes scannable and memorable. + +**Use this workflow for:** +- "The 5-Step TELOS Analysis Recipe" +- Consulting methodology playbooks +- How-to guides and processes +- Workflow documentation +- Best practice checklists +- Strategic frameworks with steps + +--- + +## Visual Aesthetic: Recipe Card with Personality + +**Think:** Cooking recipe card, but for business processes, with editorial hand-drawn style + +### Core Characteristics +1. **Numbered steps** — Clear 1, 2, 3 progression +2. **Small illustration per step** — Icon or simple visual for each action +3. **Scannable format** — Easy to reference and follow +4. **Recipe card layout** — Compact, organized, referenceable +5. **Hand-drawn icons** — Imperfect, editorial style illustrations +6. **Typography hierarchy** — 3-tier system for title, steps, details +7. **Deliverable quality** — Professional enough for client handoff + +--- + +## Color System for Recipe Cards + +### Structure +``` +Black #000000 — Step numbers, dividing lines, icon outlines +``` + +### Step Differentiation +``` +Deep Purple #4A148C — Critical steps or outcomes +Deep Teal #00796B — Supporting steps or inputs +Charcoal #2D2D2D — All body text and descriptions +``` + +### Background +``` +Light Cream #F5E6D3 — Recipe card warmth +OR +White #FFFFFF — Clean modern +``` + +### Color Strategy +- Step numbers in black (or purple for critical steps) +- Icons primarily black linework with strategic purple/teal accents +- Text in charcoal for readability +- Outcome/result step in purple (final step) + +--- + +## 🚨 MANDATORY WORKFLOW STEPS + +### Step 1: Define Process + +**Identify the recipe:** + +1. **What process are you documenting?** + - Process name + - Overall goal/outcome + +2. **How many steps?** + - Ideal: 3-7 steps (recipe card format) + - Too many steps → break into multiple recipes + +3. **What are the steps?** + - List each action in sequence + - What happens at each step + - What's the outcome + +**Output:** +``` +PROCESS NAME: [The X Recipe / X-Step Y Method] +OUTCOME: [What this process achieves] + +STEPS: +1. [Step name] — [Action description] — [Icon metaphor] +2. [Step name] — [Action description] — [Icon metaphor] +3. [Step name] — [Action description] — [Icon metaphor] +4. [Step name] — [Action description] — [Icon metaphor] +5. [Step name] — [Action description] — [Icon metaphor] + +CRITICAL STEPS (Purple): +- [Which step(s) are most important] +``` + +--- + +### Step 2: Design Recipe Card Layout + +**Plan the visual structure:** + +1. **Layout style:** + - Vertical list (top to bottom) + - Grid (2x3 for 6 steps) + - Linear horizontal flow + - Circular flow (process loops) + +2. **Step representation:** + - Number badge (circled number) + - Small icon illustration for step + - Brief text description + - Arrow to next step + +3. **Visual flow:** + - How steps connect + - Progressive visual cues + - Final outcome emphasis + +**Output:** +``` +LAYOUT: [Vertical list / Grid / Horizontal flow / Circular] + +CARD STRUCTURE: +- Title at top (Tier 1 typography) +- [X] steps arranged [vertically/in grid] +- Each step contains: + * Numbered badge (e.g., "1" in circle) + * Small hand-drawn icon/illustration + * Step name (Tier 2) + * Brief description (Tier 3) +- Arrows or lines connecting steps +- Final outcome emphasized + +ICON METAPHORS: +Step 1: [Simple icon, e.g., "magnifying glass" for discover] +Step 2: [Icon, e.g., "lightbulb" for ideate] +Step 3: [Icon, e.g., "hammer" for build] +... + +COLOR CODING: +- Step [X] (critical): Purple badge and icon accents +- Step [Y] (outcome): Purple emphasis +- Other steps: Black badges, minimal color +``` + +--- + +### Step 3: Construct Prompt + +### Prompt Template + +``` +Hand-drawn process recipe card in editorial style. + +STYLE REFERENCE: Recipe card, visual playbook, illustrated step-by-step guide + +BACKGROUND: [Light Cream #F5E6D3 OR White #FFFFFF] — clean, card-like + +AESTHETIC: +- Recipe card layout (organized, scannable) +- Hand-drawn step icons (simple, imperfect, editorial style) +- Numbered steps with clear progression +- Variable stroke weight (icons and dividing lines) +- Professional but human quality (deliverable to clients) + +LAYOUT TYPE: [Vertical list / Grid / Horizontal flow] + +CARD STRUCTURE: +[Describe the overall layout, e.g.:] +- Title at top +- 5 steps arranged vertically down the card +- Each step has: numbered badge → icon → name → description +- Arrows connecting steps showing flow +- Final step emphasized with purple accent + +TYPOGRAPHY SYSTEM (3-TIER): + +TIER 1 - RECIPE TITLE (Advocate Block Display): +- "[PROCESS NAME]" — Large at top +- Font: Advocate style, extra bold, hand-lettered, all-caps +- Size: 3x larger than body text +- Color: Black #000000 +- Example: "THE 5-STEP TELOS ANALYSIS RECIPE" + +TIER 2 - STEP NAMES (Concourse Sans): +- "Step 1: [Name]", "Step 2: [Name]", etc. +- Font: Concourse geometric sans-serif +- Size: Medium readable +- Color: Charcoal #2D2D2D (or Purple for critical step) +- Position: Next to each step icon + +TIER 3 - STEP DESCRIPTIONS (Advocate Condensed): +- Brief action description for each step +- Font: Advocate condensed (smaller) +- Size: 60% of Tier 2 +- Color: Charcoal #2D2D2D +- Position: Below step name + +PROCESS STEPS TO ILLUSTRATE: +[List each step in detail, e.g.:] + +STEP 1: [Step Name] +- Number badge: "1" in black circle +- Icon: [Hand-drawn simple icon, e.g., "magnifying glass examining document"] +- Description: "[Brief action description]" +- Color: Black linework +- Arrows: Black arrow pointing to Step 2 + +STEP 2: [Step Name] +- Number badge: "2" in black circle +- Icon: [Hand-drawn icon, e.g., "hands sorting cards"] +- Description: "[Action description]" +- Color: Black linework +- Arrows: Black arrow pointing to Step 3 + +STEP 3: [Critical Step Name] +- Number badge: "3" in Purple (#4A148C) circle — CRITICAL STEP +- Icon: [Hand-drawn icon with purple accents] +- Description: "[Action description]" +- Color: Purple (#4A148C) accents on icon and badge +- Arrows: Purple arrow pointing to Step 4 + +[Continue for all steps...] + +FINAL STEP [X]: [Outcome] +- Number badge: "[X]" in Purple (#4A148C) circle — OUTCOME +- Icon: [Success/completion icon, e.g., "trophy", "checkmark", "rocket"] +- Description: "[Outcome achieved]" +- Color: Purple (#4A148C) emphasis +- Represents: Final result of process + +CONNECTING ELEMENTS: +- Hand-drawn arrows between steps (wobbly, imperfect) +- Dotted or dashed lines for optional paths +- All arrows in Black (#000000) except critical path (Purple) + +COLOR USAGE: +- Black (#000000) for most step badges, icons, arrows +- Deep Purple (#4A148C) for critical step(s) and final outcome +- Deep Teal (#00796B) optional for input/supporting steps +- Charcoal (#2D2D2D) for all text + +CRITICAL REQUIREMENTS: +- Hand-drawn recipe card aesthetic (not polished diagram) +- Simple scannable icons for each step (not detailed illustrations) +- Clear numbered progression (1 → 2 → 3 → outcome) +- 3-tier typography hierarchy +- Strategic purple emphasis on critical/outcome steps +- No gradients, flat colors only +- Professional deliverable quality (client-ready) +- Recipe card proportions (vertical card layout) + +Optional: Sign small in bottom right corner in charcoal (#2D2D2D). +``` + +--- + +### Step 4: Determine Aspect Ratio + +| Recipe Type | Aspect Ratio | Reasoning | +|-------------|--------------|-----------| +| Vertical list (3-7 steps) | 9:16 or 4:3 | Tall card format | +| Grid layout (6-9 steps) | 1:1 | Square balanced grid | +| Horizontal flow | 16:9 | Wide linear progression | +| Circular process | 1:1 | Square for circular symmetry | + +**Default: 9:16 (vertical)** — Classic recipe card orientation + +--- + +### Step 5: Execute Generation + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR PROMPT]" \ + --size 2K \ + --aspect-ratio 9:16 \ + --output /path/to/recipe-card.png +``` + +**Model Recommendation:** nano-banana-pro (best text rendering for steps) + +**Immediately Open:** +```bash +open /path/to/recipe-card.png +``` + +--- + +### Step 6: Validation (MANDATORY) + +#### Must Have +- [ ] **Clear progression** — Steps obviously flow 1 → 2 → 3 +- [ ] **Scannable layout** — Easy to reference quickly +- [ ] **Simple icons** — Each step has recognizable illustration +- [ ] **Readable text** — All step names and descriptions legible +- [ ] **Strategic color** — Purple on critical/outcome steps +- [ ] **Hand-drawn quality** — Recipe card has editorial aesthetic +- [ ] **Professional deliverable** — Client-ready quality + +#### Must NOT Have +- [ ] Complex detailed illustrations (should be simple icons) +- [ ] Cluttered layout (too much information) +- [ ] Illegible small text +- [ ] Missing step numbers +- [ ] Unclear flow or progression +- [ ] Corporate process diagram look + +#### If Validation Fails + +| Problem | Fix | +|---------|-----| +| Icons too complex | "Simple hand-drawn icons, minimal detail, recognizable at glance" | +| Can't follow flow | "Clear numbered badges 1→2→3, black arrows connecting steps" | +| Too cluttered | Reduce description text, simplify layout | +| Looks corporate | Reference "recipe card aesthetic, hand-drawn playbook, editorial style" | +| Text unreadable | Increase Tier 2/3 text sizes, more spacing | +| Missing emphasis | "Purple (#4A148C) on Step [X] critical and final outcome step" | + +--- + +## Example Use Cases + +### Example 1: "5-Step TELOS Analysis Recipe" +- **Steps:** Context → Questions → Blockers → Constraints → Solutions +- **Icons:** Magnifying glass, question marks, roadblock, fence, lightbulb +- **Color:** Purple on final "Solutions" step +- **Layout:** Vertical 9:16 +- **Use:** Consulting deliverable + +### Example 2: "The Security Assessment Method" +- **Steps:** Assets → Threats → Vulnerabilities → Mitigations → Validation +- **Icons:** Treasure, storm, crack, shield, checkmark +- **Color:** Purple on "Mitigations" (critical) and "Validation" (outcome) +- **Layout:** Vertical 9:16 + +### Example 3: "3-Step Content Creation Recipe" +- **Steps:** Research → Create → Distribute +- **Icons:** Books, pencil, megaphone +- **Color:** Purple on final "Distribute" outcome +- **Layout:** Horizontal 16:9 (simpler process) + +--- + +## Quick Reference + +**Recipe Card Formula:** +``` +1. Define process (name, steps, outcome) +2. Design layout (vertical/grid, icons, flow) +3. Construct prompt with numbered progression +4. Choose aspect ratio for layout type +5. Generate with nano-banana-pro +6. Validate for scannability and professionalism +``` + +**Color Strategy:** +- Most steps: Black badges and icons +- Critical step: Purple badge and accents +- Final outcome: Purple emphasis +- Text: Charcoal + +**Icon Design:** +- Simple, recognizable, hand-drawn +- Not detailed illustrations +- Represents the action of that step + +--- + +**The workflow: Define → Design → Construct → Generate → Validate → Complete** diff --git a/.opencode/skills/Media/Art/Workflows/Stats.md b/.opencode/skills/Media/Art/Workflows/Stats.md new file mode 100755 index 00000000..208aeb66 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/Stats.md @@ -0,0 +1,365 @@ +# Illustrated Statistics Workflow + +**Single striking statistics illustrated as visual data points using UL aesthetic.** + +Creates **ILLUSTRATED STAT CARDS** — one number/statistic made visual with simple illustration and editorial style. + +--- + +## Purpose + +Illustrated statistics turn data points into memorable visuals. These are **single-stat cards** — one striking number with a small illustration showing what it means, designed for newsletters and social media. + +**Use this workflow for:** +- Newsletter "by the numbers" sections +- Social media stat cards +- Quick visual facts +- Data highlights +- "78% of developers use AI daily" style visuals +- Attention-grabbing numbers + +--- + +## Visual Aesthetic: Number + Tiny Context Illustration + +**Think:** Bold number dominates, small illustration shows what it means + +### Core Characteristics +1. **Number dominant** — The statistic is the hero (60-70% of visual) +2. **Massive typography** — Large bold number immediately visible +3. **Small illustration** — Tiny visual showing what stat represents (20-30%) +4. **Context text** — Brief description of what number means +5. **Hand-drawn** — Imperfect number rendering, editorial illustration +6. **Square or horizontal** — Social/newsletter friendly +7. **Scannable** — Number jumps out immediately + +--- + +## Color System for Stats + +### Number Typography +``` +Deep Purple #4A148C — Primary number (most common) +OR +Black #000000 — Alternative bold number +``` + +### Illustration +``` +Black #000000 — Small illustration linework +Deep Purple #4A148C — Accents on illustration +Deep Teal #00796B — Alternative accents +``` + +### Background +``` +Light Cream #F5E6D3 — Warm neutral +OR +White #FFFFFF — Clean modern +``` + +### Text +``` +Charcoal #2D2D2D — Context description text +``` + +### Color Strategy +- Number in purple (brand emphasis) or black (classic) +- Illustration primarily black with purple accents +- Background light for contrast +- Keep it simple: 2-3 colors total + +--- + +## 🚨 MANDATORY WORKFLOW STEPS + +### Step 1: Select Statistic + +**Identify the data point:** + +1. **What's the statistic?** + - The exact number and metric + - Must be striking or surprising + +2. **What does it represent?** + - Context explanation + - Why it matters + +3. **What tiny illustration shows it?** + - Simple visual representing what stat measures + - Not complex scene, just small icon/illustration + - Should clarify or amplify the meaning + +**Output:** +``` +STATISTIC: [Number + unit, e.g., "78%", "$2.1B", "3.5X"] +METRIC: [What's being measured] + +CONTEXT: [What this number represents] + +ILLUSTRATION: [Small visual element, e.g.:] +- "Tiny developer at computer" for developer stat +- "Stack of coins" for money stat +- "Growing arrow" for growth stat +- Size: 20-30% of image, simple, not detailed +``` + +--- + +### Step 2: Design Stat Card Layout + +**Plan the visual:** + +1. **Number placement:** + - Center dominant (number in middle) + - Left number, right illustration + - Top number, bottom illustration + +2. **Number size:** + - How large can it go + - Should fill 50-60% of image height + +3. **Illustration placement:** + - Where relative to number + - How it interacts with number (near, below, beside) + +4. **Text placement:** + - Metric description above or below number + - Context note if needed + +**Output:** +``` +LAYOUT STRUCTURE: +- Number: [Placement, e.g., "Center dominant"] +- Size: [60% of image height] +- Illustration: [Placement, e.g., "Bottom right, 25% of image"] +- Metric text: [Above number] +- Context: [Below number in smaller text] + +VISUAL RELATIONSHIP: +[How number and illustration interact, e.g.:] +- "78%" in massive purple +- Small illustrated developer sitting on top of "%" symbol +- Text above: "of developers" +- Text below: "use AI tools daily" + +COLOR SCHEME: +- Number: Purple (#4A148C) +- Illustration: Black linework with purple accents +- Background: Light cream +- Text: Charcoal +``` + +--- + +### Step 3: Construct Prompt + +### Prompt Template + +``` +Illustrated statistic card in editorial style. + +STYLE REFERENCE: Data visualization, stat card, number + icon illustration + +BACKGROUND: [Light Cream #F5E6D3 / White #FFFFFF] — flat, clean + +AESTHETIC: +- Number as dominant visual element (massive typography) +- Small simple illustration providing context +- Hand-drawn imperfect number rendering (not digital font) +- Editorial flat color with strategic purple emphasis +- Scannable, immediate impact + +STAT CARD STRUCTURE: + +NUMBER TYPOGRAPHY (Advocate Block Display - MASSIVE): +"[STATISTIC]" + +- Font: Advocate style extra bold, hand-lettered +- Size: MASSIVE — 60-70% of image area +- Color: [Deep Purple #4A148C / Black #000000] +- Style: Hand-lettered with imperfections (wobbly lines, character) +- Position: [Center / Left / Top] +- Example: "78%" in giant purple hand-lettered numbers + +METRIC TEXT (Concourse Sans - Medium): +"[what the stat measures]" + +- Font: Concourse geometric sans-serif +- Size: Medium readable (15-20% of number size) +- Color: Charcoal (#2D2D2D) +- Position: [Above / Below number] +- Example: "of developers" above the 78% + +CONTEXT TEXT (Advocate Condensed - Small): +"[additional context]" + +- Font: Advocate condensed +- Size: Small (10-15% of number size) +- Color: Charcoal (#2D2D2D) +- Position: [Below number / Bottom of card] +- Example: "use AI tools daily" below the number + +ILLUSTRATION (Small, Simple): +[Describe the tiny illustration, e.g.:] +- Small hand-drawn [icon/figure] +- Hand-drawn black (#000000) linework +- Purple (#4A148C) accents on [specific elements] +- Position: [Bottom right / Next to number / etc.] +- Size: 20-30% of image area +- Style: Simple sketch, not detailed +- Represents: [What the stat is about] +- Example: "Tiny developer sitting at computer with code on screen" + +VISUAL INTERACTION: +[How illustration and number relate, e.g.:] +- Illustration positioned [near/on/beside] the number +- Creates visual story: "Developer represents the 78%" +- Illustration does NOT compete with number (stays small) + +COLOR USAGE: +- Number: Deep Purple (#4A148C) OR Black (#000000) +- Illustration linework: Black (#000000) +- Illustration accents: Purple (#4A148C) OR Teal (#00796B) +- Metric/context text: Charcoal (#2D2D2D) +- Background: Light Cream (#F5E6D3) OR White (#FFFFFF) + +CRITICAL REQUIREMENTS: +- Number is HERO (dominates composition, 60-70%) +- Hand-lettered number quality (NOT digital font) +- Illustration SMALL and SIMPLE (supporting role, 20-30%) +- High contrast for readability +- Strategic purple emphasis (number OR illustration accents) +- No gradients, flat colors only +- Immediately scannable (number jumps out at thumbnail) +- Square 1:1 or horizontal 16:9 format + +Optional: Sign small in bottom right corner in charcoal (#2D2D2D). +``` + +--- + +### Step 4: Determine Aspect Ratio + +| Use Case | Aspect Ratio | Reasoning | +|----------|--------------|-----------| +| Social media post | 1:1 | Instagram/LinkedIn friendly | +| Newsletter inline | 16:9 | Horizontal fits email width | +| Vertical mobile | 9:16 | Instagram story format | +| Balanced | 1:1 | Works everywhere | + +**Default: 1:1 (square)** — Most versatile for social/newsletter + +--- + +### Step 5: Execute Generation + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR PROMPT]" \ + --size 2K \ + --aspect-ratio 1:1 \ + --output /path/to/stat-card.png +``` + +**Model Recommendation:** nano-banana-pro (excellent for rendering numbers clearly) + +**Immediately Open:** +```bash +open /path/to/stat-card.png +``` + +--- + +### Step 6: Validation (MANDATORY) + +#### Must Have +- [ ] **Number dominant** — Statistic is 60-70% of visual, immediately visible +- [ ] **Readable number** — Clear even at thumbnail size +- [ ] **Hand-lettered** — Imperfect, gestural quality (not digital font) +- [ ] **Illustration simple** — Small supporting visual, not complex scene +- [ ] **Context clear** — Metric/context text explains what number means +- [ ] **High contrast** — Purple or black number pops from background +- [ ] **Scannable** — Number jumps out immediately + +#### Must NOT Have +- [ ] Number too small (should dominate) +- [ ] Digital font rendering (should be hand-lettered) +- [ ] Complex detailed illustration (should be simple icon) +- [ ] Illustration competing with number +- [ ] Low contrast (can't read number) +- [ ] Missing context (unclear what stat represents) + +#### If Validation Fails + +| Problem | Fix | +|---------|-----| +| Number too small | "MASSIVE hand-lettered number filling 65% of image height" | +| Looks digital | "Hand-drawn Advocate style number, wobbly imperfect strokes" | +| Illustration too complex | "SMALL SIMPLE illustration, minimal detail, 25% of image size" | +| Can't read thumbnail | Increase number size, stronger contrast | +| Unclear meaning | Add metric text above: "of [X]", context below: "[what they do]" | +| No visual interest | "Small illustrated [icon] showing what stat represents" | + +--- + +## Example Use Cases + +### Example 1: "78% of developers use AI daily" +- **Number:** "78%" in massive purple hand-lettering +- **Metric:** "of developers" above number +- **Context:** "use AI tools daily" below +- **Illustration:** Tiny developer at computer with AI sparkles (bottom right, 25%) +- **Aspect:** 1:1 + +### Example 2: "$2.1B invested in AI safety" +- **Number:** "$2.1B" in giant black hand-lettering +- **Metric:** "invested in" above +- **Context:** "AI safety research" below +- **Illustration:** Small stack of coins with shield symbol (purple accents) +- **Aspect:** 1:1 + +### Example 3: "3.5X growth in AI adoption" +- **Number:** "3.5X" in massive purple +- **Metric:** "growth in" above +- **Context:** "enterprise AI adoption" below +- **Illustration:** Upward arrow with small building icon +- **Aspect:** 16:9 (horizontal for newsletter) + +### Example 4: "92% of security breaches involve humans" +- **Number:** "92%" in black bold hand-lettering +- **Metric:** "of breaches" above +- **Context:** "involve human error" below +- **Illustration:** Tiny person with open door/lock symbol (purple accents) +- **Aspect:** 1:1 + +--- + +## Quick Reference + +**Illustrated Stat Formula:** +``` +1. Select statistic (number, metric, context) +2. Design layout (number dominant, illustration placement) +3. Choose simple illustration (what stat represents) +4. Construct prompt with massive number +5. Use 1:1 square aspect ratio (usually) +6. Generate with nano-banana-pro +7. Validate for dominance and readability +``` + +**Color Strategy:** +- Number: Purple (emphasis) or Black (classic) +- Illustration: Black linework + purple accents +- Text: Charcoal +- Background: Light cream or white + +**Key Principle:** +- **Number IS the visual** — Illustration is small supporting context +- Immediate impact, scannable at thumbnail +- Context makes meaning clear + +--- + +**The workflow: Select → Design → Construct → Generate → Validate → Complete** diff --git a/.opencode/skills/Media/Art/Workflows/Taxonomies.md b/.opencode/skills/Media/Art/Workflows/Taxonomies.md new file mode 100755 index 00000000..360cd13f --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/Taxonomies.md @@ -0,0 +1,354 @@ +# Visual Taxonomies & Classification Grids Workflow + +**Hand-drawn classification systems, taxonomies, and reference grids using UL aesthetic.** + +Creates **VISUAL TAXONOMIES** — organized classification systems like periodic tables, capability matrices, or framework grids with editorial hand-drawn style. + +--- + +## Purpose + +Visual taxonomies organize concepts into structured classification systems. Unlike technical diagrams (which show flows/relationships) or editorial illustrations (which use metaphors), taxonomies show **organized categories and hierarchies**. + +**Use this workflow for:** +- "The Periodic Table of X" +- Classification grids and matrices +- Capability taxonomies +- Framework reference cards +- Organized typologies +- Systematic categorizations + +--- + +## Visual Aesthetic: Structured Yet Hand-Drawn + +**Think:** Hand-drawn periodic table or field guide illustration + +### Core Characteristics +1. **Grid structure** — Organized cells/boxes in systematic layout +2. **Hand-drawn imperfection** — Boxes wobbly, lines organic, human feel +3. **Consistent typography** — 3-tier system (Advocate titles, Concourse labels, italic annotations) +4. **Category organization** — Clear groupings with visual hierarchy +5. **Color coding** — Strategic use of purple/teal to show categories +6. **Editorial aesthetic** — Maintains UL flat color, black linework style +7. **Scannable layout** — Easy to reference and navigate + +--- + +## Color System for Taxonomies + +**Same UL palette, organized usage:** + +### Structure +``` +Black #000000 — All grid lines, cell borders, primary structure +``` + +### Category Differentiation +``` +Deep Purple #4A148C — Category 1 headers/highlights +Deep Teal #00796B — Category 2 headers/highlights +Charcoal #2D2D2D — All body text and labels +``` + +### Background +``` +White #FFFFFF or Light Cream #F5E6D3 — For clarity +``` + +### Color Strategy +- Use purple for one category type, teal for another +- Alternate colors by row/column for visual organization +- Keep most content black/charcoal with strategic color accents + +--- + +## 🚨 MANDATORY WORKFLOW STEPS + +### Step 1: Define Classification System + +**Identify what you're classifying:** + +1. **What is being categorized?** (e.g., AI capabilities, security threats, business models) +2. **What are the organizing dimensions?** (e.g., complexity vs. impact, offensive vs. defensive) +3. **How many categories?** (e.g., 6 types, 12 elements, 4x4 grid) +4. **What's the hierarchy?** (e.g., major categories → subcategories) + +**Output:** +``` +CLASSIFICATION SUBJECT: [What you're organizing] + +ORGANIZING DIMENSIONS: +- Dimension 1: [e.g., Complexity: Simple → Complex] +- Dimension 2: [e.g., Impact: Low → High] + +CATEGORIES: +1. [Category name] — [Description] +2. [Category name] — [Description] +3. [Category name] — [Description] +... + +ITEMS TO CLASSIFY: +- [Item 1] belongs to [Category] +- [Item 2] belongs to [Category] +... +``` + +--- + +### Step 2: Design Grid Layout + +**Plan the visual organization:** + +1. **Layout type:** + - Periodic table grid (rows and columns) + - Matrix (2x2, 3x3, 4x4) + - Hierarchical tree + - Grouped clusters + - Linear taxonomy (top to bottom) + +2. **Cell structure:** + - What information in each cell + - Size of cells (uniform or varied) + - How categories are grouped visually + +3. **Color assignment:** + - Which categories get purple + - Which get teal + - Pattern of color distribution + +**Output:** +``` +LAYOUT: [Grid type, e.g., 4x4 matrix, Periodic table style] + +GRID STRUCTURE: +- [Describe arrangement: "4 rows by 4 columns, grouped by color into quadrants"] +- Cell size: [Uniform squares, varied rectangles, etc.] +- Groupings: [How categories cluster together] + +COLOR CODING: +- Purple: [Category type 1] +- Teal: [Category type 2] +- Black: [Remaining structure] + +TYPOGRAPHY: +- Title (Tier 1): "[MAIN TITLE]" +- Category headers (Tier 2): [Category names] +- Item labels (Tier 3): [Individual items] +``` + +--- + +### Step 3: Construct Prompt + +**Use 3-tier typography system:** + +### Prompt Template + +``` +Hand-drawn taxonomy grid in editorial notebook style. + +STYLE REFERENCE: Periodic table, field guide illustration, reference card aesthetic + +BACKGROUND: [White #FFFFFF OR Light Cream #F5E6D3] — clean, flat + +AESTHETIC: +- Hand-drawn imperfect grid lines (slightly wobbly, human quality) +- Variable stroke weight (grid structure in black) +- Cell borders with slight waviness (not perfect rectangles) +- Editorial flat color aesthetic with strategic accents +- Organized layout but hand-crafted feel + +LAYOUT TYPE: [Periodic table grid / Matrix / Hierarchical tree / etc.] + +GRID STRUCTURE: +[Describe the grid organization, e.g.:] +- 4 rows by 4 columns of cells +- Each cell contains: [category icon/symbol] + [label text] +- Cells grouped by color into [quadrants/categories] +- Clear visual separation between category groups + +TYPOGRAPHY SYSTEM (3-TIER): + +TIER 1 - TAXONOMY HEADER & SUBTITLE (Valkyrie Two-Part System): +Header (Main Title): +- "[Header Text]" — Left-justified at top +- Font: Valkyrie serif italic (elegant, sophisticated) +- Size: Large - 3-4x body text (prominent, commanding attention) +- Style: Italicized, sentence case or title case (NOT all-caps) +- Color: Black #000000 (or Purple #4A148C for emphasis) +- Position: Top-left with margin +- Example: "The Periodic Table of AI Capabilities" + +Subtitle (Clarifying Detail): +- "[Subtitle Text]" — Below header +- Font: Valkyrie serif regular (warm, readable) +- Size: Small - 1-1.5x body text (noticeably smaller than header, supportive) +- Style: Regular (NOT italicized), sentence case (first letter capitalized, rest lowercase except proper nouns) +- Color: Black #000000 or Charcoal #2D2D2D +- Position: Small gap below header, aligned left +- Example: "Classification of Machine Learning Functions" + +TIER 2 - CATEGORY HEADERS (Concourse Sans): +- "[Category 1]", "[Category 2]", etc. +- Font: Concourse geometric sans-serif, clean, modern +- Size: Medium readable +- Color: Purple #4A148C for Category 1, Teal #00796B for Category 2 +- Example: "Reasoning", "Creativity", "Perception" + +TIER 3 - ITEM LABELS (Advocate Condensed): +- Individual items within cells +- Font: Advocate condensed, smaller +- Size: 60% of Tier 2 +- Color: Charcoal #2D2D2D +- Example: Item names, abbreviations, symbols + +CONTENT TO INCLUDE: +[List all categories and items to be shown, e.g.:] + +CATEGORY 1 (Purple #4A148C headers): +- Item A: [label] +- Item B: [label] +- Item C: [label] + +CATEGORY 2 (Teal #00796B headers): +- Item D: [label] +- Item E: [label] + +[etc.] + +COLOR USAGE: +- Black (#000000) for all grid structure, cell borders +- Deep Purple (#4A148C) for [Category 1] headers and accents +- Deep Teal (#00796B) for [Category 2] headers and accents +- Charcoal (#2D2D2D) for all item labels and body text + +CRITICAL REQUIREMENTS: +- Hand-drawn sketch quality — NOT polished digital grid +- Grid lines wobble slightly (human imperfection) +- Cells roughly aligned but organic (grid-aware not grid-perfect) +- No gradients, no shadows, flat colors only +- Clear typography with 3-tier hierarchy +- Scannable and reference-friendly layout +- Strategic color coding for categories + +Optional: Sign small in bottom right corner in charcoal (#2D2D2D). +``` + +--- + +### Step 4: Determine Aspect Ratio + +**Choose based on taxonomy type:** + +| Taxonomy Type | Aspect Ratio | Reasoning | +|---------------|--------------|-----------| +| Wide grid (many columns) | 16:9 or 21:9 | Horizontal periodictable layout | +| Tall hierarchy | 9:16 | Vertical tree structure | +| Square matrix | 1:1 | Balanced 4x4 or 5x5 grid | +| Reference card | 1:1 or 4:3 | Compact, poster-like | + +**Default: 1:1 (square)** — Works for most taxonomy grids + +--- + +### Step 5: Execute Generation + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR PROMPT]" \ + --size 2K \ + --aspect-ratio 1:1 \ + --output /path/to/taxonomy.png +``` + +**Model Recommendation:** nano-banana-pro (best text rendering for labels) + +**Immediately Open:** +```bash +open /path/to/taxonomy.png +``` + +--- + +### Step 6: Validation (MANDATORY) + +**Open the generated image and check:** + +#### Must Have +- [ ] **Clear grid structure** — Organized layout with visible cells/categories +- [ ] **Readable text** — All labels legible in 3-tier hierarchy +- [ ] **Hand-drawn aesthetic** — Wobbly lines, imperfect cells, human feel +- [ ] **Strategic color** — Purple/teal differentiate categories, not overwhelming +- [ ] **Scannable** — Easy to find and reference specific items +- [ ] **Hierarchical clarity** — Title > Categories > Items is obvious +- [ ] **Flat aesthetic** — No gradients, maintains UL editorial style + +#### Must NOT Have +- [ ] Perfect straight grid lines +- [ ] Polished vector graphics +- [ ] Gradients or shadows +- [ ] Illegible or tiny text +- [ ] Color chaos (too many colors) +- [ ] Confusing organization + +#### If Validation Fails + +| Problem | Fix | +|---------|-----| +| Grid too perfect | Emphasize "wobbly hand-drawn grid lines, organic imperfection" | +| Text unreadable | Increase text size, strengthen typography tier requirements | +| Too colorful | "Strategic color use — purple for [specific], teal for [specific], rest black" | +| Unclear organization | Simplify grid, reduce categories, clarify groupings | +| Looks digital | Reference "hand-drawn field guide, editorial notebook aesthetic" | + +--- + +## Example Use Cases + +### Example 1: "Periodic Table of AI Capabilities" +- **Grid:** 5x6 matrix of capabilities +- **Categories:** Reasoning (purple), Creativity (teal), Perception (black), Action (purple), Memory (teal) +- **Items:** Each cell = one capability with icon + label +- **Aspect:** 16:9 (wide grid) + +### Example 2: "Cybersecurity Threat Taxonomy" +- **Grid:** Hierarchical tree from top (threat types) to bottom (specific attacks) +- **Categories:** Network threats (purple), Application threats (teal), Human threats (purple) +- **Aspect:** 9:16 (tall tree) + +### Example 3: "Business Model Classification" +- **Grid:** 3x3 matrix (complexity vs. scalability) +- **Categories:** 9 business model archetypes +- **Color:** Purple for high-scalability, teal for low-complexity +- **Aspect:** 1:1 (square reference card) + +--- + +## Quick Reference + +**Taxonomy Formula:** +``` +1. Define classification system (what, dimensions, categories) +2. Design grid layout (structure, cells, color coding) +3. Construct prompt with 3-tier typography +4. Choose aspect ratio for layout type +5. Generate with nano-banana-pro +6. Validate for clarity and aesthetics +``` + +**Color Strategy:** +- 80% Black structure +- 10% Purple (Category 1) +- 10% Teal (Category 2) +- Text all Charcoal + +**Typography:** +- Tier 1: Massive Advocate title +- Tier 2: Medium Concourse category headers +- Tier 3: Small Advocate item labels + +--- + +**The workflow: Define → Design → Construct → Generate → Validate → Complete** diff --git a/.opencode/skills/Media/Art/Workflows/TechnicalDiagrams.md b/.opencode/skills/Media/Art/Workflows/TechnicalDiagrams.md new file mode 100755 index 00000000..bb019fcd --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/TechnicalDiagrams.md @@ -0,0 +1,223 @@ +# Technical Diagram Workflow + +**Clean Excalidraw-style technical diagrams with custom typography aesthetic.** + +--- + +## Purpose + +Technical diagrams for system architectures, process flows, and board presentations. + +**Use for:** Architecture diagrams, process flows, pipelines, infrastructure maps, board presentations. + +--- + +## Visual Aesthetic + +**Style:** Clean Excalidraw diagrams — professional, approachable, no grid background. + +### Core Rules + +1. **Excalidraw style** — Clean lines, slightly organic, professional +2. **Pure sepia #EAE9DF background** — NO grid lines, NO texture, NO decorations +3. **Custom fonts** — Specific typography hierarchy (see below) +4. **Strategic color** — Purple #4A148C for key elements, Teal #00796B for flows +5. **White primary** — 80% of elements in grey/black colors, color is accent only + + +# Example image +# Ignore for now +# ~/.opencode/skills/Art/WorkflowExamples/TechnicalDiagrams/example.png + +--- + +## Typography System (Butterick Fonts) + +**Three font families with specific visual characteristics:** + +### TIER 1: Headers & Subtitles — Valkyrie Serif + +**Valkyrie characteristics for AI prompt:** +- Elegant serif with wedge-shaped serifs (like Palatino but more refined) +- High stroke contrast (thick/thin variation) +- Sophisticated, warm, readable +- NOT generic serif — specifically elegant wedge serifs + +**Header (Main Title):** +- Font: Elegant wedge-serif italic (Valkyrie-style) +- Size: Medium-large, 3-4x body text +- Style: Italic, title case +- Color: Black #000000 +- Position: Top-left of image, left-justified + +**Subtitle:** +- Font: Elegant wedge-serif regular (Valkyrie-style) +- Size: Smaller, 1.3x body text +- Style: Regular (not italic), sentence case, no period at the end +- Color: Charcoal #2D2D2D +- Position: Below header, left-justified + +--- + +### TIER 2: Labels — Concourse T3 Geometric Sans + +**Concourse T3 characteristics for AI prompt:** +- Geometric sans-serif (like Avenir/Futura but warmer) +- Clean, technical, precise +- Even stroke weight +- Professional, no-nonsense +- NOT generic sans — specifically geometric with slight warmth + +**Usage:** +- Box labels, node names, technical identifiers +- Size: Medium, readable +- Color: Charcoal #2D2D2D or Black #000000 +- Examples: "API Gateway", "Database", "Services" + +--- + +### TIER 3: Insights — Advocate Condensed Italic + +**Advocate characteristics for AI prompt:** +- Condensed italic sans-serif +- Sporty, editorial feel (like sports jerseys or magazine callouts) +- Narrow letter spacing, italic slant +- Voice-forward, attention-grabbing +- NOT generic italic — specifically condensed sporty italic + +**Usage:** +- Key insights, commentary, callouts +- Size: Smaller, 60-70% of labels +- Color: Purple #4A148C (primary) or Teal #00796B +- Style: Always italic, always asterisks around text +- Examples: "*this is the bottleneck*", "*critical path*" + +--- + +# Color Palette + +``` +Sepia #EAE9DF - Background +Purple #4A148C — Key components, insights (10%) +Teal #00796B — Flows, connections (5%) +Charcoal #2D2D2D — Text, labels (5%) +White #FFFFFF — Primary Structure +``` + +--- + +# Composition construction + +Create a consistent, styled technical diagram using ALL of the styling guidelines here. + +BACKGROUND: Pure Black #000000— absolutely NO grid lines, NO texture, completely clean black. + +STYLE: Architect aesthetic — like an architect artist did it on the whiteboard + +TYPOGRAPHY (CRITICAL - use these exact font styles): + +HEADER: Elegant wedge-serif italic font (like Palatino but more refined, with distinctive wedge-shaped serifs and high stroke contrast). Large size, black color, top-left position, title case. + +SUBTITLE: Same elegant wedge-serif but regular weight (not italic). Smaller size, charcoal #2D2D2D color, directly below header, sentence case. + +LABELS: Geometric sans-serif font (like Avenir or Futura but slightly warmer, clean and technical with even stroke weight). Medium size, charcoal #2D2D2D color, used for all box labels and component names. Hand drawn versions of this. + +INSIGHTS: Condensed italic sans-serif font (sporty editorial style like sports jerseys or magazine callouts, narrow and slanted). Smaller size, Purple #4A148C color, used for callouts with asterisks like "*key insight*".Hand drawn versions of this. + +DIAGRAM CONTENT: +Title: '[TITLE]' (Top left, left-justified) +Subtitle: '[SUBTITLE]' (left justified to the Header, slightly below) +Art and labels and such should look like Excalidraw, but hand drawn by a talented Architect Artist that mimics our fonts. + +Have 1-3 insights for each image created. + +# Object Styling + +When you must use everyday objects to help the visual, use technically-drawn, non-cartoon-like drawing. This means: + +- NOT Cartoony +- Like they're drawn by an architect / artist type + +## Overall look and feel + +The whole image should look like it was made on a whiteboard by an extremely talented artist with Architect training, using all the styling above. Like Excalidraw, but more Architect / Artistic. + +All the art components, labels, and such should mostly look hand-drawn, similar to Excalidraw. But roughly in the style of our fonts. + +# Execution + +1. Run /cse 24 on the input content +2. Think deeply about how to construct that into a technical diagram +3. Create the composition in your mind that will perfectly render that +4. Create a PROMPT that will render that composition perfectly +5. Before creating the image, make absolutely certain that the PROMPT you've created mind takes into account everything in these instructions. No exceptions. Then proceed to #6. +6. Confirm this mentally for 1 full second +7. Create the image using intent-to-flag mapping and the CLI tool + +## Intent-to-Flag Mapping + +**Interpret user request and select appropriate flags:** + +### Model Selection + +| User Says | Flag | When to Use | +|-----------|------|-------------| +| "fast", "quick", "draft" | `--model nano-banana` | Faster iteration, slightly lower quality | +| (default), "best", "high quality" | `--model nano-banana-pro` | Best quality + text rendering (recommended) | +| "flux", "stylistic variety" | `--model flux` | Different aesthetic, stylistic variety | + +### Size Selection + +| User Says | Flag | Resolution | +|-----------|------|------------| +| "draft", "preview" | `--size 1K` | Quick iterations | +| (default), "standard" | `--size 2K` | Standard output | +| "high res", "print", "large" | `--size 4K` | Maximum resolution | + +### Aspect Ratio + +| User Says | Flag | Use Case | +|-----------|------|----------| +| "wide", "slide", "presentation" | `--aspect-ratio 16:9` | Default for diagrams | +| "square" | `--aspect-ratio 1:1` | Social media, compact | +| "ultrawide", "panoramic" | `--aspect-ratio 21:9` | Wide system diagrams | + +### Post-Processing + +| User Says | Flag | Effect | +|-----------|------|--------| +| "blog", "website" | `--thumbnail` | Creates transparent + thumb versions | +| "transparent" | `--remove-bg` | Removes background for compositing | +| "variations", "options" | `--creative-variations 3` | Multiple versions | + +### Generate Command + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model [SELECTED_MODEL] \ + --prompt "[PROMPT]" \ + --size [SELECTED_SIZE] \ + --aspect-ratio [SELECTED_RATIO] \ + [--thumbnail if for blog] \ + --output /path/to/diagram.png +``` + +# Validation + +After rendering, ensure that you have executed properly by checking this list of musts and must-nots. + +**Must have:** +- [ ] Pure sepia background #EAE9DF (NO grid or decorations) +- [ ] Elegant wedge-serif for both headers (Valkyrie-style) +- [ ] Geometric sans labels (Concourse-style) +- [ ] A title and subtitle in the top left +- [ ] 1-3 Condensed italic insights (Advocate-style) +- [ ] Strategic color usage (for accents, 70% different shades of grey and black) +- [ ] Highly technical, stylish, Architect-style look and feel, Excalidraw with style! + +**Must NOT have:** +- [ ] Grid lines or texture on background +- [ ] Generic or ugly fonts +- [ ] Cartoony or overly casual shapes or styling +- [ ] Over-coloring (everything purple/teal) + diff --git a/.opencode/skills/Media/Art/Workflows/Timelines.md b/.opencode/skills/Media/Art/Workflows/Timelines.md new file mode 100755 index 00000000..18dae510 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/Timelines.md @@ -0,0 +1,349 @@ +# Conceptual Timelines & Progressions Workflow + +**Hand-drawn timelines showing evolution, trends, and transformations using UL aesthetic.** + +Creates **ILLUSTRATED TIMELINES** — chronological progressions with visual metaphors for each stage, combining narrative arc with temporal information. + +--- + +## Purpose + +Conceptual timelines show change over time through illustrated progression. Unlike simple date lists, these timelines use **visual metaphors at each stage** to show transformation, evolution, or historical development. + +**Use this workflow for:** +- "The Evolution of X" +- Trend analysis over time +- Historical perspectives +- Before → During → After progressions +- Transformation journeys +- Era comparisons + +--- + +## Visual Aesthetic: Illustrated Progression + +**Think:** Hand-drawn timeline with small illustrations at each milestone + +### Core Characteristics +1. **Temporal flow** — Clear left-to-right or top-to-bottom progression +2. **Illustrated milestones** — Small visual metaphor at each point +3. **Hand-drawn timeline** — Organic line connecting events (not ruler-straight) +4. **Typography hierarchy** — 3-tier system for dates, labels, annotations +5. **Narrative arc** — Shows transformation, not just chronology +6. **Editorial style** — Maintains UL flat color, black linework aesthetic +7. **Scannable progression** — Easy to follow the flow of time + +--- + +## Color System for Timelines + +### Structure +``` +Black #000000 — Timeline spine/line, all primary structure +``` + +### Emphasis & Progression +``` +Deep Purple #4A148C — Key turning points, critical milestones +Deep Teal #00796B — Secondary events, supporting milestones +Charcoal #2D2D2D — All text (dates, labels, annotations) +``` + +### Background +``` +White #FFFFFF or Light Cream #F5E6D3 +``` + +### Color Strategy +- Timeline line in black +- 1-2 most important milestones in purple +- Supporting milestones in teal +- Most events remain black with charcoal text + +--- + +## 🚨 MANDATORY WORKFLOW STEPS + +### Step 1: Define Timeline Structure + +**Identify what you're showing:** + +1. **What's evolving?** (e.g., AI development, security paradigms, organizational thinking) +2. **Time span?** (e.g., 1990-2025, past 5 years, projected future) +3. **Key milestones?** (List 4-8 major points - too many becomes cluttered) +4. **What's the narrative arc?** (e.g., collapse → crisis → renewal, winter → spring → summer) + +**Output:** +``` +SUBJECT: [What's changing over time] +TIME SPAN: [Start year] → [End year] + +NARRATIVE ARC: [The transformation story, e.g., "From hype to disillusionment to practical value"] + +KEY MILESTONES: +1. [Year/Period]: [Event] — [Metaphor for this stage] +2. [Year/Period]: [Event] — [Metaphor for this stage] +3. [Year/Period]: [Event] — [Metaphor for this stage] +4. [Year/Period]: [Event] — [Metaphor for this stage] +... + +TURNING POINTS (Purple highlights): +- [Which 1-2 milestones are most critical] +``` + +--- + +### Step 2: Design Timeline Layout + +**Plan the visual flow:** + +1. **Orientation:** + - Horizontal (left-to-right): Traditional, good for desktop/wide + - Vertical (top-to-bottom): Mobile-friendly, scrollable + - Curved/organic: More artistic, less rigid + +2. **Milestone representation:** + - What small illustration represents each stage + - How milestones connect to timeline (above/below, branching) + - Size variation (bigger for more important events) + +3. **Spacing:** + - Even spacing (visual balance) + - Proportional spacing (matches actual time) + - Clustered spacing (groups related events) + +**Output:** +``` +ORIENTATION: [Horizontal / Vertical / Curved] + +TIMELINE STRUCTURE: +- Main line: [Black hand-drawn line, slightly wobbly] +- Milestones: [Small illustrated circles/nodes along line] +- Labels: [Above or below timeline] + +MILESTONE ILLUSTRATIONS: +1. [Year]: [Small icon/metaphor] — [e.g., "seedling" for beginning] +2. [Year]: [Small icon/metaphor] — [e.g., "storm" for crisis] +3. [Year]: [Small icon/metaphor] — [e.g., "sunrise" for renewal] +... + +SPACING: [Even / Proportional / Clustered] + +COLOR HIGHLIGHTS: +- Purple: [Critical milestone(s)] +- Teal: [Supporting milestone(s)] +- Black: [Standard milestones] +``` + +--- + +### Step 3: Construct Prompt + +### Prompt Template + +``` +Hand-drawn conceptual timeline in editorial illustration style. + +STYLE REFERENCE: Illustrated history timeline, hand-drawn progress chart, editorial time progression + +BACKGROUND: [White #FFFFFF OR Light Cream #F5E6D3] — clean, flat + +AESTHETIC: +- Hand-drawn timeline (organic line, slightly wobbly, not ruler-straight) +- Small illustrated metaphors at each milestone +- Variable stroke weight (timeline thicker, details thinner) +- Editorial flat color with strategic purple/teal accents +- Imperfect but intentional placement + +ORIENTATION: [Horizontal left-to-right / Vertical top-to-bottom] + +TIMELINE STRUCTURE: +- Black (#000000) timeline spine running [horizontally/vertically] +- [Number] milestone points along timeline +- Each milestone has: small circle/node + illustration + label +- Hand-drawn connecting line with slight organic waviness + +TYPOGRAPHY SYSTEM (3-TIER): + +TIER 1 - TIMELINE TITLE (Advocate Block Display): +- "[TIMELINE TITLE IN ALL-CAPS]" +- Font: Advocate style, extra bold, hand-lettered, all-caps +- Size: 3x larger than body text +- Color: Black #000000 +- Position: Top or left side +- Example: "THE EVOLUTION OF ARTIFICIAL INTELLIGENCE" + +TIER 2 - DATES/PERIODS (Concourse Sans): +- "[1990]", "[2000]", "[2010]", etc. +- Font: Concourse geometric sans-serif +- Size: Medium readable +- Color: Charcoal #2D2D2D +- Position: Along timeline at each milestone + +TIER 3 - MILESTONE DESCRIPTIONS (Advocate Condensed Italic): +- "*symbolic AI era*", "*deep learning breakthrough*", etc. +- Font: Advocate condensed italic +- Size: 60% of Tier 2 +- Color: Charcoal #2D2D2D (or Purple/Teal for highlighted events) +- Position: Near each milestone node + +MILESTONES TO ILLUSTRATE: +[List each point chronologically, e.g.:] + +1. [Year]: [Event name] + - Illustration: [Small hand-drawn icon/metaphor, e.g., "tiny seed sprouting"] + - Color: Black node with charcoal text + - Position: [Along timeline at this point] + +2. [Year]: [Critical event] + - Illustration: [Metaphor, e.g., "lightning bolt"] + - Color: Purple (#4A148C) node and illustration — KEY TURNING POINT + - Position: [Emphasized size, highlighted] + +3. [Year]: [Event name] + - Illustration: [Metaphor] + - Color: Teal (#00796B) node + - Position: [Along timeline] + +[etc. for all milestones] + +VISUAL METAPHORS: +- Each milestone illustrated with small simple icon +- Metaphors show the nature/feeling of that era +- Hand-drawn sketch quality, not detailed illustrations +- Examples: seedling, storm cloud, rising sun, mountain peak, valley, crossroads + +COLOR USAGE: +- Black (#000000) for timeline spine and most milestone nodes +- Deep Purple (#4A148C) for [1-2 critical turning points] — nodes and illustrations +- Deep Teal (#00796B) for [supporting important events] +- Charcoal (#2D2D2D) for all text + +CRITICAL REQUIREMENTS: +- Hand-drawn timeline (NOT straight digital line) +- Clear temporal progression [left-to-right / top-to-bottom] +- Small illustrated metaphors at each point (simple, sketchy) +- 3-tier typography hierarchy +- Strategic color on key milestones only +- No gradients, flat colors only +- Maintains editorial illustration aesthetic +- Easy to scan and follow progression + +Optional: Sign small in bottom right corner in charcoal (#2D2D2D). +``` + +--- + +### Step 4: Determine Aspect Ratio + +| Timeline Type | Aspect Ratio | Reasoning | +|---------------|--------------|-----------| +| Horizontal timeline | 21:9 or 16:9 | Wide format for left-to-right flow | +| Vertical timeline | 9:16 | Tall format for top-to-bottom progression | +| Balanced/compact | 1:1 | Square for shorter timelines | +| Long historical | 21:9 | Maximum width for many events | + +**Default: 16:9 (horizontal)** — Classic timeline orientation + +--- + +### Step 5: Execute Generation + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[YOUR PROMPT]" \ + --size 2K \ + --aspect-ratio 16:9 \ + --output /path/to/timeline.png +``` + +**Model Recommendation:** nano-banana-pro (best for dates/text rendering) + +**Immediately Open:** +```bash +open /path/to/timeline.png +``` + +--- + +### Step 6: Validation (MANDATORY) + +#### Must Have +- [ ] **Clear temporal flow** — Obviously progresses through time +- [ ] **Readable dates/labels** — All text legible in hierarchy +- [ ] **Illustrated milestones** — Visual metaphors at each point +- [ ] **Hand-drawn timeline** — Organic line, not digital/straight +- [ ] **Narrative arc visible** — Shows transformation, not just dates +- [ ] **Strategic color** — Purple on critical moments, not everywhere +- [ ] **Scannable** — Easy to follow progression at a glance + +#### Must NOT Have +- [ ] Perfectly straight timeline +- [ ] Generic boring milestone markers (just dots) +- [ ] Illegible dates or cluttered text +- [ ] Too many milestones (overwhelming) +- [ ] Color chaos (everything highlighted) +- [ ] Looks like Gantt chart or business timeline + +#### If Validation Fails + +| Problem | Fix | +|---------|-----| +| Timeline too straight | "Organic hand-drawn line, slight waviness, imperfect curve" | +| No visual interest | "Small illustrated metaphors at each milestone showing the era's character" | +| Text unreadable | Increase spacing, strengthen typography tier sizes | +| Too cluttered | Reduce milestones to 4-6 key points, simplify | +| Looks corporate | Reference "editorial illustration style, hand-drawn sketch aesthetic" | +| Missing narrative | Emphasize metaphors that show transformation: "seedling → storm → sunrise" | + +--- + +## Example Use Cases + +### Example 1: "AI Winter → Spring → Summer" +- **Timeline:** 1970s → 2025 → Future +- **Milestones:** Winter (snowflake), Thaw (ice melting), Spring (bud), Summer (sun) +- **Color:** Purple on "Deep Learning Breakthrough" (2012) +- **Orientation:** Horizontal 16:9 + +### Example 2: "Security Thinking Evolution" +- **Timeline:** 2000 → Present +- **Milestones:** Each era with metaphor (fortress → ecosystem → adaptive) +- **Color:** Purple on paradigm shifts +- **Orientation:** Vertical 9:16 + +### Example 3: "Startup Journey: Idea to Scale" +- **Timeline:** Year 0 → Year 5 +- **Milestones:** Seedling → Sprout → Tree → Forest +- **Color:** Teal on funding rounds, purple on profitability +- **Orientation:** Horizontal 21:9 + +--- + +## Quick Reference + +**Timeline Formula:** +``` +1. Define timeline structure (subject, span, milestones, narrative) +2. Design layout (orientation, metaphors, spacing) +3. Construct prompt with illustrated progression +4. Choose aspect ratio for orientation +5. Generate with nano-banana-pro +6. Validate for clarity and visual narrative +``` + +**Color Strategy:** +- Timeline spine: Black +- 1-2 critical moments: Purple +- Supporting events: Teal +- Text: Charcoal + +**Metaphor Selection:** +- Choose simple, recognizable icons for each era +- Icons should show the FEELING/CHARACTER of that period +- Progression should tell a visual story + +--- + +**The workflow: Define → Design → Construct → Generate → Validate → Complete** diff --git a/.opencode/skills/Media/Art/Workflows/ULWallpaper.md b/.opencode/skills/Media/Art/Workflows/ULWallpaper.md new file mode 100755 index 00000000..9a4b30ac --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/ULWallpaper.md @@ -0,0 +1,337 @@ +# Create UL Wallpaper + +**Generate branded wallpapers with embedded logo concepts for Kitty terminal and macOS desktop.** + +## Voice Notification + +```bash +curl -s -X POST http://localhost:8888/notify \ + -H "Content-Type: application/json" \ + -d '{"message": "Running the ULWallpaper workflow in the Art skill to create wallpapers"}' \ + > /dev/null 2>&1 & +``` + +Running **ULWallpaper** in **Art**... + +--- + +Creates **4K 16:9 wallpapers** that integrate Unsupervised Learning logos as organic design elements — emblazoned, embossed, or woven into the composition. + +--- + +## Purpose + +Generate cohesive wallpapers that: +- Match the existing UL wallpaper aesthetic (dark tech, circuits, geometric patterns) +- Embed logo shapes/concepts as integral design elements (not just overlaid) +- Work for both Kitty terminal backgrounds (with 0.85 tint) and macOS desktop +- Maintain the blue/purple/teal color palette + +--- + +## Prerequisites + +**Logos Directory:** `~/Projects/Logos/` +Place logo files (PNG, SVG) here. The workflow will use these as reference for shape/concept integration. + +**Wallpaper Output:** `~/Projects/Wallpaper/` +Generated wallpapers are saved here and immediately available via `k -w `. + +**Reference Wallpapers:** `~/Projects/Wallpaper/` +Existing wallpapers to match aesthetic: +- `blue-lines.png` - Abstract flowing lines +- `blue-purple-circuits.png` - Circuit board pattern +- `blue-purple-squares.png` - Geometric squares +- `circuit-board.png` - Dense circuit traces + +--- + +## Workflow Steps + +### Step 1: Gather Input + +**Required from user:** +1. **Logo selection** — Which logo from `~/Projects/Logos/` to embed +2. **Style direction** — Circuit, geometric, abstract, flowing, etc. +3. **Integration style** — How logo appears: + - **Emblazoned** — Logo shape as glowing focal point + - **Embossed** — Logo as subtle raised/pressed texture + - **Woven** — Logo dissolved into pattern (circuits flow through it) + - **Negative space** — Logo revealed by absence of pattern +4. **Output name** — Filename for the wallpaper (kebab-case, no extension) + +**If no specific direction given:** +- Default to "woven" integration (most subtle) +- Match closest existing wallpaper style +- Use primary UL logo if available + +### Step 2: Analyze Logo + +Read the selected logo file to understand: +- Primary shapes and forms +- Key geometric elements +- Aspect ratio and proportions + +```bash +# List available logos +ls ~/Projects/Logos/ + +# View selected logo +open ~/Projects/Logos/.png +``` + +### Step 3: Load Reference Wallpaper + +View an existing wallpaper to match the aesthetic: + +```bash +open ~/Projects/Wallpaper/blue-purple-circuits.png +``` + +**Key aesthetic elements to maintain:** +- Dark background (#0a0a0f to #1a1a2e) +- Blue (#4a90d9), Purple (#8b5cf6), Teal (#06b6d4) accents +- Tech/digital feel (circuits, data streams, geometric patterns) +- Depth through blur and glow effects +- High contrast accent lines/nodes + +### Step 4: Construct Prompt + +**Base prompt template:** + +``` +Dark tech wallpaper for terminal/desktop, 16:9 4K resolution. + +BACKGROUND: Deep dark blue-black gradient (#0a0a0f to #1a1a2e) + +INTEGRATION: [LOGO_NAME] logo shape [INTEGRATION_STYLE]: +- [Describe how logo integrates with the pattern] +- [Logo should feel organic to the design, not overlaid] +- [Shape emerges from or defines the pattern flow] + +PATTERN STYLE: [STYLE_DIRECTION] +- [Specific pattern elements matching style] +- [How pattern interacts with logo shape] + +COLOR PALETTE: +- Primary: Electric blue (#4a90d9) — main circuit lines/elements +- Secondary: Deep purple (#8b5cf6) — accent glows, key nodes +- Tertiary: Cyan/teal (#06b6d4) — highlights, energy points +- Background: Near-black with subtle blue undertone + +EFFECTS: +- Subtle depth of field (sharper center, soft edges) +- Glow effects on key nodes and accent points +- Fine detail in circuit traces/patterns +- Atmospheric haze in corners + +CRITICAL: +- Logo shape is INTEGRAL to design, not overlaid +- Must work as terminal background with 85% dark tint overlay +- No text, no watermarks +- High contrast details for visibility through tint +- Professional, sophisticated tech aesthetic +``` + +### Step 5: Generate Wallpaper + +```bash +bun run ~/.opencode/skills/Art/Tools/Generate.ts \ + --model nano-banana-pro \ + --prompt "[CONSTRUCTED_PROMPT]" \ + --size 4K \ + --aspect-ratio 16:9 \ + --reference-image ~/Projects/Logos/.png \ + --output ~/Projects/Wallpaper/.png +``` + +**Parameters:** +- `--size 4K` — Maximum resolution +- `--aspect-ratio 16:9` — Standard widescreen +- `--reference-image` — Logo file for shape guidance + +### Step 6: Preview and Validate + +**Open the generated wallpaper:** +```bash +open ~/Projects/Wallpaper/.png +``` + +**Validation checklist:** +- [ ] Logo shape is recognizable but integrated (not pasted on) +- [ ] Color palette matches UL aesthetic (blue/purple/teal on dark) +- [ ] Pattern has enough contrast to show through Kitty tint +- [ ] No artifacts, text, or watermarks +- [ ] Professional quality suitable for desktop/terminal + +**If validation fails:** +- Adjust prompt specificity for logo integration +- Try different integration style +- Regenerate with refined prompt + +### Step 7: Apply Wallpaper + +Once validated, apply immediately: + +```bash +k -w +``` + +This sets both Kitty terminal and macOS desktop backgrounds. + +--- + +## Integration Styles Reference + +### Emblazoned +Logo as the **glowing focal point** — circuits/patterns radiate outward from it. +``` +Logo shape as central glowing element, circuit traces emanating outward from its edges, +energy nodes at key logo vertices, pattern density increases near logo +``` + +### Embossed +Logo as **subtle texture** — raised or pressed into the pattern layer. +``` +Logo shape visible as subtle raised/depressed region in the pattern, +same color palette but slightly different luminosity, discoverable not obvious +``` + +### Woven +Logo shape **defines pattern flow** — elements flow through/around it. +``` +Circuit traces and geometric elements flow through and around logo shape, +logo boundary influences pattern direction, shape emerges from negative space +``` + +### Negative Space +Logo **revealed by absence** — pattern stops at logo boundaries. +``` +Dense pattern everywhere except logo shape, logo appears as void/window, +subtle glow at logo edges where pattern meets empty space +``` + +--- + +## Style Directions Reference + +### Circuit +Dense circuit board traces, nodes, and connection points. +``` +PCB-style traces with right-angle turns, solder points as nodes, +varying trace widths, layer depth with traces at different z-levels +``` + +### Geometric +Abstract geometric shapes, grids, and mathematical patterns. +``` +Interlocking geometric shapes, hexagonal grids, triangular tessellation, +isometric depth, clean edges with subtle glow +``` + +### Flowing +Organic flowing lines, data streams, particle flows. +``` +Smooth curved lines suggesting data flow, particle streams, +gradient intensity along flow direction, organic movement feel +``` + +### Abstract +Non-representational artistic interpretation. +``` +Abstract color fields, gradient washes, subtle texture, +artistic interpretation of tech aesthetic, minimal but sophisticated +``` + +--- + +## Example Prompts + +### Example 1: UL Logo Woven into Circuits +``` +Dark tech wallpaper for terminal/desktop, 16:9 4K resolution. + +BACKGROUND: Deep dark blue-black gradient (#0a0a0f to #1a1a2e) + +INTEGRATION: Unsupervised Learning logo shape woven into circuit pattern: +- Circuit traces flow through and around the logo silhouette +- Logo boundary subtly influences trace direction +- Shape emerges naturally from the pattern density changes +- Not overlaid — the pattern DEFINES the logo through flow + +PATTERN STYLE: Dense circuit board +- Fine PCB-style traces with right-angle routing +- Glowing nodes at trace intersections +- Multiple depth layers (foreground sharp, background soft) +- Trace density varies to create visual interest + +COLOR PALETTE: +- Primary: Electric blue (#4a90d9) — main traces +- Secondary: Deep purple (#8b5cf6) — key nodes, logo edge glow +- Tertiary: Cyan (#06b6d4) — energy highlights +- Background: Near-black (#0a0a0f) + +EFFECTS: +- Depth of field blur at edges +- Subtle purple glow where logo shape meets pattern +- Fine detail in traces (visible at 4K) +- Atmospheric corner vignette + +CRITICAL: Logo integrated into design, not overlaid. Must show through 85% dark tint. +``` + +### Example 2: Logo Emblazoned in Geometric Field +``` +Dark tech wallpaper for terminal/desktop, 16:9 4K resolution. + +BACKGROUND: Deep space gradient (#0a0a0f to #1a1a2e) + +INTEGRATION: UL logo as central emblazoned element: +- Logo shape glows at center with purple (#8b5cf6) core +- Geometric patterns radiate outward from logo edges +- Energy lines connect logo vertices to outer pattern +- Logo is the source/origin of all pattern elements + +PATTERN STYLE: Geometric hexagonal grid +- Hexagonal tessellation extending from logo +- Grid density increases toward edges +- Subtle isometric depth +- Clean geometric precision + +COLOR PALETTE: +- Primary: Electric blue (#4a90d9) — grid lines +- Secondary: Deep purple (#8b5cf6) — logo glow, accent nodes +- Tertiary: Cyan (#06b6d4) — energy connections +- Background: Near-black with blue undertone + +EFFECTS: +- Central glow around logo +- Sharp center, soft edges +- Subtle particle effects +- Corner vignette + +CRITICAL: Logo as design origin point, not pasted overlay. High contrast for tint visibility. +``` + +--- + +## Quick Reference + +| Parameter | Value | +|-----------|-------| +| Model | nano-banana-pro | +| Size | 4K | +| Aspect Ratio | 16:9 | +| Output Directory | ~/Projects/Wallpaper/ | +| Logo Source | ~/Projects/Logos/ | +| Apply Command | `k -w ` | + +**Color Palette:** +- Background: #0a0a0f to #1a1a2e +- Blue: #4a90d9 +- Purple: #8b5cf6 +- Teal/Cyan: #06b6d4 + +**Integration Styles:** Emblazoned, Embossed, Woven, Negative Space + +**Pattern Styles:** Circuit, Geometric, Flowing, Abstract diff --git a/.opencode/skills/Media/Art/Workflows/Visualize.md b/.opencode/skills/Media/Art/Workflows/Visualize.md new file mode 100755 index 00000000..eed07737 --- /dev/null +++ b/.opencode/skills/Media/Art/Workflows/Visualize.md @@ -0,0 +1,795 @@ +# Adaptive Content Visualization Workflow + +**Intelligent multi-modal visualization combining optimal approaches based on content analysis.** + +Creates **ADAPTIVE VISUALIZATIONS** — analyzes content to select and orchestrate the best combination of visualization techniques, from pure data viz to mixed-media infographics to multi-panel compositions. + +--- + +## Purpose + +The Visualize workflow is the **intelligent visualization orchestrator**. Unlike the 12 specialized workflows (which each serve specific purposes), Visualize analyzes your content and chooses the optimal visualization strategy — which may be one approach, or a sophisticated combination of multiple techniques. + +**Use this workflow when:** +- You have content but aren't sure what visualization approach to use +- The content has multiple dimensions (data + narrative + concepts) +- You want the most effective visualization, not a predetermined format +- You're asking "what's the best way to visualize this?" +- You want to leverage Nano Banana Pro's full capabilities + +**This workflow DOES NOT use:** +- Predetermined templates +- One-size-fits-all approaches +- Single-mode visualizations when combinations would be better + +--- + +## 🚨 INFOGRAPHICS: Use Excalidraw Whiteboard Style + +**Infographics use the EXCALIDRAW whiteboard sketch aesthetic** — hand-drawn with wobbly boxes, sketchy lines, and imperfect organic shapes. This is the same style as mermaid.md technical diagrams but with richer graphics and narrative. + +**Key principle:** Infographics = Excalidraw aesthetic + Rich graphics + Visual narrative + +### Excalidraw Infographic Aesthetic + +``` +STYLE: Excalidraw whiteboard sketch with rich graphics +- WOBBLY BOXES — rectangles with rough, hand-drawn edges (not perfect) +- SKETCHY LINES — arrows and connections with slight wobble +- IMPERFECT SHAPES — circles slightly oval, diamonds asymmetric +- HAND-LETTERED TEXT — labels look handwritten, not typed +- WHITEBOARD FEEL — looks like someone drew this on a whiteboard +- VARIABLE LINE WEIGHT — heavier for boxes, lighter for details +- RICH GRAPHICS — icons, illustrations, visual metaphors (all sketchy) +``` + +### What Makes a Good Excalidraw Infographic + +1. **Hand-Drawn Feel** — Everything looks sketched, not digital +2. **Wobbly Shapes** — No perfect rectangles, circles, or lines +3. **Rich Graphics** — Icons and illustrations in sketchy style +4. **Visual Narrative** — Panels flow and tell a story +5. **Strategic Color** — Purple/teal accents on key elements, mostly black + +### AVOID + +``` +❌ Perfect geometric shapes +❌ Ruler-straight lines and arrows +❌ Digital precision +❌ Smooth polished vectors +❌ Perfect alignment +❌ Clean corporate infographic style +``` + +### Color Usage + +``` +- Black (#000000): All primary structure (boxes, arrows, icons) +- Deep Purple (#4A148C): Critical elements, key stats, title (10-20%) +- Deep Teal (#00796B): Secondary highlights (5-10%) +- Charcoal (#2D2D2D): All text labels +- Background: Light Cream #F5E6D3 or White #FFFFFF +``` + +### Background Rules + +``` +DEFAULT: Light Cream/Sepia #F5E6D3 (matches blog aesthetic) +WHITE ONLY IF: User explicitly requests "white background" in prompt +TRANSPARENT: Use Images skill to remove background for overlay use +``` + +**Light Cream (#F5E6D3) is the DEFAULT background.** Only use white (#FFFFFF) if the user explicitly requests it. + +**For transparent background** — use the **Images skill** for background removal: + +```bash +bun ~/.opencode/skills/PAI/Tools/RemoveBg.ts /path/to/visualization.png +``` + +### Title/Subtitle Alignment + +``` +ALWAYS LEFT-JUSTIFIED — Never centered +- Title: Top-left with margin +- Subtitle: Below title, aligned left +``` + +### Infographic Prompt Template + +``` +Excalidraw-style whiteboard infographic with rich hand-drawn graphics. + +STYLE: Excalidraw whiteboard sketch aesthetic +- Wobbly rectangles with rough edges (not perfect boxes) +- Sketchy arrows with slight wobble (not ruler-straight) +- Imperfect shapes throughout (circles slightly oval) +- Hand-lettered text labels (natural slant, imperfect) +- Variable line weight (boxes thicker, details thinner) +- Whiteboard/sketch paper feel + +BACKGROUND: Light Cream #F5E6D3 (DEFAULT) — only use White #FFFFFF if explicitly requested + +TYPOGRAPHY SYSTEM (3-TIER): + +TIER 1 - TITLE & SUBTITLE (Valkyrie): +Title: +- "[Title]" +- Font: Valkyrie serif ITALIC +- Position: LEFT-JUSTIFIED, top-left with margin +- Color: Purple #4A148C (or Black #000000) +- Size: Large, 3-4x body text + +Subtitle: +- "[Subtitle]" +- Font: Valkyrie serif REGULAR (NOT italic) +- Position: LEFT-JUSTIFIED, below title +- Color: Charcoal #2D2D2D +- Size: Small, 1-1.5x body text + +TIER 2 - PANEL HEADERS & LABELS (Concourse + Valkyrie): +Panel Headers: +- Font: Concourse geometric sans-serif, bold +- Color: Black #000000 +- Style: Uppercase + +Content Labels: +- Technical labels: Concourse geometric sans +- Descriptions: Valkyrie serif +- Color: Charcoal #2D2D2D + +TIER 3 - ANNOTATIONS (Advocate): +- Font: Advocate condensed italic +- Color: Purple #4A148C or Teal #00796B +- Style: Smaller, insight/commentary voice + +[Describe each panel with SKETCHY VISUAL ELEMENTS:] +- Hand-drawn icons and illustrations (wobbly, organic) +- Data visualized with sketchy charts/graphics +- Panels as wobbly boxes with headers +- Flow shown with sketchy arrows + +COLOR USAGE: +- Black: All primary structure and most elements +- Purple: Title, key stats, critical accents +- Teal: Secondary highlights +- Charcoal: All body text + +CRITICAL: +- Excalidraw hand-drawn whiteboard aesthetic throughout +- All shapes imperfect, all lines wobbly +- Title/subtitle LEFT-JUSTIFIED, not centered +- Use proper font hierarchy (Valkyrie, Concourse, Advocate) +``` + +**Reference:** See `mermaid.md` for complete Excalidraw aesthetic specification. + +--- + +## Nano Banana Pro Capabilities + +**Understanding what's possible:** + +### Core Strengths +1. **Exceptional text rendering** — Clean typography, readable labels, multiple text tiers +2. **Data visualization** — Charts, graphs, quantitative displays +3. **Infographic composition** — Multi-element layouts, mixed media +4. **Iconic illustration** — Simple recognizable symbols and icons +5. **Multi-panel layouts** — Grids, sequences, comparative layouts +6. **Hybrid compositions** — Data + illustration + typography together +7. **Slide-quality output** — Presentation-ready visualizations + +### What Nano Banana Pro Excels At +- **Text-heavy compositions** — Infographics with lots of labels +- **Data + context** — Numbers with explanatory illustrations +- **Icon systems** — Repeated simplified icons showing quantities +- **Multi-tier typography** — Clear hierarchies (titles, labels, annotations) +- **Mixed media** — Charts alongside illustrations +- **Grid layouts** — Organized multi-element compositions +- **Comparative panels** — Side-by-side or sequential comparisons + +--- + +## 🚨 MANDATORY WORKFLOW STEPS + +### Step 1: Deep Content Analysis (MANDATORY - Use deep thinking) + +**🎯 CRITICAL: Use extended thinking to analyze content thoroughly before proceeding.** + +Analyze the content across these dimensions: + +#### A. Content Type Identification +What types of information are present? +- [ ] Quantitative data (numbers, statistics, metrics) +- [ ] Qualitative concepts (ideas, principles, arguments) +- [ ] Narrative elements (stories, sequences, transformations) +- [ ] Comparative elements (X vs Y, before/after, tradeoffs) +- [ ] Hierarchical structures (taxonomies, frameworks, levels) +- [ ] Temporal elements (timelines, evolution, progressions) +- [ ] Spatial relationships (maps, territories, domains) +- [ ] Process flows (steps, recipes, methodologies) + +#### B. Communication Goal +What's the primary purpose? +- Explain a complex concept → Conceptual visualization +- Show data insights → Data visualization dominant +- Compare alternatives → Comparison/split approach +- Tell a story → Sequential/narrative visualization +- Organize information → Taxonomy/grid approach +- Guide action → Process/recipe format +- Make memorable → Metaphor + data hybrid + +#### C. Audience Context +Who's this for? +- Technical audience → More data, precision, structure +- General audience → More metaphor, simplification, narrative +- Executive audience → High-level insights, clear takeaways +- Social media → Punchy, scannable, shareable +- Consulting deliverable → Professional, multi-faceted, comprehensive + +#### D. Complexity Assessment +How much information needs to be conveyed? +- **Simple (1-2 key points):** Single focused visualization +- **Medium (3-5 dimensions):** Hybrid or two-element composition +- **Complex (6+ dimensions):** Multi-panel infographic or dashboard + +**Output from Analysis:** +``` +CONTENT TYPE: [Primary and secondary types from above] +INFORMATION DENSITY: [Simple / Medium / Complex] +COMMUNICATION GOAL: [Primary purpose] +AUDIENCE: [Who this is for] + +KEY ELEMENTS TO VISUALIZE: +1. [Element type: data/concept/narrative/etc.] +2. [Element type] +3. [Element type] +... + +VISUALIZATION OPPORTUNITIES: +- Data points that could be charts/graphs +- Concepts that need metaphors or icons +- Comparisons that need side-by-side +- Sequences that need panels or flow +- Hierarchies that need taxonomies or frameworks +``` + +--- + +### Step 2: Visualization Strategy Selection (MANDATORY - Use deep thinking) + +**Based on Step 1 analysis, determine the optimal approach:** + +#### Strategy Options + +**A. SINGLE-MODE (Use one specialized workflow)** +When content clearly fits one visualization type: +- Pure data → Create data visualization +- Pure concept → Use editorial illustration or framework +- Pure comparison → Use comparison workflow +- Pure process → Use recipe card workflow + +**B. HYBRID COMPOSITION (Combine 2-3 elements)** +When content has multiple dimensions: +- **Data + Metaphor:** Chart/graph with editorial illustration accent +- **Data + Process:** Numbers showing outcomes at each step +- **Concept + Structure:** Framework with illustrated metaphors in quadrants +- **Timeline + Data:** Progression with quantitative milestones +- **Comparison + Data:** Split screen with metrics on each side + +**C. MULTI-PANEL INFOGRAPHIC (Dashboard approach)** +When content is complex and multifaceted: +- **Grid layout:** 4-6 panels each showing different aspect +- **Layered composition:** Top section data, middle concepts, bottom process +- **Dashboard:** Multiple charts/graphs with unified design +- **Slide series:** Sequential slides each focusing on one dimension + +#### Decision Framework + +``` +IF content has 1 primary dimension: + → Use specialized workflow directly + +IF content has 2-3 dimensions of equal importance: + → Design HYBRID composition + +IF content has 4+ distinct dimensions: + → Design MULTI-PANEL infographic + +IF content is primarily quantitative: + → Lead with DATA VISUALIZATION + → Add conceptual elements as context + +IF content is primarily conceptual: + → Lead with METAPHOR/FRAMEWORK + → Add data as supporting evidence + +IF content tells a story: + → Use SEQUENTIAL approach + → Could be comic, timeline, or multi-step +``` + +**Output from Strategy Selection:** +``` +VISUALIZATION STRATEGY: [Single-mode / Hybrid / Multi-panel] + +CHOSEN APPROACH: +[Describe the specific visualization approach] + +COMPOSITION ELEMENTS: +Primary element (60-70%): [Type and purpose] +Secondary element (20-30%): [Type and purpose] +Tertiary element (10%): [Type and purpose - optional] + +LAYOUT STRUCTURE: +[Describe how elements are arranged spatially] + +ASPECT RATIO: [1:1 / 16:9 / 9:16 / 4:3] +Rationale: [Why this ratio for this content] +``` + +--- + +### Step 3: Design Composition (MANDATORY - Use deep thinking) + +**Plan the visual hierarchy and spatial organization:** + +#### A. Spatial Layout +Design how elements occupy the canvas: + +**For Single-Mode:** +- Follow the specialized workflow's layout guidelines +- Optimize for Nano Banana Pro's strengths + +**For Hybrid Composition:** +``` +Example: Data + Metaphor +┌─────────────────────────────────────┐ +│ │ +│ [TITLE - Advocate Block] │ +│ │ +│ ┌───────────┐ ┌──────────────┐ │ +│ │ │ │ │ │ +│ │ DATA │ │ METAPHOR │ │ +│ │ CHART │ │ ILLUSTRATION │ │ +│ │ │ │ │ │ +│ └───────────┘ └──────────────┘ │ +│ 40% 40% │ +│ │ +│ [Explanatory text - 20%] │ +│ │ +└─────────────────────────────────────┘ +``` + +**For Multi-Panel Infographic:** +``` +Example: Dashboard Grid +┌─────────────────────────────────────┐ +│ [OVERALL TITLE] │ +├─────────────┬───────────────────────┤ +│ Panel 1: │ Panel 2: │ +│ Data viz │ Concept diagram │ +├─────────────┼───────────────────────┤ +│ Panel 3: │ Panel 4: │ +│ Timeline │ Key stat + icon │ +├─────────────┴───────────────────────┤ +│ [Synthesis/Conclusion panel] │ +└─────────────────────────────────────┘ +``` + +#### B. Visual Hierarchy +Establish information priority: +1. **Primary (Immediate attention):** 50-60% of visual weight +2. **Secondary (Supporting context):** 25-35% of visual weight +3. **Tertiary (Details/annotations):** 10-15% of visual weight + +#### C. Typography System +Apply 3-tier system across all elements: +- **Tier 1 (Advocate Block):** Main title, section headers +- **Tier 2 (Concourse Sans):** Data labels, chart axes, element labels +- **Tier 3 (Advocate Condensed Italic):** Annotations, insights, editorial voice + +#### D. Color Strategy +Maintain UL aesthetic while supporting information hierarchy: +- **Black #000000:** Primary structure (chart axes, borders, main elements) +- **Purple #4A148C:** Critical insights, key data points, optimal zones +- **Teal #00796B:** Secondary data, supporting elements, context +- **Charcoal #2D2D2D:** All body text and labels +- **Background:** Light Cream #F5E6D3 (DEFAULT — only use white if explicitly requested) + +Strategic color use: +- Don't color everything +- Purple for "look here" moments +- Teal for supporting information +- Black for structure and clarity + +**Output from Design:** +``` +COMPOSITION LAYOUT: +[Detailed spatial description or ASCII diagram] + +VISUAL HIERARCHY: +Primary (50-60%): [Element and placement] +Secondary (25-35%): [Element and placement] +Tertiary (10-15%): [Element and placement] + +TYPOGRAPHY ASSIGNMENTS: +Tier 1: [Where used - titles, headers] +Tier 2: [Where used - labels, axes] +Tier 3: [Where used - annotations, insights] + +COLOR CODING: +Purple: [Specific elements to highlight] +Teal: [Supporting elements] +Black: [Structural elements] +Text: All charcoal + +ELEMENT SPECIFICATIONS: +[For each major element, specify:] +- Type (chart/icon/illustration/text) +- Size (% of canvas) +- Position (coordinates or relative placement) +- Style (data viz / editorial / typographic) +``` + +--- + +### Step 4: Construct Comprehensive Prompt (MANDATORY - Use deep thinking) + +**Build the generation prompt leveraging Nano Banana Pro's capabilities:** + +#### Prompt Structure Template + +``` +[VISUALIZATION TYPE] in editorial infographic style optimized for Nano Banana Pro. + +OVERALL CONCEPT: "[What this visualization communicates]" + +STYLE REFERENCE: [Professional infographic / Data journalism / Editorial slide / Mixed media visualization] + +BACKGROUND: Light Cream #F5E6D3 (DEFAULT) — only use White #FFFFFF if user explicitly requests it + +AESTHETIC: +- Professional infographic quality (deliverable standard) +- Hand-drawn editorial elements where appropriate +- Clean data visualization where precise +- Variable stroke weight (thicker for structure, thinner for details) +- Flat colors, no gradients or shadows +- Readable at multiple scales (works as thumbnail and full-size) + +ASPECT RATIO: [1:1 / 16:9 / 9:16 / 4:3] + +COMPOSITION STRUCTURE: +[Detailed description of spatial layout] + +TYPOGRAPHY SYSTEM (3-TIER HIERARCHY): + +TIER 1 - VISUALIZATION HEADER & SUBTITLE (Valkyrie Two-Part System): +Header (Main Title): +- "[Header Text]" +- Font: Valkyrie serif italic (elegant, sophisticated) +- Size: Large - 3-4x body text (prominent, commanding attention) +- Style: Italicized, sentence case or title case (NOT all-caps) +- Color: Black #000000 (or Purple #4A148C for emphasis) +- Position: Top-left with margin + +Subtitle (Clarifying Detail): +- "[Subtitle Text]" +- Font: Valkyrie serif regular (warm, readable) +- Size: Small - 1-1.5x body text (noticeably smaller than header, supportive) +- Style: Regular (NOT italicized), sentence case (first letter capitalized, rest lowercase except proper nouns) +- Color: Black #000000 or Charcoal #2D2D2D +- Position: Small gap below header, aligned left + +TIER 2 - ELEMENT LABELS (Concourse Sans): +- [List all labels: chart axes, data labels, section headers] +- Font: Concourse geometric sans-serif, clean, modern +- Size: Medium readable +- Color: Charcoal #2D2D2D +- Positions: [Specify for each] + +TIER 3 - ANNOTATIONS (Advocate Condensed Italic): +- [List all annotations and insights] +- Font: Advocate condensed italic (editorial voice) +- Size: Small (60% of Tier 2) +- Color: Purple #4A148C (insights) or Teal #00796B (technical notes) +- Positions: [Near relevant elements] + +[FOR EACH MAJOR ELEMENT IN COMPOSITION:] + +ELEMENT 1: [TYPE - e.g., Bar Chart / Line Graph / Icon Grid] +- Purpose: [What this element communicates] +- Position: [Location in composition] +- Size: [Dimensions or % of canvas] +- Data to show: [Specific data points or values] +- Style: [Precise data viz / Hand-drawn editorial / Hybrid] +- Color: Black structure, Purple highlights on [specific], Teal on [specific] +- Labels: [Tier 2 typography for all labels] +- Details: [Any specific styling notes] + +ELEMENT 2: [TYPE - e.g., Editorial Illustration / Framework Diagram] +- Purpose: [What this element communicates] +- Position: [Location in composition] +- Size: [Dimensions or % of canvas] +- Content: [What to illustrate] +- Style: [Hand-drawn / Iconic / Metaphorical] +- Color: Black linework, Purple accents on [specific] +- Integration: [How it relates to other elements] + +[Continue for all elements...] + +COLOR USAGE (Strategic, not overwhelming): +- Black (#000000): [All primary structure, chart elements, borders] +- Deep Purple (#4A148C): [Critical data points, key insights, optimal zones] +- Deep Teal (#00796B): [Secondary data, supporting elements] +- Charcoal (#2D2D2D): [All text labels and annotations] +- Background: Light Cream #F5E6D3 (DEFAULT — white only if explicitly requested) + +CRITICAL REQUIREMENTS FOR NANO BANANA PRO: +- Exceptional text rendering required (multiple labels, clean typography) +- Data precision where needed (accurate chart rendering) +- Hand-drawn editorial quality where appropriate +- Multi-element composition with clear visual hierarchy +- Professional infographic / slide quality +- Readable at both thumbnail and full resolution +- No gradients, flat colors only +- Strategic color (not every element colored) +- All elements work together as unified composition + +VALIDATION CHECKPOINTS: +- Is the primary message immediately clear? +- Can each element be read/understood independently? +- Do elements work together to tell complete story? +- Is typography hierarchy obvious? +- Are data elements accurate and precise? +- Do editorial elements enhance (not distract from) information? + +Optional: Sign small in bottom right corner in charcoal (#2D2D2D). +``` + +--- + +### Step 5: Generate with Nano Banana Pro + +**Execute the visualization using intent-to-flag mapping:** + +#### Intent-to-Flag Mapping + +**Interpret user request and select appropriate flags:** + +| User Says | Flag | When to Use | +|-----------|------|-------------| +| "fast", "quick", "draft" | `--model nano-banana` | Faster iteration, slightly lower quality | +| (default), "best", "high quality" | `--model nano-banana-pro` | Best quality + text rendering (recommended) | +| "flux", "stylistic variety" | `--model flux` | Different aesthetic, stylistic variety | + +| User Says | Flag | Resolution | +|-----------|------|------------| +| "draft", "preview" | `--size 1K` | Quick iterations | +| (default), "standard" | `--size 2K` | Standard output | +| "high res", "print", "large" | `--size 4K` | Maximum resolution | + +| User Says | Flag | Use Case | +|-----------|------|----------| +| "square", "social" | `--aspect-ratio 1:1` | Social media, grids | +| "wide", "slide", "presentation" | `--aspect-ratio 16:9` | Slides, presentations | +| "portrait", "mobile" | `--aspect-ratio 9:16` | Mobile, vertical | +| "blog header" | `--thumbnail` | Creates transparent + thumb versions | +| "variations", "options" | `--creative-variations 3` | Multiple versions | + +**Construct command based on intent:** + +```bash +bun run ~/.opencode/skills/Media/Art/Tools/Generate.ts \ + --model [SELECTED_MODEL] \ + --prompt "[YOUR COMPREHENSIVE PROMPT]" \ + --size [SELECTED_SIZE] \ + --aspect-ratio [chosen ratio] \ + [--thumbnail if for blog] \ + [--creative-variations N if variations requested] \ + --output /path/to/visualization.png +``` + +**Why Nano Banana Pro for this workflow:** +- Best text rendering among all models (critical for infographics) +- Handles complex multi-element compositions well +- Excellent at data visualization elements +- Can combine precise (charts) with expressive (editorial) styles +- Reliable for professional deliverable quality + +**Immediately open for review:** +```bash +open /path/to/visualization.png +``` + +--- + +### Step 6: Comprehensive Validation (MANDATORY) + +**Validate across multiple dimensions:** + +#### Information Effectiveness +- [ ] **Primary message clear:** Main insight obvious within 3 seconds +- [ ] **Data accuracy:** Numbers, proportions, relationships accurate +- [ ] **Visual hierarchy works:** Eye flows from primary → secondary → tertiary +- [ ] **All elements readable:** Text legible, charts clear, icons recognizable +- [ ] **Story cohesion:** Elements work together, not competing + +#### Design Quality +- [ ] **Professional deliverable:** Client/publication ready +- [ ] **UL aesthetic maintained:** Flat colors, appropriate hand-drawn vs precise +- [ ] **Typography hierarchy clear:** 3 tiers obviously distinct +- [ ] **Color strategic:** Purple/teal highlight key elements, not overwhelming +- [ ] **Composition balanced:** Visual weight distributed appropriately + +#### Technical Execution +- [ ] **Text rendering clean:** No blurry or malformed letters +- [ ] **Data viz precision:** Charts/graphs accurate and clear +- [ ] **Scale works:** Readable as thumbnail AND full-size +- [ ] **No gradients/shadows:** Flat aesthetic maintained +- [ ] **Aspect ratio appropriate:** Format suits content and use case + +#### Audience Appropriateness +- [ ] **Matches audience sophistication:** Not too simple or too complex +- [ ] **Serves communication goal:** Actually achieves intended purpose +- [ ] **Platform optimized:** Works for intended distribution (social/email/presentation) + +#### If Validation Fails + +**Common issues and fixes:** + +| Problem | Diagnosis | Fix | +|---------|-----------|-----| +| **Too cluttered** | Too many elements competing | Simplify: reduce to 2-3 main elements, increase whitespace | +| **Message unclear** | No clear visual hierarchy | Strengthen primary element (make larger, add purple), reduce secondary | +| **Text unreadable** | Font too small or wrong tier | Increase label sizes, strengthen typography tier differentiation | +| **Data imprecise** | Chart rendering issues | Add specific data points in prompt, request precision explicitly | +| **Looks generic** | Missing UL aesthetic | Add hand-drawn editorial elements, strategic purple/teal, flatten any gradients | +| **Elements disconnected** | Poor composition | Redesign spatial layout, add visual connectors (arrows, borders, grouping) | +| **Color chaos** | Too much color everywhere | Limit purple to 2-3 key elements, teal to 1-2 supporting, rest black/charcoal | +| **Not professional** | Too sketchy or too rigid | Balance: data viz precise, editorial elements hand-drawn, clean typography | + +**Regeneration Process:** +1. Identify specific validation failures +2. Update prompt with targeted fixes +3. Regenerate with refined prompt +4. Re-validate against all checkpoints +5. Repeat until ALL validation criteria pass + +**CRITICAL: Do not declare completion until validation passes.** + +--- + +## Visualization Pattern Library + +**Common effective combinations:** + +### Pattern 1: Data + Metaphor Hybrid +**When:** Data needs conceptual context +**Layout:** 50% data visualization + 40% editorial illustration + 10% explanatory text +**Example:** Growth chart with rocket ship illustration showing trajectory +**Aspect:** 16:9 or 1:1 + +### Pattern 2: Comparative Dashboard +**When:** Analyzing multiple dimensions of comparison +**Layout:** Split or grid with data on each side/panel +**Example:** "Before AI vs After AI" with metrics and illustrations for each state +**Aspect:** 16:9 (split) or 1:1 (grid) + +### Pattern 3: Process + Outcomes +**When:** Showing methodology with results +**Layout:** Vertical or horizontal flow with data at key milestones +**Example:** 5-step recipe with success metrics at each step +**Aspect:** 9:16 (vertical) or 16:9 (horizontal) + +### Pattern 4: Icon Quantification +**When:** Showing quantities through repeated visual elements +**Layout:** Grid of icons where quantity = visual count +**Example:** "78 out of 100 developers" shown as 78 purple icons + 22 gray icons +**Aspect:** 1:1 or 4:3 + +### Pattern 5: Annotated Data Story +**When:** Data needs narrative explanation +**Layout:** Primary chart with hand-drawn annotations explaining insights +**Example:** Timeline chart with purple arrows: "*this is when everything changed*" +**Aspect:** 16:9 or 21:9 + +### Pattern 6: Multi-Chart Dashboard +**When:** Multiple related datasets +**Layout:** Grid of 2-4 charts with unified design language +**Example:** 4-panel view: bar chart, line graph, pie chart, key stat +**Aspect:** 16:9 or 1:1 + +### Pattern 7: Framework + Data +**When:** Conceptual model with quantitative evidence +**Layout:** Framework structure (2x2, Venn, pyramid) with data in each zone +**Example:** 2x2 matrix with percentage of companies in each quadrant +**Aspect:** 1:1 + +### Pattern 8: Infographic Slide +**When:** Comprehensive content for presentation +**Layout:** Title + multiple small visualizations + key takeaway +**Example:** Slide with 3 mini-charts + 2 key stats + insight annotation +**Aspect:** 16:9 (slide format) + +--- + +## Decision Tree Summary + +``` +START: Analyze content deeply (Step 1) + ↓ +Is content primarily ONE dimension? + ├─ YES → Use specialized workflow directly + │ (Editorial / Technical / Timeline / etc.) + │ + └─ NO → Content has multiple dimensions + ↓ + Are there 2-3 equal dimensions? + ├─ YES → HYBRID composition + │ Design complementary elements + │ (Data + Metaphor, Process + Outcomes, etc.) + │ + └─ NO → 4+ dimensions or very complex + ↓ + MULTI-PANEL infographic + Grid or layered dashboard approach + Each panel addresses one dimension + +For HYBRID or MULTI-PANEL: + ↓ +Design composition (Step 3) + → Spatial layout + → Visual hierarchy + → Typography tiers + → Color strategy + ↓ +Construct comprehensive prompt (Step 4) + → Detailed element specifications + → Leverage Nano Banana Pro strengths + → Clear validation checkpoints + ↓ +Generate with nano-banana-pro (Step 5) + ↓ +VALIDATE comprehensively (Step 6) + → Information effectiveness + → Design quality + → Technical execution + → Audience appropriateness + ↓ +PASS? → Complete +FAIL? → Diagnose, fix, regenerate +``` + +--- + +## Quick Reference + +### When to Use Visualize Workflow +- Content has multiple dimensions to visualize +- You want optimal approach, not predetermined format +- Combining data + concepts + narrative +- Creating professional infographics or slides +- Need sophisticated composition beyond single workflow + +### Nano Banana Pro Advantages +- Best text rendering (critical for labels/annotations) +- Multi-element composition handling +- Data visualization capabilities +- Professional infographic quality +- Hybrid precision + expressiveness + +### Core Principles +1. **Analyze first** — Deep content analysis before choosing approach +2. **Strategic combination** — Use hybrid only when it serves content +3. **Visual hierarchy** — Clear primary/secondary/tertiary structure +4. **Color discipline** — Purple/teal strategic, not everywhere +5. **Professional quality** — Deliverable to clients/publications +6. **Validate thoroughly** — Information + design + technical + audience + +--- + +**The workflow: Analyze → Strategy → Design → Prompt → Generate → Validate → Complete** + +**The meta-principle: Let content dictate form. Use the full power of Nano Banana Pro to create the most effective visualization, whether that's one approach or a sophisticated orchestration of multiple techniques.** diff --git a/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Audio1.png b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Audio1.png new file mode 100755 index 00000000..1abf1458 Binary files /dev/null and b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Audio1.png differ diff --git a/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main1.png b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main1.png new file mode 100755 index 00000000..809130de Binary files /dev/null and b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main1.png differ diff --git a/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main2.png b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main2.png new file mode 100755 index 00000000..5295a7a1 Binary files /dev/null and b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main2.png differ diff --git a/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main3.png b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main3.png new file mode 100755 index 00000000..1d936e5a Binary files /dev/null and b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main3.png differ diff --git a/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main4.png b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main4.png new file mode 100755 index 00000000..68d5c338 Binary files /dev/null and b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main4.png differ diff --git a/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main5.png b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main5.png new file mode 100755 index 00000000..94ee8773 Binary files /dev/null and b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main5.png differ diff --git a/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main6.png b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main6.png new file mode 100755 index 00000000..ef82ae1b Binary files /dev/null and b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main6.png differ diff --git a/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main7.png b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main7.png new file mode 100755 index 00000000..71a3e252 Binary files /dev/null and b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Main7.png differ diff --git a/.opencode/skills/Media/Art/YouTubeThumbnailExamples/SPECIFICATIONS.md b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/SPECIFICATIONS.md new file mode 100755 index 00000000..9999977c --- /dev/null +++ b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/SPECIFICATIONS.md @@ -0,0 +1,400 @@ +# YouTube Thumbnail Design Specifications + +**Analysis Date**: 2025-12-21 +**Canvas Size**: 1280 x 720 pixels (standard YouTube thumbnail) +**Aspect Ratio**: 16:9 + +--- + +## GLOBAL DESIGN SYSTEM + +### Border System +| Property | Value | +|----------|-------| +| Color | `#4A90D9` (Medium Blue) | +| Thickness | 6px | +| Corner Radius | 12px | +| Style | Solid, consistent across all thumbnails | + +### Background System +| Property | Value | +|----------|-------| +| Primary Background | `#1A2744` (Deep Navy Blue) | +| Secondary/Overlay | `#243654` (Lighter Navy) | +| Gradient Direction | None (solid with overlaid elements) | + +### Logo ("TI:" Mark) +| Property | Value | +|----------|-------| +| Symbol | Stylized "TI:" ligature | +| Color | `#1A2744` (Dark Navy, matching background) | +| Width | ~45px | +| Height | ~50px | +| Position | Top-right corner | +| Offset from right edge | 24px | +| Offset from top edge | 20px | + +--- + +## TYPOGRAPHY SYSTEM + +### Font Family Analysis +The thumbnails use a **sans-serif font family** with these characteristics: +- **Primary Font**: Appears to be **Inter**, **Montserrat**, or similar geometric sans-serif +- **Characteristics**: Clean, modern, high x-height, excellent legibility at small sizes +- **Weight Range**: Regular (400) to Bold (700) to Extra Bold (800) + +### Typography Hierarchy + +#### Line 1 - Series/Category Label +| Property | Value | +|----------|-------| +| Color | `#FFFFFF` (White) | +| Font Weight | Bold (700) | +| Font Size | 32-36px | +| Letter Spacing | 0.05em (expanded) | +| Text Transform | UPPERCASE | +| Position Y | 28px from top (inside border) | +| Position X | 28px from left edge | + +#### Line 2 - Primary Title (EMPHASIS LINE) +| Property | Value | +|----------|-------| +| Color | `#6B8DD6` (Periwinkle Blue) or `#F5A623` (Orange accent) | +| Font Weight | Extra Bold (800) | +| Font Size | 56-64px | +| Letter Spacing | 0.02em | +| Text Transform | UPPERCASE | +| Position Y | 68px from top | +| Position X | 28px from left edge | +| Special Effects | Sometimes has highlight box behind text | + +#### Line 3 - Secondary Title +| Property | Value | +|----------|-------| +| Color | `#FFFFFF` (White) or `#6B8DD6` (Periwinkle) | +| Font Weight | Bold (700) | +| Font Size | 48-56px | +| Letter Spacing | 0.02em | +| Text Transform | UPPERCASE or Title Case | +| Position Y | 130px from top | +| Position X | 28px from left edge | + +#### Line 4 - Version/Date Label +| Property | Value | +|----------|-------| +| Color | `#C084FC` (Purple/Violet) | +| Font Weight | Medium (500) | +| Font Size | 24-28px | +| Letter Spacing | 0.03em | +| Text Transform | Title Case with parentheses | +| Position Y | 185px from top | +| Position X | 28px from left edge | + +--- + +## INDIVIDUAL THUMBNAIL SPECIFICATIONS + +--- + +### Main1.png - "Personal AI Infrastructure v2" + +#### Text Content & Styling +| Line | Text | Color | Size | Weight | +|------|------|-------|------|--------| +| 1 | "A DEEPDIVE ON MY" | `#FFFFFF` | 28px | Bold | +| 1b | "CLAUDE CODE" (badge) | `#FFFFFF` on `#D97706` bg | 18px | Bold | +| 2 | "PERSONAL AI" | `#6B8DD6` | 56px | Extra Bold | +| 3 | "INFRASTRUCTURE" | `#6B8DD6` | 56px | Extra Bold | +| 4 | "v2 (December 2025)" | `#C084FC` | 24px | Medium | + +#### Claude Code Badge +| Property | Value | +|----------|-------| +| Background | `#D97706` (Orange/Amber) | +| Text Color | `#FFFFFF` | +| Padding | 4px 8px | +| Border Radius | 4px | +| Position | Inline after "MY" | + +#### Background Art +- **Type**: Technical diagram/flowchart +- **Coverage**: ~60% of frame (left and center) +- **Opacity**: 30-40% overlay +- **Content**: PAI Infrastructure architecture diagram +- **Blend**: Darkened to not compete with text + +#### Headshot +| Property | Value | +|----------|-------| +| Width | ~35% of canvas (448px) | +| Height | ~85% of canvas (612px) | +| Position X | Right edge, ~40px from border | +| Position Y | Vertically centered, slight bottom crop | +| Edge Treatment | Soft fade on left edge into background | + +--- + +### Main2.png - "Building Your Own KAI" + +#### Text Content & Styling +| Line | Text | Color | Size | Weight | +|------|------|-------|------|--------| +| 1 | "BUILDING YOUR OWN" | `#FFFFFF` | 32px | Bold | +| 1b | "\"KAI\"" | `#C084FC` | 32px | Bold | +| 2 | "PERSONAL AI" | `#6B8DD6` | 52px | Extra Bold | +| 3 | "ASSISTANT" | `#FFFFFF` | 52px | Extra Bold | + +#### Background Art +- **Type**: Dual panel - circular diagram (left) + code terminal (right) +- **Coverage**: ~55% of frame +- **Left Panel**: KAI MIC circular architecture diagram +- **Right Panel**: Claude Code terminal window with dark theme +- **Opacity**: 60-70% visible + +#### Headshot +| Property | Value | +|----------|-------| +| Width | ~40% of canvas (512px) | +| Height | ~90% of canvas (648px) | +| Position X | Right-aligned, overlapping background art | +| Position Y | Bottom-aligned with slight crop | +| Edge Treatment | Hard edge, no fade | + +--- + +### Main3.png - "Custom Agent Voices" + +#### Text Content & Styling +| Line | Text | Color | Size | Weight | +|------|------|-------|------|--------| +| 1 | "PERSONAL AI INFRASTRUCTURE" | `#FFFFFF` | 24px | Bold | +| 2 | "USING CUSTOM AGENT VOICES" | `#F5A623` (Orange) | 40px | Extra Bold | + +#### Special Elements +- **ElevenLabs Logo**: White logo, positioned left side, ~80px wide +- **Agent Names**: "KAI", "DESIGNER", "PENTESTER", "ENGINEER", "RESEARCHER" + - Color: `#90EE90` (Light Green) + - Size: 28-36px graduated + - Stacked vertically with speaking head icons + +#### Claude Code Badge (Bottom Right) +| Property | Value | +|----------|-------| +| Position | Bottom-right, 24px from edges | +| Style | Orange badge with white text | + +#### Background +- **Color**: `#1E3A5F` (Slightly brighter navy) +- **Art**: Code snippets with blur effect +- **Coverage**: Full background with overlays + +#### Headshot +| Property | Value | +|----------|-------| +| Width | ~38% of canvas | +| Height | ~80% of canvas | +| Position X | Right third | +| Position Y | Centered | +| Edge Treatment | Soft left fade | + +--- + +### Main4.png - "Ghostty Panes" (Variant A) + +#### Text Content & Styling +| Line | Text | Color | Size | Weight | +|------|------|-------|------|--------| +| 1 | "AN IDEA VIDEO HERE" | `#FFFFFF` | 28px | Bold | +| 1b | "!!!" | `#EF4444` (Red) | 28px | Bold | +| 2 | "GHOSTTY" | `#3B82F6` (Bright Blue) | 56px | Extra Bold | +| 2b | "PANES" | `#1A2744` (Dark Navy) | 56px | Extra Bold | + +#### Background Art +- **Type**: Terminal/code editor screenshot +- **Content**: Ghostty terminal with multiple panes +- **Coverage**: ~50% center-left +- **Opacity**: 40-50% + +#### Headshot +| Property | Value | +|----------|-------| +| Width | ~35% of canvas | +| Height | ~85% of canvas | +| Position X | Center-right | +| Position Y | Bottom-aligned | +| Edge Treatment | Natural edges, no fade | + +--- + +### Main5.png - "Context Engineering Series" + +#### Text Content & Styling +| Line | Text | Color | Size | Weight | +|------|------|-------|------|--------| +| 1 | "CONTEXT ENGINEERING SERIES" | `#FFFFFF` | 24px | Bold | +| 2 | "DYNAMIC CONTEXT LOADING" | `#F5A623` (Orange) | 36px | Extra Bold | +| 3 | "KAI" | `#C084FC` (Purple) | 72px | Extra Bold | + +#### Special Elements +- **Claude Code Badge**: Below KAI text, orange background +- **Anime Character**: Blue-haired figure facing right + - Width: ~30% of canvas + - Position: Left-center + - Style: Cyberpunk/futuristic aesthetic + +#### Background Art +- **Type**: Code/terminal with gradient overlay +- **Coverage**: Full background +- **Primary Color**: Purple-blue gradient tint + +#### Headshot +| Property | Value | +|----------|-------| +| Width | ~38% of canvas | +| Height | ~85% of canvas | +| Position X | Right edge | +| Position Y | Vertically centered | +| Edge Treatment | Hard edge | + +--- + +### Main6.png - "Ghostty Panes" (Variant B) + +*Identical to Main4.png - appears to be same thumbnail or minor variant* + +--- + +### Main7.png - "Conversation with Marcus Hutchins" + +#### Text Content & Styling +| Line | Text | Color | Size | Weight | +|------|------|-------|------|--------| +| 1 | "A CONVERSATION WITH" | `#FFFFFF` | 28px | Bold | +| 2 | "MARCUS HUTCHINS" | `#C084FC` (Purple) | 48px | Extra Bold | +| 3 | "ON" | `#FFFFFF` | 24px | Bold | +| 3b | "AI" | `#3B82F6` (Blue) | 36px | Extra Bold | +| 3c | "HYPE VS REALITY" | `#FFFFFF` | 36px | Extra Bold | + +#### Layout (DUAL HEADSHOT) +This is a conversation/interview format with two people: + +| Element | Left Person | Right Person | +|---------|------------|--------------| +| Width | 45% of canvas | 45% of canvas | +| Position | Bottom-left | Bottom-right | +| Name Label | "[Host Name]" | "[Guest Name]" | +| Label Style | White text, small (~14px) | + +#### Background +- **Color**: Solid `#1A2744` navy +- **No additional art** - cleaner interview format + +--- + +## COLOR PALETTE SUMMARY + +| Name | Hex | Usage | +|------|-----|-------| +| Deep Navy | `#1A2744` | Primary background | +| Border Blue | `#4A90D9` | Frame border | +| Periwinkle | `#6B8DD6` | Primary emphasis text | +| White | `#FFFFFF` | Standard text, high contrast | +| Purple/Violet | `#C084FC` | Accent text, names | +| Orange/Amber | `#F5A623` | Highlight text, badges | +| Badge Orange | `#D97706` | Badge backgrounds | +| Bright Blue | `#3B82F6` | Accent text | +| Red Alert | `#EF4444` | Exclamation marks | +| Light Green | `#90EE90` | Agent names list | + +--- + +## LAYOUT GRID SYSTEM + +### Safe Zones +| Zone | Measurement | +|------|-------------| +| Border Inset | 6px all sides | +| Content Padding | 28px from border | +| Text Block Width | ~55% of canvas (left side) | +| Headshot Zone | ~40% of canvas (right side) | + +### Vertical Rhythm +| Element | Y Position | +|---------|------------| +| Top Border | 0px | +| Line 1 Start | 34px | +| Line 2 Start | 74px | +| Line 3 Start | 134px | +| Line 4 Start | 190px | +| Logo Top | 20px | +| Headshot Top | Variable (centered or top-aligned) | + +### Z-Index Layering +1. **Background Color** (bottom) +2. **Background Art/Screenshots** (30-60% opacity) +3. **Headshot Image** +4. **Text Content** +5. **Badges/Special Elements** +6. **Border Frame** +7. **TI: Logo** (top) + +--- + +## HEADSHOT SPECIFICATIONS + +### Standard Solo Thumbnail +| Property | Value | +|----------|-------| +| Subject Position | Right 40% of frame | +| Vertical Alignment | Centered or bottom-heavy | +| Scale | Head occupies ~20-25% of frame height | +| Background Removal | Clean cutout, no background | +| Edge Treatment | Soft fade left (optional) or hard edge | +| Lighting | Bright, well-lit face | +| Expression | Neutral to friendly | + +### Interview/Dual Format +| Property | Value | +|----------|-------| +| Layout | 50/50 split | +| Subject Scale | Smaller to fit both | +| Name Labels | Bottom of each frame | +| Frame Treatment | Visible frame borders between subjects | + +--- + +## RECREATION CHECKLIST + +To recreate these thumbnails exactly: + +1. [ ] Create 1280x720 canvas +2. [ ] Fill with `#1A2744` background +3. [ ] Add 6px `#4A90D9` border with 12px radius +4. [ ] Position TI: logo at top-right (24px, 20px offset) +5. [ ] Add background art at 30-50% opacity +6. [ ] Place headshot on right side (35-40% width) +7. [ ] Apply soft fade to headshot left edge if needed +8. [ ] Add text lines following typography hierarchy +9. [ ] Apply color accents per specific thumbnail +10. [ ] Add badges/special elements as needed + +--- + +## FONT RECOMMENDATIONS + +Based on letterform analysis, recommended fonts: + +1. **Primary**: Inter (most likely match) +2. **Alternative 1**: Montserrat +3. **Alternative 2**: Poppins +4. **Alternative 3**: Work Sans + +All should be used in: +- Regular (400) for body +- Bold (700) for standard headers +- Extra Bold (800) for primary emphasis + +--- + +*Document generated by Designer Agent for exact thumbnail recreation* diff --git a/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Sponsored1.png b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Sponsored1.png new file mode 100755 index 00000000..8634e976 Binary files /dev/null and b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Sponsored1.png differ diff --git a/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Sponsored2.png b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Sponsored2.png new file mode 100755 index 00000000..4f651f77 Binary files /dev/null and b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Sponsored2.png differ diff --git a/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Sponsored3.png b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Sponsored3.png new file mode 100755 index 00000000..797b7434 Binary files /dev/null and b/.opencode/skills/Media/Art/YouTubeThumbnailExamples/Sponsored3.png differ diff --git a/.opencode/skills/Media/Remotion/ArtIntegration.md b/.opencode/skills/Media/Remotion/ArtIntegration.md new file mode 100644 index 00000000..9f8cd06c --- /dev/null +++ b/.opencode/skills/Media/Remotion/ArtIntegration.md @@ -0,0 +1,95 @@ +# Art Skill Integration + +**MANDATORY:** This skill inherits visual theming from the Art skill. + +## Before Creating Any Video Content + +1. **Load Art preferences:** + ```text + ~/.opencode/skills/PAI/USER/SKILLCUSTOMIZATIONS/Art/PREFERENCES.md + ``` + +2. **Apply the PAI Theme** derived from Art preferences: + +| Art Preference | Remotion Application | +|----------------|---------------------| +| Core aesthetic (charcoal architectural) | Dark backgrounds, sketch-like feel | +| Primary accent (purple/violet) | Accent colors, highlights, CTAs | +| Cool atmospheric washes | Background gradients, overlays | +| Paper ground (#F5F5F0) | Light text, subtle backgrounds | +| Human-scale in vast spaces | Typography hierarchy, spacing | + +3. **Use Theme Constants:** + ```text + ~/.opencode/skills/Media/Remotion/Tools/Theme.ts + ``` + +4. **Reference images** (when visual style reference needed): + ```text + ~/.opencode/skills/Media/Art/Examples/ + ``` + +## PAI Theme Quick Reference + +```typescript +import { PAI_THEME } from '~/.opencode/skills/Remotion/Tools/Theme' + +// Colors +PAI_THEME.colors.background // #0f172a - Deep slate +PAI_THEME.colors.accent // #8b5cf6 - Purple/violet +PAI_THEME.colors.text // #f1f5f9 - Light text +PAI_THEME.colors.textMuted // #94a3b8 - Muted text + +// Typography +PAI_THEME.typography.title // { fontSize: 72, fontWeight: 'bold' } +PAI_THEME.typography.subtitle // { fontSize: 48 } +PAI_THEME.typography.body // { fontSize: 24 } + +// Animation +PAI_THEME.animation.springDefault // { damping: 12, stiffness: 100 } +PAI_THEME.animation.fadeFrames // 30 frames (~1 second) +PAI_THEME.animation.staggerDelay // 10 frames + +// Spacing +PAI_THEME.spacing.page // 100px edge padding +PAI_THEME.spacing.section // 60px between sections +PAI_THEME.spacing.element // 30px between elements +``` + +## Using the Theme in Components + +```typescript +import { PAI_THEME, titleScreenStyle, fadeInterpolation } from '~/.opencode/skills/Remotion/Tools/Theme' + +export const MyScene: React.FC = () => { + const frame = useCurrentFrame() + const { fps } = useVideoConfig() + + const opacity = interpolate( + frame, + fadeInterpolation().inputRange, + fadeInterpolation().outputRange, + { extrapolateRight: 'clamp' } + ) + + const scale = spring({ + frame, fps, + config: PAI_THEME.animation.springDefault + }) + + return ( + +

+ Title Here +

+
+ ) +} +``` + +**All videos MUST use this theme unless explicitly overridden.** diff --git a/.opencode/skills/Media/Remotion/CriticalRules.md b/.opencode/skills/Media/Remotion/CriticalRules.md new file mode 100644 index 00000000..80d72fe4 --- /dev/null +++ b/.opencode/skills/Media/Remotion/CriticalRules.md @@ -0,0 +1,43 @@ +--- +name: remotion-best-practices +description: Best practices for Remotion - Video creation in React +metadata: + tags: remotion, video, react, animation, composition +--- + +## When to use + +Use this skills whenever you are dealing with Remotion code to obtain the domain-specific knowledge. + +## How to use + +Read individual rule files for detailed explanations and code examples: + +- [rules/3d.md](rules/3d.md) - 3D content in Remotion using Three.js and React Three Fiber +- [rules/animations.md](rules/animations.md) - Fundamental animation skills for Remotion +- [rules/assets.md](rules/assets.md) - Importing images, videos, audio, and fonts into Remotion +- [rules/audio.md](rules/audio.md) - Using audio and sound in Remotion - importing, trimming, volume, speed, pitch +- [rules/calculate-metadata.md](rules/calculate-metadata.md) - Dynamically set composition duration, dimensions, and props +- [rules/can-decode.md](rules/can-decode.md) - Check if a video can be decoded by the browser using Mediabunny +- [rules/charts.md](rules/charts.md) - Chart and data visualization patterns for Remotion +- [rules/compositions.md](rules/compositions.md) - Defining compositions, stills, folders, default props and dynamic metadata +- [rules/display-captions.md](rules/display-captions.md) - Displaying captions in Remotion with TikTok-style pages and word highlighting +- [rules/extract-frames.md](rules/extract-frames.md) - Extract frames from videos at specific timestamps using Mediabunny +- [rules/fonts.md](rules/fonts.md) - Loading Google Fonts and local fonts in Remotion +- [rules/get-audio-duration.md](rules/get-audio-duration.md) - Getting the duration of an audio file in seconds with Mediabunny +- [rules/get-video-dimensions.md](rules/get-video-dimensions.md) - Getting the width and height of a video file with Mediabunny +- [rules/get-video-duration.md](rules/get-video-duration.md) - Getting the duration of a video file in seconds with Mediabunny +- [rules/gifs.md](rules/gifs.md) - Displaying GIFs synchronized with Remotion's timeline +- [rules/images.md](rules/images.md) - Embedding images in Remotion using the Img component +- [rules/import-srt-captions.md](rules/import-srt-captions.md) - Importing .srt subtitle files into Remotion using @remotion/captions +- [rules/lottie.md](rules/lottie.md) - Embedding Lottie animations in Remotion +- [rules/measuring-dom-nodes.md](rules/measuring-dom-nodes.md) - Measuring DOM element dimensions in Remotion +- [rules/measuring-text.md](rules/measuring-text.md) - Measuring text dimensions, fitting text to containers, and checking overflow +- [rules/sequencing.md](rules/sequencing.md) - Sequencing patterns for Remotion - delay, trim, limit duration of items +- [rules/tailwind.md](rules/tailwind.md) - Using TailwindCSS in Remotion +- [rules/text-animations.md](rules/text-animations.md) - Typography and text animation patterns for Remotion +- [rules/timing.md](rules/timing.md) - Interpolation curves in Remotion - linear, easing, spring animations +- [rules/transcribe-captions.md](rules/transcribe-captions.md) - Transcribing audio to generate captions in Remotion +- [rules/transitions.md](rules/transitions.md) - Scene transition patterns for Remotion +- [rules/trimming.md](rules/trimming.md) - Trimming patterns for Remotion - cut the beginning or end of animations +- [rules/videos.md](rules/videos.md) - Embedding videos in Remotion - trimming, volume, speed, looping, pitch diff --git a/.opencode/skills/Media/Remotion/Patterns.md b/.opencode/skills/Media/Remotion/Patterns.md new file mode 100644 index 00000000..e9791858 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Patterns.md @@ -0,0 +1,133 @@ +# Remotion Patterns + +Common patterns and examples for Remotion video creation. + +## Basic Component Structure + +```typescript +import { useCurrentFrame, useVideoConfig, AbsoluteFill, interpolate } from 'remotion' + +export const MyVideo: React.FC = () => { + const frame = useCurrentFrame() + const { fps, durationInFrames, width, height } = useVideoConfig() + + const opacity = interpolate(frame, [0, 30], [0, 1], { + extrapolateRight: 'clamp' + }) + + return ( + +

Hello World

+
+ ) +} +``` + +## Register Composition + +```typescript +// src/Root.tsx +import { Composition } from 'remotion' +import { MyVideo } from './MyVideo' + +export const RemotionRoot: React.FC = () => { + return ( + + ) +} +``` + +## Fade In Text + +```typescript +const frame = useCurrentFrame() +const opacity = interpolate(frame, [0, 30], [0, 1], { extrapolateRight: 'clamp' }) + +

Fade In

+``` + +## Spring Animation + +```typescript +import { spring, useCurrentFrame, useVideoConfig } from 'remotion' + +const frame = useCurrentFrame() +const { fps } = useVideoConfig() + +const scale = spring({ + frame, + fps, + from: 0, + to: 1, + config: { damping: 10, stiffness: 100 } +}) + +
Bounce In
+``` + +## Sequence Multiple Elements + +```typescript +import { Sequence } from 'remotion' + + + +</Sequence> +<Sequence from={60} durationInFrames={90}> + <Content /> +</Sequence> +<Sequence from={150}> + <Outro /> +</Sequence> +``` + +## Audio with Video + +```typescript +import { Audio, Video, staticFile } from 'remotion' + +<Video src={staticFile('video.mp4')} volume={0.5} /> +<Audio src={staticFile('music.mp3')} volume={0.3} startFrom={30} /> +``` + +## Video Size Presets + +```typescript +// YouTube +{ width: 1920, height: 1080 } // 16:9 landscape +{ width: 1080, height: 1920 } // 9:16 Shorts + +// TikTok/Reels +{ width: 1080, height: 1920 } // 9:16 portrait + +// Instagram +{ width: 1080, height: 1080 } // 1:1 square +{ width: 1080, height: 1350 } // 4:5 portrait + +// Twitter/X +{ width: 1280, height: 720 } // 16:9 landscape +``` + +## Critical Rules + +1. **NO CSS animations** - They won't render. Use `useCurrentFrame()` for all animations. +2. **NO third-party animation libraries** - They cause flickering. Drive animations from frame. +3. **Use `staticFile()`** - For assets in `/public` directory. +4. **Extrapolate carefully** - Use `extrapolateRight: 'clamp'` to prevent overflow. +5. **Props with Zod** - Define schemas for type-safe, configurable compositions. + +## Reference Documentation + +For detailed patterns on specific topics, see: +``` +~/.opencode/skills/Remotion/Tools/Reference/ +``` + +Topics include: animations, audio, 3d, charts, captions, fonts, transitions, and more. diff --git a/.opencode/skills/Media/Remotion/SKILL.md b/.opencode/skills/Media/Remotion/SKILL.md new file mode 100644 index 00000000..acc57b31 --- /dev/null +++ b/.opencode/skills/Media/Remotion/SKILL.md @@ -0,0 +1,69 @@ +--- +name: Remotion +description: Programmatic video creation with React. USE WHEN video, animation, motion graphics, video rendering, React video, intro video, YouTube video, TikTok video, video production, render video. +--- + +## 🚨 MANDATORY: Voice Notification (REQUIRED BEFORE ANY ACTION) + +**You MUST send this notification BEFORE doing anything else when this skill is invoked.** + +1. **Send voice notification**: + ```bash + curl -s -X POST http://localhost:8888/notify \ + -H "Content-Type: application/json" \ + -d '{"message": "Running the WORKFLOWNAME workflow in the Remotion skill to ACTION"}' \ + > /dev/null 2>&1 & + ``` + +2. **Output text notification**: + ``` + Running the **WorkflowName** workflow in the **Remotion** skill to ACTION... + ``` + +**This is not optional. Execute this curl command immediately upon skill invocation.** + +# Remotion + +Create professional videos programmatically with React. + +## Customization + +**Before executing, check for user customizations at:** +`~/.opencode/skills/PAI/USER/SKILLCUSTOMIZATIONS/Remotion/` + +## Workflow Routing + +| Trigger | Workflow | +|---------|----------| +| "animate this", "create animations for", "video overlay" | `Workflows/ContentToAnimation.md` | + +## Quick Reference + +- **Theme:** Always use PAI_THEME from `Tools/Theme.ts` +- **Art Integration:** Load Art preferences before creating content +- **Critical:** NO CSS animations - use `useCurrentFrame()` only +- **Output:** Always to `~/Downloads/` first + +**Render command:** +```bash +npx remotion render {composition-id} ~/Downloads/{name}.mp4 +``` + +## Full Documentation + +- **Art integration:** `ArtIntegration.md` - theme constants, color mapping +- **Common patterns:** `Patterns.md` - code examples, presets +- **Critical rules:** `CriticalRules.md` - what NOT to do +- **Detailed reference:** `Tools/Ref-*.md` - 28 pattern files from Remotion + +## Tools + +| Tool | Purpose | +|------|---------| +| `Tools/Render.ts` | Render, list compositions, create projects | +| `Tools/Theme.ts` | PAI theme constants derived from Art | + +## Links + +- Remotion Docs: https://remotion.dev/docs +- GitHub: https://github.com/remotion-dev/remotion diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-3d.md b/.opencode/skills/Media/Remotion/Tools/Ref-3d.md new file mode 100644 index 00000000..31fa5c67 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-3d.md @@ -0,0 +1,86 @@ +--- +name: 3d +description: 3D content in Remotion using Three.js and React Three Fiber. +metadata: + tags: 3d, three, threejs +--- + +# Using Three.js and React Three Fiber in Remotion + +Follow React Three Fiber and Three.js best practices. +Only the following Remotion-specific rules need to be followed: + +## Prerequisites + +First, the `@remotion/three` package needs to be installed. +If it is not, use the following command: + +```bash +npx remotion add @remotion/three # If project uses npm +bunx remotion add @remotion/three # If project uses bun +yarn remotion add @remotion/three # If project uses yarn +pnpm exec remotion add @remotion/three # If project uses pnpm +``` + +## Using ThreeCanvas + +You MUST wrap 3D content in `<ThreeCanvas>` and include proper lighting. +`<ThreeCanvas>` MUST have a `width` and `height` prop. + +```tsx +import { ThreeCanvas } from "@remotion/three"; +import { useVideoConfig } from "remotion"; + +const { width, height } = useVideoConfig(); + +<ThreeCanvas width={width} height={height}> + <ambientLight intensity={0.4} /> + <directionalLight position={[5, 5, 5]} intensity={0.8} /> + <mesh> + <sphereGeometry args={[1, 32, 32]} /> + <meshStandardMaterial color="red" /> + </mesh> +</ThreeCanvas> +``` + +## No animations not driven by `useCurrentFrame()` + +Shaders, models etc MUST NOT animate by themselves. +No animations are allowed unless they are driven by `useCurrentFrame()`. +Otherwise, it will cause flickering during rendering. + +Using `useFrame()` from `@react-three/fiber` is forbidden. + +## Animate using `useCurrentFrame()` + +Use `useCurrentFrame()` to perform animations. + +```tsx +const frame = useCurrentFrame(); +const rotationY = frame * 0.02; + +<mesh rotation={[0, rotationY, 0]}> + <boxGeometry args={[2, 2, 2]} /> + <meshStandardMaterial color="#4a9eff" /> +</mesh> +``` + +## Using `<Sequence>` inside `<ThreeCanvas>` + +The `layout` prop of any `<Sequence>` inside a `<ThreeCanvas>` must be set to `none`. + +```tsx +import { Sequence } from "remotion"; +import { ThreeCanvas } from "@remotion/three"; + +const { width, height } = useVideoConfig(); + +<ThreeCanvas width={width} height={height}> + <Sequence layout="none"> + <mesh> + <boxGeometry args={[2, 2, 2]} /> + <meshStandardMaterial color="#4a9eff" /> + </mesh> + </Sequence> +</ThreeCanvas> +``` \ No newline at end of file diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-animations.md b/.opencode/skills/Media/Remotion/Tools/Ref-animations.md new file mode 100644 index 00000000..7e15623f --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-animations.md @@ -0,0 +1,29 @@ +--- +name: animations +description: Fundamental animation skills for Remotion +metadata: + tags: animations, transitions, frames, useCurrentFrame +--- + +All animations MUST be driven by the `useCurrentFrame()` hook. +Write animations in seconds and multiply them by the `fps` value from `useVideoConfig()`. + +```tsx +import { useCurrentFrame } from "remotion"; + +export const FadeIn = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const opacity = interpolate(frame, [0, 2 * fps], [0, 1], { + extrapolateRight: 'clamp', + }); + + return ( + <div style={{ opacity }}>Hello World!</div> + ); +}; +``` + +CSS transitions or animations are FORBIDDEN - they will not render correctly. +Tailwind animation class names are FORBIDDEN - they will not render correctly. \ No newline at end of file diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-assets.md b/.opencode/skills/Media/Remotion/Tools/Ref-assets.md new file mode 100644 index 00000000..04c8ad59 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-assets.md @@ -0,0 +1,78 @@ +--- +name: assets +description: Importing images, videos, audio, and fonts into Remotion +metadata: + tags: assets, staticFile, images, fonts, public +--- + +# Importing assets in Remotion + +## The public folder + +Place assets in the `public/` folder at your project root. + +## Using staticFile() + +You MUST use `staticFile()` to reference files from the `public/` folder: + +```tsx +import {Img, staticFile} from 'remotion'; + +export const MyComposition = () => { + return <Img src={staticFile('logo.png')} />; +}; +``` + +The function returns an encoded URL that works correctly when deploying to subdirectories. + +## Using with components + +**Images:** + +```tsx +import {Img, staticFile} from 'remotion'; + +<Img src={staticFile('photo.png')} />; +``` + +**Videos:** + +```tsx +import {Video} from '@remotion/media'; +import {staticFile} from 'remotion'; + +<Video src={staticFile('clip.mp4')} />; +``` + +**Audio:** + +```tsx +import {Audio} from '@remotion/media'; +import {staticFile} from 'remotion'; + +<Audio src={staticFile('music.mp3')} />; +``` + +**Fonts:** + +```tsx +import {staticFile} from 'remotion'; + +const fontFamily = new FontFace('MyFont', `url(${staticFile('font.woff2')})`); +await fontFamily.load(); +document.fonts.add(fontFamily); +``` + +## Remote URLs + +Remote URLs can be used directly without `staticFile()`: + +```tsx +<Img src="https://example.com/image.png" /> +<Video src="https://remotion.media/video.mp4" /> +``` + +## Important notes + +- Remotion components (`<Img>`, `<Video>`, `<Audio>`) ensure assets are fully loaded before rendering +- Special characters in filenames (`#`, `?`, `&`) are automatically encoded diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-audio.md b/.opencode/skills/Media/Remotion/Tools/Ref-audio.md new file mode 100644 index 00000000..48086ec9 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-audio.md @@ -0,0 +1,172 @@ +--- +name: audio +description: Using audio and sound in Remotion - importing, trimming, volume, speed, pitch +metadata: + tags: audio, media, trim, volume, speed, loop, pitch, mute, sound, sfx +--- + +# Using audio in Remotion + +## Prerequisites + +First, the @remotion/media package needs to be installed. +If it is not installed, use the following command: + +```bash +npx remotion add @remotion/media # If project uses npm +bunx remotion add @remotion/media # If project uses bun +yarn remotion add @remotion/media # If project uses yarn +pnpm exec remotion add @remotion/media # If project uses pnpm +``` + +## Importing Audio + +Use `<Audio>` from `@remotion/media` to add audio to your composition. + +```tsx +import { Audio } from "@remotion/media"; +import { staticFile } from "remotion"; + +export const MyComposition = () => { + return <Audio src={staticFile("audio.mp3")} />; +}; +``` + +Remote URLs are also supported: + +```tsx +<Audio src="https://remotion.media/audio.mp3" /> +``` + +By default, audio plays from the start, at full volume and full length. +Multiple audio tracks can be layered by adding multiple `<Audio>` components. + +## Trimming + +Use `trimBefore` and `trimAfter` to remove portions of the audio. Values are in frames. + +```tsx +const { fps } = useVideoConfig(); + +return ( + <Audio + src={staticFile("audio.mp3")} + trimBefore={2 * fps} // Skip the first 2 seconds + trimAfter={10 * fps} // End at the 10 second mark + /> +); +``` + +The audio still starts playing at the beginning of the composition - only the specified portion is played. + +## Delaying + +Wrap the audio in a `<Sequence>` to delay when it starts: + +```tsx +import { Sequence, staticFile } from "remotion"; +import { Audio } from "@remotion/media"; + +const { fps } = useVideoConfig(); + +return ( + <Sequence from={1 * fps}> + <Audio src={staticFile("audio.mp3")} /> + </Sequence> +); +``` + +The audio will start playing after 1 second. + +## Volume + +Set a static volume (0 to 1): + +```tsx +<Audio src={staticFile("audio.mp3")} volume={0.5} /> +``` + +Or use a callback for dynamic volume based on the current frame: + +```tsx +import { interpolate } from "remotion"; + +const { fps } = useVideoConfig(); + +return ( + <Audio + src={staticFile("audio.mp3")} + volume={(f) => + interpolate(f, [0, 1 * fps], [0, 1], { extrapolateRight: "clamp" }) + } + /> +); +``` + +The value of `f` starts at 0 when the audio begins to play, not the composition frame. + +## Muting + +Use `muted` to silence the audio. It can be set dynamically: + +```tsx +const frame = useCurrentFrame(); +const { fps } = useVideoConfig(); + +return ( + <Audio + src={staticFile("audio.mp3")} + muted={frame >= 2 * fps && frame <= 4 * fps} // Mute between 2s and 4s + /> +); +``` + +## Speed + +Use `playbackRate` to change the playback speed: + +```tsx +<Audio src={staticFile("audio.mp3")} playbackRate={2} /> {/* 2x speed */} +<Audio src={staticFile("audio.mp3")} playbackRate={0.5} /> {/* Half speed */} +``` + +Reverse playback is not supported. + +## Looping + +Use `loop` to loop the audio indefinitely: + +```tsx +<Audio src={staticFile("audio.mp3")} loop /> +``` + +Use `loopVolumeCurveBehavior` to control how the frame count behaves when looping: + +- `"repeat"`: Frame count resets to 0 each loop (default) +- `"extend"`: Frame count continues incrementing + +```tsx +<Audio + src={staticFile("audio.mp3")} + loop + loopVolumeCurveBehavior="extend" + volume={(f) => interpolate(f, [0, 300], [1, 0])} // Fade out over multiple loops +/> +``` + +## Pitch + +Use `toneFrequency` to adjust the pitch without affecting speed. Values range from 0.01 to 2: + +```tsx +<Audio + src={staticFile("audio.mp3")} + toneFrequency={1.5} // Higher pitch +/> +<Audio + src={staticFile("audio.mp3")} + toneFrequency={0.8} // Lower pitch +/> +``` + +Pitch shifting only works during server-side rendering, not in the Remotion Studio preview or in the `<Player />`. diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-calculate-metadata.md b/.opencode/skills/Media/Remotion/Tools/Ref-calculate-metadata.md new file mode 100644 index 00000000..06098cad --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-calculate-metadata.md @@ -0,0 +1,104 @@ +--- +name: calculate-metadata +description: Dynamically set composition duration, dimensions, and props +metadata: + tags: calculateMetadata, duration, dimensions, props, dynamic +--- + +# Using calculateMetadata + +Use `calculateMetadata` on a `<Composition>` to dynamically set duration, dimensions, and transform props before rendering. + +```tsx +<Composition id="MyComp" component={MyComponent} durationInFrames={300} fps={30} width={1920} height={1080} defaultProps={{videoSrc: 'https://remotion.media/video.mp4'}} calculateMetadata={calculateMetadata} /> +``` + +## Setting duration based on a video + +Use the `getMediaMetadata()` function from the mediabunny/metadata skill to get the video duration: + +```tsx +import {CalculateMetadataFunction} from 'remotion'; +import {getMediaMetadata} from '../get-media-metadata'; + +const calculateMetadata: CalculateMetadataFunction<Props> = async ({props}) => { + const {durationInSeconds} = await getMediaMetadata(props.videoSrc); + + return { + durationInFrames: Math.ceil(durationInSeconds * 30), + }; +}; +``` + +## Matching dimensions of a video + +```tsx +const calculateMetadata: CalculateMetadataFunction<Props> = async ({props}) => { + const {durationInSeconds, dimensions} = await getMediaMetadata(props.videoSrc); + + return { + durationInFrames: Math.ceil(durationInSeconds * 30), + width: dimensions?.width ?? 1920, + height: dimensions?.height ?? 1080, + }; +}; +``` + +## Setting duration based on multiple videos + +```tsx +const calculateMetadata: CalculateMetadataFunction<Props> = async ({props}) => { + const metadataPromises = props.videos.map((video) => getMediaMetadata(video.src)); + const allMetadata = await Promise.all(metadataPromises); + + const totalDuration = allMetadata.reduce((sum, meta) => sum + meta.durationInSeconds, 0); + + return { + durationInFrames: Math.ceil(totalDuration * 30), + }; +}; +``` + +## Setting a default outName + +Set the default output filename based on props: + +```tsx +const calculateMetadata: CalculateMetadataFunction<Props> = async ({props}) => { + return { + defaultOutName: `video-${props.id}.mp4`, + }; +}; +``` + +## Transforming props + +Fetch data or transform props before rendering: + +```tsx +const calculateMetadata: CalculateMetadataFunction<Props> = async ({props, abortSignal}) => { + const response = await fetch(props.dataUrl, {signal: abortSignal}); + const data = await response.json(); + + return { + props: { + ...props, + fetchedData: data, + }, + }; +}; +``` + +The `abortSignal` cancels stale requests when props change in the Studio. + +## Return value + +All fields are optional. Returned values override the `<Composition>` props: + +- `durationInFrames`: Number of frames +- `width`: Composition width in pixels +- `height`: Composition height in pixels +- `fps`: Frames per second +- `props`: Transformed props passed to the component +- `defaultOutName`: Default output filename +- `defaultCodec`: Default codec for rendering diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-can-decode.md b/.opencode/skills/Media/Remotion/Tools/Ref-can-decode.md new file mode 100644 index 00000000..b7146c9c --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-can-decode.md @@ -0,0 +1,75 @@ +--- +name: can-decode +description: Check if a video can be decoded by the browser using Mediabunny +metadata: + tags: decode, validation, video, audio, compatibility, browser +--- + +# Checking if a video can be decoded + +Use Mediabunny to check if a video can be decoded by the browser before attempting to play it. + +## The `canDecode()` function + +This function can be copy-pasted into any project. + +```tsx +import { Input, ALL_FORMATS, UrlSource } from "mediabunny"; + +export const canDecode = async (src: string) => { + const input = new Input({ + formats: ALL_FORMATS, + source: new UrlSource(src, { + getRetryDelay: () => null, + }), + }); + + try { + await input.getFormat(); + } catch { + return false; + } + + const videoTrack = await input.getPrimaryVideoTrack(); + if (videoTrack && !(await videoTrack.canDecode())) { + return false; + } + + const audioTrack = await input.getPrimaryAudioTrack(); + if (audioTrack && !(await audioTrack.canDecode())) { + return false; + } + + return true; +}; +``` + +## Usage + +```tsx +const src = "https://remotion.media/video.mp4"; +const isDecodable = await canDecode(src); + +if (isDecodable) { + console.log("Video can be decoded"); +} else { + console.log("Video cannot be decoded by this browser"); +} +``` + +## Using with Blob + +For file uploads or drag-and-drop, use `BlobSource`: + +```tsx +import { Input, ALL_FORMATS, BlobSource } from "mediabunny"; + +export const canDecodeBlob = async (blob: Blob) => { + const input = new Input({ + formats: ALL_FORMATS, + source: new BlobSource(blob), + }); + + // Same validation logic as above +}; +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-charts.md b/.opencode/skills/Media/Remotion/Tools/Ref-charts.md new file mode 100644 index 00000000..a402ed53 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-charts.md @@ -0,0 +1,58 @@ +--- +name: charts +description: Chart and data visualization patterns for Remotion. Use when creating bar charts, pie charts, histograms, progress bars, or any data-driven animations. +metadata: + tags: charts, data, visualization, bar-chart, pie-chart, graphs +--- + +# Charts in Remotion + +You can create bar charts in Remotion by using regular React code - HTML and SVG is allowed, as well as D3.js. + +## No animations not powered by `useCurrentFrame()` + +Disable all animations by third party libraries. +They will cause flickering during rendering. +Instead, drive all animations from `useCurrentFrame()`. + +## Bar Chart Animations + +See [Bar Chart Example](assets/charts/bar-chart.tsx) for a basic example implmentation. + +### Staggered Bars + +You can animate the height of the bars and stagger them like this: + +```tsx +const STAGGER_DELAY = 5; +const frame = useCurrentFrame(); +const {fps} = useVideoConfig(); + +const bars = data.map((item, i) => { + const delay = i * STAGGER_DELAY; + const height = spring({ + frame, + fps, + delay, + config: {damping: 200}, + }); + return <div style={{height: height * item.value}} />; +}); +``` + +## Pie Chart Animation + +Animate segments using stroke-dashoffset, starting from 12 o'clock. + +```tsx +const frame = useCurrentFrame(); +const {fps} = useVideoConfig(); + +const progress = interpolate(frame, [0, 100], [0, 1]); + +const circumference = 2 * Math.PI * radius; +const segmentLength = (value / total) * circumference; +const offset = interpolate(progress, [0, 1], [segmentLength, 0]); + +<circle r={radius} cx={center} cy={center} fill="none" stroke={color} strokeWidth={strokeWidth} strokeDasharray={`${segmentLength} ${circumference}`} strokeDashoffset={offset} transform={`rotate(-90 ${center} ${center})`} />; +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-compositions.md b/.opencode/skills/Media/Remotion/Tools/Ref-compositions.md new file mode 100644 index 00000000..27b61bb1 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-compositions.md @@ -0,0 +1,146 @@ +--- +name: compositions +description: Defining compositions, stills, folders, default props and dynamic metadata +metadata: + tags: composition, still, folder, props, metadata +--- + +A `<Composition>` defines the component, width, height, fps and duration of a renderable video. + +It normally is placed in the `src/Root.tsx` file. + +```tsx +import { Composition } from "remotion"; +import { MyComposition } from "./MyComposition"; + +export const RemotionRoot = () => { + return ( + <Composition + id="MyComposition" + component={MyComposition} + durationInFrames={100} + fps={30} + width={1080} + height={1080} + /> + ); +}; +``` + +## Default Props + +Pass `defaultProps` to provide initial values for your component. +Values must be JSON-serializable (`Date`, `Map`, `Set`, and `staticFile()` are supported). + +```tsx +import { Composition } from "remotion"; +import { MyComposition, MyCompositionProps } from "./MyComposition"; + +export const RemotionRoot = () => { + return ( + <Composition + id="MyComposition" + component={MyComposition} + durationInFrames={100} + fps={30} + width={1080} + height={1080} + defaultProps={{ + title: "Hello World", + color: "#ff0000", + } satisfies MyCompositionProps} + /> + ); +}; +``` + +Use `type` declarations for props rather than `interface` to ensure `defaultProps` type safety. + +## Folders + +Use `<Folder>` to organize compositions in the sidebar. +Folder names can only contain letters, numbers, and hyphens. + +```tsx +import { Composition, Folder } from "remotion"; + +export const RemotionRoot = () => { + return ( + <> + <Folder name="Marketing"> + <Composition id="Promo" /* ... */ /> + <Composition id="Ad" /* ... */ /> + </Folder> + <Folder name="Social"> + <Folder name="Instagram"> + <Composition id="Story" /* ... */ /> + <Composition id="Reel" /* ... */ /> + </Folder> + </Folder> + </> + ); +}; +``` + +## Stills + +Use `<Still>` for single-frame images. It does not require `durationInFrames` or `fps`. + +```tsx +import { Still } from "remotion"; +import { Thumbnail } from "./Thumbnail"; + +export const RemotionRoot = () => { + return ( + <Still + id="Thumbnail" + component={Thumbnail} + width={1280} + height={720} + /> + ); +}; +``` + +## Calculate Metadata + +Use `calculateMetadata` to make dimensions, duration, or props dynamic based on data. + +```tsx +import { Composition, CalculateMetadataFunction } from "remotion"; +import { MyComposition, MyCompositionProps } from "./MyComposition"; + +const calculateMetadata: CalculateMetadataFunction<MyCompositionProps> = async ({ + props, + abortSignal, +}) => { + const data = await fetch(`https://api.example.com/video/${props.videoId}`, { + signal: abortSignal, + }).then((res) => res.json()); + + return { + durationInFrames: Math.ceil(data.duration * 30), + props: { + ...props, + videoUrl: data.url, + }, + }; +}; + +export const RemotionRoot = () => { + return ( + <Composition + id="MyComposition" + component={MyComposition} + durationInFrames={100} // Placeholder, will be overridden + fps={30} + width={1080} + height={1080} + defaultProps={{ videoId: "abc123" }} + calculateMetadata={calculateMetadata} + /> + ); +}; +``` + +The function can return `props`, `durationInFrames`, `width`, `height`, `fps`, and codec-related defaults. It runs once before rendering begins. \ No newline at end of file diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-display-captions.md b/.opencode/skills/Media/Remotion/Tools/Ref-display-captions.md new file mode 100644 index 00000000..1f70b702 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-display-captions.md @@ -0,0 +1,126 @@ +--- +name: display-captions +description: Displaying captions in Remotion with TikTok-style pages and word highlighting +metadata: + tags: captions, subtitles, display, tiktok, highlight +--- + +# Displaying captions in Remotion + +This guide explains how to display captions in Remotion, assuming you already have captions in the `Caption` format. + +## Prerequisites + +First, the @remotion/captions package needs to be installed. +If it is not installed, use the following command: + +```bash +npx remotion add @remotion/captions # If project uses npm +bunx remotion add @remotion/captions # If project uses bun +yarn remotion add @remotion/captions # If project uses yarn +pnpm exec remotion add @remotion/captions # If project uses pnpm +``` + +## Creating pages + +Use `createTikTokStyleCaptions()` to group captions into pages. The `combineTokensWithinMilliseconds` option controls how many words appear at once: + +```tsx +import {useMemo} from 'react'; +import {createTikTokStyleCaptions} from '@remotion/captions'; +import type {Caption} from '@remotion/captions'; + +// How often captions should switch (in milliseconds) +// Higher values = more words per page +// Lower values = fewer words (more word-by-word) +const SWITCH_CAPTIONS_EVERY_MS = 1200; + +const {pages} = useMemo(() => { + return createTikTokStyleCaptions({ + captions, + combineTokensWithinMilliseconds: SWITCH_CAPTIONS_EVERY_MS, + }); +}, [captions]); +``` + +## Rendering with Sequences + +Map over the pages and render each one in a `<Sequence>`. Calculate the start frame and duration from the page timing: + +```tsx +import {Sequence, useVideoConfig, AbsoluteFill} from 'remotion'; +import type {TikTokPage} from '@remotion/captions'; + +const CaptionedContent: React.FC = () => { + const {fps} = useVideoConfig(); + + return ( + <AbsoluteFill> + {pages.map((page, index) => { + const nextPage = pages[index + 1] ?? null; + const startFrame = (page.startMs / 1000) * fps; + const endFrame = Math.min( + nextPage ? (nextPage.startMs / 1000) * fps : Infinity, + startFrame + (SWITCH_CAPTIONS_EVERY_MS / 1000) * fps, + ); + const durationInFrames = endFrame - startFrame; + + if (durationInFrames <= 0) { + return null; + } + + return ( + <Sequence + key={index} + from={startFrame} + durationInFrames={durationInFrames} + > + <CaptionPage page={page} /> + </Sequence> + ); + })} + </AbsoluteFill> + ); +}; +``` + +## Word highlighting + +A caption page contains `tokens` which you can use to highlight the currently spoken word: + +```tsx +import {AbsoluteFill, useCurrentFrame, useVideoConfig} from 'remotion'; +import type {TikTokPage} from '@remotion/captions'; + +const HIGHLIGHT_COLOR = '#39E508'; + +const CaptionPage: React.FC<{page: TikTokPage}> = ({page}) => { + const frame = useCurrentFrame(); + const {fps} = useVideoConfig(); + + // Current time relative to the start of the sequence + const currentTimeMs = (frame / fps) * 1000; + // Convert to absolute time by adding the page start + const absoluteTimeMs = page.startMs + currentTimeMs; + + return ( + <AbsoluteFill style={{justifyContent: 'center', alignItems: 'center'}}> + <div style={{fontSize: 80, fontWeight: 'bold', whiteSpace: 'pre'}}> + {page.tokens.map((token) => { + const isActive = + token.fromMs <= absoluteTimeMs && token.toMs > absoluteTimeMs; + + return ( + <span + key={token.fromMs} + style={{color: isActive ? HIGHLIGHT_COLOR : 'white'}} + > + {token.text} + </span> + ); + })} + </div> + </AbsoluteFill> + ); +}; +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-extract-frames.md b/.opencode/skills/Media/Remotion/Tools/Ref-extract-frames.md new file mode 100644 index 00000000..46e63efc --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-extract-frames.md @@ -0,0 +1,229 @@ +--- +name: extract-frames +description: Extract frames from videos at specific timestamps using Mediabunny +metadata: + tags: frames, extract, video, thumbnail, filmstrip, canvas +--- + +# Extracting frames from videos + +Use Mediabunny to extract frames from videos at specific timestamps. This is useful for generating thumbnails, filmstrips, or processing individual frames. + +## The `extractFrames()` function + +This function can be copy-pasted into any project. + +```tsx +import { + ALL_FORMATS, + Input, + UrlSource, + VideoSample, + VideoSampleSink, +} from "mediabunny"; + +type Options = { + track: { width: number; height: number }; + container: string; + durationInSeconds: number | null; +}; + +export type ExtractFramesTimestampsInSecondsFn = ( + options: Options +) => Promise<number[]> | number[]; + +export type ExtractFramesProps = { + src: string; + timestampsInSeconds: number[] | ExtractFramesTimestampsInSecondsFn; + onVideoSample: (sample: VideoSample) => void; + signal?: AbortSignal; +}; + +export async function extractFrames({ + src, + timestampsInSeconds, + onVideoSample, + signal, +}: ExtractFramesProps): Promise<void> { + using input = new Input({ + formats: ALL_FORMATS, + source: new UrlSource(src), + }); + + const [durationInSeconds, format, videoTrack] = await Promise.all([ + input.computeDuration(), + input.getFormat(), + input.getPrimaryVideoTrack(), + ]); + + if (!videoTrack) { + throw new Error("No video track found in the input"); + } + + if (signal?.aborted) { + throw new Error("Aborted"); + } + + const timestamps = + typeof timestampsInSeconds === "function" + ? await timestampsInSeconds({ + track: { + width: videoTrack.displayWidth, + height: videoTrack.displayHeight, + }, + container: format.name, + durationInSeconds, + }) + : timestampsInSeconds; + + if (timestamps.length === 0) { + return; + } + + if (signal?.aborted) { + throw new Error("Aborted"); + } + + const sink = new VideoSampleSink(videoTrack); + + for await (using videoSample of sink.samplesAtTimestamps(timestamps)) { + if (signal?.aborted) { + break; + } + + if (!videoSample) { + continue; + } + + onVideoSample(videoSample); + } +} +``` + +## Basic usage + +Extract frames at specific timestamps: + +```tsx +await extractFrames({ + src: "https://remotion.media/video.mp4", + timestampsInSeconds: [0, 1, 2, 3, 4], + onVideoSample: (sample) => { + const canvas = document.createElement("canvas"); + canvas.width = sample.displayWidth; + canvas.height = sample.displayHeight; + const ctx = canvas.getContext("2d"); + sample.draw(ctx!, 0, 0); + }, +}); +``` + +## Creating a filmstrip + +Use a callback function to dynamically calculate timestamps based on video metadata: + +```tsx +const canvasWidth = 500; +const canvasHeight = 80; +const fromSeconds = 0; +const toSeconds = 10; + +await extractFrames({ + src: "https://remotion.media/video.mp4", + timestampsInSeconds: async ({ track, durationInSeconds }) => { + const aspectRatio = track.width / track.height; + const amountOfFramesFit = Math.ceil( + canvasWidth / (canvasHeight * aspectRatio) + ); + const segmentDuration = toSeconds - fromSeconds; + const timestamps: number[] = []; + + for (let i = 0; i < amountOfFramesFit; i++) { + timestamps.push( + fromSeconds + (segmentDuration / amountOfFramesFit) * (i + 0.5) + ); + } + + return timestamps; + }, + onVideoSample: (sample) => { + console.log(`Frame at ${sample.timestamp}s`); + + const canvas = document.createElement("canvas"); + canvas.width = sample.displayWidth; + canvas.height = sample.displayHeight; + const ctx = canvas.getContext("2d"); + sample.draw(ctx!, 0, 0); + }, +}); +``` + +## Cancellation with AbortSignal + +Cancel frame extraction after a timeout: + +```tsx +const controller = new AbortController(); + +setTimeout(() => controller.abort(), 5000); + +try { + await extractFrames({ + src: "https://remotion.media/video.mp4", + timestampsInSeconds: [0, 1, 2, 3, 4], + onVideoSample: (sample) => { + using frame = sample; + const canvas = document.createElement("canvas"); + canvas.width = frame.displayWidth; + canvas.height = frame.displayHeight; + const ctx = canvas.getContext("2d"); + frame.draw(ctx!, 0, 0); + }, + signal: controller.signal, + }); + + console.log("Frame extraction complete!"); +} catch (error) { + console.error("Frame extraction was aborted or failed:", error); +} +``` + +## Timeout with Promise.race + +```tsx +const controller = new AbortController(); + +const timeoutPromise = new Promise<never>((_, reject) => { + const timeoutId = setTimeout(() => { + controller.abort(); + reject(new Error("Frame extraction timed out after 10 seconds")); + }, 10000); + + controller.signal.addEventListener("abort", () => clearTimeout(timeoutId), { + once: true, + }); +}); + +try { + await Promise.race([ + extractFrames({ + src: "https://remotion.media/video.mp4", + timestampsInSeconds: [0, 1, 2, 3, 4], + onVideoSample: (sample) => { + using frame = sample; + const canvas = document.createElement("canvas"); + canvas.width = frame.displayWidth; + canvas.height = frame.displayHeight; + const ctx = canvas.getContext("2d"); + frame.draw(ctx!, 0, 0); + }, + signal: controller.signal, + }), + timeoutPromise, + ]); + + console.log("Frame extraction complete!"); +} catch (error) { + console.error("Frame extraction was aborted or failed:", error); +} +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-fonts.md b/.opencode/skills/Media/Remotion/Tools/Ref-fonts.md new file mode 100644 index 00000000..c10cd83e --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-fonts.md @@ -0,0 +1,152 @@ +--- +name: fonts +description: Loading Google Fonts and local fonts in Remotion +metadata: + tags: fonts, google-fonts, typography, text +--- + +# Using fonts in Remotion + +## Google Fonts with @remotion/google-fonts + +The recommended way to use Google Fonts. It's type-safe and automatically blocks rendering until the font is ready. + +### Prerequisites + +First, the @remotion/google-fonts package needs to be installed. +If it is not installed, use the following command: + +```bash +npx remotion add @remotion/google-fonts # If project uses npm +bunx remotion add @remotion/google-fonts # If project uses bun +yarn remotion add @remotion/google-fonts # If project uses yarn +pnpm exec remotion add @remotion/google-fonts # If project uses pnpm +``` + +```tsx +import { loadFont } from "@remotion/google-fonts/Lobster"; + +const { fontFamily } = loadFont(); + +export const MyComposition = () => { + return <div style={{ fontFamily }}>Hello World</div>; +}; +``` + +Preferrably, specify only needed weights and subsets to reduce file size: + +```tsx +import { loadFont } from "@remotion/google-fonts/Roboto"; + +const { fontFamily } = loadFont("normal", { + weights: ["400", "700"], + subsets: ["latin"], +}); +``` + +### Waiting for font to load + +Use `waitUntilDone()` if you need to know when the font is ready: + +```tsx +import { loadFont } from "@remotion/google-fonts/Lobster"; + +const { fontFamily, waitUntilDone } = loadFont(); + +await waitUntilDone(); +``` + +## Local fonts with @remotion/fonts + +For local font files, use the `@remotion/fonts` package. + +### Prerequisites + +First, install @remotion/fonts: + +```bash +npx remotion add @remotion/fonts # If project uses npm +bunx remotion add @remotion/fonts # If project uses bun +yarn remotion add @remotion/fonts # If project uses yarn +pnpm exec remotion add @remotion/fonts # If project uses pnpm +``` + +### Loading a local font + +Place your font file in the `public/` folder and use `loadFont()`: + +```tsx +import { loadFont } from "@remotion/fonts"; +import { staticFile } from "remotion"; + +await loadFont({ + family: "MyFont", + url: staticFile("MyFont-Regular.woff2"), +}); + +export const MyComposition = () => { + return <div style={{ fontFamily: "MyFont" }}>Hello World</div>; +}; +``` + +### Loading multiple weights + +Load each weight separately with the same family name: + +```tsx +import { loadFont } from "@remotion/fonts"; +import { staticFile } from "remotion"; + +await Promise.all([ + loadFont({ + family: "Inter", + url: staticFile("Inter-Regular.woff2"), + weight: "400", + }), + loadFont({ + family: "Inter", + url: staticFile("Inter-Bold.woff2"), + weight: "700", + }), +]); +``` + +### Available options + +```tsx +loadFont({ + family: "MyFont", // Required: name to use in CSS + url: staticFile("font.woff2"), // Required: font file URL + format: "woff2", // Optional: auto-detected from extension + weight: "400", // Optional: font weight + style: "normal", // Optional: normal or italic + display: "block", // Optional: font-display behavior +}); +``` + +## Using in components + +Call `loadFont()` at the top level of your component or in a separate file that's imported early: + +```tsx +import { loadFont } from "@remotion/google-fonts/Montserrat"; + +const { fontFamily } = loadFont("normal", { + weights: ["400", "700"], + subsets: ["latin"], +}); + +export const Title: React.FC<{ text: string }> = ({ text }) => { + return ( + <h1 + style={{ + fontFamily, + fontSize: 80, + fontWeight: "bold", + }} + > + {text} + </h1> + ); +}; +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-get-audio-duration.md b/.opencode/skills/Media/Remotion/Tools/Ref-get-audio-duration.md new file mode 100644 index 00000000..fc57d914 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-get-audio-duration.md @@ -0,0 +1,58 @@ +--- +name: get-audio-duration +description: Getting the duration of an audio file in seconds with Mediabunny +metadata: + tags: duration, audio, length, time, seconds, mp3, wav +--- + +# Getting audio duration with Mediabunny + +Mediabunny can extract the duration of an audio file. It works in browser, Node.js, and Bun environments. + +## Getting audio duration + +```tsx +import { Input, ALL_FORMATS, UrlSource } from "mediabunny"; + +export const getAudioDuration = async (src: string) => { + const input = new Input({ + formats: ALL_FORMATS, + source: new UrlSource(src, { + getRetryDelay: () => null, + }), + }); + + const durationInSeconds = await input.computeDuration(); + return durationInSeconds; +}; +``` + +## Usage + +```tsx +const duration = await getAudioDuration("https://remotion.media/audio.mp3"); +console.log(duration); // e.g. 180.5 (seconds) +``` + +## Using with local files + +For local files, use `FileSource` instead of `UrlSource`: + +```tsx +import { Input, ALL_FORMATS, FileSource } from "mediabunny"; + +const input = new Input({ + formats: ALL_FORMATS, + source: new FileSource(file), // File object from input or drag-drop +}); + +const durationInSeconds = await input.computeDuration(); +``` + +## Using with staticFile in Remotion + +```tsx +import { staticFile } from "remotion"; + +const duration = await getAudioDuration(staticFile("audio.mp3")); +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-get-video-dimensions.md b/.opencode/skills/Media/Remotion/Tools/Ref-get-video-dimensions.md new file mode 100644 index 00000000..b1b212a0 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-get-video-dimensions.md @@ -0,0 +1,68 @@ +--- +name: get-video-dimensions +description: Getting the width and height of a video file with Mediabunny +metadata: + tags: dimensions, width, height, resolution, size, video +--- + +# Getting video dimensions with Mediabunny + +Mediabunny can extract the width and height of a video file. It works in browser, Node.js, and Bun environments. + +## Getting video dimensions + +```tsx +import { Input, ALL_FORMATS, UrlSource } from "mediabunny"; + +export const getVideoDimensions = async (src: string) => { + const input = new Input({ + formats: ALL_FORMATS, + source: new UrlSource(src, { + getRetryDelay: () => null, + }), + }); + + const videoTrack = await input.getPrimaryVideoTrack(); + if (!videoTrack) { + throw new Error("No video track found"); + } + + return { + width: videoTrack.displayWidth, + height: videoTrack.displayHeight, + }; +}; +``` + +## Usage + +```tsx +const dimensions = await getVideoDimensions("https://remotion.media/video.mp4"); +console.log(dimensions.width); // e.g. 1920 +console.log(dimensions.height); // e.g. 1080 +``` + +## Using with local files + +For local files, use `FileSource` instead of `UrlSource`: + +```tsx +import { Input, ALL_FORMATS, FileSource } from "mediabunny"; + +const input = new Input({ + formats: ALL_FORMATS, + source: new FileSource(file), // File object from input or drag-drop +}); + +const videoTrack = await input.getPrimaryVideoTrack(); +const width = videoTrack.displayWidth; +const height = videoTrack.displayHeight; +``` + +## Using with staticFile in Remotion + +```tsx +import { staticFile } from "remotion"; + +const dimensions = await getVideoDimensions(staticFile("video.mp4")); +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-get-video-duration.md b/.opencode/skills/Media/Remotion/Tools/Ref-get-video-duration.md new file mode 100644 index 00000000..92365516 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-get-video-duration.md @@ -0,0 +1,58 @@ +--- +name: get-video-duration +description: Getting the duration of a video file in seconds with Mediabunny +metadata: + tags: duration, video, length, time, seconds +--- + +# Getting video duration with Mediabunny + +Mediabunny can extract the duration of a video file. It works in browser, Node.js, and Bun environments. + +## Getting video duration + +```tsx +import { Input, ALL_FORMATS, UrlSource } from "mediabunny"; + +export const getVideoDuration = async (src: string) => { + const input = new Input({ + formats: ALL_FORMATS, + source: new UrlSource(src, { + getRetryDelay: () => null, + }), + }); + + const durationInSeconds = await input.computeDuration(); + return durationInSeconds; +}; +``` + +## Usage + +```tsx +const duration = await getVideoDuration("https://remotion.media/video.mp4"); +console.log(duration); // e.g. 10.5 (seconds) +``` + +## Using with local files + +For local files, use `FileSource` instead of `UrlSource`: + +```tsx +import { Input, ALL_FORMATS, FileSource } from "mediabunny"; + +const input = new Input({ + formats: ALL_FORMATS, + source: new FileSource(file), // File object from input or drag-drop +}); + +const durationInSeconds = await input.computeDuration(); +``` + +## Using with staticFile in Remotion + +```tsx +import { staticFile } from "remotion"; + +const duration = await getVideoDuration(staticFile("video.mp4")); +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-gifs.md b/.opencode/skills/Media/Remotion/Tools/Ref-gifs.md new file mode 100644 index 00000000..d067c759 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-gifs.md @@ -0,0 +1,138 @@ +--- +name: gif +description: Displaying GIFs, APNG, AVIF and WebP in Remotion +metadata: + tags: gif, animation, images, animated, apng, avif, webp +--- + +# Using Animated images in Remotion + +## Basic usage + +Use `<AnimatedImage>` to display a GIF, APNG, AVIF or WebP image synchronized with Remotion's timeline: + +```tsx +import {AnimatedImage, staticFile} from 'remotion'; + +export const MyComposition = () => { + return <AnimatedImage src={staticFile('animation.gif')} width={500} height={500} />; +}; +``` + +Remote URLs are also supported (must have CORS enabled): + +```tsx +<AnimatedImage src="https://example.com/animation.gif" width={500} height={500} /> +``` + +## Sizing and fit + +Control how the image fills its container with the `fit` prop: + +```tsx +// Stretch to fill (default) +<AnimatedImage src={staticFile("animation.gif")} width={500} height={300} fit="fill" /> + +// Maintain aspect ratio, fit inside container +<AnimatedImage src={staticFile("animation.gif")} width={500} height={300} fit="contain" /> + +// Fill container, crop if needed +<AnimatedImage src={staticFile("animation.gif")} width={500} height={300} fit="cover" /> +``` + +## Playback speed + +Use `playbackRate` to control the animation speed: + +```tsx +<AnimatedImage src={staticFile("animation.gif")} width={500} height={500} playbackRate={2} /> {/* 2x speed */} +<AnimatedImage src={staticFile("animation.gif")} width={500} height={500} playbackRate={0.5} /> {/* Half speed */} +``` + +## Looping behavior + +Control what happens when the animation finishes: + +```tsx +// Loop indefinitely (default) +<AnimatedImage src={staticFile("animation.gif")} width={500} height={500} loopBehavior="loop" /> + +// Play once, show final frame +<AnimatedImage src={staticFile("animation.gif")} width={500} height={500} loopBehavior="pause-after-finish" /> + +// Play once, then clear canvas +<AnimatedImage src={staticFile("animation.gif")} width={500} height={500} loopBehavior="clear-after-finish" /> +``` + +## Styling + +Use the `style` prop for additional CSS (use `width` and `height` props for sizing): + +```tsx +<AnimatedImage + src={staticFile('animation.gif')} + width={500} + height={500} + style={{ + borderRadius: 20, + position: 'absolute', + top: 100, + left: 50, + }} +/> +``` + +## Getting GIF duration + +Use `getGifDurationInSeconds()` from `@remotion/gif` to get the duration of a GIF. + +```bash +npx remotion add @remotion/gif # If project uses npm +bunx remotion add @remotion/gif # If project uses bun +yarn remotion add @remotion/gif # If project uses yarn +pnpm exec remotion add @remotion/gif # If project uses pnpm +``` + +```tsx +import {getGifDurationInSeconds} from '@remotion/gif'; +import {staticFile} from 'remotion'; + +const duration = await getGifDurationInSeconds(staticFile('animation.gif')); +console.log(duration); // e.g. 2.5 +``` + +This is useful for setting the composition duration to match the GIF: + +```tsx +import {getGifDurationInSeconds} from '@remotion/gif'; +import {staticFile, CalculateMetadataFunction} from 'remotion'; + +const calculateMetadata: CalculateMetadataFunction = async () => { + const duration = await getGifDurationInSeconds(staticFile('animation.gif')); + return { + durationInFrames: Math.ceil(duration * 30), + }; +}; +``` + +## Alternative + +If `<AnimatedImage>` does not work (only supported in Chrome and Firefox), you can use `<Gif>` from `@remotion/gif` instead. + +```bash +npx remotion add @remotion/gif # If project uses npm +bunx remotion add @remotion/gif # If project uses bun +yarn remotion add @remotion/gif # If project uses yarn +pnpm exec remotion add @remotion/gif # If project uses pnpm +``` + +```tsx +import {Gif} from '@remotion/gif'; +import {staticFile} from 'remotion'; + +export const MyComposition = () => { + return <Gif src={staticFile('animation.gif')} width={500} height={500} />; +}; +``` + +The `<Gif>` component has the same props as `<AnimatedImage>` but only supports GIF files. diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-images.md b/.opencode/skills/Media/Remotion/Tools/Ref-images.md new file mode 100644 index 00000000..262bb57b --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-images.md @@ -0,0 +1,130 @@ +--- +name: images +description: Embedding images in Remotion using the <Img> component +metadata: + tags: images, img, staticFile, png, jpg, svg, webp +--- + +# Using images in Remotion + +## The `<Img>` component + +Always use the `<Img>` component from `remotion` to display images: + +```tsx +import { Img, staticFile } from "remotion"; + +export const MyComposition = () => { + return <Img src={staticFile("photo.png")} />; +}; +``` + +## Important restrictions + +**You MUST use the `<Img>` component from `remotion`.** Do not use: + +- Native HTML `<img>` elements +- Next.js `<Image>` component +- CSS `background-image` + +The `<Img>` component ensures images are fully loaded before rendering, preventing flickering and blank frames during video export. + +## Local images with staticFile() + +Place images in the `public/` folder and use `staticFile()` to reference them: + +``` +my-video/ +├─ public/ +│ ├─ logo.png +│ ├─ avatar.jpg +│ └─ icon.svg +├─ src/ +├─ package.json +``` + +```tsx +import { Img, staticFile } from "remotion"; + +<Img src={staticFile("logo.png")} /> +``` + +## Remote images + +Remote URLs can be used directly without `staticFile()`: + +```tsx +<Img src="https://example.com/image.png" /> +``` + +Ensure remote images have CORS enabled. + +For animated GIFs, use the `<Gif>` component from `@remotion/gif` instead. + +## Sizing and positioning + +Use the `style` prop to control size and position: + +```tsx +<Img + src={staticFile("photo.png")} + style={{ + width: 500, + height: 300, + position: "absolute", + top: 100, + left: 50, + objectFit: "cover", + }} +/> +``` + +## Dynamic image paths + +Use template literals for dynamic file references: + +```tsx +import { Img, staticFile, useCurrentFrame } from "remotion"; + +const frame = useCurrentFrame(); + +// Image sequence +<Img src={staticFile(`frames/frame${frame}.png`)} /> + +// Selecting based on props +<Img src={staticFile(`avatars/${props.userId}.png`)} /> + +// Conditional images +<Img src={staticFile(`icons/${isActive ? "active" : "inactive"}.svg`)} /> +``` + +This pattern is useful for: + +- Image sequences (frame-by-frame animations) +- User-specific avatars or profile images +- Theme-based icons +- State-dependent graphics + +## Getting image dimensions + +Use `getImageDimensions()` to get the dimensions of an image: + +```tsx +import { getImageDimensions, staticFile } from "remotion"; + +const { width, height } = await getImageDimensions(staticFile("photo.png")); +``` + +This is useful for calculating aspect ratios or sizing compositions: + +```tsx +import { getImageDimensions, staticFile, CalculateMetadataFunction } from "remotion"; + +const calculateMetadata: CalculateMetadataFunction = async () => { + const { width, height } = await getImageDimensions(staticFile("photo.png")); + return { + width, + height, + }; +}; +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-import-srt-captions.md b/.opencode/skills/Media/Remotion/Tools/Ref-import-srt-captions.md new file mode 100644 index 00000000..0b9c5bb3 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-import-srt-captions.md @@ -0,0 +1,67 @@ +--- +name: import-srt-captions +description: Importing .srt subtitle files into Remotion using @remotion/captions +metadata: + tags: captions, subtitles, srt, import, parse +--- + +# Importing .srt subtitles into Remotion + +If you have an existing `.srt` subtitle file, you can import it into Remotion using `parseSrt()` from `@remotion/captions`. + +## Prerequisites + +First, the @remotion/captions package needs to be installed. +If it is not installed, use the following command: + +```bash +npx remotion add @remotion/captions # If project uses npm +bunx remotion add @remotion/captions # If project uses bun +yarn remotion add @remotion/captions # If project uses yarn +pnpm exec remotion add @remotion/captions # If project uses pnpm +``` + +## Reading an .srt file + +Use `staticFile()` to reference an `.srt` file in your `public` folder, then fetch and parse it: + +```tsx +import {useState, useEffect, useCallback} from 'react'; +import {AbsoluteFill, staticFile, useDelayRender} from 'remotion'; +import {parseSrt} from '@remotion/captions'; +import type {Caption} from '@remotion/captions'; + +export const MyComponent: React.FC = () => { + const [captions, setCaptions] = useState<Caption[] | null>(null); + const {delayRender, continueRender, cancelRender} = useDelayRender(); + const [handle] = useState(() => delayRender()); + + const fetchCaptions = useCallback(async () => { + try { + const response = await fetch(staticFile('subtitles.srt')); + const text = await response.text(); + const {captions: parsed} = parseSrt({input: text}); + setCaptions(parsed); + continueRender(handle); + } catch (e) { + cancelRender(e); + } + }, [continueRender, cancelRender, handle]); + + useEffect(() => { + fetchCaptions(); + }, [fetchCaptions]); + + if (!captions) { + return null; + } + + return <AbsoluteFill>{/* Use captions here */}</AbsoluteFill>; +}; +``` + +Remote URLs are also supported - you can `fetch()` a remote file via URL instead of using `staticFile()`. + +## Using imported captions + +Once parsed, the captions are in the `Caption` format and can be used with all `@remotion/captions` utilities. diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-lottie.md b/.opencode/skills/Media/Remotion/Tools/Ref-lottie.md new file mode 100644 index 00000000..4d07acde --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-lottie.md @@ -0,0 +1,68 @@ +--- +name: lottie +description: Embedding Lottie animations in Remotion. +metadata: + category: Animation +--- + +# Using Lottie Animations in Remotion + +## Prerequisites + +First, the @remotion/lottie package needs to be installed. +If it is not, use the following command: + +```bash +npx remotion add @remotion/lottie # If project uses npm +bunx remotion add @remotion/lottie # If project uses bun +yarn remotion add @remotion/lottie # If project uses yarn +pnpm exec remotion add @remotion/lottie # If project uses pnpm +``` + +## Displaying a Lottie file + +To import a Lottie animation: + +- Fetch the Lottie asset +- Wrap the loading process in `delayRender()` and `continueRender()` +- Save the animation data in a state +- Render the Lottie animation using the `Lottie` component from the `@remotion/lottie` package + +```tsx +import {Lottie, LottieAnimationData} from '@remotion/lottie'; +import {useEffect, useState} from 'react'; +import {cancelRender, continueRender, delayRender} from 'remotion'; + +export const MyAnimation = () => { + const [handle] = useState(() => delayRender('Loading Lottie animation')); + + const [animationData, setAnimationData] = useState<LottieAnimationData | null>(null); + + useEffect(() => { + fetch('https://assets4.lottiefiles.com/packages/lf20_zyquagfl.json') + .then((data) => data.json()) + .then((json) => { + setAnimationData(json); + continueRender(handle); + }) + .catch((err) => { + cancelRender(err); + }); + }, [handle]); + + if (!animationData) { + return null; + } + + return <Lottie animationData={animationData} />; +}; +``` + +## Styling and animating + +Lottie supports the `style` prop to allow styles and animations: + +```tsx +return <Lottie animationData={animationData} style={{width: 400, height: 400}} />; +``` + diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-measuring-dom-nodes.md b/.opencode/skills/Media/Remotion/Tools/Ref-measuring-dom-nodes.md new file mode 100644 index 00000000..1f9d2bfa --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-measuring-dom-nodes.md @@ -0,0 +1,35 @@ +--- +name: measuring-dom-nodes +description: Measuring DOM element dimensions in Remotion +metadata: + tags: measure, layout, dimensions, getBoundingClientRect, scale +--- + +# Measuring DOM nodes in Remotion + +Remotion applies a `scale()` transform to the video container, which affects values from `getBoundingClientRect()`. Use `useCurrentScale()` to get correct measurements. + +## Measuring element dimensions + +```tsx +import { useCurrentScale } from "remotion"; +import { useRef, useEffect, useState } from "react"; + +export const MyComponent = () => { + const ref = useRef<HTMLDivElement>(null); + const scale = useCurrentScale(); + const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); + + useEffect(() => { + if (!ref.current) return; + const rect = ref.current.getBoundingClientRect(); + setDimensions({ + width: rect.width / scale, + height: rect.height / scale, + }); + }, [scale]); + + return <div ref={ref}>Content to measure</div>; +}; +``` + diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-measuring-text.md b/.opencode/skills/Media/Remotion/Tools/Ref-measuring-text.md new file mode 100644 index 00000000..1bcb33ce --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-measuring-text.md @@ -0,0 +1,143 @@ +--- +name: measuring-text +description: Measuring text dimensions, fitting text to containers, and checking overflow +metadata: + tags: measure, text, layout, dimensions, fitText, fillTextBox +--- + +# Measuring text in Remotion + +## Prerequisites + +Install @remotion/layout-utils if it is not already installed: + +```bash +npx remotion add @remotion/layout-utils # If project uses npm +bunx remotion add @remotion/layout-utils # If project uses bun +yarn remotion add @remotion/layout-utils # If project uses yarn +pnpm exec remotion add @remotion/layout-utils # If project uses pnpm +``` + +## Measuring text dimensions + +Use `measureText()` to calculate the width and height of text: + +```tsx +import { measureText } from "@remotion/layout-utils"; + +const { width, height } = measureText({ + text: "Hello World", + fontFamily: "Arial", + fontSize: 32, + fontWeight: "bold", +}); +``` + +Results are cached - duplicate calls return the cached result. + +## Fitting text to a width + +Use `fitText()` to find the optimal font size for a container: + +```tsx +import { fitText } from "@remotion/layout-utils"; + +const { fontSize } = fitText({ + text: "Hello World", + withinWidth: 600, + fontFamily: "Inter", + fontWeight: "bold", +}); + +return ( + <div + style={{ + fontSize: Math.min(fontSize, 80), // Cap at 80px + fontFamily: "Inter", + fontWeight: "bold", + }} + > + Hello World + </div> +); +``` + +## Checking text overflow + +Use `fillTextBox()` to check if text exceeds a box: + +```tsx +import { fillTextBox } from "@remotion/layout-utils"; + +const box = fillTextBox({ maxBoxWidth: 400, maxLines: 3 }); + +const words = ["Hello", "World", "This", "is", "a", "test"]; +for (const word of words) { + const { exceedsBox } = box.add({ + text: word + " ", + fontFamily: "Arial", + fontSize: 24, + }); + if (exceedsBox) { + // Text would overflow, handle accordingly + break; + } +} +``` + +## Best practices + +**Load fonts first:** Only call measurement functions after fonts are loaded. + +```tsx +import { loadFont } from "@remotion/google-fonts/Inter"; + +const { fontFamily, waitUntilDone } = loadFont("normal", { + weights: ["400"], + subsets: ["latin"], +}); + +waitUntilDone().then(() => { + // Now safe to measure + const { width } = measureText({ + text: "Hello", + fontFamily, + fontSize: 32, + }); +}) +``` + +**Use validateFontIsLoaded:** Catch font loading issues early: + +```tsx +measureText({ + text: "Hello", + fontFamily: "MyCustomFont", + fontSize: 32, + validateFontIsLoaded: true, // Throws if font not loaded +}); +``` + +**Match font properties:** Use the same properties for measurement and rendering: + +```tsx +const fontStyle = { + fontFamily: "Inter", + fontSize: 32, + fontWeight: "bold" as const, + letterSpacing: "0.5px", +}; + +const { width } = measureText({ + text: "Hello", + ...fontStyle, +}); + +return <div style={fontStyle}>Hello</div>; +``` + +**Avoid padding and border:** Use `outline` instead of `border` to prevent layout differences: + +```tsx +<div style={{ outline: "2px solid red" }}>Text</div> +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-sequencing.md b/.opencode/skills/Media/Remotion/Tools/Ref-sequencing.md new file mode 100644 index 00000000..42671376 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-sequencing.md @@ -0,0 +1,106 @@ +--- +name: sequencing +description: Sequencing patterns for Remotion - delay, trim, limit duration of items +metadata: + tags: sequence, series, timing, delay, trim +--- + +Use `<Sequence>` to delay when an element appears in the timeline. + +```tsx +import { Sequence } from "remotion"; + +const {fps} = useVideoConfig(); + +<Sequence from={1 * fps} durationInFrames={2 * fps} premountFor={1 * fps}> + <Title /> +</Sequence> +<Sequence from={2 * fps} durationInFrames={2 * fps} premountFor={1 * fps}> + <Subtitle /> +</Sequence> +``` + +This will by default wrap the component in an absolute fill element. +If the items should not be wrapped, use the `layout` prop: + +```tsx +<Sequence layout="none"> + <Title /> +</Sequence> +``` + +## Premounting + +This loads the component in the timeline before it is actually played. +Always premount any `<Sequence>`! + +```tsx +<Sequence premountFor={1 * fps}> + <Title /> +</Sequence> +``` + +## Series + +Use `<Series>` when elements should play one after another without overlap. + +```tsx +import {Series} from 'remotion'; + +<Series> + <Series.Sequence durationInFrames={45}> + <Intro /> + </Series.Sequence> + <Series.Sequence durationInFrames={60}> + <MainContent /> + </Series.Sequence> + <Series.Sequence durationInFrames={30}> + <Outro /> + </Series.Sequence> +</Series>; +``` + +Same as with `<Sequence>`, the items will be wrapped in an absolute fill element by default when using `<Series.Sequence>`, unless the `layout` prop is set to `none`. + +### Series with overlaps + +Use negative offset for overlapping sequences: + +```tsx +<Series> + <Series.Sequence durationInFrames={60}> + <SceneA /> + </Series.Sequence> + <Series.Sequence offset={-15} durationInFrames={60}> + {/* Starts 15 frames before SceneA ends */} + <SceneB /> + </Series.Sequence> +</Series> +``` + +## Frame References Inside Sequences + +Inside a Sequence, `useCurrentFrame()` returns the local frame (starting from 0): + +```tsx +<Sequence from={60} durationInFrames={30}> + <MyComponent /> + {/* Inside MyComponent, useCurrentFrame() returns 0-29, not 60-89 */} +</Sequence> +``` + +## Nested Sequences + +Sequences can be nested for complex timing: + +```tsx +<Sequence from={0} durationInFrames={120}> + <Background /> + <Sequence from={15} durationInFrames={90} layout="none"> + <Title /> + </Sequence> + <Sequence from={45} durationInFrames={60} layout="none"> + <Subtitle /> + </Sequence> +</Sequence> +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-tailwind.md b/.opencode/skills/Media/Remotion/Tools/Ref-tailwind.md new file mode 100644 index 00000000..d2f51952 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-tailwind.md @@ -0,0 +1,11 @@ +--- +name: tailwind +description: Using TailwindCSS in Remotion. +metadata: +--- + +You can and should use TailwindCSS in Remotion, if TailwindCSS is installed in the project. + +Don't use `transition-*` or `animate-*` classes - always animate using the `useCurrentFrame()` hook. + +Tailwind must be installed and enabled first in a Remotion project - fetch https://www.remotion.dev/docs/tailwind using WebFetch for instructions. \ No newline at end of file diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-text-animations.md b/.opencode/skills/Media/Remotion/Tools/Ref-text-animations.md new file mode 100644 index 00000000..e38b1c57 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-text-animations.md @@ -0,0 +1,20 @@ +--- +name: text-animations +description: Typography and text animation patterns for Remotion. +metadata: + tags: typography, text, typewriter, highlighter ken +--- + +## Text animations + +Based on `useCurrentFrame()`, reduce the string character by character to create a typewriter effect. + +## Typewriter Effect + +See [Typewriter](assets/text-animations-typewriter.tsx) for an advanced example with a blinking cursor and a pause after the first sentence. + +Always use string slicing for typewriter effects. Never use per-character opacity. + +## Word Highlighting + +See [Word Highlight](assets/text-animations-word-highlight.tsx) for an example for how a word highlight is animated, like with a highlighter pen. diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-timing.md b/.opencode/skills/Media/Remotion/Tools/Ref-timing.md new file mode 100644 index 00000000..42084a22 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-timing.md @@ -0,0 +1,178 @@ +--- +name: timing +description: Interpolation curves in Remotion - linear, easing, spring animations +metadata: + tags: spring, bounce, easing, interpolation +--- + +A simple linear interpolation is done using the `interpolate` function. + +```ts title="Going from 0 to 1 over 100 frames" +import {interpolate} from 'remotion'; + +const opacity = interpolate(frame, [0, 100], [0, 1]); +``` + +By default, the values are not clamped, so the value can go outside the range [0, 1]. +Here is how they can be clamped: + +```ts title="Going from 0 to 1 over 100 frames with extrapolation" +const opacity = interpolate(frame, [0, 100], [0, 1], { + extrapolateRight: 'clamp', + extrapolateLeft: 'clamp', +}); +``` + +## Spring animations + +Spring animations have a more natural motion. +They go from 0 to 1 over time. + +```ts title="Spring animation from 0 to 1 over 100 frames" +import {spring, useCurrentFrame, useVideoConfig} from 'remotion'; + +const frame = useCurrentFrame(); +const {fps} = useVideoConfig(); + +const scale = spring({ + frame, + fps, +}); +``` + +### Physical properties + +The default configuration is: `mass: 1, damping: 10, stiffness: 100`. +This leads to the animation having a bit of bounce before it settles. + +The config can be overwritten like this: + +```ts +const scale = spring({ + frame, + fps, + config: {damping: 200}, +}); +``` + +The recommended configuration for a natural motion without a bounce is: `{ damping: 200 }`. + +Here are some common configurations: + +```tsx +const smooth = {damping: 200}; // Smooth, no bounce (subtle reveals) +const snappy = {damping: 20, stiffness: 200}; // Snappy, minimal bounce (UI elements) +const bouncy = {damping: 8}; // Bouncy entrance (playful animations) +const heavy = {damping: 15, stiffness: 80, mass: 2}; // Heavy, slow, small bounce +``` + +### Delay + +The animation starts immediately by default. +To delay the animation, subtract the delay in frames from the `frame` parameter. + +```tsx +const entrance = spring({ + frame: frame - ENTRANCE_DELAY, + fps, +}); +``` + +### Duration + +A `spring()` has a natural duration based on the physical properties. +To stretch the animation to a specific duration, use the `durationInFrames` parameter. + +```tsx +const springProgress = spring({ + frame, + fps, + durationInFrames: 40, +}); +``` + +### Combining spring() with interpolate() + +Map spring output (0-1) to custom ranges: + +```tsx +const springProgress = spring({ + frame, + fps, +}); + +// Map to rotation +const rotation = interpolate(springProgress, [0, 1], [0, 360]); + +<div style={{rotate: rotation + 'deg'}} />; +``` + +### Adding springs + +Springs return just numbers, so math can be performed: + +```tsx +const frame = useCurrentFrame(); +const {fps, durationInFrames} = useVideoConfig(); + +const inAnimation = spring({ + frame, + fps, +}); +const outAnimation = spring({ + frame, + fps, + durationInFrames: 1 * fps, + delay: durationInFrames - 1 * fps, +}); + +const scale = inAnimation - outAnimation; +``` + +## Easing + +Easing can be added to the `interpolate` function: + +```ts +import {interpolate, Easing} from 'remotion'; + +const value1 = interpolate(frame, [0, 100], [0, 1], { + easing: Easing.inOut(Easing.quad), + extrapolateLeft: 'clamp', + extrapolateRight: 'clamp', +}); +``` + +The default easing is `Easing.linear`. +There are various other convexities: + +- `Easing.in` for starting slow and accelerating +- `Easing.out` for starting fast and slowing down +- `Easing.inOut` + +and curves (sorted from most linear to most curved): + +- `Easing.quad` +- `Easing.sin` +- `Easing.exp` +- `Easing.circle` + +Convexities and curves need be combined for an easing function: + +```ts +const value1 = interpolate(frame, [0, 100], [0, 1], { + easing: Easing.inOut(Easing.quad), + extrapolateLeft: 'clamp', + extrapolateRight: 'clamp', +}); +``` + +Cubic bezier curves are also supported: + +```ts +const value1 = interpolate(frame, [0, 100], [0, 1], { + easing: Easing.bezier(0.8, 0.22, 0.96, 0.65), + extrapolateLeft: 'clamp', + extrapolateRight: 'clamp', +}); +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-transcribe-captions.md b/.opencode/skills/Media/Remotion/Tools/Ref-transcribe-captions.md new file mode 100644 index 00000000..b338486b --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-transcribe-captions.md @@ -0,0 +1,19 @@ +--- +name: transcribe-captions +description: Transcribing audio to generate captions in Remotion +metadata: + tags: captions, transcribe, whisper, audio, speech-to-text +--- + +# Transcribing audio + +Remotion provides several built-in options for transcribing audio to generate captions: + +- `@remotion/install-whisper-cpp` - Transcribe locally on a server using Whisper.cpp. Fast and free, but requires server infrastructure. + https://remotion.dev/docs/install-whisper-cpp + +- `@remotion/whisper-web` - Transcribe in the browser using WebAssembly. No server needed and free, but slower due to WASM overhead. + https://remotion.dev/docs/whisper-web + +- `@remotion/openai-whisper` - Use OpenAI Whisper API for cloud-based transcription. Fast and no server needed, but requires payment. + https://remotion.dev/docs/openai-whisper/openai-whisper-api-to-captions diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-transitions.md b/.opencode/skills/Media/Remotion/Tools/Ref-transitions.md new file mode 100644 index 00000000..c363cd19 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-transitions.md @@ -0,0 +1,122 @@ +--- +name: transitions +description: Fullscreen scene transitions for Remotion. +metadata: + tags: transitions, fade, slide, wipe, scenes +--- + +## Fullscreen transitions + +Using `<TransitionSeries>` to animate between multiple scenes or clips. +This will absolutely position the children. + +## Prerequisites + +First, the @remotion/transitions package needs to be installed. +If it is not, use the following command: + +```bash +npx remotion add @remotion/transitions # If project uses npm +bunx remotion add @remotion/transitions # If project uses bun +yarn remotion add @remotion/transitions # If project uses yarn +pnpm exec remotion add @remotion/transitions # If project uses pnpm +``` + +## Example usage + +```tsx +import {TransitionSeries, linearTiming} from '@remotion/transitions'; +import {fade} from '@remotion/transitions/fade'; + +<TransitionSeries> + <TransitionSeries.Sequence durationInFrames={60}> + <SceneA /> + </TransitionSeries.Sequence> + <TransitionSeries.Transition presentation={fade()} timing={linearTiming({durationInFrames: 15})} /> + <TransitionSeries.Sequence durationInFrames={60}> + <SceneB /> + </TransitionSeries.Sequence> +</TransitionSeries>; +``` + +## Available Transition Types + +Import transitions from their respective modules: + +```tsx +import {fade} from '@remotion/transitions/fade'; +import {slide} from '@remotion/transitions/slide'; +import {wipe} from '@remotion/transitions/wipe'; +import {flip} from '@remotion/transitions/flip'; +import {clockWipe} from '@remotion/transitions/clock-wipe'; +``` + +## Slide Transition with Direction + +Specify slide direction for enter/exit animations. + +```tsx +import {slide} from '@remotion/transitions/slide'; + +<TransitionSeries.Transition presentation={slide({direction: 'from-left'})} timing={linearTiming({durationInFrames: 20})} />; +``` + +Directions: `"from-left"`, `"from-right"`, `"from-top"`, `"from-bottom"` + +## Timing Options + +```tsx +import {linearTiming, springTiming} from '@remotion/transitions'; + +// Linear timing - constant speed +linearTiming({durationInFrames: 20}); + +// Spring timing - organic motion +springTiming({config: {damping: 200}, durationInFrames: 25}); +``` + +## Duration calculation + +Transitions overlap adjacent scenes, so the total composition length is **shorter** than the sum of all sequence durations. + +For example, with two 60-frame sequences and a 15-frame transition: + +- Without transitions: `60 + 60 = 120` frames +- With transition: `60 + 60 - 15 = 105` frames + +The transition duration is subtracted because both scenes play simultaneously during the transition. + +### Getting the duration of a transition + +Use the `getDurationInFrames()` method on the timing object: + +```tsx +import {linearTiming, springTiming} from '@remotion/transitions'; + +const linearDuration = linearTiming({durationInFrames: 20}).getDurationInFrames({fps: 30}); +// Returns 20 + +const springDuration = springTiming({config: {damping: 200}}).getDurationInFrames({fps: 30}); +// Returns calculated duration based on spring physics +``` + +For `springTiming` without an explicit `durationInFrames`, the duration depends on `fps` because it calculates when the spring animation settles. + +### Calculating total composition duration + +```tsx +import {linearTiming} from '@remotion/transitions'; + +const scene1Duration = 60; +const scene2Duration = 60; +const scene3Duration = 60; + +const timing1 = linearTiming({durationInFrames: 15}); +const timing2 = linearTiming({durationInFrames: 20}); + +const transition1Duration = timing1.getDurationInFrames({fps: 30}); +const transition2Duration = timing2.getDurationInFrames({fps: 30}); + +const totalDuration = scene1Duration + scene2Duration + scene3Duration - transition1Duration - transition2Duration; +// 60 + 60 + 60 - 15 - 20 = 145 frames +``` diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-trimming.md b/.opencode/skills/Media/Remotion/Tools/Ref-trimming.md new file mode 100644 index 00000000..f20963a3 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-trimming.md @@ -0,0 +1,53 @@ +--- +name: trimming +description: Trimming patterns for Remotion - cut the beginning or end of animations +metadata: + tags: sequence, trim, clip, cut, offset +--- + +Use `<Sequence>` with a negative `from` value to trim the start of an animation. + +## Trim the Beginning + +A negative `from` value shifts time backwards, making the animation start partway through: + +```tsx +import { Sequence, useVideoConfig } from "remotion"; + +const fps = useVideoConfig(); + +<Sequence from={-0.5 * fps}> + <MyAnimation /> +</Sequence> +``` + +The animation appears 15 frames into its progress - the first 15 frames are trimmed off. +Inside `<MyAnimation>`, `useCurrentFrame()` starts at 15 instead of 0. + +## Trim the End + +Use `durationInFrames` to unmount content after a specified duration: + +```tsx + +<Sequence durationInFrames={1.5 * fps}> + <MyAnimation /> +</Sequence> +``` + +The animation plays for 45 frames, then the component unmounts. + +## Trim and Delay + +Nest sequences to both trim the beginning and delay when it appears: + +```tsx +<Sequence from={30}> + <Sequence from={-15}> + <MyAnimation /> + </Sequence> +</Sequence> +``` + +The inner sequence trims 15 frames from the start, and the outer sequence delays the result by 30 frames. + diff --git a/.opencode/skills/Media/Remotion/Tools/Ref-videos.md b/.opencode/skills/Media/Remotion/Tools/Ref-videos.md new file mode 100644 index 00000000..ff73e401 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Ref-videos.md @@ -0,0 +1,171 @@ +--- +name: videos +description: Embedding videos in Remotion - trimming, volume, speed, looping, pitch +metadata: + tags: video, media, trim, volume, speed, loop, pitch +--- + +# Using videos in Remotion + +## Prerequisites + +First, the @remotion/media package needs to be installed. +If it is not, use the following command: + +```bash +npx remotion add @remotion/media # If project uses npm +bunx remotion add @remotion/media # If project uses bun +yarn remotion add @remotion/media # If project uses yarn +pnpm exec remotion add @remotion/media # If project uses pnpm +``` + +Use `<Video>` from `@remotion/media` to embed videos into your composition. + +```tsx +import { Video } from "@remotion/media"; +import { staticFile } from "remotion"; + +export const MyComposition = () => { + return <Video src={staticFile("video.mp4")} />; +}; +``` + +Remote URLs are also supported: + +```tsx +<Video src="https://remotion.media/video.mp4" /> +``` + +## Trimming + +Use `trimBefore` and `trimAfter` to remove portions of the video. Values are in frames. + +```tsx +const { fps } = useVideoConfig(); + +return ( + <Video + src={staticFile("video.mp4")} + trimBefore={2 * fps} // Skip the first 2 seconds + trimAfter={10 * fps} // End at the 10 second mark + /> +); +``` + +## Delaying + +Wrap the video in a `<Sequence>` to delay when it appears: + +```tsx +import { Sequence, staticFile } from "remotion"; +import { Video } from "@remotion/media"; + +const { fps } = useVideoConfig(); + +return ( + <Sequence from={1 * fps}> + <Video src={staticFile("video.mp4")} /> + </Sequence> +); +``` + +The video will appear after 1 second. + +## Sizing and Position + +Use the `style` prop to control size and position: + +```tsx +<Video + src={staticFile("video.mp4")} + style={{ + width: 500, + height: 300, + position: "absolute", + top: 100, + left: 50, + objectFit: "cover", + }} +/> +``` + +## Volume + +Set a static volume (0 to 1): + +```tsx +<Video src={staticFile("video.mp4")} volume={0.5} /> +``` + +Or use a callback for dynamic volume based on the current frame: + +```tsx +import { interpolate } from "remotion"; + +const { fps } = useVideoConfig(); + +return ( + <Video + src={staticFile("video.mp4")} + volume={(f) => + interpolate(f, [0, 1 * fps], [0, 1], { extrapolateRight: "clamp" }) + } + /> +); +``` + +Use `muted` to silence the video entirely: + +```tsx +<Video src={staticFile("video.mp4")} muted /> +``` + +## Speed + +Use `playbackRate` to change the playback speed: + +```tsx +<Video src={staticFile("video.mp4")} playbackRate={2} /> {/* 2x speed */} +<Video src={staticFile("video.mp4")} playbackRate={0.5} /> {/* Half speed */} +``` + +Reverse playback is not supported. + +## Looping + +Use `loop` to loop the video indefinitely: + +```tsx +<Video src={staticFile("video.mp4")} loop /> +``` + +Use `loopVolumeCurveBehavior` to control how the frame count behaves when looping: + +- `"repeat"`: Frame count resets to 0 each loop (for `volume` callback) +- `"extend"`: Frame count continues incrementing + +```tsx +<Video + src={staticFile("video.mp4")} + loop + loopVolumeCurveBehavior="extend" + volume={(f) => interpolate(f, [0, 300], [1, 0])} // Fade out over multiple loops +/> +``` + +## Pitch + +Use `toneFrequency` to adjust the pitch without affecting speed. Values range from 0.01 to 2: + +```tsx +<Video + src={staticFile("video.mp4")} + toneFrequency={1.5} // Higher pitch +/> +<Video + src={staticFile("video.mp4")} + toneFrequency={0.8} // Lower pitch +/> +``` + +Pitch shifting only works during server-side rendering, not in the Remotion Studio preview or in the `<Player />`. diff --git a/.opencode/skills/Media/Remotion/Tools/Render.ts b/.opencode/skills/Media/Remotion/Tools/Render.ts new file mode 100644 index 00000000..75a271d8 --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Render.ts @@ -0,0 +1,343 @@ +/** + * Remotion Code-First Interface + * + * TypeScript wrappers for Remotion CLI operations. + * Enables programmatic video rendering with full control. + */ + +import { $ } from 'bun' + +export interface RenderOptions { + /** Composition ID to render */ + compositionId: string + /** Output file path */ + outputPath: string + /** Video codec */ + codec?: 'h264' | 'h265' | 'vp8' | 'vp9' | 'prores' | 'gif' + /** Constant Rate Factor (quality, lower = better, 0-51) */ + crf?: number + /** Frames per second */ + fps?: number + /** Video width */ + width?: number + /** Video height */ + height?: number + /** Props to pass to composition */ + inputProps?: Record<string, any> + /** Project directory (defaults to cwd) */ + projectDir?: string + /** Specific frames to render (e.g., "0-100") */ + frames?: string + /** Image sequence output format */ + imageFormat?: 'png' | 'jpeg' + /** JPEG quality (0-100) */ + jpegQuality?: number + /** Scale factor */ + scale?: number + /** Mute audio */ + muted?: boolean + /** Audio codec */ + audioCodec?: 'aac' | 'mp3' | 'opus' | 'wav' | 'pcm' + /** Number of render threads */ + concurrency?: number + /** Verbose output */ + verbose?: boolean +} + +export interface Composition { + id: string + width: number + height: number + fps: number + durationInFrames: number + defaultProps?: Record<string, any> +} + +export interface RenderResult { + success: boolean + outputPath: string + duration?: number + error?: string +} + +/** + * Render a Remotion composition to video file + * + * @param options - Render configuration + * @returns Render result + */ +export async function render(options: RenderOptions): Promise<RenderResult> { + const args: string[] = ['npx', 'remotion', 'render', options.compositionId, options.outputPath] + + if (options.codec) args.push('--codec', options.codec) + if (options.crf !== undefined) args.push('--crf', String(options.crf)) + if (options.fps) args.push('--fps', String(options.fps)) + if (options.width) args.push('--width', String(options.width)) + if (options.height) args.push('--height', String(options.height)) + if (options.frames) args.push('--frames', options.frames) + if (options.imageFormat) args.push('--image-format', options.imageFormat) + if (options.jpegQuality) args.push('--jpeg-quality', String(options.jpegQuality)) + if (options.scale) args.push('--scale', String(options.scale)) + if (options.muted) args.push('--muted') + if (options.audioCodec) args.push('--audio-codec', options.audioCodec) + if (options.concurrency) args.push('--concurrency', String(options.concurrency)) + + if (options.inputProps) { + args.push('--props', JSON.stringify(options.inputProps)) + } + + const startTime = Date.now() + const cwd = options.projectDir || process.cwd() + + try { + const result = await $`${args}`.cwd(cwd).text() + const duration = (Date.now() - startTime) / 1000 + + return { + success: true, + outputPath: options.outputPath, + duration + } + } catch (error: any) { + return { + success: false, + outputPath: options.outputPath, + error: error.message || String(error) + } + } +} + +/** + * Render a still image from a composition + * + * @param options - Still render configuration + * @returns Render result + */ +export async function renderStill(options: { + compositionId: string + outputPath: string + frame?: number + inputProps?: Record<string, any> + projectDir?: string + imageFormat?: 'png' | 'jpeg' + jpegQuality?: number + scale?: number +}): Promise<RenderResult> { + const args: string[] = ['npx', 'remotion', 'still', options.compositionId, options.outputPath] + + if (options.frame !== undefined) args.push('--frame', String(options.frame)) + if (options.imageFormat) args.push('--image-format', options.imageFormat) + if (options.jpegQuality) args.push('--jpeg-quality', String(options.jpegQuality)) + if (options.scale) args.push('--scale', String(options.scale)) + + if (options.inputProps) { + args.push('--props', JSON.stringify(options.inputProps)) + } + + const cwd = options.projectDir || process.cwd() + + try { + await $`${args}`.cwd(cwd).text() + + return { + success: true, + outputPath: options.outputPath + } + } catch (error: any) { + return { + success: false, + outputPath: options.outputPath, + error: error.message || String(error) + } + } +} + +/** + * List all compositions in a Remotion project + * + * @param projectDir - Project directory (defaults to cwd) + * @returns Array of compositions + */ +export async function listCompositions(projectDir?: string): Promise<Composition[]> { + const cwd = projectDir || process.cwd() + + try { + const result = await $`npx remotion compositions --json`.cwd(cwd).text() + return JSON.parse(result) + } catch (error: any) { + console.error('Failed to list compositions:', error.message) + return [] + } +} + +/** + * Start the Remotion studio preview server + * + * @param options - Studio options + */ +export async function startStudio(options?: { + projectDir?: string + port?: number + browserArgs?: string[] +}): Promise<void> { + const args: string[] = ['npx', 'remotion', 'studio'] + + if (options?.port) args.push('--port', String(options.port)) + + const cwd = options?.projectDir || process.cwd() + + // Run in background - studio stays open + $`${args}`.cwd(cwd).nothrow() + + console.log(`Remotion Studio starting at http://localhost:${options?.port || 3000}`) +} + +/** + * Create a new Remotion project + * + * @param options - Project creation options + */ +export async function createProject(options: { + name: string + template?: 'blank' | 'hello-world' | 'three' | 'audiogram' | 'tts' + outputDir?: string +}): Promise<{ success: boolean; path: string; error?: string }> { + const args: string[] = ['npx', 'create-video@latest', options.name] + + if (options.template) { + args.push('--template', options.template) + } + + const cwd = options.outputDir || process.cwd() + + try { + await $`${args}`.cwd(cwd).text() + + return { + success: true, + path: `${cwd}/${options.name}` + } + } catch (error: any) { + return { + success: false, + path: `${cwd}/${options.name}`, + error: error.message || String(error) + } + } +} + +/** + * Upgrade Remotion packages in a project + * + * @param projectDir - Project directory + */ +export async function upgrade(projectDir?: string): Promise<{ success: boolean; error?: string }> { + const cwd = projectDir || process.cwd() + + try { + await $`npx remotion upgrade`.cwd(cwd).text() + return { success: true } + } catch (error: any) { + return { + success: false, + error: error.message || String(error) + } + } +} + +/** + * Get video metadata using Mediabunny + */ +export async function getVideoMetadata(videoPath: string): Promise<{ + width: number + height: number + durationInSeconds: number + fps: number +} | null> { + try { + // This requires @remotion/media-utils in the project + const result = await $`npx remotion parse-video ${videoPath} --json`.text() + return JSON.parse(result) + } catch { + return null + } +} + +/** + * Get audio duration using Mediabunny + */ +export async function getAudioDuration(audioPath: string): Promise<number | null> { + try { + const result = await $`npx remotion parse-audio ${audioPath} --json`.text() + const data = JSON.parse(result) + return data.durationInSeconds + } catch { + return null + } +} + +// CLI entry point +if (import.meta.main) { + const args = process.argv.slice(2) + const command = args[0] + + switch (command) { + case 'render': { + const [_, compositionId, outputPath, ...rest] = args + if (!compositionId || !outputPath) { + console.error('Usage: bun run index.ts render <compositionId> <outputPath> [--crf N] [--fps N]') + process.exit(1) + } + + const options: RenderOptions = { compositionId, outputPath } + + // Parse optional args + for (let i = 0; i < rest.length; i++) { + if (rest[i] === '--crf' && rest[i + 1]) options.crf = parseInt(rest[++i]) + if (rest[i] === '--fps' && rest[i + 1]) options.fps = parseInt(rest[++i]) + if (rest[i] === '--codec' && rest[i + 1]) options.codec = rest[++i] as any + if (rest[i] === '--width' && rest[i + 1]) options.width = parseInt(rest[++i]) + if (rest[i] === '--height' && rest[i + 1]) options.height = parseInt(rest[++i]) + } + + const result = await render(options) + console.log(JSON.stringify(result, null, 2)) + break + } + + case 'list': { + const compositions = await listCompositions(args[1]) + console.log(JSON.stringify(compositions, null, 2)) + break + } + + case 'create': { + const name = args[1] + const template = args[2] as any + + if (!name) { + console.error('Usage: bun run index.ts create <name> [template]') + process.exit(1) + } + + const result = await createProject({ name, template }) + console.log(JSON.stringify(result, null, 2)) + break + } + + default: + console.log(` +Remotion CLI Wrapper + +Commands: + render <compositionId> <outputPath> [--crf N] [--fps N] [--codec TYPE] + list [projectDir] + create <name> [template] + +Examples: + bun run index.ts render my-video out/video.mp4 --crf 18 + bun run index.ts list + bun run index.ts create new-project hello-world +`) + } +} diff --git a/.opencode/skills/Media/Remotion/Tools/Theme.ts b/.opencode/skills/Media/Remotion/Tools/Theme.ts new file mode 100644 index 00000000..12fe555c --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/Theme.ts @@ -0,0 +1,130 @@ +/** + * PAI Theme for Remotion + * + * Derived from Art skill preferences at: + * .opencode/skills/PAI/USER/SKILLCUSTOMIZATIONS/Art/PREFERENCES.md + * + * Core aesthetic: Charcoal architectural sketch with purple accents + * Visual feel: Monumental emotional spaces, gestural linework, cool washes + */ + +export const PAI_THEME = { + // Colors (from Art color palette) + colors: { + // Backgrounds (vast architectural space feel) + background: '#0f172a', // Deep slate + backgroundAlt: '#1e293b', // Slightly lighter slate + backgroundDark: '#020617', // Near black + + // Accents (purple/violet from Art prefs) + accent: '#8b5cf6', // Primary purple + accentLight: '#a78bfa', // Lighter purple + accentDark: '#7c3aed', // Darker purple + accentMuted: '#6366f1', // Indigo variant + + // Text (paper ground inspired) + text: '#f1f5f9', // Light text + textMuted: '#94a3b8', // Muted/secondary text + textDark: '#64748b', // De-emphasized text + + // Special + paperGround: '#F5F5F0', // Cream/off-white from Art prefs + coolWash: 'rgba(139, 92, 246, 0.1)', // Purple atmospheric wash + warmWash: 'rgba(251, 191, 36, 0.1)', // Amber contrast wash + + // Utility + success: '#10b981', // Green + warning: '#f59e0b', // Amber + error: '#ef4444', // Red + info: '#3b82f6', // Blue + }, + + // Typography (production design quality) + typography: { + fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', + fontFamilyMono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', + + // Size scale + title: { fontSize: 72, fontWeight: 'bold' as const, lineHeight: 1.1 }, + subtitle: { fontSize: 48, fontWeight: '600' as const, lineHeight: 1.2 }, + heading: { fontSize: 36, fontWeight: '600' as const, lineHeight: 1.3 }, + body: { fontSize: 24, fontWeight: 'normal' as const, lineHeight: 1.5 }, + caption: { fontSize: 18, fontWeight: 'normal' as const, lineHeight: 1.4 }, + small: { fontSize: 14, fontWeight: 'normal' as const, lineHeight: 1.4 }, + }, + + // Animation feel (gestural, organic - not mechanical) + animation: { + // Spring configs (organic feel like gestural linework) + springFast: { damping: 15, stiffness: 150 }, + springDefault: { damping: 12, stiffness: 100 }, + springSlow: { damping: 10, stiffness: 80 }, + springBouncy: { damping: 8, stiffness: 120 }, + + // Frame durations at 30fps + fadeFrames: 30, // ~1 second fade + quickFade: 15, // ~0.5 second + slowFade: 45, // ~1.5 seconds + + // Stagger delays + staggerDelay: 10, // Frames between sequential elements + staggerFast: 5, // Quick succession + staggerSlow: 15, // Dramatic reveal + }, + + // Spacing (human-scale in vast spaces) + spacing: { + page: 100, // Edge padding for full-screen + section: 60, // Between major sections + element: 30, // Between related elements + tight: 15, // Compact spacing + + // For text blocks + paragraphGap: 24, + listItemGap: 16, + }, + + // Shadows and effects + effects: { + textShadow: '0 2px 4px rgba(0,0,0,0.5)', + boxShadow: '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06)', + boxShadowLarge: '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05)', + glow: '0 0 20px rgba(139, 92, 246, 0.5)', + }, + + // Border radius + borderRadius: { + small: 8, + medium: 16, + large: 24, + full: 9999, + }, +} as const + +// Type exports +export type PAITheme = typeof PAI_THEME +export type PAIColors = typeof PAI_THEME.colors +export type PAITypography = typeof PAI_THEME.typography +export type PAIAnimation = typeof PAI_THEME.animation + +// Utility: Get interpolate input/output for fade +export const fadeInterpolation = (startFrame = 0) => ({ + inputRange: [startFrame, startFrame + PAI_THEME.animation.fadeFrames], + outputRange: [0, 1] as [number, number], +}) + +// Utility: Style preset for centered title screen +export const titleScreenStyle = { + backgroundColor: PAI_THEME.colors.background, + display: 'flex' as const, + justifyContent: 'center' as const, + alignItems: 'center' as const, + fontFamily: PAI_THEME.typography.fontFamily, +} + +// Utility: Style preset for content screen +export const contentScreenStyle = { + backgroundColor: PAI_THEME.colors.background, + padding: PAI_THEME.spacing.page, + fontFamily: PAI_THEME.typography.fontFamily, +} diff --git a/.opencode/skills/Media/Remotion/Tools/package.json b/.opencode/skills/Media/Remotion/Tools/package.json new file mode 100644 index 00000000..dc51e4ed --- /dev/null +++ b/.opencode/skills/Media/Remotion/Tools/package.json @@ -0,0 +1,28 @@ +{ + "name": "@pai/remotion", + "version": "1.0.0", + "description": "PAI Remotion skill - programmatic video creation with React", + "main": "index.ts", + "type": "module", + "scripts": { + "render": "bun run index.ts render", + "list": "bun run index.ts list", + "create": "bun run index.ts create" + }, + "keywords": [ + "remotion", + "video", + "react", + "animation", + "pai", + "skill" + ], + "dependencies": {}, + "peerDependencies": { + "remotion": ">=4.0.0" + }, + "devDependencies": { + "bun-types": "latest", + "typescript": "^5.0.0" + } +}