diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..1b6e31e --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,18 @@ +{ + "name": "minimax-skills", + "description": "MiniMax AI skills library for frontend, fullstack, and Android native development", + "owner": { + "name": "MiniMax AI" + }, + "plugins": [ + { + "name": "minimax-skills", + "description": "MiniMax AI skills library: frontend development, fullstack development, and Android native development", + "version": "1.0.0", + "source": "./", + "author": { + "name": "MiniMax AI" + } + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..ebe2879 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "minimax-skills", + "description": "MiniMax AI skills library: frontend development, fullstack development, and Android native development", + "version": "1.0.0", + "author": { + "name": "MiniMax AI" + }, + "homepage": "https://github.com/MiniMax-AI/skills", + "repository": "https://github.com/MiniMax-AI/skills", + "license": "MIT", + "keywords": ["skills", "frontend", "fullstack", "android", "minimax"] +} diff --git a/.codex/INSTALL.md b/.codex/INSTALL.md new file mode 100644 index 0000000..dd3b05d --- /dev/null +++ b/.codex/INSTALL.md @@ -0,0 +1,58 @@ +# Installing MiniMax Skills for Codex + +Enable MiniMax skills in Codex via native skill discovery. Just clone and symlink. + +## Prerequisites + +- Git + +## Installation + +1. **Clone the repository:** + ```bash + git clone https://github.com/MiniMax-AI/skills.git ~/.codex/minimax-skills + ``` + +2. **Create the skills symlink:** + ```bash + mkdir -p ~/.agents/skills + ln -s ~/.codex/minimax-skills/skills ~/.agents/skills/minimax-skills + ``` + + **Windows (PowerShell):** + ```powershell + New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills" + cmd /c mklink /J "$env:USERPROFILE\.agents\skills\minimax-skills" "$env:USERPROFILE\.codex\minimax-skills\skills" + ``` + +3. **Restart Codex** (quit and relaunch the CLI) to discover the skills. + +## Available Skills + +- **frontend-dev** — Frontend development with UI design, animations, AI-generated media assets +- **fullstack-dev** — Full-stack backend architecture and frontend-backend integration +- **android-native-dev** — Android native application development with Material Design 3 + +## Verify + +```bash +ls -la ~/.agents/skills/minimax-skills +``` + +You should see a symlink (or junction on Windows) pointing to your minimax-skills skills directory. + +## Updating + +```bash +cd ~/.codex/minimax-skills && git pull +``` + +Skills update instantly through the symlink. + +## Uninstalling + +```bash +rm ~/.agents/skills/minimax-skills +``` + +Optionally delete the clone: `rm -rf ~/.codex/minimax-skills`. diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json new file mode 100644 index 0000000..30014da --- /dev/null +++ b/.cursor-plugin/plugin.json @@ -0,0 +1,14 @@ +{ + "name": "minimax-skills", + "displayName": "MiniMax Skills", + "description": "MiniMax AI skills library: frontend development, fullstack development, and Android native development", + "version": "1.0.0", + "author": { + "name": "MiniMax AI" + }, + "homepage": "https://github.com/MiniMax-AI/skills", + "repository": "https://github.com/MiniMax-AI/skills", + "license": "MIT", + "keywords": ["skills", "frontend", "fullstack", "android", "minimax"], + "skills": "./skills/" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dbf8603 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Python +__pycache__/ +*.py[cod] +*.egg-info/ +dist/ +build/ +venv/ +.venv/ + +# Environment +.env +.env.local + +# OS +.DS_Store +Thumbs.db + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Node (opencode plugin) +node_modules/ diff --git a/.opencode/INSTALL.md b/.opencode/INSTALL.md new file mode 100644 index 0000000..884978a --- /dev/null +++ b/.opencode/INSTALL.md @@ -0,0 +1,71 @@ +# Installing MiniMax Skills for OpenCode + +## Prerequisites + +- [OpenCode.ai](https://opencode.ai) installed + +## Installation + +Add minimax-skills to the `plugin` array in your `opencode.json` (global or project-level): + +```json +{ + "plugin": ["minimax-skills@git+https://github.com/MiniMax-AI/skills.git"] +} +``` + +Restart OpenCode. That's it — the plugin auto-installs and registers all skills. + +Verify by asking: "List available skills" + +## Available Skills + +- **frontend-dev** — Frontend development with UI design, animations, AI-generated media assets +- **fullstack-dev** — Full-stack backend architecture and frontend-backend integration +- **android-native-dev** — Android native application development with Material Design 3 + +## Usage + +Use OpenCode's native `skill` tool: + +``` +use skill tool to list skills +use skill tool to load minimax-skills/frontend-dev +``` + +## Updating + +MiniMax Skills updates automatically when you restart OpenCode. + +To pin a specific version: + +```json +{ + "plugin": ["minimax-skills@git+https://github.com/MiniMax-AI/skills.git#v1.0.0"] +} +``` + +## Troubleshooting + +### Plugin not loading + +1. Check logs: `opencode run --print-logs "hello" 2>&1 | grep -i minimax` +2. Verify the plugin line in your `opencode.json` +3. Make sure you're running a recent version of OpenCode + +### Skills not found + +1. Use `skill` tool to list what's discovered +2. Check that the plugin is loading (see above) + +### Tool mapping + +When skills reference Claude Code tools: +- `TodoWrite` → `todowrite` +- `Task` with subagents → `@mention` syntax +- `Skill` tool → OpenCode's native `skill` tool +- File operations → your native tools + +## Getting Help + +- Report issues: https://github.com/MiniMax-AI/skills/issues diff --git a/.opencode/plugins/minimax-skills.js b/.opencode/plugins/minimax-skills.js new file mode 100644 index 0000000..7f1f05c --- /dev/null +++ b/.opencode/plugins/minimax-skills.js @@ -0,0 +1,73 @@ +/** + * MiniMax Skills plugin for OpenCode.ai + * + * Registers the skills directory and injects bootstrap context via system prompt transform. + */ + +import path from 'path'; +import fs from 'fs'; +import os from 'os'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +export const MiniMaxSkillsPlugin = async () => { + const homeDir = os.homedir(); + const skillsDir = path.resolve(__dirname, '../../skills'); + const envConfigDir = process.env.OPENCODE_CONFIG_DIR + ? path.resolve(process.env.OPENCODE_CONFIG_DIR.replace(/^~/, homeDir)) + : null; + const configDir = envConfigDir || path.join(homeDir, '.config/opencode'); + + // Discover available skills by scanning the skills directory + const getAvailableSkills = () => { + if (!fs.existsSync(skillsDir)) return []; + return fs.readdirSync(skillsDir, { withFileTypes: true }) + .filter(d => d.isDirectory() && fs.existsSync(path.join(skillsDir, d.name, 'SKILL.md'))) + .map(d => d.name); + }; + + const getBootstrapContent = () => { + const skills = getAvailableSkills(); + if (skills.length === 0) return null; + + const skillList = skills.map(s => `- \`${s}\``).join('\n'); + + return ` +You have MiniMax Skills available. + +**Available skills:** +${skillList} + +**Tool Mapping for OpenCode:** +When skills reference tools you don't have, substitute OpenCode equivalents: +- \`TodoWrite\` → \`todowrite\` +- \`Task\` tool with subagents → Use OpenCode's subagent system (@mention) +- \`Skill\` tool → OpenCode's native \`skill\` tool +- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools + +**Skills location:** +MiniMax skills are in \`${configDir}/skills/minimax-skills/\` +Use OpenCode's native \`skill\` tool to list and load skills. +`; + }; + + return { + // Inject skills path into live config so OpenCode discovers skills + config: async (config) => { + config.skills = config.skills || {}; + config.skills.paths = config.skills.paths || []; + if (!config.skills.paths.includes(skillsDir)) { + config.skills.paths.push(skillsDir); + } + }, + + // Use system prompt transform to inject bootstrap + 'experimental.chat.system.transform': async (_input, output) => { + const bootstrap = getBootstrapContent(); + if (bootstrap) { + (output.system ||= []).push(bootstrap); + } + } + }; +}; diff --git a/README.md b/README.md new file mode 100644 index 0000000..f00237b --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# MiniMax Skills + +[中文版](./README_zh.md) + +> **Beta** — This project is under active development. Skills, APIs, and configuration formats may change without notice. We welcome feedback and contributions. + +Development skills for AI coding agents. Plug into your favorite AI coding tool and get structured, production-quality guidance for frontend, fullstack, and Android development. + +## Skills + +| Skill | Description | +|---------------------------------------|-------------| +| `frontend-dev` | Full-stack frontend development combining premium UI design, cinematic animations (Framer Motion, GSAP), AI-generated media assets via MiniMax API (image, video, audio, music, TTS), persuasive copywriting (AIDA framework), and generative art (p5.js, Three.js, Canvas). Tech stack: React / Next.js, Tailwind CSS. | +| `fullstack-dev` | Full-stack backend architecture and frontend-backend integration. REST API design, auth flows (JWT, session, OAuth), real-time features (SSE, WebSocket), database integration (SQL / NoSQL), production hardening, and release checklist. Guided workflow: requirements → architecture → implementation. | +| `android-native-dev` | Android native application development with Material Design 3. Kotlin / Jetpack Compose, adaptive layouts, Gradle configuration, accessibility (WCAG), build troubleshooting, performance optimization, and motion system. | + +## Installation + +### Claude Code + +```bash +claude plugin install --from "https://github.com/MiniMax-AI/skills" +``` + +### Cursor + +```bash +git clone https://github.com/MiniMax-AI/skills.git ~/.cursor/minimax-skills +``` + +Add to your Cursor settings — point the skills path to `~/.cursor/minimax-skills/skills/`. + +### Codex + +```bash +git clone https://github.com/MiniMax-AI/skills.git ~/.codex/minimax-skills + +mkdir -p ~/.agents/skills +ln -s ~/.codex/minimax-skills/skills ~/.agents/skills/minimax-skills +``` + +Restart Codex to discover the skills. See [`.codex/INSTALL.md`](.codex/INSTALL.md) for Windows instructions and details. + +### OpenCode + +Add to your `opencode.json`: + +```json +{ + "plugin": ["minimax-skills@git+https://github.com/MiniMax-AI/skills.git"] +} +``` + +Restart OpenCode. See [`.opencode/INSTALL.md`](.opencode/INSTALL.md) for details. + +## License + +[MIT](./LICENSE) diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..78ad93f --- /dev/null +++ b/README_zh.md @@ -0,0 +1,58 @@ +# MiniMax Skills + +[English](./README.md) + +> **Beta** — 本项目正在积极开发中。技能内容、API 和配置格式可能会在不另行通知的情况下变更。欢迎反馈和贡献。 + +面向 AI 编程工具的开发技能库。接入你常用的 AI 编程工具,获得结构化的、生产级质量的前端、全栈和 Android 开发指导。 + +## 技能列表 + +| 技能 | 简介 | +|---------------------------------------|------| +| `frontend-dev` | 全栈前端开发,融合高级 UI 设计、电影级动画(Framer Motion、GSAP)、通过 MiniMax API 生成媒体资源(图片、视频、音频、音乐、TTS)、基于 AIDA 框架的说服力文案、生成艺术(p5.js、Three.js、Canvas)。技术栈:React / Next.js、Tailwind CSS。 | +| `fullstack-dev` | 全栈后端架构与前后端集成。REST API 设计、认证流程(JWT、Session、OAuth)、实时功能(SSE、WebSocket)、数据库集成(SQL / NoSQL)、生产环境加固与发布清单。引导式工作流:需求收集 → 架构决策 → 实现。 | +| `android-native-dev` | 基于 Material Design 3 的 Android 原生应用开发。Kotlin / Jetpack Compose、自适应布局、Gradle 配置、无障碍(WCAG)、构建问题排查、性能优化与动效系统。 | + +## 安装 + +### Claude Code + +```bash +claude plugin install --from "https://github.com/MiniMax-AI/skills" +``` + +### Cursor + +```bash +git clone https://github.com/MiniMax-AI/skills.git ~/.cursor/minimax-skills +``` + +在 Cursor 设置中将 skills 路径指向 `~/.cursor/minimax-skills/skills/`。 + +### Codex + +```bash +git clone https://github.com/MiniMax-AI/skills.git ~/.codex/minimax-skills + +mkdir -p ~/.agents/skills +ln -s ~/.codex/minimax-skills/skills ~/.agents/skills/minimax-skills +``` + +重启 Codex 以发现技能。Windows 安装说明见 [`.codex/INSTALL.md`](.codex/INSTALL.md)。 + +### OpenCode + +在 `opencode.json` 中添加: + +```json +{ + "plugin": ["minimax-skills@git+https://github.com/MiniMax-AI/skills.git"] +} +``` + +重启 OpenCode。详见 [`.opencode/INSTALL.md`](.opencode/INSTALL.md)。 + +## 许可证 + +[MIT](./LICENSE) diff --git a/skills/android-native-dev/SKILL.md b/skills/android-native-dev/SKILL.md new file mode 100644 index 0000000..a42a363 --- /dev/null +++ b/skills/android-native-dev/SKILL.md @@ -0,0 +1,782 @@ +--- +name: android-native-dev +description: Android native application development and UI design guide. Covers Material Design 3, Kotlin/Compose development, project configuration, accessibility, and build troubleshooting. Read this before Android native application development. +license: MIT +metadata: + version: "1.0.0" + category: mobile + sources: + - Material Design 3 Guidelines (material.io) + - Android Developer Documentation (developer.android.com) + - Google Play Quality Guidelines + - WCAG Accessibility Guidelines +--- + +## 1. Project Scenario Assessment + +Before starting development, assess the current project state: + +| Scenario | Characteristics | Approach | +|----------|-----------------|----------| +| **Empty Directory** | No files present | Full initialization required, including Gradle Wrapper | +| **Has Gradle Wrapper** | `gradlew` and `gradle/wrapper/` exist | Use `./gradlew` directly for builds | +| **Android Studio Project** | Complete project structure, may lack wrapper | Check wrapper, run `gradle wrapper` if needed | +| **Incomplete Project** | Partial files present | Check missing files, complete configuration | + +**Key Principles**: +- Before writing business logic, ensure `./gradlew assembleDebug` succeeds +- If `gradle.properties` is missing, create it first and configure AndroidX + +### 1.1 Required Files Checklist + +``` +MyApp/ +├── gradle.properties # Configure AndroidX and other settings +├── settings.gradle.kts +├── build.gradle.kts # Root level +├── gradle/wrapper/ +│ └── gradle-wrapper.properties +├── app/ +│ ├── build.gradle.kts # Module level +│ └── src/main/ +│ ├── AndroidManifest.xml +│ ├── java/com/example/myapp/ +│ │ └── MainActivity.kt +│ └── res/ +│ ├── values/ +│ │ ├── strings.xml +│ │ ├── colors.xml +│ │ └── themes.xml +│ └── mipmap-*/ # App icons +``` + +--- + +## 2. Project Configuration + +### 2.1 gradle.properties + +```properties +# Required configuration +android.useAndroidX=true +android.enableJetifier=true + +# Build optimization +org.gradle.parallel=true +kotlin.code.style=official + +# JVM memory settings (adjust based on project size) +# Small projects: 2048m, Medium: 4096m, Large: 8192m+ +# org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 +``` + +> **Note**: If you encounter `OutOfMemoryError` during build, increase `-Xmx` value. Large projects with many dependencies may require 8GB or more. + +### 2.2 Dependency Declaration Standards + +```kotlin +dependencies { + // Use BOM to manage Compose versions + implementation(platform("androidx.compose:compose-bom:2024.02.00")) + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.material3:material3") + + // Activity & ViewModel + implementation("androidx.activity:activity-compose:1.8.2") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") +} +``` + +### 2.3 Build Variants & Product Flavors + +Product Flavors allow you to create different versions of your app (e.g., free/paid, dev/staging/prod). + +**Configuration in app/build.gradle.kts**: + +```kotlin +android { + // Define flavor dimensions + flavorDimensions += "environment" + + productFlavors { + create("dev") { + dimension = "environment" + applicationIdSuffix = ".dev" + versionNameSuffix = "-dev" + + // Different config values per flavor + buildConfigField("String", "API_BASE_URL", "\"https://dev-api.example.com\"") + buildConfigField("Boolean", "ENABLE_LOGGING", "true") + + // Different resources + resValue("string", "app_name", "MyApp Dev") + } + + create("staging") { + dimension = "environment" + applicationIdSuffix = ".staging" + versionNameSuffix = "-staging" + + buildConfigField("String", "API_BASE_URL", "\"https://staging-api.example.com\"") + buildConfigField("Boolean", "ENABLE_LOGGING", "true") + resValue("string", "app_name", "MyApp Staging") + } + + create("prod") { + dimension = "environment" + // No suffix for production + + buildConfigField("String", "API_BASE_URL", "\"https://api.example.com\"") + buildConfigField("Boolean", "ENABLE_LOGGING", "false") + resValue("string", "app_name", "MyApp") + } + } + + buildTypes { + debug { + isDebuggable = true + isMinifyEnabled = false + } + release { + isDebuggable = false + isMinifyEnabled = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } +} +``` + +**Build Variant Naming**: `{flavor}{BuildType}` → e.g., `devDebug`, `prodRelease` + +**Gradle Build Commands**: + +```bash +# List all available build variants +./gradlew tasks --group="build" + +# Build specific variant (flavor + buildType) +./gradlew assembleDevDebug # Dev flavor, Debug build +./gradlew assembleStagingDebug # Staging flavor, Debug build +./gradlew assembleProdRelease # Prod flavor, Release build + +# Build all variants of a specific flavor +./gradlew assembleDev # All Dev variants (debug + release) +./gradlew assembleProd # All Prod variants + +# Build all variants of a specific build type +./gradlew assembleDebug # All flavors, Debug build +./gradlew assembleRelease # All flavors, Release build + +# Install specific variant to device +./gradlew installDevDebug +./gradlew installProdRelease + +# Build and install in one command +./gradlew installDevDebug && adb shell am start -n com.example.myapp.dev/.MainActivity +``` + +**Access BuildConfig in Code**: + +> **Note**: Starting from AGP 8.0, `BuildConfig` is no longer generated by default. You must explicitly enable it in your `build.gradle.kts`: +> ```kotlin +> android { +> buildFeatures { +> buildConfig = true +> } +> } +> ``` + +```kotlin +// Use build config values in your code +val apiUrl = BuildConfig.API_BASE_URL +val isLoggingEnabled = BuildConfig.ENABLE_LOGGING + +if (BuildConfig.DEBUG) { + // Debug-only code +} +``` + +**Flavor-Specific Source Sets**: + +``` +app/src/ +├── main/ # Shared code for all flavors +├── dev/ # Dev-only code and resources +│ ├── java/ +│ └── res/ +├── staging/ # Staging-only code and resources +├── prod/ # Prod-only code and resources +├── debug/ # Debug build type code +└── release/ # Release build type code +``` + +**Multiple Flavor Dimensions** (e.g., environment + tier): + +```kotlin +android { + flavorDimensions += listOf("environment", "tier") + + productFlavors { + create("dev") { dimension = "environment" } + create("prod") { dimension = "environment" } + + create("free") { dimension = "tier" } + create("paid") { dimension = "tier" } + } +} +// Results in: devFreeDebug, devPaidDebug, prodFreeRelease, etc. +``` + +--- + +## 3. Kotlin Development Standards + +### 3.1 Naming Conventions + +| Type | Convention | Example | +|------|------------|---------| +| Class/Interface | PascalCase | `UserRepository`, `MainActivity` | +| Function/Variable | camelCase | `getUserName()`, `isLoading` | +| Constant | SCREAMING_SNAKE | `MAX_RETRY_COUNT` | +| Package | lowercase | `com.example.myapp` | +| Composable | PascalCase | `@Composable fun UserCard()` | + +### 3.2 Code Standards (Important) + +**Null Safety**: +```kotlin +// ❌ Avoid: Non-null assertion !! (may crash) +val name = user!!.name + +// ✅ Recommended: Safe call + default value +val name = user?.name ?: "Unknown" + +// ✅ Recommended: let handling +user?.let { processUser(it) } +``` + +**Exception Handling**: +```kotlin +// ❌ Avoid: Random try-catch in business layer swallowing exceptions +fun loadData() { + try { + val data = api.fetch() + } catch (e: Exception) { + // Swallowing exception, hard to debug + } +} + +// ✅ Recommended: Let exceptions propagate, handle at appropriate layer +suspend fun loadData(): Result { + return try { + Result.success(api.fetch()) + } catch (e: Exception) { + Result.failure(e) // Wrap and return, let caller decide handling + } +} + +// ✅ Recommended: Unified handling in ViewModel +viewModelScope.launch { + runCatching { repository.loadData() } + .onSuccess { _uiState.value = UiState.Success(it) } + .onFailure { _uiState.value = UiState.Error(it.message) } +} +``` + +### 3.3 Threading & Coroutines (Critical) + +**Thread Selection Principles**: + +| Operation Type | Thread | Description | +|----------------|--------|-------------| +| UI Updates | `Dispatchers.Main` | Update View, State, LiveData | +| Network Requests | `Dispatchers.IO` | HTTP calls, API requests | +| File I/O | `Dispatchers.IO` | Local storage, database operations | +| Compute Intensive | `Dispatchers.Default` | JSON parsing, sorting, encryption | + +**Correct Usage**: +```kotlin +// In ViewModel +viewModelScope.launch { + // Default Main thread, can update UI State + _uiState.value = UiState.Loading + + // Switch to IO thread for network request + val result = withContext(Dispatchers.IO) { + repository.fetchData() + } + + // Automatically returns to Main thread, update UI + _uiState.value = UiState.Success(result) +} + +// In Repository (suspend functions should be main-safe) +suspend fun fetchData(): Data = withContext(Dispatchers.IO) { + api.getData() +} +``` + +**Common Mistakes**: +```kotlin +// ❌ Wrong: Updating UI on IO thread +viewModelScope.launch(Dispatchers.IO) { + val data = api.fetch() + _uiState.value = data // Crash or warning! +} + +// ❌ Wrong: Executing time-consuming operation on Main thread +viewModelScope.launch { + val data = api.fetch() // Blocking main thread! ANR +} + +// ✅ Correct: Fetch on IO, update on Main +viewModelScope.launch { + val data = withContext(Dispatchers.IO) { api.fetch() } + _uiState.value = data +} +``` + +### 3.4 Visibility Rules + +```kotlin +// Default is public, declare explicitly when needed +class UserRepository { // public + private val cache = mutableMapOf() // Visible only within class + internal fun clearCache() {} // Visible only within module +} + +// data class properties are public by default, be careful when used across modules +data class User( + val id: String, // public + val name: String +) +``` + +### 3.5 Common Syntax Pitfalls + +```kotlin +// ❌ Wrong: Accessing uninitialized lateinit +class MyViewModel : ViewModel() { + lateinit var data: String + fun process() = data.length // May crash +} + +// ✅ Correct: Use nullable or default value +class MyViewModel : ViewModel() { + var data: String? = null + fun process() = data?.length ?: 0 +} + +// ❌ Wrong: Using return in lambda +list.forEach { item -> + if (item.isEmpty()) return // Returns from outer function! +} + +// ✅ Correct: Use return@forEach +list.forEach { item -> + if (item.isEmpty()) return@forEach +} +``` + +### 3.6 Server Response Data Class Fields Must Be Nullable + +```kotlin +// ❌ Wrong: Fields declared as non-null (server may not return them) +data class UserResponse( + val id: String = "", + val name: String = "", + val avatar: String = "" +) + +// ✅ Correct: All fields declared as nullable +data class UserResponse( + @SerializedName("id") + val id: String? = null, + @SerializedName("name") + val name: String? = null, + @SerializedName("avatar") + val avatar: String? = null +) +``` + +### 3.7 Lifecycle Resource Management + +```kotlin +// ❌ Wrong: Only adding Observer, not removing +class MyView : View { + override fun onAttachedToWindow() { + super.onAttachedToWindow() + activity?.lifecycle?.addObserver(this) + } + // Memory leak! +} + +// ✅ Correct: Paired add and remove +class MyView : View { + override fun onAttachedToWindow() { + super.onAttachedToWindow() + activity?.lifecycle?.addObserver(this) + } + + override fun onDetachedFromWindow() { + activity?.lifecycle?.removeObserver(this) + super.onDetachedFromWindow() + } +} +``` + +### 3.8 Logging Level Usage + +```kotlin +import android.util.Log + +// Info: Key checkpoints in normal flow +Log.i(TAG, "loadData: started, userId = $userId") + +// Warning: Abnormal but recoverable situations +Log.w(TAG, "loadData: cache miss, fallback to network") + +// Error: Failure/error situations +Log.e(TAG, "loadData failed: ${error.message}") +``` + +| Level | Use Case | +|-------|----------| +| `i` (Info) | Normal flow, method entry, key parameters | +| `w` (Warning) | Recoverable exceptions, fallback handling, null returns | +| `e` (Error) | Request failures, caught exceptions, unrecoverable errors | + +--- + +## 4. Jetpack Compose Standards + +### 4.1 @Composable Context Rules + +```kotlin +// ❌ Wrong: Calling Composable from non-Composable function +fun showError(message: String) { + Text(message) // Compile error! +} + +// ✅ Correct: Mark as @Composable +@Composable +fun ErrorMessage(message: String) { + Text(message) +} + +// ❌ Wrong: Using suspend outside LaunchedEffect +@Composable +fun MyScreen() { + val data = fetchData() // Error! +} + +// ✅ Correct: Use LaunchedEffect +@Composable +fun MyScreen() { + var data by remember { mutableStateOf(null) } + LaunchedEffect(Unit) { + data = fetchData() + } +} +``` + +### 4.2 State Management + +```kotlin +// Basic State +var count by remember { mutableStateOf(0) } + +// Derived State (avoid redundant computation) +val isEven by remember { derivedStateOf { count % 2 == 0 } } + +// Persist across recomposition (e.g., scroll position) +val scrollState = rememberScrollState() + +// State in ViewModel +class MyViewModel : ViewModel() { + private val _uiState = MutableStateFlow(UiState()) + val uiState: StateFlow = _uiState.asStateFlow() +} +``` + +### 4.3 Common Compose Mistakes + +```kotlin +// ❌ Wrong: Creating objects in Composable (created on every recomposition) +@Composable +fun MyScreen() { + val viewModel = MyViewModel() // Wrong! +} + +// ✅ Correct: Use viewModel() or remember +@Composable +fun MyScreen(viewModel: MyViewModel = viewModel()) { + // ... +} +``` + +--- + +## 5. Resources & Icons + +### 5.1 App Icon Requirements + +Must provide multi-resolution icons: + +| Directory | Size | Purpose | +|-----------|------|---------| +| mipmap-mdpi | 48x48 | Baseline | +| mipmap-hdpi | 72x72 | 1.5x | +| mipmap-xhdpi | 96x96 | 2x | +| mipmap-xxhdpi | 144x144 | 3x | +| mipmap-xxxhdpi | 192x192 | 4x | + +Recommended: Use Adaptive Icon (Android 8+): + +```xml + + + + + +``` + +### 5.2 Resource Naming Conventions + +| Type | Prefix | Example | +|------|--------|---------| +| Layout | layout_ | `layout_main.xml` | +| Image | ic_, img_, bg_ | `ic_user.png` | +| Color | color_ | `color_primary` | +| String | - | `app_name`, `btn_submit` | + +### 5.3 Avoid Android Reserved Names (Important) + +Variable names, resource IDs, colors, icons, and XML elements **must not** use Android reserved words or system resource names. Using reserved names causes build errors or resource conflicts. + +**Common Reserved Names to Avoid**: + +| Category | Reserved Names (Do NOT Use) | +|----------|----------------------------| +| Colors | `background`, `foreground`, `transparent`, `white`, `black` | +| Icons/Drawables | `icon`, `logo`, `image`, `drawable` | +| Views | `view`, `text`, `button`, `layout`, `container` | +| Attributes | `id`, `name`, `type`, `style`, `theme`, `color` | +| System | `app`, `android`, `content`, `data`, `action` | + +**Examples**: + +```xml + +#FFFFFF +#000000 + + +#FFFFFF +#000000 +``` + +```kotlin +// ❌ Wrong: Variable names conflict with system +val icon = R.drawable.my_icon +val background = Color.White + +// ✅ Correct: Use descriptive names +val appIcon = R.drawable.my_icon +val screenBackground = Color.White +``` + +```xml + + + + + +``` + +--- + +## 6. Build Error Diagnosis & Fixes + +### 6.1 Common Error Quick Reference + +| Error Keyword | Cause | Fix | +|---------------|-------|-----| +| `Unresolved reference` | Missing import or undefined | Check imports, verify dependencies | +| `Type mismatch` | Type incompatibility | Check parameter types, add conversion | +| `Cannot access` | Visibility issue | Check public/private/internal | +| `@Composable invocations` | Composable context error | Ensure caller is also @Composable | +| `Duplicate class` | Dependency conflict | Use `./gradlew dependencies` to investigate | +| `AAPT: error` | Resource file error | Check XML syntax and resource references | + +### 6.2 Fix Best Practices + +1. **Read the complete error message first**: Locate file and line number +2. **Check recent changes**: Problems usually in latest modifications +3. **Clean Build**: `./gradlew clean assembleDebug` +4. **Check dependency versions**: Version conflicts are common causes +5. **Refresh dependencies if needed**: Clear cache and rebuild + +### 6.3 Debugging Commands + +```bash +# Clean and build +./gradlew clean assembleDebug + +# View dependency tree (investigate conflicts) +./gradlew :app:dependencies + +# View detailed errors +./gradlew assembleDebug --stacktrace + +# Refresh dependencies +./gradlew --refresh-dependencies +``` + +--- + +## 7. Material Design 3 Guidelines + +Review Android UI files for compliance with Material Design 3 Guidelines and Android best practices. + +### Design Philosophy + +#### M3 Core Principles + +| Principle | Description | +|-----------|-------------| +| **Personal** | Dynamic color based on user preferences and wallpaper | +| **Adaptive** | Responsive across all screen sizes and form factors | +| **Expressive** | Bold colors and typography with personality | +| **Accessible** | Inclusive design for all users | + +#### M3 Expressive (Latest) + +The latest evolution adds emotion-driven UX through: +- Vibrant, dynamic colors +- Intuitive motion physics +- Adaptive components +- Flexible typography +- Contrasting shapes (35 new shape options) + +### App Style Selection + +**Critical Decision**: Match visual style to app category and target audience. + +| App Category | Visual Style | Key Characteristics | +|--------------|--------------|---------------------| +| Utility/Tool | Minimalist | Clean, efficient, neutral colors | +| Finance/Banking | Professional Trust | Conservative colors, security-focused | +| Health/Wellness | Calm & Natural | Soft colors, organic shapes | +| Kids (3-5) | Playful Simple | Bright colors, large targets (56dp+) | +| Kids (6-12) | Fun & Engaging | Vibrant, gamified feedback | +| Social/Entertainment | Expressive | Brand-driven, gesture-rich | +| Productivity | Clean & Focused | Minimal, high contrast | +| E-commerce | Conversion-focused | Clear CTAs, scannable | + +See [Design Style Guide](references/design-style-guide.md) for detailed style profiles. + +### Quick Reference: Key Specifications + +#### Color Contrast Requirements + +| Element | Minimum Ratio | +|---------|---------------| +| Body text | **4.5:1** | +| Large text (18sp+) | **3:1** | +| UI components | **3:1** | + +#### Touch Targets + +| Type | Size | +|------|------| +| Minimum | 48 × 48dp | +| Recommended (primary actions) | 56 × 56dp | +| Kids apps | 56dp+ | +| Spacing between targets | 8dp minimum | + +#### 8dp Grid System + +| Token | Value | Usage | +|-------|-------|-------| +| xs | 4dp | Icon padding | +| sm | 8dp | Tight spacing | +| md | 16dp | Default padding | +| lg | 24dp | Section spacing | +| xl | 32dp | Large gaps | +| xxl | 48dp | Screen margins | + +#### Typography Scale (Summary) + +| Category | Sizes | +|----------|-------| +| Display | 57sp, 45sp, 36sp | +| Headline | 32sp, 28sp, 24sp | +| Title | 22sp, 16sp, 14sp | +| Body | 16sp, 14sp, 12sp | +| Label | 14sp, 12sp, 11sp | + +#### Animation Duration + +| Type | Duration | +|------|----------| +| Micro (ripples) | 50-100ms | +| Short (simple) | 100-200ms | +| Medium (expand/collapse) | 200-300ms | +| Long (complex) | 300-500ms | + +#### Component Dimensions + +| Component | Height | Min Width | +|-----------|--------|-----------| +| Button | 40dp | 64dp | +| FAB | 56dp | 56dp | +| Text Field | 56dp | 280dp | +| App Bar | 64dp | - | +| Bottom Nav | 80dp | - | + +### Anti-Patterns (Must Avoid) + +#### UI Anti-Patterns +- More than 5 bottom navigation items +- Multiple FABs on same screen +- Touch targets smaller than 48dp +- Inconsistent spacing (non-8dp multiples) +- Missing dark theme support +- Text on colored backgrounds without contrast check + +#### Performance Anti-Patterns +- Startup time > 2 seconds without progress indicator +- Frame rate < 60 FPS (> 16ms per frame) +- Crash rate > 1.09% (Google Play threshold) +- ANR rate > 0.47% (Google Play threshold) + +#### Accessibility Anti-Patterns +- Missing contentDescription on interactive elements +- Element type in labels (e.g., "Save button" instead of "Save") +- Complex gestures in kids apps +- Text-only buttons for non-readers + +### Review Checklist + +- [ ] 8dp spacing grid compliance +- [ ] 48dp minimum touch targets +- [ ] Proper typography scale usage +- [ ] Color contrast compliance (4.5:1+ for text) +- [ ] Dark theme support +- [ ] contentDescription on all interactive elements +- [ ] Startup < 2 seconds or shows progress +- [ ] Visual style matches app category + +### Design References + +| Topic | Reference | +|-------|-----------| +| Colors, Typography, Spacing, Shapes | [Visual Design](references/visual-design.md) | +| Animation & Transitions | [Motion System](references/motion-system.md) | +| Accessibility Guidelines | [Accessibility](references/accessibility.md) | +| Large Screens & Foldables | [Adaptive Screens](references/adaptive-screens.md) | +| Android Vitals & Performance | [Performance & Stability](references/performance-stability.md) | +| Privacy & Security | [Privacy & Security](references/privacy-security.md) | +| Audio, Video, Notifications | [Functional Requirements](references/functional-requirements.md) | +| App Style by Category | [Design Style Guide](references/design-style-guide.md) | diff --git a/skills/android-native-dev/references/accessibility.md b/skills/android-native-dev/references/accessibility.md new file mode 100644 index 0000000..e1eba6b --- /dev/null +++ b/skills/android-native-dev/references/accessibility.md @@ -0,0 +1,209 @@ +# Accessibility Guidelines + +Comprehensive accessibility requirements for Android applications. + +## Core Requirements + +### Minimum Standards + +| Requirement | Specification | +|-------------|---------------| +| Color contrast (text) | 4.5:1 minimum | +| Color contrast (large text) | 3:1 minimum | +| Color contrast (UI components) | 3:1 minimum | +| Touch targets | 48 × 48dp minimum | +| Content descriptions | All interactive elements | +| Focus indicators | Clearly visible | +| Screen reader support | Proper semantics | + +## Content Labels + +### contentDescription + +Use for non-text interactive elements. + +**When to use:** +- ImageView, ImageButton +- CheckBox, Switch (state description) +- Custom drawable views +- Icons that convey meaning + +**When NOT to use:** +- TextView (uses text content automatically) +- Decorative images (set to null) +- Elements with labelFor relationship + +### android:hint + +Use for editable text fields to show placeholder text. + +**Important**: Don't use contentDescription on EditText—it interferes with accessibility services. + +### android:labelFor + +Link labels to input fields by setting labelFor on the TextView to reference the EditText ID. + +## Label Best Practices + +### Do's + +| Practice | Example | +|----------|---------| +| Be concise | "Save" not "Click here to save" | +| Describe action/purpose | "Delete message" | +| Be unique in context | "Delete item 3" not just "Delete" | +| Update dynamically | "Pause" ↔ "Play" based on state | + +### Don'ts + +| Avoid | Reason | +|-------|--------| +| Include element type | TalkBack announces "button" automatically | +| Say "button", "image", etc. | Redundant with accessibility info | +| Use "click" or "tap" | Input method varies | +| Leave empty/generic | "Button" or "Image" is unhelpful | + +### Examples + +| Bad | Good | +|-----|------| +| "Save button" | "Save" | +| "Click here to submit" | "Submit" | +| "Image" | "Profile photo of John" | +| "Button 1" | "Add to cart" | + +## Focus and Navigation + +### Focus Groups + +Group related elements using `screenReaderFocusable="true"` on the container and `focusable="false"` on child elements. TalkBack will announce all children's content in a single utterance. + +### Headings + +Mark section headers with `accessibilityHeading="true"`. Users can navigate between headings for quick scanning. + +### Pane Titles + +Identify screen regions with `accessibilityPaneTitle`. Accessibility services announce pane changes. + +### Focus Order + +- Natural reading order (top-to-bottom, start-to-end) +- Use `accessibilityTraversalBefore/After` for custom order +- Ensure all interactive elements are focusable +- Skip decorative elements + +## Decorative Elements + +Skip elements that don't convey information: +- Set `contentDescription="@null"` +- Or set `importantForAccessibility="no"` + +## Custom Accessibility Actions + +### Adding Actions + +Provide alternatives for gesture-based interactions using `ViewCompat.addAccessibilityAction()`. This exposes swipe actions to accessibility services. + +### Replacing Action Labels + +Make default actions more descriptive using `ViewCompat.replaceAccessibilityAction()`. Example: "Double tap and hold to add to favorites" instead of generic "long press". + +## Color and Visual Cues + +### Don't Rely on Color Alone + +Combine color with other indicators: + +| Information | Color + Alternative | +|-------------|---------------------| +| Error state | Red + error icon + text | +| Success | Green + checkmark + text | +| Required field | Red asterisk + "Required" label | +| Selected item | Highlight + checkmark + bold | +| Link text | Blue + underline | + +### Contrast Testing + +Use tools to verify contrast: +- Android Accessibility Scanner +- Contrast Checker plugins +- Manual calculation: (L1 + 0.05) / (L2 + 0.05) + +## Touch Targets + +### Minimum Sizes + +| Element | Minimum | Recommended | +|---------|---------|-------------| +| Standard | 48 × 48dp | 48 × 48dp | +| Primary actions | 48 × 48dp | 56 × 56dp | +| Kids apps | 56 × 56dp | 64 × 64dp | + +### Spacing + +- Minimum 8dp between adjacent touch targets +- Visual element can be smaller if touch area is adequate (use padding) + +## Screen Reader Announcements + +### Live Regions + +Announce dynamic content changes using `accessibilityLiveRegion`: + +| Mode | Usage | +|------|-------| +| polite | Announces when user is idle | +| assertive | Interrupts current speech | +| none | No automatic announcements | + +### Custom Announcements + +Use `announceForAccessibility()` sparingly—prefer live regions. + +## Keyboard and Hardware Navigation + +### Focus Indicators + +- Visible focus state for all interactive elements +- Don't remove default focus indicators +- Custom focus: 2dp+ border or background change + +### Keyboard Shortcuts + +- Support Tab navigation +- Enter/Space for activation +- Arrow keys for lists/grids +- Escape for dismissal + +## Testing Accessibility + +### Manual Testing + +1. **TalkBack**: Navigate entire app with screen reader +2. **Switch Access**: Test with switch navigation +3. **Keyboard**: Navigate with external keyboard only +4. **Magnification**: Test with zoom enabled +5. **Large text**: Test with 200% font scale +6. **High contrast**: Test with high contrast mode + +### Automated Testing + +| Tool | Purpose | +|------|---------| +| Accessibility Scanner | On-device scanning | +| Espresso Accessibility Checks | Automated UI tests | +| Lint checks | Static analysis | + +### Checklist + +- [ ] All interactive elements have descriptions +- [ ] Touch targets are 48dp minimum +- [ ] Color contrast meets requirements +- [ ] Focus order is logical +- [ ] Headings are properly marked +- [ ] Custom actions have descriptive labels +- [ ] Live regions announce important changes +- [ ] Keyboard navigation works +- [ ] Works with TalkBack enabled +- [ ] Works with large text (200%) diff --git a/skills/android-native-dev/references/adaptive-screens.md b/skills/android-native-dev/references/adaptive-screens.md new file mode 100644 index 0000000..d371113 --- /dev/null +++ b/skills/android-native-dev/references/adaptive-screens.md @@ -0,0 +1,231 @@ +# Adaptive Screens Guidelines + +Requirements for large screens, tablets, foldables, and multi-window support. + +## Adaptive Quality Tiers + +Google defines three progressive quality tiers for adaptive apps: + +### Tier 3: Adaptive Ready (Basic) + +Minimum requirements for all apps: + +| Requirement | Description | +|-------------|-------------| +| Full screen | App fills display, no letterboxing | +| Configuration changes | Handles rotation, folding, resizing | +| Multi-window | Supports split-screen mode | +| Basic input | Keyboard, mouse, trackpad support | + +### Tier 2: Adaptive Optimized (Better) + +Enhanced experience: + +| Requirement | Description | +|-------------|-------------| +| Layout optimization | Responsive layouts for all sizes | +| Enhanced input | Full keyboard shortcuts, mouse hover states | +| Continuity | Seamless state preservation | + +### Tier 1: Adaptive Differentiated (Best) + +Device-specific excellence: + +| Requirement | Description | +|-------------|-------------| +| Multitasking | Drag and drop, activity embedding | +| Foldable postures | Table-top mode, book mode support | +| Stylus | Full stylus input support | +| Desktop | Windowed mode optimization | + +## Screen Size Classes + +### Width-Based Classes + +| Class | Width | Typical Devices | +|-------|-------|-----------------| +| Compact | < 600dp | Phone portrait | +| Medium | 600-840dp | Tablet portrait, phone landscape | +| Expanded | > 840dp | Tablet landscape, desktop | + +### Layout Strategies + +| Screen Class | Navigation | Content Layout | +|--------------|------------|----------------| +| Compact | Bottom nav | Single pane | +| Medium | Nav rail | List-detail (optional) | +| Expanded | Nav drawer/rail | List-detail, multi-pane | + +## Configuration Changes + +### Must Handle + +| Change | Trigger | +|--------|---------| +| Rotation | Device rotated | +| Fold/Unfold | Foldable state change | +| Window resize | Multi-window adjustment | +| Split screen | Enter/exit split mode | +| Keyboard | External keyboard attach/detach | + +### Configuration Handling + +| Approach | Description | +|----------|-------------| +| Let system handle | Default, activity recreated | +| Handle manually | Declare configChanges, implement onConfigurationChanged | + +### State Preservation + +- Use ViewModel for UI state +- Use SavedStateHandle for process death +- Test with "Don't keep activities" enabled + +## Multi-Window Support + +### Requirements + +| Feature | Status | +|---------|--------| +| resizeableActivity | true (default API 24+) | +| Minimum size | Support 220dp width | +| State handling | Preserve across resize | + +### Best Practices + +- Don't assume full-screen ownership +- Handle onConfigurationChanged gracefully +- Test at minimum supported size +- Support free-form windows (desktop mode) + +## Foldable Devices + +### Postures + +| Posture | Description | Use Case | +|---------|-------------|----------| +| Flat | Fully open | Normal tablet use | +| Half-opened (tabletop) | Hinged at ~90° horizontal | Video calls, media | +| Half-opened (book) | Hinged at ~90° vertical | Reading, productivity | +| Folded | Closed | Compact phone mode | + +### Design Considerations + +- Avoid placing interactive elements on the fold +- Consider separate content for each screen segment +- Support continuity when fold state changes +- Use WindowInfoTracker to detect fold state + +## External Input Devices + +### Keyboard Support + +| Requirement | Implementation | +|-------------|----------------| +| Tab navigation | Focusable elements in order | +| Enter/Space | Activates focused element | +| Arrow keys | Navigate lists, grids | +| Shortcuts | Common actions (Ctrl+S, etc.) | +| Focus indicators | Visible focus states | + +### Mouse/Trackpad Support + +| Requirement | Implementation | +|-------------|----------------| +| Hover states | Visual feedback on hover | +| Right-click | Context menu support | +| Scroll | Smooth scrolling | +| Pointer cursor | Appropriate cursor types | + +### Stylus Support + +| Feature | Implementation | +|---------|----------------| +| Pressure sensitivity | Variable stroke width | +| Palm rejection | Ignore palm touches | +| Tilt detection | Shading effects | +| Hover preview | Show cursor before touch | + +## Navigation Patterns + +### By Screen Width + +| Width | Primary Nav | Secondary Nav | +|-------|-------------|---------------| +| < 600dp | Bottom nav (3-5 items) | Hamburger menu | +| 600-840dp | Navigation rail | Drawer on demand | +| > 840dp | Permanent drawer or rail | Drawer or none | + +### Navigation Rail Specs + +| Property | Value | +|----------|-------| +| Width | 80dp | +| Icon size | 24dp | +| Touch target | 56dp | +| Items | 3-7 destinations | +| FAB | Optional, at top | + +### Permanent Navigation Drawer + +| Property | Value | +|----------|-------| +| Width | 256-360dp | +| Position | Left edge (LTR) | +| Behavior | Always visible | +| Content | Full labels, icons | + +## Responsive Layouts + +### Breakpoints + +| Class | Width Range | +|-------|-------------| +| COMPACT | < 600dp | +| MEDIUM | 600-840dp | +| EXPANDED | > 840dp | + +Use WindowSizeClass to determine current breakpoint and adapt layout accordingly. + +## Content Considerations + +### Text Readability + +- Line length: 45-75 characters max +- Use multiple columns on wide screens +- Maintain hierarchy with consistent spacing + +### Media + +- Support multiple aspect ratios +- Provide high-resolution assets +- Consider picture-in-picture for video + +### Touch vs. Precise Input + +- Large screens often use mouse/keyboard +- Don't assume touch-only interaction +- Provide hover states and tooltips + +## Testing + +### Device Matrix + +| Device Type | Test Priority | +|-------------|---------------| +| Phone (portrait) | Required | +| Phone (landscape) | Required | +| Tablet (both orientations) | Required | +| Foldable (all postures) | High | +| Desktop/Chromebook | Medium | + +### Test Cases + +- [ ] App fills screen in all configurations +- [ ] No letterboxing or black bars +- [ ] State preserved across configuration changes +- [ ] Multi-window works at minimum size +- [ ] Keyboard navigation functional +- [ ] Mouse hover states present +- [ ] Foldable postures handled (if applicable) +- [ ] Navigation adapts to screen width diff --git a/skills/android-native-dev/references/design-style-guide.md b/skills/android-native-dev/references/design-style-guide.md new file mode 100644 index 0000000..7a17d06 --- /dev/null +++ b/skills/android-native-dev/references/design-style-guide.md @@ -0,0 +1,365 @@ +# Design Style Guide + +Match visual design to app category and target audience for cohesive user experience. + +## Style Selection Principle + +> **The visual style must match the app's purpose and audience.** +> A finance app should feel trustworthy, not playful. +> A children's app should feel fun, not corporate. + +## Style Selection Matrix + +| App Category | Visual Style | Color Palette | Typography | Interaction | +|--------------|--------------|---------------|------------|-------------| +| Utility/Tool | Minimalist | Neutral + 1 accent | Clean sans-serif | Direct, efficient | +| Finance/Banking | Professional Trust | Blue/Green/Navy | Conservative | Secure, deliberate | +| Health/Wellness | Calm & Natural | Soft greens, earth tones | Rounded, friendly | Gentle, encouraging | +| Kids (3-5) | Playful Simple | Bright primary colors | Large, rounded | Big targets, forgiving | +| Kids (6-12) | Fun & Engaging | Vibrant, varied | Bold, readable | Gamified feedback | +| Social/Entertainment | Expressive | Brand-driven | Dynamic | Gesture-rich | +| Productivity | Clean & Focused | Minimal, high contrast | Professional | Keyboard-friendly | +| E-commerce | Conversion-focused | Brand + CTA colors | Scannable | Quick actions | +| Gaming | Immersive | Theme-driven | Stylized | Custom gestures | + +## Detailed Style Profiles + +### Minimalist / iOS-like (Utility Apps) + +**When to use**: Tools, utilities, calculators, file managers, settings apps + +**Visual Characteristics**: + +| Element | Specification | +|---------|---------------| +| Colors | 2-3 colors max, neutral base | +| Whitespace | Generous, 24-48dp margins | +| Typography | Single font family, clear hierarchy | +| Icons | Line-based, consistent stroke | +| Shadows | Subtle or none | +| Borders | Thin (1dp) or none | +| Shapes | Subtle corners (8-12dp) | + +**Interaction Style**: +- Direct manipulation +- Immediate feedback +- No unnecessary animations +- Efficient task completion + +**Color Palette**: + +| Role | Light Mode | Dark Mode | +|------|------------|-----------| +| Background | #FAFAFA | #1C1C1E | +| Surface | #FFFFFF | #2C2C2E | +| Primary | #007AFF | #0A84FF | +| Text | #000000 | #FFFFFF | +| Secondary | #8E8E93 | #8E8E93 | + +**Reference Apps**: iOS Settings, Apple Notes, Google Calculator + +--- + +### Professional Trust (Finance/Business) + +**When to use**: Banking, investment, enterprise, B2B applications + +**Visual Characteristics**: + +| Element | Specification | +|---------|---------------| +| Colors | Blues, greens, navy (trust colors) | +| Whitespace | Structured, grid-based | +| Typography | Formal, conservative weights | +| Icons | Filled or outlined, consistent | +| Data visualization | Clear, accurate charts | +| Security indicators | Prominent locks, badges | + +**Interaction Style**: +- Confirmatory (double-check important actions) +- Deliberate (not rushed) +- Secure-feeling +- Clear feedback on transactions + +**Color Palette**: + +| Role | Color | Name | +|------|-------|------| +| Primary | #00695C or #1565C0 | Teal 800 / Blue 800 | +| Secondary | #37474F | Blue Grey 800 | +| Accent | #FFC107 | Amber | +| Background | #ECEFF1 | Blue Grey 50 | +| Success | #2E7D32 | Green 800 | +| Error | #C62828 | Red 800 | + +**Key Patterns**: +- Balance summaries prominent +- Transaction history easily scannable +- Secure entry for sensitive data +- Biometric authentication prompts + +**Reference Apps**: Banking apps, Trading platforms, Enterprise tools + +--- + +### Calm & Wellness (Health Apps) + +**When to use**: Meditation, fitness tracking, health monitoring, therapy + +**Visual Characteristics**: + +| Element | Specification | +|---------|---------------| +| Colors | Soft, muted, natural | +| Whitespace | Abundant (breathing room) | +| Typography | Rounded, friendly fonts | +| Shapes | Organic, soft corners (16dp+) | +| Animation | Gentle, slow transitions | +| Imagery | Nature, soft gradients | + +**Interaction Style**: +- Encouraging, not demanding +- Progress-oriented +- Gentle reminders +- Celebration of achievements + +**Color Palette**: + +| Role | Color | Name | +|------|-------|------| +| Primary | #4CAF50 | Green 500 | +| Secondary | #81C784 | Green 300 | +| Tertiary | #B2DFDB | Teal 100 | +| Background | #F1F8E9 | Light Green 50 | +| Text | #33691E | Light Green 900 | +| Accent | #FFB74D | Orange 300 | + +**Key Patterns**: +- Progress rings and charts +- Streak tracking +- Motivational messages +- Quiet notification style + +**Reference Apps**: Headspace, Calm, Apple Fitness + +--- + +### Playful & Kid-Friendly (Children's Apps) + +**When to use**: Educational games, children's content, family apps + +#### Ages 3-5 + +**Visual Characteristics**: + +| Element | Specification | +|---------|---------------| +| Colors | Bright, saturated primary colors | +| Touch targets | 56dp minimum, 64dp recommended | +| Shapes | Very rounded (full radius) | +| Typography | Large (18sp+ minimum), simple fonts | +| Icons | Large, colorful, recognizable | +| Animation | Frequent, rewarding | + +**Interaction Style**: +- Simple gestures only (tap, drag) +- No multi-finger gestures +- Forgiving error handling +- Immediate, multi-sensory feedback (sound + visual + haptic) +- No text-only buttons + +**Color Palette**: + +| Role | Color | Name | +|------|-------|------| +| Primary | #F44336 | Red 500 | +| Secondary | #FFEB3B | Yellow 500 | +| Tertiary | #2196F3 | Blue 500 | +| Background | #FFFFFF | White or soft pastels | +| Accent | #4CAF50 | Green 500 | + +#### Ages 6-12 + +**Visual Characteristics**: + +| Element | Specification | +|---------|---------------| +| Colors | Vibrant, varied palette | +| Touch targets | 48dp minimum | +| Shapes | Rounded but can be varied | +| Typography | Bold, readable, can include text | +| Icons | Stylized, character-driven | +| Animation | Gamified, achievement-based | + +**Interaction Style**: +- Can introduce some complexity +- Gamification elements +- Progress and rewards +- Some text is acceptable + +**Key Patterns for All Kids Apps**: +- Icon-based navigation (no text-only) +- Home button always visible +- Back navigation clear +- Parent gate for settings (math problem, hold button) +- Multi-sensory feedback +- Encouraging error states (no punishment) +- Joint engagement opportunities with parents + +**Reference Apps**: PBS Kids, Khan Academy Kids, Duolingo ABC + +--- + +### Expressive & Social (Entertainment Apps) + +**When to use**: Social media, content creation, entertainment + +**Visual Characteristics**: + +| Element | Specification | +|---------|---------------| +| Colors | Bold brand colors | +| Typography | Dynamic, personality-driven | +| Media | Rich, prominent | +| Animation | Expressive, delightful | +| Shapes | Brand-specific | + +**Interaction Style**: +- Gesture-rich +- Quick actions +- Social interactions prominent +- Content-first design + +**Key Patterns**: +- Feed-based layouts +- Quick action buttons (like, share, comment) +- Stories/ephemeral content +- Creation tools accessible +- Notification badges + +**Reference Apps**: Instagram, TikTok, Snapchat + +--- + +### Clean & Focused (Productivity Apps) + +**When to use**: Note-taking, task management, email, documents + +**Visual Characteristics**: + +| Element | Specification | +|---------|---------------| +| Colors | High contrast, minimal | +| Whitespace | Strategic, content-focused | +| Typography | Highly readable, clear hierarchy | +| Icons | Functional, consistent | +| Density | Adjustable (compact to comfortable) | + +**Interaction Style**: +- Keyboard-friendly +- Batch operations +- Drag and drop +- Quick capture +- Search-centric + +**Color Palette**: + +| Role | Light Mode | Dark Mode | +|------|------------|-----------| +| Primary | #1976D2 | #64B5F6 | +| Background | #FFFFFF | #121212 | +| Surface | #F5F5F5 | #1E1E1E | +| Text | #212121 | #E0E0E0 | +| Accent/Priority | #FF5722 | #FF7043 | + +**Key Patterns**: +- List views with swipe actions +- Quick add buttons +- Checkbox interactions +- Due dates and reminders +- Tags and categories + +**Reference Apps**: Notion, Todoist, Google Tasks + +--- + +### Conversion-Focused (E-commerce) + +**When to use**: Shopping, marketplace, booking apps + +**Visual Characteristics**: + +| Element | Specification | +|---------|---------------| +| Colors | Brand + clear CTA colors | +| Images | High quality, zoomable | +| Typography | Scannable, price prominent | +| Cards | Product-focused | +| Badges | Sale, new, limited | + +**Interaction Style**: +- Quick add to cart +- Easy checkout flow +- Comparison features +- Reviews accessible +- Wishlist/save for later + +**Key Patterns**: +- Grid and list view toggle +- Filter and sort +- Product detail with gallery +- Cart always accessible +- One-tap purchase options + +**Reference Apps**: Amazon, Shopify apps, Booking.com + +--- + +## Consistency Principles + +### Match Style to Subject Matter + +| App Purpose | Style Should Feel | +|-------------|-------------------| +| Utility | Efficient, invisible | +| Finance | Trustworthy, secure | +| Health | Supportive, calm | +| Kids | Safe, fun | +| Social | Expressive, personal | +| Productivity | Focused, powerful | +| Shopping | Exciting, trustworthy | + +### Internal Consistency Rules + +| Rule | Implementation | +|------|----------------| +| Same icon style | All outlined OR all filled | +| Consistent color meaning | Red = destructive, Green = success | +| Uniform spacing | Use 8dp grid | +| Predictable interaction | Same gesture = same result | +| Typography system | Use M3 type scale | + +## Anti-Patterns: Style Mismatch + +| Mismatch | Problem | +|----------|---------| +| Playful colors in banking app | Undermines trust | +| Complex gestures in kids app | Frustrates young users | +| Cluttered UI in wellness app | Defeats calming purpose | +| Boring visuals in entertainment | Fails to engage | +| Aggressive CTAs in health app | Feels manipulative | +| Childish design in professional tool | Lacks credibility | +| Dense information in casual app | Overwhelms users | + +## Implementation Checklist + +- [ ] Identified app category and target audience +- [ ] Selected appropriate style profile +- [ ] Color palette matches style +- [ ] Typography matches style +- [ ] Interaction patterns match style +- [ ] Touch targets appropriate for audience +- [ ] Animation style consistent +- [ ] Internal consistency maintained +- [ ] No style mismatches +- [ ] Tested with target users diff --git a/skills/android-native-dev/references/functional-requirements.md b/skills/android-native-dev/references/functional-requirements.md new file mode 100644 index 0000000..2d19627 --- /dev/null +++ b/skills/android-native-dev/references/functional-requirements.md @@ -0,0 +1,229 @@ +# Functional Requirements + +Audio, video, notifications, and other functional behavior requirements. + +## Audio + +### Playback Initialization + +| Requirement | Specification | +|-------------|---------------| +| Response time | < 1 second | +| If delayed | Show visual progress indicator | +| User feedback | Immediate acknowledgment of action | + +### Audio Focus Rules + +| Event | Required Action | +|-------|-----------------| +| Another app requests focus | Pause or reduce volume | +| Focus regained | Resume or restore volume | +| Playback stops | Abandon focus | + +### Audio Focus Handling + +| Focus Change | Action | +|--------------|--------| +| AUDIOFOCUS_LOSS | Stop playback | +| AUDIOFOCUS_LOSS_TRANSIENT | Pause playback | +| AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK | Reduce volume | +| AUDIOFOCUS_GAIN | Resume playback | + +### Background Playback + +| Requirement | Implementation | +|-------------|----------------| +| Continue when backgrounded | Use Foreground Service | +| Notification | MediaStyle notification required | +| Media controls | System media controls integration | +| Session | MediaSession for system integration | + +## Video + +### Picture-in-Picture (PiP) + +| Requirement | Specification | +|-------------|---------------| +| Video apps | Should support PiP | +| Aspect ratio | 16:9 to 2.39:1 | +| Auto-enter | When user navigates away during playback | + +### Video Encoding + +| Standard | Requirement | +|----------|-------------| +| Compression | HEVC (H.265) recommended | +| Fallback | H.264 for compatibility | +| Quality | Adaptive based on network | + +### Video Player Requirements + +| Feature | Implementation | +|---------|----------------| +| Fullscreen | Support landscape | +| Controls | Play, pause, seek, volume | +| Captions | Support closed captions | +| Resume | Remember playback position | + +## Notifications + +### Channel Best Practices + +| Practice | Reason | +|----------|--------| +| Multiple channels | User can control each type | +| Descriptive names | User understands purpose | +| Appropriate importance | Match user expectation | +| Don't share channels | Different content = different channel | + +### Notification Priority + +| Importance | Usage | +|------------|-------| +| HIGH | Time-sensitive (messages, calls) | +| DEFAULT | Normal notifications | +| LOW | Background info | +| MIN | Minimal interruption | + +### Notification Content Rules + +| Do | Don't | +|-----|-------| +| Relevant information | Cross-promotion | +| Clear, concise text | Advertising other products | +| Actionable content | Unnecessary interruptions | +| Set timeouts | Persistent non-ongoing notifications | + +### Messaging Apps Requirements + +| Feature | Description | +|---------|-------------| +| MessagingStyle | Use for conversation notifications | +| Direct reply | Support inline reply action | +| Conversation shortcuts | Enable direct share | +| Bubbles | Support floating conversations | + +### Notification Grouping + +Group related notifications together with a summary notification. Set appropriate group keys and summary flags. + +## Sharing + +### Android Sharesheet + +Use the system sharesheet for sharing content. Create an ACTION_SEND intent with appropriate type and extras, then use createChooser(). + +### Direct Share + +Provide conversation shortcuts for Direct Share ranking: +- Create ShortcutInfo for each conversation +- Set appropriate categories +- Push dynamic shortcuts + +## Background Services + +### Service Restrictions + +| Rule | Implementation | +|------|----------------| +| Avoid long-running services | Use WorkManager | +| No background starts (API 26+) | Use foreground service or JobScheduler | +| Battery-efficient | Batch work, respect Doze | + +### Poor Background Service Uses + +| Don't Use For | Alternative | +|---------------|-------------| +| Maintaining network connection | FCM (push notifications) | +| Persistent Bluetooth | Companion device manager | +| Keeping GPS on | Geofencing, fused location | +| Polling server | FCM or WorkManager | + +## State Management + +### State Preservation Requirements + +| Scenario | Required Behavior | +|----------|-------------------| +| App switcher return | Exact previous state | +| Device wake | Exact previous state | +| Process death | Restore critical state | +| Configuration change | Seamless transition | + +### State Categories + +| State Type | Storage | +|------------|---------| +| UI state (scroll, selection) | ViewModel + SavedState | +| User input (forms) | SavedState | +| Navigation | NavController state | +| Persistent data | Room database | + +## Navigation + +### Back Button/Gesture + +| Requirement | Implementation | +|-------------|----------------| +| System back | Navigate to previous screen | +| Gesture navigation | Support back gesture | +| No custom back buttons | Use system navigation | +| Predictable | User knows what back does | + +## Gestures + +### Gesture Navigation Support + +| Gesture | Default Action | +|---------|----------------| +| Swipe from left edge | Back | +| Swipe up from bottom | Home | +| Swipe up and hold | Recent apps | + +### Custom Gestures + +| Practice | Reason | +|----------|--------| +| Avoid edge swipes | Conflicts with navigation | +| Provide alternatives | Not all users gesture-capable | +| Test with gesture nav | Ensure no conflicts | + +Handle system gesture insets to avoid conflicts with edge gestures. + +## Functional Checklist + +### Audio +- [ ] Playback starts within 1 second +- [ ] Audio focus requested and released +- [ ] Responds to focus changes (duck/pause) +- [ ] Background playback with notification +- [ ] MediaSession integration + +### Video +- [ ] Picture-in-picture supported +- [ ] HEVC encoding used +- [ ] Playback position remembered +- [ ] Captions supported + +### Notifications +- [ ] Appropriate channels defined +- [ ] Correct importance levels +- [ ] No promotional content +- [ ] Grouped when appropriate +- [ ] Timeouts set where applicable + +### Messaging (if applicable) +- [ ] MessagingStyle used +- [ ] Direct reply supported +- [ ] Conversation shortcuts +- [ ] Bubbles supported + +### Background +- [ ] WorkManager for background work +- [ ] No long-running services +- [ ] Battery-efficient design + +### Navigation +- [ ] Standard back behavior +- [ ] Gesture navigation supported +- [ ] State preserved across lifecycle diff --git a/skills/android-native-dev/references/motion-system.md b/skills/android-native-dev/references/motion-system.md new file mode 100644 index 0000000..c0dd87e --- /dev/null +++ b/skills/android-native-dev/references/motion-system.md @@ -0,0 +1,203 @@ +# Motion System Guidelines + +Animation and transition specifications for Material Design 3. + +## Motion Principles + +### Four Core Characteristics + +| Principle | Description | +|-----------|-------------| +| **Responsive** | Quickly responds to user input at the point of interaction | +| **Natural** | Follows real-world physics (gravity, friction, momentum) | +| **Aware** | Elements are aware of surroundings and other elements | +| **Intentional** | Guides focus to the right place at the right time | + +## Duration Guidelines + +### By Interaction Type + +| Type | Duration | Usage | +|------|----------|-------| +| Micro | 50-100ms | Ripples, state changes, hover | +| Short | 100-200ms | Simple transitions, toggles | +| Medium | 200-300ms | Expanding, collapsing, revealing | +| Long | 300-500ms | Complex choreography, page transitions | + +### By Device Type + +| Device | Typical Duration | Adjustment | +|--------|------------------|------------| +| Mobile | 300ms | Baseline | +| Tablet | 390ms | +30% slower | +| Desktop | 150-200ms | Faster, more responsive | +| Wearable | 210ms | -30% faster | + +### Duration Rules + +- **Maximum**: Keep under 400ms for most transitions +- **User-initiated**: Faster (closer to instant feedback) +- **System-initiated**: Can be slightly longer +- **Loading states**: Use indeterminate indicators for unknown duration + +## Easing Curves + +### Standard Curves + +| Curve | Usage | Characteristics | +|-------|-------|-----------------| +| **Standard** | Most common transitions | Quick acceleration, slow deceleration | +| **Emphasized** | Important/significant transitions | More dramatic curve | +| **Decelerate** | Elements entering screen | Starts fast, ends slow | +| **Accelerate** | Elements leaving screen permanently | Starts slow, ends fast | +| **Sharp** | Elements temporarily leaving | Quick, snappy motion | + +### Curve Values (Cubic Bezier) + +| Curve | Value | +|-------|-------| +| Standard | cubic-bezier(0.2, 0.0, 0.0, 1.0) | +| Emphasized | cubic-bezier(0.2, 0.0, 0.0, 1.0) | +| Decelerate | cubic-bezier(0.0, 0.0, 0.0, 1.0) | +| Accelerate | cubic-bezier(0.3, 0.0, 1.0, 1.0) | + +## Movement Patterns + +### Arc Motion + +- Use natural, concave arcs for diagonal movement +- Single-axis movement (horizontal/vertical only) stays straight +- Elements entering/exiting screen move on single axis + +### Choreography + +- **Stagger**: Offset timing for related elements (20-40ms between) +- **Cascade**: Sequential reveal from a focal point +- **Shared motion**: Elements that move together maintain relationship + +## Transition Patterns + +### Container Transform + +Best for: Navigation from card/list item to detail screen + +- Origin container morphs into destination +- Maintains visual continuity +- Content fades during transformation + +### Shared Axis + +Best for: Same-level navigation (tabs, stepper) + +| Axis | Direction | Usage | +|------|-----------|-------| +| X-axis | Horizontal | Tabs, horizontal paging | +| Y-axis | Vertical | Vertical lists, feeds | +| Z-axis | Depth | Parent-child relationships | + +### Fade Through + +Best for: Unrelated screen transitions + +- Outgoing content fades out +- Incoming content fades in +- Brief overlap period +- No shared elements + +### Fade + +Best for: Show/hide single elements + +- Simple opacity change +- Optionally combine with scale +- Quick duration (100-200ms) + +## Component-Specific Motion + +### FAB + +| State | Animation | +|-------|-----------| +| Appear | Scale up + fade in | +| Disappear | Scale down + fade out | +| Transform | Morph to extended FAB | +| Press | Elevation change (3dp → 8dp) | + +### Bottom Sheet + +| State | Animation | +|-------|-----------| +| Expand | Slide up with decelerate curve | +| Collapse | Slide down with accelerate curve | +| Dismiss | Swipe down with velocity-based duration | + +### Navigation + +| Pattern | Animation | +|---------|-----------| +| Push | Incoming slides from right, outgoing shifts left | +| Pop | Incoming slides from left, outgoing shifts right | +| Modal | Slide up from bottom | + +### Cards + +| State | Animation | +|-------|-----------| +| Expand | Container transform to detail | +| Press | Subtle elevation increase | +| Reorder | Follow finger with physics | + +## Loading & Progress + +### Indeterminate Indicators + +- Use for unknown duration +- Continuous, looping animation +- M3 Expressive: Customizable waveform and thickness + +### Determinate Indicators + +- Use when progress is measurable +- Smooth, linear progression +- Update frequently for responsiveness + +### Skeleton Screens + +- Show layout structure immediately +- Subtle shimmer animation +- Replace with content as it loads + +## Accessibility Considerations + +### Reduced Motion + +- Respect prefers-reduced-motion setting +- Provide alternatives: + - Instant transitions (no animation) + - Simple fade instead of complex motion + - Static loading indicators + +### Motion Duration + +- Keep essential feedback < 100ms +- Avoid motion that could trigger vestibular issues +- Test with motion sensitivity settings enabled + +## Implementation Notes + +### Android Animation APIs + +| API | Usage | +|-----|-------| +| MotionLayout | Complex, coordinated animations | +| Transition | Activity/Fragment transitions | +| Animator | Property animations | +| AnimatedContent | Compose content transitions | +| animateContentSize | Compose size changes | + +### Performance Tips + +- Use hardware layers for complex animations +- Avoid animating layout properties (use transform) +- Profile with GPU rendering tools +- Target 60 FPS (16ms per frame) diff --git a/skills/android-native-dev/references/performance-stability.md b/skills/android-native-dev/references/performance-stability.md new file mode 100644 index 0000000..209fbc2 --- /dev/null +++ b/skills/android-native-dev/references/performance-stability.md @@ -0,0 +1,223 @@ +# Performance & Stability Guidelines + +Android Vitals thresholds, performance requirements, and stability best practices. + +## Android Vitals Thresholds + +### Core Metrics (Google Play) + +Exceeding these thresholds affects app visibility on Google Play: + +| Metric | Overall Threshold | Per Phone Model | Per Watch Model | +|--------|-------------------|-----------------|-----------------| +| User-perceived crash rate | **1.09%** | 8% | 4% | +| User-perceived ANR rate | **0.47%** | 8% | 5% | +| Excessive battery usage | 1% | - | 1% | +| Excessive wake locks | 5% | - | - | + +### Consequences of Exceeding Thresholds + +- Reduced app visibility in Google Play +- Warning label on store listing +- Lower ranking in search results +- Negative impact on user trust + +## Startup Performance + +### Requirements + +| Metric | Target | Maximum | +|--------|--------|---------| +| Cold start | < 1 second | 2 seconds | +| Warm start | < 500ms | 1 second | +| Hot start | < 100ms | 500ms | + +### If Startup Exceeds 2 Seconds + +Must provide visual feedback: +- Progress indicator +- Splash screen with animation +- Loading skeleton + +### Optimization Techniques + +| Technique | Impact | +|-----------|--------| +| Lazy initialization | Defer non-critical work | +| Async loading | Move I/O off main thread | +| View hierarchy optimization | Reduce layout depth | +| App Startup library | Initialize components efficiently | +| Baseline Profiles | Pre-compile hot paths | + +## Rendering Performance + +### Frame Rate Requirements + +| Target | Frame Time | Notes | +|--------|------------|-------| +| 60 FPS | ≤ 16.67ms | Standard requirement | +| 90 FPS | ≤ 11.11ms | High refresh rate displays | +| 120 FPS | ≤ 8.33ms | Premium devices | + +### Jank Detection + +| Metric | Threshold | Severity | +|--------|-----------|----------| +| Slow frames | > 16ms | Warning | +| Frozen frames | > 700ms | Critical | +| Jank rate | > 1% of frames | Poor experience | + +### Common Rendering Issues + +| Issue | Cause | Solution | +|-------|-------|----------| +| Overdraw | Multiple layers drawn | Reduce background stacking | +| Deep hierarchy | Complex view nesting | Use ConstraintLayout, Compose | +| Main thread work | Blocking operations | Move to background thread | +| Large bitmaps | Unoptimized images | Downsample, use vector | + +## ANR Prevention + +### ANR Triggers + +| Scenario | Timeout | +|----------|---------| +| Input dispatch | 5 seconds | +| Broadcast receiver | 10 seconds | +| Service start | 20 seconds | + +### Prevention Strategies + +- Never perform network calls on main thread +- Never perform database operations on main thread +- Never perform file I/O on main thread +- Use coroutines, RxJava, or other async mechanisms +- Reduce synchronized block contention + +### Common ANR Causes + +| Cause | Solution | +|-------|----------| +| Network on main thread | Use coroutines/RxJava | +| Database on main thread | Use Room with suspend | +| File I/O on main thread | Use Dispatchers.IO | +| Lock contention | Reduce synchronized blocks | +| Dead locks | Careful threading design | + +## Battery Optimization + +### Wake Lock Guidelines + +| Rule | Implementation | +|------|----------------| +| Minimize duration | Release as soon as possible | +| Use appropriate type | PARTIAL_WAKE_LOCK only when needed | +| Always release | Use try-finally or lifecycle | +| Prefer WorkManager | System-managed scheduling | + +### Background Restrictions + +| Feature | Best Practice | +|---------|---------------| +| Background services | Use WorkManager instead | +| Location | Request only when necessary | +| Network | Batch requests, respect connectivity | +| Alarms | Use inexact alarms when possible | + +### Doze and App Standby + +| Mode | Behavior | Adaptation | +|------|----------|------------| +| Doze | Limited network, alarms delayed | Use FCM for high-priority | +| App Standby | Background work restricted | Use expedited WorkManager | +| Buckets | Frequency limits by usage | Design for infrequent execution | + +## Memory Management + +### Memory Best Practices + +| Practice | Benefit | +|----------|---------| +| Avoid memory leaks | Prevent OutOfMemoryError | +| Use weak references | Allow garbage collection | +| Recycle bitmaps | Reduce memory pressure | +| Monitor heap | Profile regularly | + +### Common Memory Issues + +| Issue | Detection | Solution | +|-------|-----------|----------| +| Activity leak | LeakCanary | Fix lifecycle references | +| Bitmap leak | Memory profiler | Recycle, use Glide/Coil | +| Context leak | Static analysis | Use application context | +| Handler leak | Lint warning | Use WeakReference | + +## StrictMode + +### What StrictMode Detects + +| Category | Issues | +|----------|--------| +| Thread | Disk reads/writes, network, slow calls | +| VM | Leaked objects, unsafe intents, content URI exposure | + +Enable StrictMode in debug builds to detect violations during development. + +## SDK Requirements + +### Version Requirements + +| Property | Requirement | +|----------|-------------| +| targetSdk | Latest Android SDK (Google Play requirement) | +| compileSdk | Latest Android SDK | +| minSdk | Based on target audience | + +### Third-Party SDK Management + +| Practice | Reason | +|----------|--------| +| Keep updated | Security fixes, compatibility | +| Audit regularly | Remove unused dependencies | +| Monitor crashes | SDKs can cause issues | +| Check permissions | SDKs may request excessive permissions | + +### Non-SDK Interface Restrictions + +- Don't use reflection for hidden APIs +- Use Android Studio lint to detect +- APIs may break in future versions + +## Monitoring and Profiling + +### Tools + +| Tool | Purpose | +|------|---------| +| Android Studio Profiler | CPU, memory, network, energy | +| Android Vitals (Play Console) | Production crash/ANR data | +| Firebase Performance | Real-time performance monitoring | +| Perfetto | Advanced system tracing | +| Benchmark library | Reproducible measurements | + +### Key Metrics to Track + +| Metric | Tool | +|--------|------| +| Startup time | Macrobenchmark | +| Frame timing | JankStats | +| Memory usage | Memory Profiler | +| Network latency | Network Profiler | +| Battery drain | Energy Profiler | + +## Performance Checklist + +- [ ] Cold startup < 2 seconds +- [ ] Rendering at 60 FPS +- [ ] No StrictMode violations +- [ ] Crash rate < 1.09% +- [ ] ANR rate < 0.47% +- [ ] No memory leaks +- [ ] Background work uses WorkManager +- [ ] Wake locks properly released +- [ ] SDKs up to date diff --git a/skills/android-native-dev/references/privacy-security.md b/skills/android-native-dev/references/privacy-security.md new file mode 100644 index 0000000..807739e --- /dev/null +++ b/skills/android-native-dev/references/privacy-security.md @@ -0,0 +1,244 @@ +# Privacy & Security Guidelines + +Security best practices and privacy requirements for Android applications. + +## Permissions + +### Principle of Least Privilege + +| Rule | Implementation | +|------|----------------| +| Request minimum | Only permissions essential for core features | +| Request when needed | At point of use, not app startup | +| Explain why | Show rationale before system dialog | +| Degrade gracefully | App works (limited) if denied | + +### Permission Request Flow + +1. Check if already granted +2. If not, show educational UI (rationale) +3. Request permission +4. Handle result (grant or denial) +5. If denied, offer alternative or reduced functionality + +### Sensitive Permissions + +| Permission | Consideration | +|------------|---------------| +| Location | Use coarse if fine not needed | +| Camera | Request only when capturing | +| Microphone | Request only when recording | +| Contacts | Consider contact picker intent | +| Storage | Use scoped storage | +| SMS/Call Log | Restricted, needs approval | + +### Alternative Approaches + +| Instead of... | Consider... | +|---------------|-------------| +| READ_CONTACTS | Contact picker intent | +| ACCESS_FINE_LOCATION | Coarse location | +| READ_EXTERNAL_STORAGE | Storage Access Framework | +| CAMERA | Camera intent | + +## Data Storage + +### Storage Types + +| Type | Security | Usage | +|------|----------|-------| +| Internal storage | Private to app | Sensitive data | +| External storage | World-readable | Shared files only | +| SharedPreferences | Private, unencrypted | Non-sensitive settings | +| EncryptedSharedPreferences | Private, encrypted | Sensitive settings | +| Room database | Private, optional encryption | Structured data | + +### Sensitive Data Rules + +| Rule | Implementation | +|------|----------------| +| Store internally | Use internal storage, not external | +| Encrypt at rest | Use EncryptedSharedPreferences, SQLCipher | +| Don't log | Never log PII or credentials | +| Clear on logout | Wipe user data completely | + +### Data Logging + +Never log sensitive data such as passwords, emails, tokens, or personal information. Only log non-sensitive operational information. + +## Network Security + +### HTTPS Requirements + +- All network traffic must use SSL/TLS +- Configure Network Security Config +- Don't allow cleartext traffic + +### Network Security Config + +Define a network security configuration that: +- Disables cleartext traffic +- Specifies trusted certificate authorities +- Optionally implements certificate pinning for high-security apps + +### Certificate Pinning (Optional) + +For high-security apps, pin certificates to prevent MITM attacks. Include backup pins and plan for certificate rotation. + +## User Identity + +### Credential Manager + +Integrate Credential Manager for unified sign-in supporting: +- Passkeys +- Federated identity +- Traditional passwords + +### Biometric Authentication + +Use biometric authentication for sensitive operations like: +- Financial transactions +- Accessing sensitive documents +- Confirming identity + +### Autofill Support + +Provide autofill hints on input fields: +- emailAddress, username for identity fields +- password for credential fields +- creditCardNumber, postalCode for payment fields + +## App Components Security + +### Exported Components + +| Component | Exported Rule | +|-----------|---------------| +| Launcher Activity | exported="true" with intent-filter | +| Internal Activity | exported="false" | +| Internal Service | exported="false" | +| Content Provider (shared) | exported="true" with permissions | + +Always explicitly set the exported attribute on all components. + +### Custom Permissions + +Use signature-level protection for custom permissions that control access between your own apps. + +### Intent Validation + +- Validate all intent data before use +- Check URI scheme and host +- Use explicit intents when possible +- Don't trust extras from unknown sources + +### PendingIntent Security + +Use FLAG_IMMUTABLE for PendingIntents unless mutability is required. This prevents other apps from modifying the intent. + +## WebView Security + +### Safe WebView Configuration + +| Setting | Recommendation | +|---------|----------------| +| JavaScript | Disabled unless required | +| File access | Disabled | +| Content access | Disabled | +| Universal file access | Never enable | + +### Avoid Dangerous Practices + +| Don't | Why | +|-------|-----| +| setAllowUniversalAccessFromFileURLs(true) | Security vulnerability | +| addJavascriptInterface() with untrusted content | Code injection risk | +| Load untrusted URLs | XSS, phishing | + +## Cryptography + +### Use Platform APIs + +- Use Android Keystore for key storage +- Use standard algorithms (AES-GCM, RSA) +- Never implement custom cryptography +- Use SecureRandom for random generation + +### Avoid + +- Custom encryption implementations +- Weak algorithms (MD5, SHA1 for security) +- Hardcoded keys or secrets +- Non-cryptographic random generators + +## Code Security + +### No Dynamic Code Loading + +| Don't | Do Instead | +|-------|------------| +| Load code at runtime | Android App Bundles | +| Download DEX files | Play Feature Delivery | +| Execute scripts | Predefined functionality | + +### Debug Code Removal + +- Set debuggable=false in release builds +- Enable minification (R8/ProGuard) +- Remove debug libraries from production + +## Device Identifiers + +### Don't Use Hardware IDs + +| Identifier | Status | +|------------|--------| +| IMEI | Don't use | +| MAC address | Don't use | +| Serial number | Don't use | +| Android ID | Limited use only | + +### Recommended Alternatives + +| Use Case | Solution | +|----------|----------| +| Analytics | Firebase Analytics ID | +| Advertising | Advertising ID (resettable) | +| App instance | Generate UUID on install | +| User identity | Account-based ID | + +## Google Play Policies + +### Data Safety + +- Declare all data collected +- Explain data usage +- Provide privacy policy +- Allow data deletion requests + +### User Data Policy + +| Rule | Requirement | +|------|-------------| +| Transparency | Clear disclosure of data use | +| Security | Protect user data appropriately | +| Minimization | Collect only what's needed | +| Control | Allow users to manage data | + +## Security Checklist + +- [ ] Permissions requested only when needed +- [ ] Permissions explained to user +- [ ] Sensitive data stored internally +- [ ] No sensitive data in logs +- [ ] All network traffic over HTTPS +- [ ] Network security config defined +- [ ] Components export status explicit +- [ ] Custom permissions use signature protection +- [ ] Intents validated before use +- [ ] PendingIntents use FLAG_IMMUTABLE +- [ ] WebView configured securely +- [ ] Platform crypto APIs used +- [ ] No debug code in production +- [ ] No hardware IDs used +- [ ] Privacy policy available diff --git a/skills/android-native-dev/references/visual-design.md b/skills/android-native-dev/references/visual-design.md new file mode 100644 index 0000000..b1599e1 --- /dev/null +++ b/skills/android-native-dev/references/visual-design.md @@ -0,0 +1,246 @@ +# Visual Design Guidelines + +Detailed specifications for colors, typography, spacing, elevation, and shapes in Material Design 3. + +## Color System + +### Color Roles (Tokens) + +Material Design 3 uses a token-based color system with three accent groups: + +| Role | Usage | +|------|-------| +| **Primary** | Key components, FAB, prominent buttons | +| **Secondary** | Less prominent components, filters, chips | +| **Tertiary** | Accent, complementary elements | +| **Error** | Error states, destructive actions | +| **Surface** | Backgrounds, cards, dialogs | + +Each role includes variants: base color, onColor, container, onContainer. + +### Color Contrast Requirements + +| Element | Minimum Contrast Ratio | Notes | +|---------|----------------------|-------| +| Body text | **4.5:1** | WCAG AA compliance | +| Large text (18sp+) | **3:1** | 14sp bold also qualifies | +| UI components | **3:1** | Icons, borders, controls | +| Focus indicators | **3:1** | Must be clearly visible | + +### Recommended Color Palettes + +#### Modern Professional (Business Apps) + +| Role | Color | Name | +|------|-------|------| +| Primary | #1976D2 | Blue 700 | +| Secondary | #455A64 | Blue Grey 700 | +| Tertiary | #00897B | Teal 600 | +| Background | #FAFAFA | Grey 50 | + +#### Vibrant & Playful (Consumer Apps) + +| Role | Color | Name | +|------|-------|------| +| Primary | #6200EE | Deep Purple | +| Secondary | #03DAC6 | Teal Accent | +| Tertiary | #FF5722 | Deep Orange | +| Background | #FFFFFF | White | + +#### Dark & Elegant (Premium Apps) + +| Role | Color | Name | +|------|-------|------| +| Primary | #BB86FC | Purple 200 | +| Secondary | #03DAC6 | Teal 200 | +| Tertiary | #CF6679 | Red 200 | +| Background | #121212 | Dark surface | + +#### Nature & Wellness (Health Apps) + +| Role | Color | Name | +|------|-------|------| +| Primary | #4CAF50 | Green 500 | +| Secondary | #8BC34A | Light Green 500 | +| Tertiary | #FFEB3B | Yellow 500 | +| Background | #F1F8E9 | Light Green 50 | + +#### Finance & Trust (Banking Apps) + +| Role | Color | Name | +|------|-------|------| +| Primary | #00695C | Teal 800 | +| Secondary | #37474F | Blue Grey 800 | +| Tertiary | #FFC107 | Amber 500 | +| Background | #ECEFF1 | Blue Grey 50 | + +### Dark Theme Requirements + +- Background: #121212 or darker +- Surface colors use elevation-based tonal overlay +- Primary colors should be lighter variants (200-300 range) +- Maintain contrast ratios in dark mode +- Test all states (hover, focus, pressed) in dark mode + +## Typography System + +### Type Scale + +| Style | Size | Weight | Line Height | Usage | +|-------|------|--------|-------------|-------| +| Display Large | 57sp | 400 | 64sp | Hero text | +| Display Medium | 45sp | 400 | 52sp | Large headers | +| Display Small | 36sp | 400 | 44sp | Section headers | +| Headline Large | 32sp | 400 | 40sp | Screen titles | +| Headline Medium | 28sp | 400 | 36sp | Subsection titles | +| Headline Small | 24sp | 400 | 32sp | Card titles | +| Title Large | 22sp | 400 | 28sp | App bar titles | +| Title Medium | 16sp | 500 | 24sp | List item titles | +| Title Small | 14sp | 500 | 20sp | Tabs | +| Body Large | 16sp | 400 | 24sp | Primary body text | +| Body Medium | 14sp | 400 | 20sp | Secondary body text | +| Body Small | 12sp | 400 | 16sp | Captions | +| Label Large | 14sp | 500 | 20sp | Button text | +| Label Medium | 12sp | 500 | 16sp | Navigation labels | +| Label Small | 11sp | 500 | 16sp | Badges | + +### Recommended Fonts + +| Category | Fonts | +|----------|-------| +| Primary | Roboto (system default) | +| Display | Roboto Serif, Google Sans | +| Monospace | Roboto Mono, JetBrains Mono | + +### Text Readability + +- **Line length**: 45-75 characters per line (including spaces) +- **Paragraph spacing**: 1.5x line height between paragraphs +- **Letter spacing**: Use default unless brand requires adjustment +- **Text alignment**: Left-aligned for body text (LTR languages) + +## Spacing & Layout + +### 8dp Grid System + +All spacing values should be multiples of 8dp (with 4dp for fine adjustments). + +| Token | Value | Usage | +|-------|-------|-------| +| xs | 4dp | Icon padding, fine adjustments | +| sm | 8dp | Tight spacing, inline elements | +| md | 16dp | Default padding, card content | +| lg | 24dp | Section spacing | +| xl | 32dp | Large gaps, group separation | +| xxl | 48dp | Screen margins, major sections | + +### Component Dimensions + +| Component | Height | Min Width | Notes | +|-----------|--------|-----------|-------| +| Button | 40dp | 64dp | Touch target 48dp | +| FAB | 56dp | 56dp | Standard size | +| Mini FAB | 40dp | 40dp | Secondary actions | +| Extended FAB | 56dp | 80dp | With text label | +| Text Field | 56dp | 280dp | Including label | +| App Bar | 64dp | - | Top app bar | +| Bottom Nav | 80dp | - | With labels | +| Nav Rail | - | 80dp | Tablet/desktop | +| List Item | 56-88dp | - | Depends on content | +| Chip | 32dp | - | Filter/action chips | + +### Touch Targets + +| Type | Size | Notes | +|------|------|-------| +| Minimum | 48 × 48dp | WCAG requirement | +| Recommended | 56 × 56dp | Primary actions | +| Kids apps | 56dp+ | Larger for motor skills | +| Spacing | 8dp minimum | Between adjacent targets | + +## Elevation & Shadows + +### Elevation Levels + +| Level | Elevation | Usage | +|-------|-----------|-------| +| Level 0 | 0dp | Flat surfaces | +| Level 1 | 1dp | Cards, elevated buttons | +| Level 2 | 3dp | FAB (resting), raised elements | +| Level 3 | 6dp | Navigation drawer, bottom sheet | +| Level 4 | 8dp | FAB (pressed), menus | +| Level 5 | 12dp | Dialogs, modal surfaces | + +### Shadow Guidelines + +- Use elevation consistently for same component types +- Higher elevation = more important/prominent +- In dark theme, use surface tint instead of shadows +- Avoid excessive elevation (keeps UI grounded) + +## Shape System + +### Corner Radius + +| Size | Radius | Usage | +|------|--------|-------| +| None | 0dp | Sharp edges, dividers | +| Extra Small | 4dp | Badges, small chips | +| Small | 8dp | Buttons, chips, small cards | +| Medium | 12dp | Cards, dialogs, text fields | +| Large | 16dp | FAB, bottom sheets | +| Extra Large | 28dp | Large sheets, expanded cards | +| Full | 50% | Pills, avatars, circular buttons | + +### M3 Expressive Shapes + +Material 3 Expressive introduces 35 new decorative shapes: +- Organic curves +- Asymmetric corners +- Cut corners +- Scalloped edges + +Use sparingly for brand differentiation and visual interest. + +### Shape Consistency Rules + +- Same component type = same shape +- Related components should share shape family +- Don't mix too many shape styles on one screen +- Consider shape in dark/light theme transitions + +## Icons + +### Size Specifications + +| Size | Dimensions | Usage | +|------|------------|-------| +| Small | 20 × 20dp | Compact UI, inline | +| Standard | 24 × 24dp | Default for most uses | +| Large | 40 × 40dp | Emphasis, empty states | + +### Icon Guidelines + +- **Touch target**: Always wrap in 48dp minimum clickable area +- **Style**: Outlined (default), Filled (selected/active states) +- **Stroke width**: 2dp for outlined icons +- **Optical alignment**: May need visual adjustments +- **Color**: Use semantic colors (primary, error, etc.) + +### Recommended Icon Sets + +| Set | Usage | +|-----|-------| +| Material Symbols | Recommended, variable font support | +| Material Icons | Legacy, still widely used | + +### Adaptive Icons (App Icon) + +| Property | Value | +|----------|-------| +| Canvas size | 108 × 108dp | +| Safe zone | 66 × 66dp (centered circle) | +| Logo size | 48-66dp | +| Max display | 72 × 72dp | +| Layers | Foreground + Background (both 108dp) | +| Android 13+ | Include monochrome layer for theming | diff --git a/skills/frontend-dev/SKILL.md b/skills/frontend-dev/SKILL.md new file mode 100644 index 0000000..8856972 --- /dev/null +++ b/skills/frontend-dev/SKILL.md @@ -0,0 +1,567 @@ +--- +name: frontend-dev +description: | + Full-stack frontend development combining premium UI design, cinematic animations, + AI-generated media assets, persuasive copywriting, and visual art. Builds complete, + visually striking web pages with real media, advanced motion, and compelling copy. + Use when: building landing pages, marketing sites, product pages, dashboards, + generating media assets (image/video/audio/music), writing conversion copy, + creating generative art, or implementing cinematic scroll animations. +license: MIT +metadata: + version: "1.0.0" + category: frontend + sources: + - Framer Motion documentation + - GSAP / GreenSock documentation + - Three.js documentation + - Tailwind CSS documentation + - React / Next.js documentation + - AIDA Framework (Elmo Lewis) + - p5.js documentation +--- + +# Frontend Studio + +Build complete, production-ready frontend pages by orchestrating 5 specialized capabilities: design engineering, motion systems, AI-generated assets, persuasive copy, and generative art. + +## Invocation + +``` +/frontend-dev +``` + +The user provides their request as natural language (e.g. "build a landing page for a music streaming app"). + +## Skill Structure + +``` +frontend-dev/ +├── SKILL.md # Core skill (this file) +├── scripts/ # Asset generation scripts +│ ├── minimax_tts.py # Text-to-speech +│ ├── minimax_music.py # Music generation +│ ├── minimax_video.py # Video generation (async) +│ └── minimax_image.py # Image generation +├── references/ # Detailed guides (read as needed) +│ ├── minimax-cli-reference.md # CLI flags quick reference +│ ├── asset-prompt-guide.md # Asset prompt engineering rules +│ ├── minimax-tts-guide.md # TTS usage & voices +│ ├── minimax-music-guide.md # Music prompts & lyrics format +│ ├── minimax-video-guide.md # Camera commands & models +│ ├── minimax-image-guide.md # Ratios & batch generation +│ ├── minimax-voice-catalog.md # All voice IDs +│ ├── motion-recipes.md # Animation code snippets +│ ├── env-setup.md # Environment setup +│ └── troubleshooting.md # Common issues +├── templates/ # Visual art templates +│ ├── viewer.html # p5.js interactive art base +│ └── generator_template.js # p5.js code reference +└── canvas-fonts/ # Static art fonts (TTF + licenses) +``` + +## Project Structure + +### Assets (Universal) + +All frameworks use the same asset organization: + +``` +assets/ +├── images/ +│ ├── hero-landing-1710xxx.webp +│ ├── icon-feature-01.webp +│ └── bg-pattern.svg +├── videos/ +│ ├── hero-bg-1710xxx.mp4 +│ └── demo-preview.mp4 +└── audio/ + ├── bgm-ambient-1710xxx.mp3 + └── tts-intro-1710xxx.mp3 +``` + +**Asset naming:** `{type}-{descriptor}-{timestamp}.{ext}` + +### By Framework + +| Framework | Asset Location | Component Location | +|-----------|---------------|-------------------| +| **Pure HTML** | `./assets/` | N/A (inline or `./js/`) | +| **React/Next.js** | `public/assets/` | `src/components/` | +| **Vue/Nuxt** | `public/assets/` | `src/components/` | +| **Svelte/SvelteKit** | `static/assets/` | `src/lib/components/` | +| **Astro** | `public/assets/` | `src/components/` | + +### Pure HTML + +``` +project/ +├── index.html +├── assets/ +│ ├── images/ +│ ├── videos/ +│ └── audio/ +├── css/ +│ └── styles.css +└── js/ + └── main.js # Animations (GSAP/vanilla) +``` + +### React / Next.js + +``` +project/ +├── public/assets/ # Static assets +├── src/ +│ ├── components/ +│ │ ├── ui/ # Button, Card, Input +│ │ ├── sections/ # Hero, Features, CTA +│ │ └── motion/ # RevealSection, StaggerGrid +│ ├── lib/ +│ ├── styles/ +│ └── app/ # Pages +└── package.json +``` + +### Vue / Nuxt + +``` +project/ +├── public/assets/ +├── src/ # or root for Nuxt +│ ├── components/ +│ │ ├── ui/ +│ │ ├── sections/ +│ │ └── motion/ +│ ├── composables/ # Shared logic +│ ├── pages/ +│ └── assets/ # Processed assets (optional) +└── package.json +``` + +### Astro + +``` +project/ +├── public/assets/ +├── src/ +│ ├── components/ # .astro, .tsx, .vue, .svelte +│ ├── layouts/ +│ ├── pages/ +│ └── styles/ +└── package.json +``` + +**Component naming:** PascalCase (`HeroSection.tsx`, `HeroSection.vue`, `HeroSection.astro`) + +--- + +## Compliance + +**All rules in this skill are mandatory. Violating any rule is a blocking error — fix before proceeding or delivering.** + +--- + +## Workflow +### Phase 1: Design Architecture +1. Analyze the request — determine page type and context +2. Set design dials based on page type +3. Plan layout sections and identify asset needs + +### Phase 2: Motion Architecture +1. Select animation tools per section (see Tool Selection Matrix) +2. Plan motion sequences following performance guardrails + +### Phase 3: Asset Generation +Generate all image/video/audio assets using `scripts/`. NEVER use placeholder URLs (unsplash, picsum, placeholder.com, via.placeholder, placehold.co, etc.) or external URLs. + +1. Parse asset requirements (type, style, spec, usage) +2. Craft optimized prompts, show to user, confirm before generating +3. Execute via scripts, save to project — do NOT proceed to Phase 5 until all assets are saved locally + +### Phase 4: Copywriting & Content +Follow copywriting frameworks (AIDA, PAS, FAB) to craft all text content. Do NOT use "Lorem ipsum" — write real copy. + +### Phase 5: Build UI +Scaffold the project and build each section following Design and Motion rules. Integrate generated assets and copy. All ``, ``, ``, and CSS `background-image` MUST reference local assets from Phase 3. + +### Phase 6: Quality Gates +Run final checklist (see Quality Gates section). + +--- + +# 1. Design Engineering + +## 1.1 Baseline Configuration + +| Dial | Default | Range | +|------|---------|-------| +| DESIGN_VARIANCE | 8 | 1=Symmetry, 10=Asymmetric | +| MOTION_INTENSITY | 6 | 1=Static, 10=Cinematic | +| VISUAL_DENSITY | 4 | 1=Airy, 10=Packed | + +Adapt dynamically based on user requests. + +## 1.2 Architecture Conventions +- **DEPENDENCY VERIFICATION:** Check `package.json` before importing any library. Output install command if missing. +- **Framework:** React/Next.js. Default to Server Components. Interactive components must be isolated `"use client"` leaf components. +- **Styling:** Tailwind CSS. Check version in `package.json` — NEVER mix v3/v4 syntax. +- **ANTI-EMOJI POLICY:** NEVER use emojis anywhere. Use Phosphor or Radix icons only. +- **Viewport:** Use `min-h-[100dvh]` not `h-screen`. Use CSS Grid not flex percentage math. +- **Layout:** `max-w-[1400px] mx-auto` or `max-w-7xl`. + +## 1.3 Design Rules +| Rule | Directive | +|------|-----------| +| Typography | Headlines: `text-4xl md:text-6xl tracking-tighter`. Body: `text-base leading-relaxed max-w-[65ch]`. **NEVER** use Inter — use Geist/Outfit/Satoshi. **NEVER** use Serif on dashboards. | +| Color | Max 1 accent, saturation < 80%. **NEVER** use AI purple/blue. Stick to one palette. | +| Layout | **NEVER** use centered heroes when VARIANCE > 4. Force split-screen or asymmetric layouts. | +| Cards | **NEVER** use generic cards when DENSITY > 7. Use `border-t`, `divide-y`, or spacing. | +| States | **ALWAYS** implement: Loading (skeleton), Empty, Error, Tactile feedback (`scale-[0.98]`). | +| Forms | Label above input. Error below. `gap-2` for input blocks. | + +## 1.4 Anti-Slop Techniques + +- **Liquid Glass:** `backdrop-blur` + `border-white/10` + `shadow-[inset_0_1px_0_rgba(255,255,255,0.1)]` +- **Magnetic Buttons:** Use `useMotionValue`/`useTransform` — never `useState` for continuous animations +- **Perpetual Motion:** When INTENSITY > 5, add infinite micro-animations (Pulse, Float, Shimmer) +- **Layout Transitions:** Use Framer `layout` and `layoutId` props +- **Stagger:** Use `staggerChildren` or CSS `animation-delay: calc(var(--index) * 100ms)` + +## 1.5 Forbidden Patterns +| Category | Banned | +|----------|--------| +| Visual | Neon glows, pure black (#000), oversaturated accents, gradient text on headers, custom cursors | +| Typography | Inter font, oversized H1s, Serif on dashboards | +| Layout | 3-column equal card rows, floating elements with awkward gaps | +| Components | Default shadcn/ui without customization | + +## 1.6 Creative Arsenal + +| Category | Patterns | +|----------|----------| +| Navigation | Dock magnification, Magnetic button, Gooey menu, Dynamic island, Radial menu, Speed dial, Mega menu | +| Layout | Bento grid, Masonry, Chroma grid, Split-screen scroll, Curtain reveal | +| Cards | Parallax tilt, Spotlight border, Glassmorphism, Holographic foil, Swipe stack, Morphing modal | +| Scroll | Sticky stack, Horizontal hijack, Locomotive sequence, Zoom parallax, Progress path, Liquid swipe | +| Gallery | Dome gallery, Coverflow, Drag-to-pan, Accordion slider, Hover trail, Glitch effect | +| Text | Kinetic marquee, Text mask reveal, Scramble effect, Circular path, Gradient stroke, Kinetic grid | +| Micro | Particle explosion, Pull-to-refresh, Skeleton shimmer, Directional hover, Ripple click, SVG draw, Mesh gradient, Lens blur | + +## 1.7 Bento Paradigm + +- **Palette:** Background `#f9fafb`, cards pure white with `border-slate-200/50` +- **Surfaces:** `rounded-[2.5rem]`, diffusion shadow +- **Typography:** Geist/Satoshi, `tracking-tight` headers +- **Labels:** Outside and below cards +- **Animation:** Spring physics (`stiffness: 100, damping: 20`), infinite loops, `React.memo` isolation + +**5-Card Archetypes:** +1. Intelligent List — auto-sorting with `layoutId` +2. Command Input — typewriter + blinking cursor +3. Live Status — breathing indicators +4. Wide Data Stream — infinite horizontal carousel +5. Contextual UI — staggered highlight + float-in toolbar + +## 1.8 Brand Override + +When brand styling is active: +- Dark: `#141413`, Light: `#faf9f5`, Mid: `#b0aea5`, Subtle: `#e8e6dc` +- Accents: Orange `#d97757`, Blue `#6a9bcc`, Green `#788c5d` +- Fonts: Poppins (headings), Lora (body) + +--- + +# 2. Motion Engine + +## 2.1 Tool Selection Matrix + +| Need | Tool | +|------|------| +| UI enter/exit/layout | **Framer Motion** — `AnimatePresence`, `layoutId`, springs | +| Scroll storytelling (pin, scrub) | **GSAP + ScrollTrigger** — frame-accurate control | +| Looping icons | **Lottie** — lazy-load (~50KB) | +| 3D/WebGL | **Three.js / R3F** — isolated ``, own `"use client"` boundary | +| Hover/focus states | **CSS only** — zero JS cost | +| Native scroll-driven | **CSS** — `animation-timeline: scroll()` | + +**Conflict Rules [MANDATORY]:** +- NEVER mix GSAP + Framer Motion in same component +- R3F MUST live in isolated Canvas wrapper +- ALWAYS lazy-load Lottie, GSAP, Three.js + +## 2.2 Intensity Scale + +| Level | Techniques | +|-------|------------| +| 1-2 Subtle | CSS transitions only, 150-300ms | +| 3-4 Smooth | CSS keyframes + Framer animate, stagger ≤3 items | +| 5-6 Fluid | `whileInView`, magnetic hover, parallax tilt | +| 7-8 Cinematic | GSAP ScrollTrigger, pinned sections, horizontal hijack | +| 9-10 Immersive | Full scroll sequences, Three.js particles, WebGL shaders | + +## 2.3 Animation Recipes + +See `references/motion-recipes.md` for full code. Summary: + +| Recipe | Tool | Use For | +|--------|------|---------| +| Scroll Reveal | Framer | Fade+slide on viewport entry | +| Stagger Grid | Framer | Sequential list animations | +| Pinned Timeline | GSAP | Horizontal scroll with pinning | +| Tilt Card | Framer | Mouse-tracking 3D perspective | +| Magnetic Button | Framer | Cursor-attracted buttons | +| Text Scramble | Vanilla | Matrix-style decode effect | +| SVG Path Draw | CSS | Scroll-linked path animation | +| Horizontal Scroll | GSAP | Vertical-to-horizontal hijack | +| Particle Background | R3F | Decorative WebGL particles | +| Layout Morph | Framer | Card-to-modal expansion | + +## 2.4 Performance Rules +**GPU-only properties (ONLY animate these):** `transform`, `opacity`, `filter`, `clip-path` + +**NEVER animate:** `width`, `height`, `top`, `left`, `margin`, `padding`, `font-size` — if you need these effects, use `transform: scale()` or `clip-path` instead. + +**Isolation:** +- Perpetual animations MUST be in `React.memo` leaf components +- `will-change: transform` ONLY during animation +- `contain: layout style paint` on heavy containers + +**Mobile:** +- ALWAYS respect `prefers-reduced-motion` +- ALWAYS disable parallax/3D on `pointer: coarse` +- Cap particles: desktop 800, tablet 300, mobile 100 +- Disable GSAP pin on mobile < 768px + +**Cleanup:** Every `useEffect` with GSAP/observers MUST `return () => ctx.revert()` + +## 2.5 Springs & Easings + +| Feel | Framer Config | +|------|---------------| +| Snappy | `stiffness: 300, damping: 30` | +| Smooth | `stiffness: 150, damping: 20` | +| Bouncy | `stiffness: 100, damping: 10` | +| Heavy | `stiffness: 60, damping: 20` | + +| CSS Easing | Value | +|------------|-------| +| Smooth decel | `cubic-bezier(0.16, 1, 0.3, 1)` | +| Smooth accel | `cubic-bezier(0.7, 0, 0.84, 0)` | +| Elastic | `cubic-bezier(0.34, 1.56, 0.64, 1)` | + +## 2.6 Accessibility +- ALWAYS wrap motion in `prefers-reduced-motion` check +- NEVER flash content > 3 times/second (seizure risk) +- ALWAYS provide visible focus rings (use `outline` not `box-shadow`) +- ALWAYS add `aria-live="polite"` for dynamically revealed content +- ALWAYS include pause button for auto-playing animations + +## 2.7 Dependencies + +```bash +npm install framer-motion # UI (keep at top level) +npm install gsap # Scroll (lazy-load) +npm install lottie-react # Icons (lazy-load) +npm install three @react-three/fiber @react-three/drei # 3D (lazy-load) +``` + +--- + +# 3. Asset Generation + +## 3.1 Scripts + +| Type | Script | Pattern | +|------|--------|---------| +| TTS | `scripts/minimax_tts.py` | Sync | +| Music | `scripts/minimax_music.py` | Sync | +| Video | `scripts/minimax_video.py` | Async (create → poll → download) | +| Image | `scripts/minimax_image.py` | Sync | + +Env: `MINIMAX_API_KEY` (required). + +## 3.2 Workflow +1. **Parse:** type, quantity, style, spec, usage +2. **Craft prompt:** Be specific (composition, lighting, style). **NEVER** include text in image prompts. +3. **Execute:** Show prompt to user, **MUST confirm before generating**, then run script +4. **Save:** `/public/assets/{images,videos,audio}/` as `{type}-{descriptor}-{timestamp}.{ext}` — **MUST save locally** +5. **Post-process:** Images → WebP, Videos → ffmpeg compress, Audio → normalize +6. **Deliver:** File path + code snippet + CSS suggestion + +## 3.3 Preset Shortcuts + +| Shortcut | Spec | +|----------|------| +| `hero` | 16:9, cinematic, text-safe | +| `thumb` | 1:1, centered subject | +| `icon` | 1:1, flat, clean background | +| `avatar` | 1:1, portrait, circular crop ready | +| `banner` | 21:9, OG/social | +| `bg-video` | 768P, 6s, `[Static shot]` | +| `video-hd` | 1080P, 6s | +| `bgm` | 30s, no vocals, loopable | +| `tts` | MiniMax HD, MP3 | + +## 3.4 Reference + +- `references/minimax-cli-reference.md` — CLI flags +- `references/asset-prompt-guide.md` — Prompt rules +- `references/minimax-voice-catalog.md` — Voice IDs +- `references/minimax-tts-guide.md` — TTS usage +- `references/minimax-music-guide.md` — Music generation (prompts, lyrics, structure tags) +- `references/minimax-video-guide.md` — Camera commands +- `references/minimax-image-guide.md` — Ratios, batch + +--- + +# 4. Copywriting + +## 4.1 Core Job + +1. Grab attention → 2. Create desire → 3. Remove friction → 4. Prompt action + +## 4.2 Frameworks + +**AIDA** (landing pages, emails): +``` +ATTENTION: Bold headline (promise or pain) +INTEREST: Elaborate problem ("yes, that's me") +DESIRE: Show transformation +ACTION: Clear CTA +``` + +**PAS** (pain-driven products): +``` +PROBLEM: State clearly +AGITATE: Make urgent +SOLUTION: Your product +``` + +**FAB** (product differentiation): +``` +FEATURE: What it does +ADVANTAGE: Why it matters +BENEFIT: What customer gains +``` + +## 4.3 Headlines + +| Formula | Example | +|---------|---------| +| Promise | "Double open rates in 30 days" | +| Question | "Still wasting 10 hours/week?" | +| How-To | "How to automate your pipeline" | +| Number | "7 mistakes killing conversions" | +| Negative | "Stop losing leads" | +| Curiosity | "The one change that tripled bookings" | +| Transformation | "From 50 to 500 leads" | + +Be specific. Lead with outcome, not method. + +## 4.4 CTAs + +**Bad:** Submit, Click here, Learn more + +**Good:** "Start my free trial", "Get the template now", "Book my strategy call" + +**Formula:** [Action Verb] + [What They Get] + [Urgency/Ease] + +Place: above fold, after value, multiple on long pages. + +## 4.5 Emotional Triggers + +| Trigger | Example | +|---------|---------| +| FOMO | "Only 3 spots left" | +| Fear of loss | "Every day without this, you're losing $X" | +| Status | "Join 10,000+ top agencies" | +| Ease | "Set it up once. Forget forever." | +| Frustration | "Tired of tools that deliver nothing?" | +| Hope | "Yes, you CAN hit $10K MRR" | + +## 4.6 Objection Handling + +| Objection | Response | +|-----------|----------| +| Too expensive | Show ROI: "Pays for itself in 2 weeks" | +| Won't work for me | Social proof from similar customer | +| No time | "Setup takes 10 minutes" | +| What if it fails | "30-day money-back guarantee" | +| Need to think | Urgency/scarcity | + +Place in FAQ, testimonials, near CTA. + +## 4.7 Proof Types + +Testimonials (with name/title), Case studies, Data/metrics, Social proof, Certifications + +--- + +# 5. Visual Art + +Philosophy-first workflow. Two output modes. + +## 5.1 Output Modes + +| Mode | Output | When | +|------|--------|------| +| Static | PDF/PNG | Posters, print, design assets | +| Interactive | HTML (p5.js) | Generative art, explorable variations | + +## 5.2 Workflow + +### Step 1: Philosophy Creation +Name the movement (1-2 words). Articulate philosophy (4-6 paragraphs) covering: +- Static: space, form, color, scale, rhythm, hierarchy +- Interactive: computation, emergence, noise, parametric variation + +### Step 2: Conceptual Seed +Identify subtle, niche reference — sophisticated, not literal. Jazz musician quoting another song. + +### Step 3: Creation + +**Static Mode:** +- Single page, highly visual, design-forward +- Repeating patterns, perfect shapes +- Sparse typography from `canvas-fonts/` +- Nothing overlaps, proper margins +- Output: `.pdf` or `.png` + philosophy `.md` + +**Interactive Mode:** +1. Read `templates/viewer.html` first +2. Keep FIXED sections (header, sidebar, seed controls) +3. Replace VARIABLE sections (algorithm, parameters) +4. Seeded randomness: `randomSeed(seed); noiseSeed(seed);` +5. Output: single self-contained HTML + +### Step 4: Refinement +Refine, don't add. Make it crisp. Polish into masterpiece. + +--- + +# Quality Gates +**Design:** +- [ ] Mobile layout collapse (`w-full`, `px-4`) for high-variance designs +- [ ] `min-h-[100dvh]` not `h-screen` +- [ ] Empty, loading, error states provided +- [ ] Cards omitted where spacing suffices + +**Motion:** +- [ ] Correct tool per selection matrix +- [ ] No GSAP + Framer mixed in same component +- [ ] All `useEffect` have cleanup returns +- [ ] `prefers-reduced-motion` respected +- [ ] Perpetual animations in `React.memo` leaf components +- [ ] Only GPU properties animated +- [ ] Heavy libraries lazy-loaded + +**General:** +- [ ] Dependencies verified in `package.json` +- [ ] **No placeholder URLs** — grep the output for `unsplash`, `picsum`, `placeholder`, `placehold`, `via.placeholder`, `lorem.space`, `dummyimage`. If ANY found, STOP and replace with generated assets before delivering. +- [ ] **All media assets exist as local files** in the project's assets directory +- [ ] Asset prompts confirmed with user before generation + +--- + +*React and Next.js are trademarks of Meta Platforms, Inc. and Vercel, Inc., respectively. Vue.js is a trademark of Evan You. Tailwind CSS is a trademark of Tailwind Labs Inc. Svelte and SvelteKit are trademarks of their respective owners. GSAP/GreenSock is a trademark of GreenSock Inc. Three.js, Framer Motion, Lottie, Astro, and all other product names are trademarks of their respective owners.* diff --git a/skills/frontend-dev/canvas-fonts/ArsenalSC-OFL.txt b/skills/frontend-dev/canvas-fonts/ArsenalSC-OFL.txt new file mode 100644 index 0000000..1dad6ca --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/ArsenalSC-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2012 The Arsenal Project Authors (andrij.design@gmail.com) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/ArsenalSC-Regular.ttf b/skills/frontend-dev/canvas-fonts/ArsenalSC-Regular.ttf new file mode 100644 index 0000000..fe5409b Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/ArsenalSC-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/BigShoulders-Bold.ttf b/skills/frontend-dev/canvas-fonts/BigShoulders-Bold.ttf new file mode 100644 index 0000000..fc5f8fd Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/BigShoulders-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/BigShoulders-OFL.txt b/skills/frontend-dev/canvas-fonts/BigShoulders-OFL.txt new file mode 100644 index 0000000..b220280 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/BigShoulders-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2019 The Big Shoulders Project Authors (https://github.com/xotypeco/big_shoulders) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/BigShoulders-Regular.ttf b/skills/frontend-dev/canvas-fonts/BigShoulders-Regular.ttf new file mode 100644 index 0000000..de8308c Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/BigShoulders-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Boldonse-OFL.txt b/skills/frontend-dev/canvas-fonts/Boldonse-OFL.txt new file mode 100644 index 0000000..1890cb1 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/Boldonse-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2024 The Boldonse Project Authors (https://github.com/googlefonts/boldonse) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/Boldonse-Regular.ttf b/skills/frontend-dev/canvas-fonts/Boldonse-Regular.ttf new file mode 100644 index 0000000..43fa30a Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Boldonse-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/BricolageGrotesque-Bold.ttf b/skills/frontend-dev/canvas-fonts/BricolageGrotesque-Bold.ttf new file mode 100644 index 0000000..f3b1ded Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/BricolageGrotesque-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/BricolageGrotesque-OFL.txt b/skills/frontend-dev/canvas-fonts/BricolageGrotesque-OFL.txt new file mode 100644 index 0000000..fc2b216 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/BricolageGrotesque-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Bricolage Grotesque Project Authors (https://github.com/ateliertriay/bricolage) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/BricolageGrotesque-Regular.ttf b/skills/frontend-dev/canvas-fonts/BricolageGrotesque-Regular.ttf new file mode 100644 index 0000000..0674ae3 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/BricolageGrotesque-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/CrimsonPro-Bold.ttf b/skills/frontend-dev/canvas-fonts/CrimsonPro-Bold.ttf new file mode 100644 index 0000000..58730fb Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/CrimsonPro-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/CrimsonPro-Italic.ttf b/skills/frontend-dev/canvas-fonts/CrimsonPro-Italic.ttf new file mode 100644 index 0000000..786a1bd Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/CrimsonPro-Italic.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/CrimsonPro-OFL.txt b/skills/frontend-dev/canvas-fonts/CrimsonPro-OFL.txt new file mode 100644 index 0000000..f976fdc --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/CrimsonPro-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2018 The Crimson Pro Project Authors (https://github.com/Fonthausen/CrimsonPro) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/CrimsonPro-Regular.ttf b/skills/frontend-dev/canvas-fonts/CrimsonPro-Regular.ttf new file mode 100644 index 0000000..f5666b9 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/CrimsonPro-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/DMMono-OFL.txt b/skills/frontend-dev/canvas-fonts/DMMono-OFL.txt new file mode 100644 index 0000000..5b17f0c --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/DMMono-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The DM Mono Project Authors (https://www.github.com/googlefonts/dm-mono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/DMMono-Regular.ttf b/skills/frontend-dev/canvas-fonts/DMMono-Regular.ttf new file mode 100644 index 0000000..7efe813 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/DMMono-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/EricaOne-OFL.txt b/skills/frontend-dev/canvas-fonts/EricaOne-OFL.txt new file mode 100644 index 0000000..490d012 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/EricaOne-OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2011 by LatinoType Limitada (luciano@latinotype.com), +with Reserved Font Names "Erica One" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/EricaOne-Regular.ttf b/skills/frontend-dev/canvas-fonts/EricaOne-Regular.ttf new file mode 100644 index 0000000..8bd91d1 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/EricaOne-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/GeistMono-Bold.ttf b/skills/frontend-dev/canvas-fonts/GeistMono-Bold.ttf new file mode 100644 index 0000000..736ff7c Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/GeistMono-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/GeistMono-OFL.txt b/skills/frontend-dev/canvas-fonts/GeistMono-OFL.txt new file mode 100644 index 0000000..679a685 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/GeistMono-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font.git) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/GeistMono-Regular.ttf b/skills/frontend-dev/canvas-fonts/GeistMono-Regular.ttf new file mode 100644 index 0000000..1a30262 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/GeistMono-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Gloock-OFL.txt b/skills/frontend-dev/canvas-fonts/Gloock-OFL.txt new file mode 100644 index 0000000..363acd3 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/Gloock-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Gloock Project Authors (https://github.com/duartp/gloock) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/Gloock-Regular.ttf b/skills/frontend-dev/canvas-fonts/Gloock-Regular.ttf new file mode 100644 index 0000000..3e58c4e Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Gloock-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/IBMPlexMono-Bold.ttf b/skills/frontend-dev/canvas-fonts/IBMPlexMono-Bold.ttf new file mode 100644 index 0000000..247979c Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/IBMPlexMono-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/IBMPlexMono-OFL.txt b/skills/frontend-dev/canvas-fonts/IBMPlexMono-OFL.txt new file mode 100644 index 0000000..e423b74 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/IBMPlexMono-OFL.txt @@ -0,0 +1,93 @@ +Copyright © 2017 IBM Corp. with Reserved Font Name "Plex" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/IBMPlexMono-Regular.ttf b/skills/frontend-dev/canvas-fonts/IBMPlexMono-Regular.ttf new file mode 100644 index 0000000..601ae94 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/IBMPlexMono-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Bold.ttf b/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Bold.ttf new file mode 100644 index 0000000..78f6e50 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/IBMPlexSerif-BoldItalic.ttf b/skills/frontend-dev/canvas-fonts/IBMPlexSerif-BoldItalic.ttf new file mode 100644 index 0000000..369b89d Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/IBMPlexSerif-BoldItalic.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Italic.ttf b/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Italic.ttf new file mode 100644 index 0000000..a4d859a Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Italic.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Regular.ttf b/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Regular.ttf new file mode 100644 index 0000000..35f454c Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/IBMPlexSerif-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/InstrumentSans-Bold.ttf b/skills/frontend-dev/canvas-fonts/InstrumentSans-Bold.ttf new file mode 100644 index 0000000..f602dce Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/InstrumentSans-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/InstrumentSans-BoldItalic.ttf b/skills/frontend-dev/canvas-fonts/InstrumentSans-BoldItalic.ttf new file mode 100644 index 0000000..122b273 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/InstrumentSans-BoldItalic.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/InstrumentSans-Italic.ttf b/skills/frontend-dev/canvas-fonts/InstrumentSans-Italic.ttf new file mode 100644 index 0000000..4b98fb8 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/InstrumentSans-Italic.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/InstrumentSans-OFL.txt b/skills/frontend-dev/canvas-fonts/InstrumentSans-OFL.txt new file mode 100644 index 0000000..4bb9914 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/InstrumentSans-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Instrument Sans Project Authors (https://github.com/Instrument/instrument-sans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/InstrumentSans-Regular.ttf b/skills/frontend-dev/canvas-fonts/InstrumentSans-Regular.ttf new file mode 100644 index 0000000..14c6113 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/InstrumentSans-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/InstrumentSerif-Italic.ttf b/skills/frontend-dev/canvas-fonts/InstrumentSerif-Italic.ttf new file mode 100644 index 0000000..8fa958d Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/InstrumentSerif-Italic.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/InstrumentSerif-Regular.ttf b/skills/frontend-dev/canvas-fonts/InstrumentSerif-Regular.ttf new file mode 100644 index 0000000..9763031 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/InstrumentSerif-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Italiana-OFL.txt b/skills/frontend-dev/canvas-fonts/Italiana-OFL.txt new file mode 100644 index 0000000..ba8af21 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/Italiana-OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2011, Santiago Orozco (hi@typemade.mx), with Reserved Font Name "Italiana". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/Italiana-Regular.ttf b/skills/frontend-dev/canvas-fonts/Italiana-Regular.ttf new file mode 100644 index 0000000..a9b828c Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Italiana-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/JetBrainsMono-Bold.ttf b/skills/frontend-dev/canvas-fonts/JetBrainsMono-Bold.ttf new file mode 100644 index 0000000..1926c80 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/JetBrainsMono-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/JetBrainsMono-OFL.txt b/skills/frontend-dev/canvas-fonts/JetBrainsMono-OFL.txt new file mode 100644 index 0000000..5ceee00 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/JetBrainsMono-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/JetBrainsMono-Regular.ttf b/skills/frontend-dev/canvas-fonts/JetBrainsMono-Regular.ttf new file mode 100644 index 0000000..436c982 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/JetBrainsMono-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Jura-Light.ttf b/skills/frontend-dev/canvas-fonts/Jura-Light.ttf new file mode 100644 index 0000000..dffbb33 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Jura-Light.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Jura-Medium.ttf b/skills/frontend-dev/canvas-fonts/Jura-Medium.ttf new file mode 100644 index 0000000..4bf91a3 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Jura-Medium.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Jura-OFL.txt b/skills/frontend-dev/canvas-fonts/Jura-OFL.txt new file mode 100644 index 0000000..64ad4c6 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/Jura-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2019 The Jura Project Authors (https://github.com/ossobuffo/jura) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/LibreBaskerville-OFL.txt b/skills/frontend-dev/canvas-fonts/LibreBaskerville-OFL.txt new file mode 100644 index 0000000..8c531fa --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/LibreBaskerville-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2012 The Libre Baskerville Project Authors (https://github.com/impallari/Libre-Baskerville) with Reserved Font Name Libre Baskerville. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/LibreBaskerville-Regular.ttf b/skills/frontend-dev/canvas-fonts/LibreBaskerville-Regular.ttf new file mode 100644 index 0000000..c1abc26 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/LibreBaskerville-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Lora-Bold.ttf b/skills/frontend-dev/canvas-fonts/Lora-Bold.ttf new file mode 100644 index 0000000..edae21e Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Lora-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Lora-BoldItalic.ttf b/skills/frontend-dev/canvas-fonts/Lora-BoldItalic.ttf new file mode 100644 index 0000000..12dea8c Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Lora-BoldItalic.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Lora-Italic.ttf b/skills/frontend-dev/canvas-fonts/Lora-Italic.ttf new file mode 100644 index 0000000..e24b69b Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Lora-Italic.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Lora-OFL.txt b/skills/frontend-dev/canvas-fonts/Lora-OFL.txt new file mode 100644 index 0000000..4cf1b95 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/Lora-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2011 The Lora Project Authors (https://github.com/cyrealtype/Lora-Cyrillic), with Reserved Font Name "Lora". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/Lora-Regular.ttf b/skills/frontend-dev/canvas-fonts/Lora-Regular.ttf new file mode 100644 index 0000000..dc751db Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Lora-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/NationalPark-Bold.ttf b/skills/frontend-dev/canvas-fonts/NationalPark-Bold.ttf new file mode 100644 index 0000000..f4d7c02 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/NationalPark-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/NationalPark-OFL.txt b/skills/frontend-dev/canvas-fonts/NationalPark-OFL.txt new file mode 100644 index 0000000..f4ec3fb --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/NationalPark-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2025 The National Park Project Authors (https://github.com/benhoepner/National-Park) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/NationalPark-Regular.ttf b/skills/frontend-dev/canvas-fonts/NationalPark-Regular.ttf new file mode 100644 index 0000000..e4cbfbf Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/NationalPark-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/NothingYouCouldDo-OFL.txt b/skills/frontend-dev/canvas-fonts/NothingYouCouldDo-OFL.txt new file mode 100644 index 0000000..c81eccd --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/NothingYouCouldDo-OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2010, Kimberly Geswein (kimberlygeswein.com) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/NothingYouCouldDo-Regular.ttf b/skills/frontend-dev/canvas-fonts/NothingYouCouldDo-Regular.ttf new file mode 100644 index 0000000..b086bce Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/NothingYouCouldDo-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Outfit-Bold.ttf b/skills/frontend-dev/canvas-fonts/Outfit-Bold.ttf new file mode 100644 index 0000000..f9f2f72 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Outfit-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Outfit-OFL.txt b/skills/frontend-dev/canvas-fonts/Outfit-OFL.txt new file mode 100644 index 0000000..fd0cb99 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/Outfit-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2021 The Outfit Project Authors (https://github.com/Outfitio/Outfit-Fonts) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/Outfit-Regular.ttf b/skills/frontend-dev/canvas-fonts/Outfit-Regular.ttf new file mode 100644 index 0000000..3939ab2 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Outfit-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/PixelifySans-Medium.ttf b/skills/frontend-dev/canvas-fonts/PixelifySans-Medium.ttf new file mode 100644 index 0000000..95cd372 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/PixelifySans-Medium.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/PixelifySans-OFL.txt b/skills/frontend-dev/canvas-fonts/PixelifySans-OFL.txt new file mode 100644 index 0000000..b02d1b6 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/PixelifySans-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2021 The Pixelify Sans Project Authors (https://github.com/eifetx/Pixelify-Sans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/PoiretOne-OFL.txt b/skills/frontend-dev/canvas-fonts/PoiretOne-OFL.txt new file mode 100644 index 0000000..607bdad --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/PoiretOne-OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2011, Denis Masharov (denis.masharov@gmail.com) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/PoiretOne-Regular.ttf b/skills/frontend-dev/canvas-fonts/PoiretOne-Regular.ttf new file mode 100644 index 0000000..b339511 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/PoiretOne-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/RedHatMono-Bold.ttf b/skills/frontend-dev/canvas-fonts/RedHatMono-Bold.ttf new file mode 100644 index 0000000..a6e3cf1 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/RedHatMono-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/RedHatMono-OFL.txt b/skills/frontend-dev/canvas-fonts/RedHatMono-OFL.txt new file mode 100644 index 0000000..16cf394 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/RedHatMono-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2024 The Red Hat Project Authors (https://github.com/RedHatOfficial/RedHatFont) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/RedHatMono-Regular.ttf b/skills/frontend-dev/canvas-fonts/RedHatMono-Regular.ttf new file mode 100644 index 0000000..3bf6a69 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/RedHatMono-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Silkscreen-OFL.txt b/skills/frontend-dev/canvas-fonts/Silkscreen-OFL.txt new file mode 100644 index 0000000..a1fe7d5 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/Silkscreen-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2001 The Silkscreen Project Authors (https://github.com/googlefonts/silkscreen) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/Silkscreen-Regular.ttf b/skills/frontend-dev/canvas-fonts/Silkscreen-Regular.ttf new file mode 100644 index 0000000..8abaa7c Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Silkscreen-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/SmoochSans-Medium.ttf b/skills/frontend-dev/canvas-fonts/SmoochSans-Medium.ttf new file mode 100644 index 0000000..0af9ead Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/SmoochSans-Medium.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/SmoochSans-OFL.txt b/skills/frontend-dev/canvas-fonts/SmoochSans-OFL.txt new file mode 100644 index 0000000..4c2f033 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/SmoochSans-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2016 The Smooch Sans Project Authors (https://github.com/googlefonts/smooch-sans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/Tektur-Medium.ttf b/skills/frontend-dev/canvas-fonts/Tektur-Medium.ttf new file mode 100644 index 0000000..34fc797 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Tektur-Medium.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/Tektur-OFL.txt b/skills/frontend-dev/canvas-fonts/Tektur-OFL.txt new file mode 100644 index 0000000..2cad55f --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/Tektur-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2023 The Tektur Project Authors (https://www.github.com/hyvyys/Tektur) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/Tektur-Regular.ttf b/skills/frontend-dev/canvas-fonts/Tektur-Regular.ttf new file mode 100644 index 0000000..f280fba Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/Tektur-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/WorkSans-Bold.ttf b/skills/frontend-dev/canvas-fonts/WorkSans-Bold.ttf new file mode 100644 index 0000000..5c97989 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/WorkSans-Bold.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/WorkSans-BoldItalic.ttf b/skills/frontend-dev/canvas-fonts/WorkSans-BoldItalic.ttf new file mode 100644 index 0000000..54418b8 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/WorkSans-BoldItalic.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/WorkSans-Italic.ttf b/skills/frontend-dev/canvas-fonts/WorkSans-Italic.ttf new file mode 100644 index 0000000..40529b6 Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/WorkSans-Italic.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/WorkSans-OFL.txt b/skills/frontend-dev/canvas-fonts/WorkSans-OFL.txt new file mode 100644 index 0000000..070f341 --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/WorkSans-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/WorkSans-Regular.ttf b/skills/frontend-dev/canvas-fonts/WorkSans-Regular.ttf new file mode 100644 index 0000000..d24586c Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/WorkSans-Regular.ttf differ diff --git a/skills/frontend-dev/canvas-fonts/YoungSerif-OFL.txt b/skills/frontend-dev/canvas-fonts/YoungSerif-OFL.txt new file mode 100644 index 0000000..f09443c --- /dev/null +++ b/skills/frontend-dev/canvas-fonts/YoungSerif-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2023 The Young Serif Project Authors (https://github.com/noirblancrouge/YoungSerif) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/skills/frontend-dev/canvas-fonts/YoungSerif-Regular.ttf b/skills/frontend-dev/canvas-fonts/YoungSerif-Regular.ttf new file mode 100644 index 0000000..f454fbe Binary files /dev/null and b/skills/frontend-dev/canvas-fonts/YoungSerif-Regular.ttf differ diff --git a/skills/frontend-dev/references/asset-prompt-guide.md b/skills/frontend-dev/references/asset-prompt-guide.md new file mode 100644 index 0000000..bbaca50 --- /dev/null +++ b/skills/frontend-dev/references/asset-prompt-guide.md @@ -0,0 +1,43 @@ +# Prompt Engineering Guide + +## Image Prompts + +- Be specific about composition: "left-aligned subject with negative space on the right for text overlay" +- Specify lighting: "soft studio lighting", "golden hour backlight", "flat diffused light" +- Include style modifiers: "editorial photography", "3D render", "flat vector illustration" +- Add technical specs: "4K resolution, sharp focus, shallow depth of field" +- For web assets: always mention "clean background", "web-optimized", "high contrast for readability" +- **NEVER** include text in image prompts unless explicitly requested — AI text rendering is unreliable + +## Video Prompts + +- Use MiniMax camera commands in brackets: `[Push in]`, `[Truck left]`, `[Tracking shot]`, etc. +- Describe scene, subject, lighting, and mood — the API auto-optimizes prompts by default +- For web backgrounds: keep 6s duration, add `[Static shot]` for stability +- Max 2,000 characters + +## Audio / TTS + +- Specify genre, tempo (BPM), mood, and instruments +- For background music: "no vocals, suitable for background, not distracting" +- For sound effects: be extremely specific about the sound event +- For TTS: choose voice matching content language and speaker gender + +## Preset Shortcuts + +| Shortcut | Spec | +|----------|------| +| `hero` | 16:9 (1280x720) image, cinematic, text-safe space | +| `thumb` | 1:1 (1024x1024) image, centered subject | +| `icon` | 1:1 (1024x1024), flat style, clean background | +| `avatar` | 1:1 (1024x1024), portrait, circular crop ready | +| `banner` | 21:9 (1344x576), OG/social banner | +| `portrait` | 2:3 (832x1248), vertical portrait | +| `mobile` | 9:16 (720x1280), mobile fullscreen | +| `bg-video` | 768P, 6s, `[Static shot]`, MiniMax Hailuo-2.3 | +| `video` | 768P, 6s, MiniMax Hailuo-2.3, prompt auto-optimized | +| `video-hd` | 1080P, 6s, MiniMax Hailuo-2.3 | +| `bgm` | 30s background music, no vocals, loopable | +| `sfx` | Short sound effect, < 3s | +| `tts` | Text-to-speech, MiniMax HD, MP3 | +| `narration` | Expressive narration voice, MiniMax | diff --git a/skills/frontend-dev/references/env-setup.md b/skills/frontend-dev/references/env-setup.md new file mode 100644 index 0000000..169f6f1 --- /dev/null +++ b/skills/frontend-dev/references/env-setup.md @@ -0,0 +1,33 @@ +# Getting Started + +## 1. Set API key + +```bash +export MINIMAX_API_KEY="" +``` + +## 2. Install dependencies + +```bash +pip install requests + +# FFmpeg (optional, for audio post-processing) +# macOS: +brew install ffmpeg +# Ubuntu: +sudo apt install ffmpeg +``` + +## 3. Quick test + +```bash +python scripts/minimax_tts.py "Hello world" -o test.mp3 +``` + +If successful, you'll see `OK: xxxxx bytes -> test.mp3`. + +## Next steps + +- **Voice selection**: See [minimax-voice-catalog.md](minimax-voice-catalog.md) +- **TTS workflows**: See [minimax-tts-guide.md](minimax-tts-guide.md) +- **Troubleshooting**: See [troubleshooting.md](troubleshooting.md) diff --git a/skills/frontend-dev/references/minimax-cli-reference.md b/skills/frontend-dev/references/minimax-cli-reference.md new file mode 100644 index 0000000..6b6497b --- /dev/null +++ b/skills/frontend-dev/references/minimax-cli-reference.md @@ -0,0 +1,133 @@ +# Provider Reference — MiniMax + +All asset generation uses MiniMax API. Env: `MINIMAX_API_KEY` (required). + +## Audio (Sync TTS) + +**Script:** `scripts/minimax_tts.py` + +```bash +python scripts/minimax_tts.py "Hello world" -o output.mp3 +python scripts/minimax_tts.py "你好" -o hi.mp3 -v female-shaonv +python scripts/minimax_tts.py "Welcome" -o out.wav -v male-qn-jingying --speed 0.8 --format wav +``` + +**Model:** `speech-2.8-hd` (default). + +| Flag | Default | Range / Options | +|------|---------|-----------------| +| `-o` | (required) | Output file path | +| `-v` | `male-qn-qingse` | Voice ID | +| `--model` | `speech-2.8-hd` | speech-2.8-hd / speech-2.8-turbo / speech-2.6-hd / speech-2.6-turbo | +| `--speed` | 1.0 | 0.5–2.0 | +| `--volume` | 1.0 | 0.1–10 | +| `--pitch` | 0 | -12 to 12 | +| `--emotion` | (auto) | happy / sad / angry / fearful / disgusted / surprised / calm / fluent / whisper | +| `--format` | mp3 | mp3 / wav / flac | +| `--lang` | auto | Language boost | + +**Programmatic:** +```python +from minimax_tts import tts +audio_bytes = tts("Hello", voice_id="female-shaonv") +``` + + +## Video (Text-to-Video) + +**Script:** `scripts/minimax_video.py` + +```bash +python scripts/minimax_video.py "A cat playing piano" -o cat.mp4 +python scripts/minimax_video.py "Ocean waves [Truck left]" -o waves.mp4 --duration 10 +python scripts/minimax_video.py "City skyline [Push in]" -o city.mp4 --resolution 1080P +``` + +**Model:** `MiniMax-Hailuo-2.3` (default). Async: script handles create → poll → download automatically. + +| Flag | Default | Options | +|------|---------|---------| +| `-o` | (required) | Output file path (.mp4) | +| `--model` | `MiniMax-Hailuo-2.3` | MiniMax-Hailuo-2.3 / MiniMax-Hailuo-02 / T2V-01-Director / T2V-01 | +| `--duration` | 6 | 6 / 10 (10s only at 768P with Hailuo models) | +| `--resolution` | 768P | 720P / 768P / 1080P (1080P only 6s) | +| `--no-optimize` | false | Disable prompt auto-optimization | +| `--poll-interval` | 10 | Seconds between status checks | +| `--max-wait` | 600 | Max wait time in seconds | + +**Camera commands** — insert `[Command]` in prompt: `[Push in]`, `[Truck left]`, `[Pan right]`, `[Zoom out]`, `[Static shot]`, `[Tracking shot]`, etc. + +**Programmatic:** +```python +from minimax_video import generate +generate("A cat playing piano", "cat.mp4", model="MiniMax-Hailuo-2.3", duration=6) +``` + +See [minimax-video-guide.md](minimax-video-guide.md) for full camera command list and model compatibility. + +## Image (Text-to-Image) + +**Script:** `scripts/minimax_image.py` + +```bash +python scripts/minimax_image.py "A cat astronaut in space" -o cat.png +python scripts/minimax_image.py "Mountain landscape" -o hero.png --ratio 16:9 +python scripts/minimax_image.py "Product icons, flat style" -o icons.png -n 4 --seed 42 +``` + +**Model:** `image-01`. Sync: returns image URL (or base64) immediately. + +| Flag | Default | Options | +|------|---------|---------| +| `-o` | (required) | Output file path (.png/.jpg) | +| `--ratio` | 1:1 | 1:1 / 16:9 / 4:3 / 3:2 / 2:3 / 3:4 / 9:16 / 21:9 | +| `-n` | 1 | Number of images (1–9) | +| `--seed` | (random) | Seed for reproducibility | +| `--optimize` | false | Enable prompt auto-optimization | +| `--base64` | false | Return base64 instead of URL | + +**Batch output:** with `-n > 1`, files are named `out-0.png`, `out-1.png`, etc. + +**Programmatic:** +```python +from minimax_image import generate_image, download_and_save +result = generate_image("A cat in space", aspect_ratio="16:9") +download_and_save(result["data"]["image_urls"][0], "cat.png") +``` + +See [minimax-image-guide.md](minimax-image-guide.md) for ratio dimensions and details. + +## Music (Text-to-Music) + +**Script:** `scripts/minimax_music.py` + +```bash +python scripts/minimax_music.py --prompt "Indie folk, melancholic" --lyrics "[verse]\nStreetlights flicker" -o song.mp3 +python scripts/minimax_music.py --prompt "Upbeat pop, energetic" --auto-lyrics -o pop.mp3 +python scripts/minimax_music.py --prompt "Jazz piano, smooth, relaxing" --instrumental -o jazz.mp3 +``` + +**Model:** `music-2.5+` (default). Sync: returns audio hex or URL. + +| Flag | Default | Options | +|------|---------|---------| +| `-o` | (required) | Output file path (.mp3/.wav) | +| `--prompt` | (empty) | Music description: style, mood, scenario (max 2000 chars) | +| `--lyrics` | (empty) | Song lyrics with structure tags (max 3500 chars) | +| `--lyrics-file` | (empty) | Read lyrics from file | +| `--model` | `music-2.5+` | music-2.5+ / music-2.5 | +| `--instrumental` | false | Generate instrumental only (no vocals, music-2.5+ only) | +| `--auto-lyrics` | false | Auto-generate lyrics from prompt | +| `--format` | mp3 | mp3 / wav / pcm | +| `--sample-rate` | 44100 | 16000 / 24000 / 32000 / 44100 | +| `--bitrate` | 256000 | 32000 / 64000 / 128000 / 256000 | + +**Lyrics structure tags:** `[Intro]`, `[Verse]`, `[Pre Chorus]`, `[Chorus]`, `[Interlude]`, `[Bridge]`, `[Outro]`, `[Post Chorus]`, `[Transition]`, `[Break]`, `[Hook]`, `[Build Up]`, `[Inst]`, `[Solo]` + +**Programmatic:** +```python +from minimax_music import generate_music +result = generate_music(prompt="Jazz piano", is_instrumental=True) +with open("jazz.mp3", "wb") as f: + f.write(result["audio_bytes"]) +``` diff --git a/skills/frontend-dev/references/minimax-image-guide.md b/skills/frontend-dev/references/minimax-image-guide.md new file mode 100644 index 0000000..b4938ac --- /dev/null +++ b/skills/frontend-dev/references/minimax-image-guide.md @@ -0,0 +1,65 @@ +# Image Generation Guide + +## CLI usage + +```bash +# Basic (1:1, 1024x1024) +python scripts/minimax_image.py "A cat astronaut floating in space" -o cat.png + +# 16:9 for hero banner +python scripts/minimax_image.py "Mountain landscape at golden hour" -o hero.png --ratio 16:9 + +# Batch: 4 images at once +python scripts/minimax_image.py "Minimalist product icon" -o icons.png -n 4 + +# With seed for reproducibility +python scripts/minimax_image.py "Abstract gradient background" -o bg.png --seed 42 + +# Enable prompt optimization +python scripts/minimax_image.py "a dog" -o dog.png --optimize + +# Base64 mode (no URL download, save directly) +python scripts/minimax_image.py "Logo concept" -o logo.png --base64 +``` + +## Programmatic usage + +```python +from minimax_image import generate_image, download_and_save + +# Generate and get URL +result = generate_image("A cat in space", aspect_ratio="16:9") +url = result["data"]["image_urls"][0] +download_and_save(url, "cat.png") + +# Generate multiple +result = generate_image("Icon design", n=4, aspect_ratio="1:1") +for i, url in enumerate(result["data"]["image_urls"]): + download_and_save(url, f"icon-{i}.png") +``` + +## Model + +Currently only `image-01`. + +## Aspect ratios & dimensions + +| Ratio | Pixels | Use case | +|-------|--------|----------| +| `1:1` | 1024x1024 | Avatar, icon, square thumbnail | +| `16:9` | 1280x720 | Hero banner, video thumbnail | +| `4:3` | 1152x864 | Standard landscape | +| `3:2` | 1248x832 | Photo-style | +| `2:3` | 832x1248 | Portrait, mobile | +| `3:4` | 864x1152 | Portrait card | +| `9:16` | 720x1280 | Mobile fullscreen, story | +| `21:9` | 1344x576 | Ultra-wide banner | + +Custom dimensions also supported: width/height in [512, 2048], must be divisible by 8. + +## Limits + +- Prompt: max 1,500 characters +- Batch: 1–9 images per request +- URL expires after 24 hours (use `--base64` to avoid expiry) +- Seed: set for reproducible results across identical prompts diff --git a/skills/frontend-dev/references/minimax-music-guide.md b/skills/frontend-dev/references/minimax-music-guide.md new file mode 100644 index 0000000..6b9df67 --- /dev/null +++ b/skills/frontend-dev/references/minimax-music-guide.md @@ -0,0 +1,216 @@ +# Music Generation Guide + +## CLI Usage + +```bash +# Instrumental (no vocals) +python scripts/minimax_music.py --prompt "Jazz piano, smooth, relaxing" --instrumental -o jazz.mp3 + +# With custom lyrics +python scripts/minimax_music.py --prompt "Indie folk, melancholic" --lyrics "[verse]\nStreetlights flicker\nOn empty roads" -o song.mp3 + +# Auto-generate lyrics from prompt +python scripts/minimax_music.py --prompt "Upbeat pop, energetic, summer vibes" --auto-lyrics -o pop.mp3 + +# From lyrics file +python scripts/minimax_music.py --prompt "Soulful blues, rainy night" --lyrics-file lyrics.txt -o blues.mp3 + +# Custom audio settings +python scripts/minimax_music.py --prompt "Lo-fi beats" --instrumental -o lofi.wav --format wav --sample-rate 44100 --bitrate 256000 +``` + +## Programmatic Usage + +```python +from minimax_music import generate_music + +# Instrumental +result = generate_music(prompt="Jazz piano, smooth", is_instrumental=True) +with open("jazz.mp3", "wb") as f: + f.write(result["audio_bytes"]) + +# With lyrics +result = generate_music( + prompt="Indie folk, acoustic guitar", + lyrics="[verse]\nWalking through the rain\n[chorus]\nI'll find my way home", +) + +# Auto-generate lyrics +result = generate_music( + prompt="Upbeat pop, summer anthem", + lyrics_optimizer=True, +) + +# Access metadata +print(f"Duration: {result['duration']}ms") +print(f"Sample rate: {result['sample_rate']}") +print(f"Size: {result['size']} bytes") +``` + +## Models + +| Model | Features | +|-------|----------| +| `music-2.5+` | Recommended. Supports instrumental mode, complete song structures, hi-fi audio | +| `music-2.5` | Standard model. No instrumental mode | + +## Prompt Writing + +The `prompt` parameter describes music style using comma-separated descriptors: + +| Category | Examples | +|----------|----------| +| Genre | Blues, Pop, Rock, Jazz, Electronic, Hip-hop, Folk, Classical | +| Mood | Soulful, Melancholy, Upbeat, Energetic, Peaceful, Dark, Nostalgic | +| Scenario | Rainy night, Summer day, Road trip, Late night, Sunrise | +| Instrumentation | Electric guitar, Piano, Acoustic, Synthesizer, Strings | +| Vocal type | Male vocals, Female vocals, Soft vocals, Powerful vocals | +| Tempo | Slow tempo, Fast tempo, Mid-tempo, Relaxed | + +**Example prompts:** +``` +"Soulful Blues, Rainy Night, Melancholy, Male Vocals, Slow Tempo" +"Upbeat Pop, Summer Vibes, Female Vocals, Energetic, Synth-heavy" +"Lo-fi Hip-hop, Chill, Relaxed, Instrumental, Piano samples" +"Cinematic Orchestral, Epic, Building tension, Strings and Brass" +``` + +## Lyrics Format + +Use structure tags in brackets to organize song sections: + +### Structure Tags + +| Tag | Purpose | +|-----|---------| +| `[Intro]` | Opening section (can be instrumental) | +| `[Verse]` / `[Verse 1]` | Story/narrative sections | +| `[Pre-Chorus]` | Build-up before chorus | +| `[Chorus]` | Main hook, typically repeated | +| `[Post Chorus]` | Extension after chorus | +| `[Bridge]` | Contrasting section near end | +| `[Interlude]` | Instrumental break | +| `[Solo]` | Instrumental solo (add direction: "slow, bluesy") | +| `[Outro]` | Closing section | +| `[Break]` | Short pause or transition | +| `[Hook]` | Catchy repeated phrase | +| `[Build Up]` | Tension building section | +| `[Inst]` | Instrumental section | +| `[Transition]` | Section change | + +### Backing Vocals & Directions + +Use parentheses for backing vocals or performance notes: +``` +(Ooh, yeah) +(Harmonize) +(Whispered) +(Fade out...) +``` + +### Example Lyrics + +``` +[Intro] +(Soft piano) + +[Verse 1] +Streetlights flicker on empty roads +The rain keeps falling, the wind still blows +I'm walking home with nowhere to go +Just memories of what I used to know + +[Pre-Chorus] +And I can feel it coming back to me +(Coming back to me) + +[Chorus] +Under the neon lights tonight +I'm searching for what feels right +(Oh, feels right) +These city streets will guide me home +I'm tired of feeling so alone + +[Verse 2] +Coffee shops and midnight trains +The faces change but the feeling remains +... + +[Bridge] +Maybe tomorrow will be different +Maybe I'll finally understand +(Understand...) + +[Solo] +(Slow, mournful, bluesy guitar) + +[Outro] +(Fade out...) +Under the neon lights... +``` + +## Audio Settings + +| Parameter | Options | Default | Notes | +|-----------|---------|---------|-------| +| `format` | mp3, wav, pcm | mp3 | WAV for highest quality | +| `sample_rate` | 16000, 24000, 32000, 44100 | 44100 | 44100 recommended | +| `bitrate` | 32000, 64000, 128000, 256000 | 256000 | Higher = better quality | + +## Generation Modes + +### 1. Instrumental Only +```bash +python scripts/minimax_music.py --prompt "Ambient electronic, space theme" --instrumental -o ambient.mp3 +``` +- Requires `music-2.5+` model +- Only `prompt` needed, no lyrics + +### 2. With Custom Lyrics +```bash +python scripts/minimax_music.py --prompt "Pop ballad, emotional" --lyrics "[verse]\nYour lyrics here" -o ballad.mp3 +``` +- Provide both `prompt` (style) and `lyrics` (words + structure) + +### 3. Auto-Generated Lyrics +```bash +python scripts/minimax_music.py --prompt "Rock anthem about freedom" --auto-lyrics -o rock.mp3 +``` +- System generates lyrics from prompt +- Good for quick generation when lyrics aren't critical + +## Limits + +- **Prompt:** max 2,000 characters +- **Lyrics:** 1–3,500 characters +- **Duration:** ~25-30 seconds per generation (varies) +- **URL expiration:** 24 hours (when using URL output mode) + +## Best Practices + +1. **Layer style descriptors** — Combine genre + mood + instrumentation for precise results +2. **Use structure tags** — Even simple `[verse]` `[chorus]` improves arrangement +3. **Include backing vocal cues** — `(Ooh)`, `(Yeah)` add production polish +4. **Match prompt to lyrics mood** — Conflicting prompt/lyrics produce inconsistent results +5. **Instrumental for backgrounds** — Use `--instrumental` for BGM, avoiding vocal distractions +6. **High bitrate for production** — Use 256000 for final assets, lower for drafts + +## Common Use Cases + +| Use Case | Command | +|----------|---------| +| Background music | `--prompt "Lo-fi, calm, ambient" --instrumental` | +| Landing page hero | `--prompt "Cinematic, inspiring, building" --instrumental` | +| Podcast intro | `--prompt "Upbeat, energetic, short" --instrumental` | +| Demo song | `--prompt "Pop, catchy" --auto-lyrics` | +| Custom jingle | `--prompt "Happy, bright, corporate" --lyrics "[hook]\nYour brand name"` | + +## Error Handling + +| Error Code | Meaning | Solution | +|------------|---------|----------| +| 1002 | Rate limit | Wait and retry | +| 1004 | Auth failed | Check API key | +| 1008 | Insufficient balance | Top up account | +| 1026 | Content flagged | Rephrase prompt/lyrics | +| 2013 | Invalid parameters | Check prompt/lyrics length | diff --git a/skills/frontend-dev/references/minimax-tts-guide.md b/skills/frontend-dev/references/minimax-tts-guide.md new file mode 100644 index 0000000..a663be7 --- /dev/null +++ b/skills/frontend-dev/references/minimax-tts-guide.md @@ -0,0 +1,78 @@ +# TTS Guide + +## CLI usage (recommended) + +```bash +# Basic +python scripts/minimax_tts.py "Hello world" -o output.mp3 + +# Custom voice and speed +python scripts/minimax_tts.py "你好世界" -o hi.mp3 -v female-shaonv --speed 0.9 + +# WAV format, high quality +python scripts/minimax_tts.py "Welcome" -o out.wav -v male-qn-jingying --format wav --sample-rate 32000 + +# With emotion (for speech-2.6 models) +python scripts/minimax_tts.py "Great news!" -o happy.mp3 -v female-shaonv --emotion happy --model speech-2.6-hd +``` + +## Programmatic usage + +```python +from minimax_tts import tts + +# Basic +audio_bytes = tts("Hello world") + +# With options +audio_bytes = tts( + text="Welcome to our product.", + voice_id="female-shaonv", + model="speech-2.8-hd", + speed=0.9, + fmt="mp3", +) + +# Save to file +with open("output.mp3", "wb") as f: + f.write(audio_bytes) +``` + +## Limits + +- **Sync TTS:** max 10,000 characters per request +- **Pause markers:** insert `<#1.5#>` for a 1.5s pause (range: 0.01–99.99s) + +## Model selection + +| Model | Best for | +|-------|----------| +| `speech-2.8-hd` | Highest quality, auto emotion (recommended) | +| `speech-2.8-turbo` | Fast, good quality | +| `speech-2.6-hd` | Manual emotion control needed | +| `speech-2.6-turbo` | Fast + manual emotion | + +## Voice selection + +See [minimax-voice-catalog.md](minimax-voice-catalog.md) for the full list. + +Common voices: + +| Voice ID | Gender | Style | +|----------|--------|-------| +| `male-qn-qingse` | Male | Young, gentle | +| `male-qn-jingying` | Male | Elite, authoritative | +| `male-qn-badao` | Male | Dominant, powerful | +| `female-shaonv` | Female | Young, bright | +| `female-yujie` | Female | Mature, elegant | +| `female-chengshu` | Female | Sophisticated | +| `presenter_male` | Male | News presenter | +| `presenter_female` | Female | News presenter | +| `audiobook_male_1` | Male | Audiobook narrator | +| `audiobook_female_1` | Female | Audiobook narrator | + +## Best practices + +- Use `speech-2.8-hd` and let emotion auto-match — don't manually set emotion unless needed +- Use 32000 sample rate for web audio (good balance of quality and file size) +- For long text (>10,000 chars), split into chunks and merge with FFmpeg diff --git a/skills/frontend-dev/references/minimax-video-guide.md b/skills/frontend-dev/references/minimax-video-guide.md new file mode 100644 index 0000000..2700a2c --- /dev/null +++ b/skills/frontend-dev/references/minimax-video-guide.md @@ -0,0 +1,82 @@ +# Video Generation Guide + +## CLI usage + +```bash +# Basic +python scripts/minimax_video.py "A cat playing piano in a cozy room" -o cat.mp4 + +# With camera control +python scripts/minimax_video.py "Ocean waves crashing on rocks [Truck left]" -o waves.mp4 + +# 10 seconds, 1080P +python scripts/minimax_video.py "City skyline at sunset [Push in]" -o city.mp4 --duration 10 --resolution 1080P + +# Disable prompt auto-optimization +python scripts/minimax_video.py "Exact prompt I want used" -o out.mp4 --no-optimize +``` + +## Programmatic usage + +```python +from minimax_video import generate, create_task, poll_task, download_video + +# Full pipeline (blocking) +generate("A cat playing piano", "cat.mp4", model="MiniMax-Hailuo-2.3", duration=6) + +# Step by step +task_id = create_task("A cat playing piano") +file_id = poll_task(task_id, interval=10, max_wait=600) +download_video(file_id, "cat.mp4") +``` + +## Models + +| Model | Resolution | Duration | Notes | +|-------|-----------|----------|-------| +| `MiniMax-Hailuo-2.3` | 768P, 1080P | 6s, 10s (768P only) | Latest, recommended | +| `MiniMax-Hailuo-02` | 768P, 1080P | 6s, 10s (768P only) | Previous gen | +| `T2V-01-Director` | 720P | 6s | Camera control optimized | +| `T2V-01` | 720P | 6s | Base model | + +## Camera commands + +Insert `[Command]` in prompt text to control camera movement: + +| Command | Effect | +|---------|--------| +| `[Truck left]` | Camera moves left | +| `[Truck right]` | Camera moves right | +| `[Push in]` | Camera moves toward subject | +| `[Pull out]` | Camera moves away from subject | +| `[Pan left]` | Camera rotates left (fixed position) | +| `[Pan right]` | Camera rotates right (fixed position) | +| `[Tilt up]` | Camera tilts upward | +| `[Tilt down]` | Camera tilts downward | +| `[Pedestal up]` | Camera rises vertically | +| `[Pedestal down]` | Camera lowers vertically | +| `[Zoom in]` | Lens zooms in | +| `[Zoom out]` | Lens zooms out | +| `[Static shot]` | No camera movement | +| `[Tracking shot]` | Camera follows subject | +| `[Shake]` | Handheld shake effect | + +Example: `"A runner sprints through a forest trail [Tracking shot]"` + +## Pipeline + +The script handles the full async flow: + +1. **Create task** — `POST /v1/video_generation` → returns `task_id` +2. **Poll status** — `GET /v1/query/video_generation?task_id=xxx` → poll until `Success` + - Status values: `Preparing` → `Queueing` → `Processing` → `Success` / `Fail` +3. **Download** — `GET /v1/files/retrieve?file_id=xxx` → get `download_url` (valid 1 hour) → save file + +Typical generation time: 1–5 minutes depending on duration and resolution. + +## Limits + +- Prompt: max 2,000 characters +- 1080P: only supports 6s duration +- 10s duration: only available at 768P with Hailuo-2.3/02 +- Download URL expires after 1 hour diff --git a/skills/frontend-dev/references/minimax-voice-catalog.md b/skills/frontend-dev/references/minimax-voice-catalog.md new file mode 100644 index 0000000..2e4c999 --- /dev/null +++ b/skills/frontend-dev/references/minimax-voice-catalog.md @@ -0,0 +1,686 @@ +# MiniMax Voice Catalog + +Complete reference for all available voices in the MiniMax Voice API. + +## Contents + +- [Voice Recommendation](#voice-recommendation) - Find voices by content type and characteristics +- [System Voices List (categorized by language)](#system-voices-list-categorized-by-language) - Complete voice database by language +- [Voice Parameters](#voice-parameters) - Configure voice settings (speed, volume, pitch, emotion) +- [Custom Voices](#custom-voices) - Voice cloning and voice design options +- [Voice Comparison Table](#voice-comparison-table) - Quick reference comparison +- [Voice IDs for Quick Reference](#voice-ids-for-quick-reference) - Most popular voices at a glance + +--- + +## 1. How to Choose a Voice + +When selecting a voice, follow this two-step decision process to ensure the voice matches the scenario, gender, age, and language of the character. + +### Step 1: Identify the Usage Scenario + +First, determine whether your content falls into one of the **three professional domains** listed in **Section 2.1**: + +| Professional Domain | Examples | +|---|---| +| **Narration & Narrator in Storytelling** | suitable for the narrator in Audiobooks, fiction narration, storytelling | +| **News & Announcements** | suitable for news broadcasts, formal announcements, press releases | +| **Documentary** | suitable for documentary narration, commentary, educational films | + +**If your content matches one of these professional domains:** +→ Prioritize selecting from the recommended voices in **Section 2.1**, filtering by scenario and the speaker's **gender**. +These voices are specifically optimized for their respective professional use cases (pacing, clarity, tone). + +**If your content does NOT fall into these three professional domains:** +→ Proceed to Step 2 below. + +### Step 2: Select by Character Traits (Gender + Age + Language) + +For non-professional scenarios, select a voice from **Section 2.2** based on the following three character traits, in strict priority order: + +1. **Gender** (highest priority, non-negotiable) + - Male characters → **must** use male voices + - Female characters → **must** use female voices + - Never mismatch gender, even if other traits seem to fit + +2. **Age** (determines which subsection to look in) + - **Children** → Section 2.2 "Children's Voices" + - **Youth** (teens, young adults) → Section 2.2 "Youthful Voices" + - **Adult** → Section 2.2 "Adult Voices" + - **Elderly** → Section 2.2 "Elderly Voices" + +3. **Language** (must match the content language) + - The voice **must** match the language of the content being generated + - Chinese content → select Chinese voices; Korean content → select Korean voices; English content → select English voices, etc. + - If no exact language match exists in Section 2.2, fall back to the full **System Voices List** (Section 3) for the target language + +After narrowing down candidates by these three traits, choose the best match based on the voice's **personality**, **tone**, and **use case** as described in each voice entry. + +### Quick Reference Decision Flow + +``` +Content Type? +├── Story/Narration/News/Documentary → Section 2.1 (filter by scenario + gender) +└── Other scenarios → Section 2.2: + ├── 1. Match Gender (mandatory) + ├── 2. Match Age Group (Children/Youth/Adult/Elderly/Professional) + ├── 3. Match Language (must match content language) + └── 4. Choose best fit by personality/tone +``` + +--- + + +## 2. Voice Recommendation + +### 2.1 By Content Type + +**Narration & Narrator in Storytelling** +- Recommended: `audiobook_female_1`, `audiobook_male_1` +- Characteristics: suitable for narrating stories, sustained performance, clear articulation, good pacing + +**News & Announcements** +- Recommended: `Chinese (Mandarin)_News_Anchor`, `Chinese (Mandarin)_Male_Announcer` +- Characteristics: Authoritative, clear, professional pacing + +**Documentary** +- Recommended: `doc_commentary` +- Characteristics: Professional, clear, consistent pacing + + +### 2.2 By Characteristics + +#### Children's Voices + +| voice_id | Name | Description | Best For | Language | +|----------|------|-------------|----------|----------| +| `clever_boy` | 聪明男童 | Smart, witty boy voice | Children's content, educational | Chinese (Mandarin) | +| `cute_boy` | 可爱男童 | Adorable young boy voice | Kids' content, animations | Chinese (Mandarin) | +| `lovely_girl` | 萌萌女童 | Cute, sweet girl voice | Children's stories, games | Chinese (Mandarin) | +| `cartoon_pig` | 卡通猪小琪 | Cartoon character voice | Animations, comedy, entertainment | Chinese (Mandarin) | +| `Korean_SweetGirl` | Sweet Girl | Sweet, adorable young girl voice | Children's content, romance | Korean | +| `Indonesian_SweetGirl` | Sweet Girl | Sweet, adorable girl voice | Children's content, friendly | Indonesian | +| `English_Sweet_Girl` | Sweet Girl | Sweet, innocent young girl voice | Children's content, friendly | English | +| `Spanish_Kind-heartedGirl` | Kind-hearted Girl | Warm, compassionate girl voice | Children's content, warm | Spanish | +| `Portuguese_Kind-heartedGirl` | Kind-hearted Girl | Warm, compassionate girl voice | Children's content, warm | Portuguese | + +#### Youthful Voices + +| voice_id | Name | Description | Best For | Language | +|----------|------|-------------|----------|----------| +| `male-qn-qingse` | 青涩青年 | Youthful, inexperienced young man voice | Campus stories, coming-of-age content | Chinese (Mandarin) | +| `male-qn-daxuesheng` | 青年大学生 | Young university student voice | Campus content, educational | Chinese (Mandarin) | +| `female-shaonv` | 少女 | Young maiden voice | Romance, youth content | Chinese (Mandarin) | +| `bingjiao_didi` | 病娇弟弟 | Tsundere young brother voice | Romance, character-driven content | Chinese (Mandarin) | +| `junlang_nanyou` | 俊朗男友 | Handsome boyfriend voice | Romance, dating content | Chinese (Mandarin) | +| `chunzhen_xuedi` | 纯真学弟 | Innocent junior student voice | Campus stories, youth content | Chinese (Mandarin) | +| `lengdan_xiongzhang` | 冷淡学长 | Cool senior student voice | Campus stories, romance | Chinese (Mandarin) | +| `diadia_xuemei` | 嗲嗲学妹 | Flirty junior girl voice | Romance, dating content | Chinese (Mandarin) | +| `danya_xuejie` | 淡雅学姐 | Elegant senior girl voice | Campus stories, romance | Chinese (Mandarin) | +| `Chinese (Mandarin)_Straightforward_Boy` | 率真弟弟 | Frank, straightforward boy voice | Casual, direct content | Chinese (Mandarin) | +| `Chinese (Mandarin)_Sincere_Adult` | 真诚青年 | Sincere young adult voice | Honest, genuine content | Chinese (Mandarin) | +| `Chinese (Mandarin)_Pure-hearted_Boy` | 清澈邻家弟弟 | Pure-hearted neighbor boy voice | Innocent, wholesome content | Chinese (Mandarin) | +| `Korean_CheerfulBoyfriend` | Cheerful Boyfriend | Energetic, loving boyfriend voice | Romance, dating content | Korean | +| `Korean_ShyGirl` | Shy Girl | Timid, reserved girl voice | Comedy, romance | Korean | +| `Japanese_SportyStudent` | Sporty Student | Energetic athletic student voice | Sports, youth content | Japanese | +| `Japanese_InnocentBoy` | Innocent Boy | Pure, naive young boy voice | Children's content | Japanese | +| `Spanish_SincereTeen` | SincereTeen | Honest, genuine teenager voice | Youth, authentic | Spanish | +| `Spanish_Strong-WilledBoy` | Strong-willed Boy | Determined, persistent boy voice | Youth, motivation | Spanish | + +#### Adult Voices + +| voice_id | Name | Description | Best For | Language | +|----------|------|-------------|----------|----------| +| `female-chengshu` | 成熟女性 | Mature woman voice | Sophisticated, adult content | Chinese (Mandarin) | +| `female-yujie` | 御姐 | Mature, elegant woman voice | Romance, professional content | Chinese (Mandarin) | +| `female-tianmei` | 甜美女性 | Sweet, pleasant woman voice | Soft, gentle content | Chinese (Mandarin) | +| `badao_shaoye` | 霸道少爷 | Arrogant young master voice | Drama, character roles | Chinese (Mandarin) | +| `wumei_yujie` | 妩媚御姐 | Charming mature woman voice | Romance, mature content | Chinese (Mandarin) | +| `Chinese (Mandarin)_Gentleman` | 温润男声 | Gentle, refined male voice | Narration, storytelling | Chinese (Mandarin) | +| `Chinese (Mandarin)_Unrestrained_Young_Man` | 不羁青年 | Unrestrained young man voice | Casual, entertainment content | Chinese (Mandarin) | +| `Chinese (Mandarin)_Southern_Young_Man` | 南方小哥 | Southern young man voice | Regional character, casual content | Chinese (Mandarin) | +| `Chinese (Mandarin)_Gentle_Youth` | 温润青年 | Gentle young man voice | Narration, calm content | Chinese (Mandarin) | +| `Chinese (Mandarin)_Warm_Girl` | 温暖少女 | Warm young girl voice | Friendly, supportive content | Chinese (Mandarin) | +| `Chinese (Mandarin)_Soft_Girl` | 柔和少女 | Soft, gentle girl voice | Calm, soothing content | Chinese (Mandarin) | +| `Korean_PlayboyCharmer` | Playboy Charmer | Smooth, flirtatious male voice | Romance, entertainment | Korean | +| `Korean_CalmLady` | Calm Lady | Composed, serene female voice | Meditation, relaxation | Korean | +| `Spanish_ConfidentWoman` | Confident Woman | Self-assured, capable woman voice | Professional, empowerment | Spanish | +| `Portuguese_ConfidentWoman` | Confident Woman | Self-assured, capable woman voice | Professional, empowerment | Portuguese | + +#### Elderly Voices + +| voice_id | Name | Description | Best For | Language | +|----------|------|-------------|----------|----------| +| `Chinese (Mandarin)_Humorous_Elder` | 搞笑大爷 | Humorous old man voice | Comedy, entertainment | Chinese (Mandarin) | +| `Chinese (Mandarin)_Kind-hearted_Elder` | 花甲奶奶 | Kind elderly lady voice | Stories, warm content | Chinese (Mandarin) | +| `Chinese (Mandarin)_Kind-hearted_Antie` | 热心大婶 | Kind-hearted auntie voice | Warm, friendly content | Chinese (Mandarin) | +| `Japanese_IntellectualSenior` | Intellectual Senior | Wise, knowledgeable elder voice | Narration, educational | Japanese | +| `Korean_IntellectualSenior` | Intellectual Senior | Wise, knowledgeable elder voice | Educational, narration | Korean | +| `Spanish_Wiselady` | Wise Lady | Experienced, wise woman voice | Guidance, advice | Spanish | +| `Portuguese_Wiselady` | Wise Lady | Experienced, wise woman voice | Guidance, advice | Portuguese | +| `Spanish_SereneElder` | Serene Elder | Calm, peaceful elderly voice | Meditation, wisdom | Spanish | +| `Portuguese_SereneElder` | Serene Elder | Calm, peaceful elderly voice | Meditation, wisdom | Portuguese | +| `English_Gentle-voiced_man` | Gentle-voiced Man | Soft-spoken, kind male voice | Calm, supportive content | English | + +--- + +## System Voices List (categorized by language) + +### Chinese Mandarin Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `male-qn-qingse` | 青涩青年 | Youthful, inexperienced young man voice | Campus stories, coming-of-age content | +| `male-qn-badao` | 霸道青年 | Arrogant, dominant young man voice | Drama, romance, character roles | +| `male-qn-daxuesheng` | 青年大学生 | Young university student voice | Campus content, educational | +| `female-shaonv` | 少女 | Young maiden voice | Romance, youth content | +| `female-yujie` | 御姐 | Mature, elegant woman voice | Romance, professional content | +| `female-chengshu` | 成熟女性 | Mature woman voice | Sophisticated, adult content | +| `female-tianmei` | 甜美女性 | Sweet, pleasant woman voice | Soft, gentle content | +| `clever_boy` | 聪明男童 | Smart, witty boy voice | Children's content, educational | +| `cute_boy` | 可爱男童 | Adorable young boy voice | Kids' content, animations | +| `lovely_girl` | 萌萌女童 | Cute, sweet girl voice | Children's stories, games | +| `cartoon_pig` | 卡通猪小琪 | Cartoon character voice | Animations, comedy, entertainment | +| `bingjiao_didi` | 病娇弟弟 | Tsundere young brother voice | Romance, character-driven content | +| `junlang_nanyou` | 俊朗男友 | Handsome boyfriend voice | Romance, dating content | +| `chunzhen_xuedi` | 纯真学弟 | Innocent junior student voice | Campus stories, youth content | +| `lengdan_xiongzhang` | 冷淡学长 | Cool senior student voice | Campus stories, romance | +| `badao_shaoye` | 霸道少爷 | Arrogant young master voice | Drama, character roles | +| `tianxin_xiaoling` | 甜心小玲 | Sweet Xiao Ling voice | Character roles, animations | +| `qiaopi_mengmei` | 俏皮萌妹 | Playful cute girl voice | Comedy, light-hearted content | +| `wumei_yujie` | 妩媚御姐 | Charming mature woman voice | Romance, mature content | +| `diadia_xuemei` | 嗲嗲学妹 | Flirty junior girl voice | Romance, dating content | +| `danya_xuejie` | 淡雅学姐 | Elegant senior girl voice | Campus stories, romance | +| `Arrogant_Miss` | 嚣张小姐 | Arrogant young lady voice | Drama, character roles | +| `Robot_Armor` | 机械战甲 | Robotic armor voice | Sci-fi, game characters | +| `Chinese (Mandarin)_Reliable_Executive` | 沉稳高管 | Reliable executive voice | Corporate, business content | +| `Chinese (Mandarin)_News_Anchor` | 新闻女声 | News anchor female voice | News broadcasts, current affairs | +| `Chinese (Mandarin)_Mature_Woman` | 傲娇御姐 | Tsundere mature woman voice | Romance, character-driven content | +| `Chinese (Mandarin)_Unrestrained_Young_Man` | 不羁青年 | Unrestrained young man voice | Casual, entertainment content | +| `male-qn-jingying` | 精英青年 | Elite, ambitious young man voice | Business, professional content | +| `Chinese (Mandarin)_Kind-hearted_Antie` | 热心大婶 | Kind-hearted auntie voice | Warm, friendly content | +| `Chinese (Mandarin)_HK_Flight_Attendant` | 港普空姐 | HK accent flight attendant voice | Regional character, entertainment | +| `Chinese (Mandarin)_Humorous_Elder` | 搞笑大爷 | Humorous old man voice | Comedy, entertainment | +| `Chinese (Mandarin)_Gentleman` | 温润男声 | Gentle, refined male voice | Narration, storytelling | +| `Chinese (Mandarin)_Warm_Bestie` | 温暖闺蜜 | Warm bestie female voice | Friendly, supportive content | +| `Chinese (Mandarin)_Male_Announcer` | 播报男声 | Male announcer voice | Announcements, broadcasts | +| `Chinese (Mandarin)_Sweet_Lady` | 甜美女声 | Sweet lady voice | Soft, gentle content | +| `Chinese (Mandarin)_Southern_Young_Man` | 南方小哥 | Southern young man voice | Regional character, casual content | +| `Chinese (Mandarin)_Wise_Women` | 阅历姐姐 | Experienced wise woman voice | Advice, guidance content | +| `Chinese (Mandarin)_Gentle_Youth` | 温润青年 | Gentle young man voice | Narration, calm content | +| `Chinese (Mandarin)_Warm_Girl` | 温暖少女 | Warm young girl voice | Friendly, supportive content | +| `Chinese (Mandarin)_Kind-hearted_Elder` | 花甲奶奶 | Kind elderly lady voice | Stories, warm content | +| `Chinese (Mandarin)_Cute_Spirit` | 憨憨萌兽 | Cute cartoon spirit voice | Animations, children's content | +| `Chinese (Mandarin)_Radio_Host` | 电台男主播 | Radio host male voice | Podcasts, radio shows | +| `Chinese (Mandarin)_Lyrical_Voice` | 抒情男声 | Lyrical male singing voice | Music, singing content | +| `Chinese (Mandarin)_Straightforward_Boy` | 率真弟弟 | Frank, straightforward boy voice | Casual, direct content | +| `Chinese (Mandarin)_Sincere_Adult` | 真诚青年 | Sincere young adult voice | Honest, genuine content | +| `Chinese (Mandarin)_Gentle_Senior` | 温柔学姐 | Gentle senior girl voice | Campus stories, supportive content | +| `Chinese (Mandarin)_Stubborn_Friend` | 嘴硬竹马 | Stubborn childhood friend voice | Drama, character-driven content | +| `Chinese (Mandarin)_Crisp_Girl` | 清脆少女 | Crisp, clear young girl voice | Clear, bright content | +| `Chinese (Mandarin)_Pure-hearted_Boy` | 清澈邻家弟弟 | Pure-hearted neighbor boy voice | Innocent, wholesome content | +| `Chinese (Mandarin)_Soft_Girl` | 柔和少女 | Soft, gentle girl voice | Calm, soothing content | + +### Chinese Cantonese Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Cantonese_ProfessionalHost(F)` | 专业女主持 | Professional female host voice | Cantonese broadcasts, hosting | +| `Cantonese_GentleLady` | 温柔女声 | Gentle Cantonese female voice | Soft, warm Cantonese content | +| `Cantonese_ProfessionalHost(M)` | 专业男主持 | Professional male host voice | Cantonese broadcasts, hosting | +| `Cantonese_PlayfulMan` | 活泼男声 | Playful Cantonese male voice | Entertainment, casual content | +| `Cantonese_CuteGirl` | 可爱女孩 | Cute Cantonese girl voice | Children's content, animations | +| `Cantonese_KindWoman` | 善良女声 | Kind Cantonese female voice | Warm, friendly content | + +### English Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Santa_Claus` | Santa Claus | Festive, jolly male voice | Holiday content, children's stories | +| `Grinch` | Grinch | Whiny, mischievous voice | Comedy, entertainment, holiday | +| `Rudolph` | Rudolph | Cute, nasal reindeer voice | Children's content, holiday | +| `Arnold` | Arnold | Deep, robotic terminator voice | Sci-fi, action, character roles | +| `Charming_Santa` | Charming Santa | Smooth, charismatic Santa voice | Holiday, entertainment | +| `Charming_Lady` | Charming Lady | Elegant, sophisticated female voice | Professional, romance | +| `Sweet_Girl` | Sweet Girl | Sweet, innocent young girl voice | Children's content, friendly | +| `Cute_Elf` | Cute Elf | Playful, tiny elf voice | Fantasy, children's content | +| `Attractive_Girl` | Attractive Girl | Attractive, engaging female voice | Entertainment, marketing | +| `Serene_Woman` | Serene Woman | Calm, peaceful female voice | Meditation, relaxation | +| `English_Trustworthy_Man` | Trustworthy Man | Reliable, sincere male voice | Business, narration | +| `English_Graceful_Lady` | Graceful Lady | Elegant, refined female voice | Formal, professional | +| `English_Aussie_Bloke` | Aussie Bloke | Casual, friendly Australian male voice | Casual, entertainment | +| `English_Whispering_girl` | Whispering Girl | Soft, whisper voice | Romance, intimate content | +| `English_Diligent_Man` | Diligent Man | Hardworking, earnest male voice | Motivational, educational | +| `English_Gentle-voiced_man` | Gentle-voiced Man | Soft-spoken, kind male voice | Calm, supportive content | + +### Japanese Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Japanese_IntellectualSenior` | Intellectual Senior | Wise, knowledgeable elder voice | Narration, educational | +| `Japanese_DecisivePrincess` | Decisive Princess | Confident, royal princess voice | Animation, games, drama | +| `Japanese_LoyalKnight` | Loyal Knight | Brave, faithful knight voice | Fantasy, games, stories | +| `Japanese_DominantMan` | Dominant Man | Powerful, commanding male voice | Action, leadership | +| `Japanese_SeriousCommander` | Serious Commander | Stern, authoritative commander voice | Military, games | +| `Japanese_ColdQueen` | Cold Queen | Distant, majestic queen voice | Drama, fantasy | +| `Japanese_DependableWoman` | Dependable Woman | Reliable, supportive female voice | Supportive, guidance | +| `Japanese_GentleButler` | Gentle Butler | Polite, refined servant voice | Comedy, animation | +| `Japanese_KindLady` | Kind Lady | Warm, gentle noblewoman voice | Warm, comforting | +| `Japanese_CalmLady` | Calm Lady | Composed, serene female voice | Meditation, relaxation | +| `Japanese_OptimisticYouth` | Optimistic Youth | Cheerful, positive young person voice | Youth content, motivation | +| `Japanese_GenerousIzakayaOwner` | Generous Izakaya Owner | Friendly, welcoming tavern owner voice | Casual, comedy | +| `Japanese_SportyStudent` | Sporty Student | Energetic athletic student voice | Sports, youth content | +| `Japanese_InnocentBoy` | Innocent Boy | Pure, naive young boy voice | Children's content | +| `Japanese_GracefulMaiden` | Graceful Maiden | Elegant, gentle young woman voice | Romance, drama | + +### Korean Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Korean_SweetGirl` | Sweet Girl | Sweet, adorable young girl voice | Children's content, romance | +| `Korean_CheerfulBoyfriend` | Cheerful Boyfriend | Energetic, loving boyfriend voice | Romance, dating content | +| `Korean_EnchantingSister` | Enchanting Sister | Charming, captivating sister voice | Family, drama | +| `Korean_ShyGirl` | Shy Girl | Timid, reserved girl voice | Comedy, romance | +| `Korean_ReliableSister` | Reliable Sister | Trustworthy, dependable sister voice | Supportive, guidance | +| `Korean_StrictBoss` | Strict Boss | Authoritative, demanding boss voice | Business, drama | +| `Korean_SassyGirl` | Sassy Girl | Bold, witty girl voice | Comedy, entertainment | +| `Korean_ChildhoodFriendGirl` | Childhood Friend Girl | Familiar, friendly childhood friend voice | Romance, nostalgia | +| `Korean_PlayboyCharmer` | Playboy Charmer | Smooth, flirtatious male voice | Romance, entertainment | +| `Korean_ElegantPrincess` | Elegant Princess | Graceful, royal princess voice | Animation, fantasy | +| `Korean_BraveFemaleWarrior` | Brave Female Warrior | Courageous female warrior voice | Action, fantasy | +| `Korean_BraveYouth` | Brave Youth | Heroic young person voice | Action, youth | +| `Korean_CalmLady` | Calm Lady | Composed, serene female voice | Meditation, relaxation | +| `Korean_EnthusiasticTeen` | EnthusiasticTeen | Excited, energetic teenager voice | Youth content | +| `Korean_SoothingLady` | Soothing Lady | Calming, comforting female voice | Relaxation, support | +| `Korean_IntellectualSenior` | Intellectual Senior | Wise, knowledgeable elder voice | Educational, narration | +| `Korean_LonelyWarrior` | Lonely Warrior | Solitary, melancholic warrior voice | Drama, fantasy | +| `Korean_MatureLady` | MatureLady | Sophisticated, adult female voice | Professional, drama | +| `Korean_InnocentBoy` | Innocent Boy | Pure, naive young boy voice | Children's content | +| `Korean_CharmingSister` | Charming Sister | Attractive, delightful sister voice | Family, romance | +| `Korean_AthleticStudent` | Athletic Student | Sporty, energetic student voice | Sports, youth | +| `Korean_BraveAdventurer` | Brave Adventurer | Courageous explorer voice | Adventure, fantasy | +| `Korean_CalmGentleman` | Calm Gentleman | Composed, refined gentleman voice | Formal, professional | +| `Korean_WiseElf` | Wise Elf | Ancient, mystical elf voice | Fantasy, narration | +| `Korean_CheerfulCoolJunior` | Cheerful Cool Junior | Popular, friendly junior voice | Youth, entertainment | +| `Korean_DecisiveQueen` | Decisive Queen | Authoritative, commanding queen voice | Drama, fantasy | +| `Korean_ColdYoungMan` | Cold Young Man | Distant, aloof young man voice | Drama, romance | +| `Korean_MysteriousGirl` | Mysterious Girl | Enigmatic, secretive girl voice | Mystery, drama | +| `Korean_QuirkyGirl` | Quirky Girl | Eccentric, unique girl voice | Comedy, entertainment | +| `Korean_ConsiderateSenior` | Considerate Senior | Thoughtful, caring elder voice | Warm, supportive | +| `Korean_CheerfulLittleSister` | Cheerful Little Sister | Playful, adorable younger sister voice | Family, comedy | +| `Korean_DominantMan` | Dominant Man | Powerful, commanding male voice | Leadership, action | +| `Korean_AirheadedGirl` | Airheaded Girl | Bubbly, spacey girl voice | Comedy, entertainment | +| `Korean_ReliableYouth` | Reliable Youth | Trustworthy, dependable young person voice | Supportive, youth | +| `Korean_FriendlyBigSister` | Friendly Big Sister | Warm, protective elder sister voice | Family, support | +| `Korean_GentleBoss` | Gentle Boss | Kind, understanding boss voice | Business, supportive | +| `Korean_ColdGirl` | Cold Girl | Aloof, distant girl voice | Drama, romance | +| `Korean_HaughtyLady` | Haughty Lady | Arrogant, proud woman voice | Drama, comedy | +| `Korean_CharmingElderSister` | Charming Elder Sister | Attractive, graceful elder sister voice | Romance, family | +| `Korean_IntellectualMan` | Intellectual Man | Smart, knowledgeable male voice | Educational, professional | +| `Korean_CaringWoman` | Caring Woman | Nurturing, supportive woman voice | Supportive, warm | +| `Korean_WiseTeacher` | Wise Teacher | Experienced, knowledgeable teacher voice | Educational | +| `Korean_ConfidentBoss` | Confident Boss | Self-assured, capable boss voice | Business, leadership | +| `Korean_AthleticGirl` | Athletic Girl | Sporty, energetic girl voice | Sports, fitness | +| `Korean_PossessiveMan` | PossessiveMan | Intense, protective male voice | Romance, drama | +| `Korean_GentleWoman` | Gentle Woman | Soft-spoken, kind woman voice | Calm, supportive | +| `Korean_CockyGuy` | Cocky Guy | Confident, slightly arrogant male voice | Comedy, entertainment | +| `Korean_ThoughtfulWoman` | ThoughtfulWoman | Reflective, caring woman voice | Drama, support | +| `Korean_OptimisticYouth` | Optimistic Youth | Positive, hopeful young person voice | Motivation, youth | + +### Spanish Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Spanish_SereneWoman` | Serene Woman | Calm, peaceful female voice | Relaxation, meditation | +| `Spanish_MaturePartner` | Mature Partner | Sophisticated, adult partner voice | Romance, drama | +| `Spanish_CaptivatingStoryteller` | Captivating Storyteller | Engaging, magnetic narrator voice | Audiobooks, storytelling | +| `Spanish_Narrator` | Narrator | Professional narrative voice | Documentaries, narration | +| `Spanish_WiseScholar` | Wise Scholar | Knowledgeable, wise scholar voice | Educational, historical | +| `Spanish_Kind-heartedGirl` | Kind-hearted Girl | Warm, compassionate girl voice | Children's content, warm | +| `Spanish_DeterminedManager` | Determined Manager | Ambitious, driven manager voice | Business, motivation | +| `Spanish_BossyLeader` | Bossy Leader | Commanding, authoritative leader voice | Leadership, drama | +| `Spanish_ReservedYoungMan` | Reserved Young Man | Quiet, introverted young man voice | Drama, realistic characters | +| `Spanish_ConfidentWoman` | Confident Woman | Self-assured, capable woman voice | Professional, empowerment | +| `Spanish_ThoughtfulMan` | ThoughtfulMan | Reflective, intelligent man voice | Educational, drama | +| `Spanish_Strong-WilledBoy` | Strong-willed Boy | Determined, persistent boy voice | Youth, motivation | +| `Spanish_SophisticatedLady` | SophisticatedLady | Elegant, refined woman voice | Formal, romance | +| `Spanish_RationalMan` | Rational Man | Logical, analytical man voice | Educational, business | +| `Spanish_AnimeCharacter` | Anime Character | Exaggerated anime-style voice | Animation, entertainment | +| `Spanish_Deep-tonedMan` | Deep-toned Man | Deep, resonant male voice | Attractive, commanding | +| `Spanish_Fussyhostess` | Fussy Hostess | Particular, demanding hostess voice | Comedy, drama | +| `Spanish_SincereTeen` | SincereTeen | Honest, genuine teenager voice | Youth, authentic | +| `Spanish_FrankLady` | Frank Lady | Direct, honest woman voice | Comedy, drama | +| `Spanish_Comedian` | Comedian | Humorous, entertaining voice | Comedy, entertainment | +| `Spanish_Debator` | Debator | Argumentative, persuasive voice | Debate, discussion | +| `Spanish_ToughBoss` | Tough Boss | Harsh, demanding boss voice | Business, drama | +| `Spanish_Wiselady` | Wise Lady | Experienced, wise woman voice | Guidance, advice | +| `Spanish_Steadymentor` | Steady Mentor | Reliable, supportive mentor voice | Educational, guidance | +| `Spanish_Jovialman` | Jovial Man | Cheerful, friendly man voice | Entertainment, casual | +| `Spanish_SantaClaus` | Santa Claus | Festive Santa voice | Holiday, children | +| `Spanish_Rudolph` | Rudolph | Reindeer voice | Holiday, children | +| `Spanish_Intonategirl` | Intonate Girl | Musical, melodic girl voice | Music, singing | +| `Spanish_Arnold` | Arnold | Robotic, mechanical voice | Sci-fi, action | +| `Spanish_Ghost` | Ghost | Spooky, ethereal voice | Horror, mystery | +| `Spanish_HumorousElder` | Humorous Elder | Funny, elderly person voice | Comedy, entertainment | +| `Spanish_EnergeticBoy` | Energetic Boy | Active, lively boy voice | Youth, sports | +| `Spanish_WhimsicalGirl` | Whimsical Girl | Playful, imaginative girl voice | Children's, fantasy | +| `Spanish_StrictBoss` | Strict Boss | Strict, demanding boss voice | Business, education | +| `Spanish_ReliableMan` | Reliable Man | Trustworthy, dependable man voice | Professional, support | +| `Spanish_SereneElder` | Serene Elder | Calm, peaceful elderly voice | Meditation, wisdom | +| `Spanish_AngryMan` | Angry Man | Frustrated, irritated male voice | Drama, comedy | +| `Spanish_AssertiveQueen` | Assertive Queen | Confident, commanding queen voice | Drama, fantasy | +| `Spanish_CaringGirlfriend` | Caring Girlfriend | Nurturing, loving girlfriend voice | Romance, relationship | +| `Spanish_PowerfulSoldier` | Powerful Soldier | Strong, brave soldier voice | Action, military | +| `Spanish_PassionateWarrior` | Passionate Warrior | Fierce, dedicated warrior voice | Action, fantasy | +| `Spanish_ChattyGirl` | Chatty Girl | Talkative, sociable girl voice | Comedy, social | +| `Spanish_RomanticHusband` | Romantic Husband | Loving, romantic husband voice | Romance, family | +| `Spanish_CompellingGirl` | CompellingGirl | Persuasive, magnetic girl voice | Marketing, entertainment | +| `Spanish_PowerfulVeteran` | Powerful Veteran | Experienced, strong veteran voice | Military, drama | +| `Spanish_SensibleManager` | Sensible Manager | Practical, reasonable manager voice | Business, guidance | +| `Spanish_ThoughtfulLady` | Thoughtful Lady | Considerate, kind lady voice | Supportive, advice | + +### Portuguese Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Portuguese_SentimentalLady` | Sentimental Lady | Emotional, sensitive lady voice | Drama, romance | +| `Portuguese_BossyLeader` | Bossy Leader | Commanding, authoritative leader voice | Leadership, drama | +| `Portuguese_Wiselady` | Wise Lady | Experienced, wise woman voice | Guidance, advice | +| `Portuguese_Strong-WilledBoy` | Strong-willed Boy | Determined, persistent boy voice | Youth, motivation | +| `Portuguese_Deep-VoicedGentleman` | Deep-voiced Gentleman | Deep, rich male voice | Attractive, commanding | +| `Portuguese_UpsetGirl` | Upset Girl | Distressed, emotional girl voice | Drama, realistic | +| `Portuguese_PassionateWarrior` | Passionate Warrior | Fierce, dedicated warrior voice | Action, fantasy | +| `Portuguese_AnimeCharacter` | Anime Character | Exaggerated anime-style voice | Animation, entertainment | +| `Portuguese_ConfidentWoman` | Confident Woman | Self-assured, capable woman voice | Professional, empowerment | +| `Portuguese_AngryMan` | Angry Man | Frustrated, irritated male voice | Drama, comedy | +| `Portuguese_CaptivatingStoryteller` | Captivating Storyteller | Engaging, magnetic narrator voice | Audiobooks, storytelling | +| `Portuguese_Godfather` | Godfather | Authoritative, powerful father figure voice | Drama, powerful | +| `Portuguese_ReservedYoungMan` | Reserved Young Man | Quiet, introverted young man voice | Drama, realistic | +| `Portuguese_SmartYoungGirl` | Smart Young Girl | Intelligent, clever girl voice | Educational, youth | +| `Portuguese_Kind-heartedGirl` | Kind-hearted Girl | Warm, compassionate girl voice | Children's content, warm | +| `Portuguese_Pompouslady` | Pompous Lady | Self-important, arrogant lady voice | Comedy, drama | +| `Portuguese_Grinch` | Grinch | Whiny, mischievous voice | Comedy, entertainment | +| `Portuguese_Debator` | Debator | Argumentative, persuasive voice | Debate, discussion | +| `Portuguese_SweetGirl` | Sweet Girl | Sweet, adorable girl voice | Children's content, romance | +| `Portuguese_AttractiveGirl` | Attractive Girl | Charming, appealing girl voice | Entertainment, romance | +| `Portuguese_ThoughtfulMan` | Thoughtful Man | Reflective, intelligent man voice | Educational, drama | +| `Portuguese_PlayfulGirl` | Playful Girl | Playful, fun-loving girl voice | Comedy, children's content | +| `Portuguese_GorgeousLady` | Gorgeous Lady | Beautiful, stunning lady voice | Romance, entertainment | +| `Portuguese_LovelyLady` | Lovely Lady | Sweet, endearing lady voice | Warm, friendly | +| `Portuguese_SereneWoman` | Serene Woman | Calm, peaceful female voice | Relaxation, meditation | +| `Portuguese_SadTeen` | Sad Teen | Melancholic, teenage voice | Drama, emotional | +| `Portuguese_MaturePartner` | Mature Partner | Sophisticated, adult partner voice | Romance, drama | +| `Portuguese_Comedian` | Comedian | Humorous, entertaining voice | Comedy, entertainment | +| `Portuguese_NaughtySchoolgirl` | Naughty Schoolgirl | Mischievous, playful student voice | Comedy, school | +| `Portuguese_Narrator` | Narrator | Professional narrative voice | Documentaries, narration | +| `Portuguese_ToughBoss` | Tough Boss | Harsh, demanding boss voice | Business, drama | +| `Portuguese_Fussyhostess` | Fussy Hostess | Particular, demanding hostess voice | Comedy, drama | +| `Portuguese_Dramatist` | Dramatist | Theatrical, expressive voice | Drama, storytelling | +| `Portuguese_Steadymentor` | Steady Mentor | Reliable, supportive mentor voice | Educational, guidance | +| `Portuguese_Jovialman` | Jovial Man | Cheerful, friendly man voice | Entertainment, casual | +| `Portuguese_CharmingQueen` | Charming Queen | Elegant, captivating queen voice | Drama, fantasy | +| `Portuguese_SantaClaus` | Santa Claus | Festive Santa voice | Holiday, children | +| `Portuguese_Rudolph` | Rudolph | Reindeer voice | Holiday, children | +| `Portuguese_Arnold` | Arnold | Robotic, mechanical voice | Sci-fi, action | +| `Portuguese_CharmingSanta` | Charming Santa | Smooth, charismatic Santa voice | Holiday, entertainment | +| `Portuguese_CharmingLady` | Charming Lady | Elegant, sophisticated lady voice | Professional, romance | +| `Portuguese_Ghost` | Ghost | Spooky, ethereal voice | Horror, mystery | +| `Portuguese_HumorousElder` | Humorous Elder | Funny, elderly person voice | Comedy, entertainment | +| `Portuguese_CalmLeader` | Calm Leader | Composed, steady leader voice | Leadership, guidance | +| `Portuguese_GentleTeacher` | Gentle Teacher | Kind, patient teacher voice | Educational, supportive | +| `Portuguese_EnergeticBoy` | Energetic Boy | Active, lively boy voice | Youth, sports | +| `Portuguese_ReliableMan` | Reliable Man | Trustworthy, dependable man voice | Professional, support | +| `Portuguese_SereneElder` | Serene Elder | Calm, peaceful elderly voice | Meditation, wisdom | +| `Portuguese_GrimReaper` | Grim Reaper | Dark, ominous voice | Horror, fantasy | +| `Portuguese_AssertiveQueen` | Assertive Queen | Confident, commanding queen voice | Drama, fantasy | +| `Portuguese_WhimsicalGirl` | Whimsical Girl | Playful, imaginative girl voice | Children's, fantasy | +| `Portuguese_StressedLady` | Stressed Lady | Anxious, overwhelmed lady voice | Comedy, realistic | +| `Portuguese_FriendlyNeighbor` | Friendly Neighbor | Warm, helpful neighbor voice | Community, family | +| `Portuguese_CaringGirlfriend` | Caring Girlfriend | Nurturing, loving girlfriend voice | Romance, relationship | +| `Portuguese_PowerfulSoldier` | Powerful Soldier | Strong, brave soldier voice | Action, military | +| `Portuguese_FascinatingBoy` | Fascinating Boy | Charming, intriguing boy voice | Romance, youth | +| `Portuguese_RomanticHusband` | Romantic Husband | Loving, romantic husband voice | Romance, family | +| `Portuguese_StrictBoss` | Strict Boss | Strict, demanding boss voice | Business, education | +| `Portuguese_InspiringLady` | Inspiring Lady | Motivating, encouraging lady voice | Motivation, leadership | +| `Portuguese_PlayfulSpirit` | Playful Spirit | Cheerful, mischievous spirit voice | Fantasy, children's | +| `Portuguese_ElegantGirl` | Elegant Girl | Graceful, refined girl voice | Formal, romance | +| `Portuguese_CompellingGirl` | Compelling Girl | Persuasive, magnetic girl voice | Marketing, entertainment | +| `Portuguese_PowerfulVeteran` | Powerful Veteran | Experienced, strong veteran voice | Military, drama | +| `Portuguese_SensibleManager` | Sensible Manager | Practical, reasonable manager voice | Business, guidance | +| `Portuguese_ThoughtfulLady` | Thoughtful Lady | Considerate, kind lady voice | Supportive, advice | +| `Portuguese_TheatricalActor` | Theatrical Actor | Dramatic, expressive actor voice | Drama, entertainment | +| `Portuguese_FragileBoy` | Fragile Boy | Sensitive, vulnerable boy voice | Drama, emotional | +| `Portuguese_ChattyGirl` | Chatty Girl | Talkative, sociable girl voice | Comedy, social | +| `Portuguese_Conscientiousinstructor` | Conscientious Instructor | Careful, diligent instructor voice | Educational, training | +| `Portuguese_RationalMan` | Rational Man | Logical, analytical man voice | Educational, business | +| `Portuguese_WiseScholar` | Wise Scholar | Knowledgeable, wise scholar voice | Educational, historical | +| `Portuguese_FrankLady` | Frank Lady | Direct, honest woman voice | Comedy, drama | +| `Portuguese_DeterminedManager` | Determined Manager | Ambitious, driven manager voice | Business, motivation | + +### French Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `French_Male_Speech_New` | Level-Headed Man | Calm, reasonable male voice | Professional, narration | +| `French_Female_News Anchor` | Patient Female Presenter | Clear, patient news presenter voice | News, broadcasts | +| `French_CasualMan` | Casual Man | Relaxed, informal male voice | Casual, entertainment | +| `French_MovieLeadFemale` | Movie Lead Female | Dramatic, expressive female voice | Drama, entertainment | +| `French_FemaleAnchor` | Female Anchor | Professional female anchor voice | News, broadcasts | + +### Indonesian Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Indonesian_SweetGirl` | Sweet Girl | Sweet, adorable girl voice | Children's content, friendly | +| `Indonesian_ReservedYoungMan` | Reserved Young Man | Quiet, introverted young man voice | Drama, realistic | +| `Indonesian_CharmingGirl` | Charming Girl | Attractive, appealing girl voice | Entertainment, romance | +| `Indonesian_CalmWoman` | Calm Woman | Composed, peaceful female voice | Relaxation, meditation | +| `Indonesian_ConfidentWoman` | Confident Woman | Self-assured, capable woman voice | Professional, empowerment | +| `Indonesian_CaringMan` | Caring Man | Nurturing, supportive man voice | Supportive, family | +| `Indonesian_BossyLeader` | Bossy Leader | Commanding, authoritative leader voice | Leadership, drama | +| `Indonesian_DeterminedBoy` | Determined Boy | Ambitious, persistent boy voice | Youth, motivation | +| `Indonesian_GentleGirl` | Gentle Girl | Soft-spoken, kind girl voice | Calm, supportive | + +### German Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `German_FriendlyMan` | Friendly Man | Warm, approachable male voice | Casual, friendly | +| `German_SweetLady` | Sweet Lady | Pleasant, kind lady voice | Warm, supportive | +| `German_PlayfulMan` | Playful Man | Fun-loving, humorous male voice | Comedy, entertainment | + +### Russian Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Russian_HandsomeChildhoodFriend` | Handsome Childhood Friend | Charming childhood friend voice | Romance, nostalgia | +| `Russian_BrightHeroine` | Bright Queen | Lively, strong female lead voice | Drama, action | +| `Russian_AmbitiousWoman` | Ambitious Woman | Driven, determined woman voice | Professional, motivation | +| `Russian_ReliableMan` | Reliable Man | Trustworthy, dependable man voice | Professional, support | +| `Russian_CrazyQueen` | Crazy Girl | Wild, unpredictable female voice | Comedy, drama | +| `Russian_PessimisticGirl` | Pessimistic Girl | Gloomy, negative girl voice | Comedy, drama | +| `Russian_AttractiveGuy` | Attractive Guy | Charming, appealing male voice | Romance, entertainment | +| `Russian_Bad-temperedBoy` | Bad-tempered Boy | Irritable, grumpy boy voice | Comedy, drama | + +### Italian Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Italian_BraveHeroine` | Brave Heroine | Courageous, heroic female voice | Action, fantasy | +| `Italian_Narrator` | Narrator | Professional narrative voice | Documentaries, storytelling | +| `Italian_WanderingSorcerer` | Wandering Sorcerer | Mysterious, traveling magician voice | Fantasy, adventure | +| `Italian_DiligentLeader` | Diligent Leader | Hardworking, dedicated leader voice | Leadership, business | + +### Arabic Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Arabic_CalmWoman` | Calm Woman | Composed, peaceful female voice | Relaxation, meditation | +| `Arabic_FriendlyGuy` | Friendly Guy | Warm, approachable male voice | Casual, friendly | + +### Turkish Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Turkish_CalmWoman` | Calm Woman | Composed, peaceful female voice | Relaxation, meditation | +| `Turkish_Trustworthyman` | Trustworthy Man | Reliable, sincere male voice | Professional, business | + +### Ukrainian Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Ukrainian_CalmWoman` | Calm Woman | Composed, peaceful female voice | Relaxation, meditation | +| `Ukrainian_WiseScholar` | Wise Scholar | Knowledgeable, wise scholar voice | Educational, historical | + +### Dutch Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Dutch_kindhearted_girl` | Kind-hearted girl | Warm, compassionate girl voice | Children's content, warm | +| `Dutch_bossy_leader` | Bossy leader | Commanding, authoritative leader voice | Leadership, drama | + +### Vietnamese Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Vietnamese_kindhearted_girl` | Kind-hearted girl | Warm, compassionate girl voice | Children's content, warm | + +### Thai Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Thai_male_1_sample8` | Serene Man | Calm, peaceful male voice | Relaxation, meditation | +| `Thai_male_2_sample2` | Friendly Man | Warm, approachable male voice | Casual, friendly | +| `Thai_female_1_sample1` | Confident Woman | Self-assured, capable woman voice | Professional, empowerment | +| `Thai_female_2_sample2` | Energetic Woman | Active, lively female voice | Motivation, energy | + +### Polish Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Polish_male_1_sample4` | Male Narrator | Professional narrative voice | Documentaries, narration | +| `Polish_male_2_sample3` | Male Anchor | Professional male anchor voice | News, broadcasts | +| `Polish_female_1_sample1` | Calm Woman | Composed, peaceful female voice | Relaxation, meditation | +| `Polish_female_2_sample3` | Casual Woman | Relaxed, informal female voice | Casual, entertainment | + +### Romanian Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `Romanian_male_1_sample2` | Reliable Man | Trustworthy, dependable man voice | Professional, support | +| `Romanian_male_2_sample1` | Energetic Youth | Active, lively young person voice | Youth, motivation | +| `Romanian_female_1_sample4` | Optimistic Youth | Positive, hopeful young person voice | Motivation, youth | +| `Romanian_female_2_sample1` | Gentle Woman | Soft-spoken, kind woman voice | Calm, supportive | + +### Greek Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `greek_male_1a_v1` | Thoughtful Mentor | Reflective, wise mentor voice | Educational, guidance | +| `Greek_female_1_sample1` | Gentle Lady | Soft-spoken, kind lady voice | Calm, supportive | +| `Greek_female_2_sample3` | Girl Next Door | Friendly, approachable girl voice | Casual, friendly | + +### Czech Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `czech_male_1_v1` | Assured Presenter | Confident, professional presenter voice | Presentations, broadcasts | +| `czech_female_5_v7` | Steadfast Narrator | Reliable, consistent narrator voice | Documentaries, storytelling | +| `czech_female_2_v2` | Elegant Lady | Graceful, refined lady voice | Formal, professional | + +### Finnish Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `finnish_male_3_v1` | Upbeat Man | Cheerful, energetic male voice | Motivation, entertainment | +| `finnish_male_1_v2` | Friendly Boy | Warm, approachable boy voice | Children's content, friendly | +| `finnish_female_4_v1` | Assertive Woman | Confident, strong female voice | Professional, empowerment | + +### Hindi Voices + +| voice_id | Name | Description | Best For | +|----------|------|-------------|----------| +| `hindi_male_1_v2` | Trustworthy Advisor | Reliable, wise advisor voice | Guidance, advice | +| `hindi_female_2_v1` | Tranquil Woman | Calm, peaceful female voice | Relaxation, meditation | +| `hindi_female_1_v2` | News Anchor | Professional news anchor voice | News, broadcasts | + +--- + +## Voice Parameters + +### VoiceSetting Dataclass + +```python +from utils import VoiceSetting + +voice = VoiceSetting( + voice_id="male-qn-qingse", # Required: Voice ID + speed=1.0, # Optional: 0.5 (slower) to 2.0 (faster), default 1.0 + volume=1.0, # Optional: 0.1 (quieter) to 10.0 (louder), default 1.0 + pitch=0, # Optional: -12 (deeper) to 12 (higher), default 0 + emotion="calm", # Optional: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper +) +``` + +### Parameter Guidelines + +**Speed** +- 0.75: Slower, deliberate speech (news, tutorials) +- 1.0: Normal pace (most content) +- 1.25: Slightly faster (energetic content) +- 1.5+: Fast pace (time-sensitive content) + +**Volume** +- 0.8-1.0: Normal listening levels +- 1.0-1.5: Louder for attention-grabbing content +- < 0.8: Softer, intimate feeling + +**Pitch** +- -6 to -3: Deeper, more authoritative +- 0: Natural pitch +- +3 to +6: Higher, more energetic + +**Emotion** +- `calm`: Calm, neutral tone +- `fluent`: Fluent, natural tone +- `whisper`: Whisper, soft, gentle tone +- `happy`: Cheerful, upbeat tone +- `sad`: Melancholic, somber tone +- `angry`: Frustrated, intense tone +- `fearful`: Anxious, nervous tone +- `disgusted`: Repulsed, revolted tone +- `surprised`: Astonished, amazed tone + + +## Custom Voices + +### Voice Cloning + +Create custom voices from audio samples for unique brand voices. + +**Requirements:** +- Source audio: 10 seconds to 5 minutes +- Format: mp3, wav, m4a +- Size: Max 20MB +- Quality: Clear, no background noise, single speaker + +**Best Practices:** +- Use 30-60 seconds of clean speech +- Include varied intonation and emotion +- Record in quiet environment +- Consistent volume throughout + +### Voice Design + +Generate new voices through text descriptions for creative projects. + +**When to Use:** +- No existing voice matches your needs +- Need unique character voices +- Prototype before full voice cloning + +**Prompt Guidelines:** +- Include: gender, age, vocal characteristics, emotional tone, use case +- Be specific about pacing, tone, and intended audience +- Example: "A warm, grandmotherly voice with gentle pacing, perfect for bedtime stories" + diff --git a/skills/frontend-dev/references/motion-recipes.md b/skills/frontend-dev/references/motion-recipes.md new file mode 100644 index 0000000..589ae9b --- /dev/null +++ b/skills/frontend-dev/references/motion-recipes.md @@ -0,0 +1,407 @@ +# Motion Recipes + +Production-ready animation code snippets. Copy and adapt as needed. + +## 1. Scroll-Triggered Reveal (Framer Motion) + +Elements fade and slide up when entering viewport. + +```tsx +"use client"; +import { motion } from "framer-motion"; + +const fadeSlideUp = { + hidden: { opacity: 0, y: 40 }, + visible: { + opacity: 1, + y: 0, + transition: { type: "spring", stiffness: 100, damping: 20 }, + }, +}; + +export function RevealSection({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} +``` + +## 2. Staggered List Orchestration (Framer Motion) + +Children animate sequentially with blur effect. + +```tsx +"use client"; +import { motion } from "framer-motion"; + +const container = { + hidden: {}, + visible: { transition: { staggerChildren: 0.08, delayChildren: 0.1 } }, +}; + +const item = { + hidden: { opacity: 0, y: 24, filter: "blur(4px)" }, + visible: { + opacity: 1, + y: 0, + filter: "blur(0px)", + transition: { type: "spring", stiffness: 120, damping: 20 }, + }, +}; + +export function StaggerGrid({ items }: { items: React.ReactNode[] }) { + return ( + + {items.map((child, i) => ( + + {child} + + ))} + + ); +} +``` + +## 3. GSAP ScrollTrigger Pinned Section + +Horizontal scroll panels with pinning. + +```tsx +"use client"; +import { useRef, useEffect } from "react"; +import gsap from "gsap"; +import { ScrollTrigger } from "gsap/ScrollTrigger"; + +gsap.registerPlugin(ScrollTrigger); + +export function PinnedTimeline() { + const containerRef = useRef(null); + const panelsRef = useRef(null); + + useEffect(() => { + const ctx = gsap.context(() => { + const panels = gsap.utils.toArray(".panel"); + gsap.to(panels, { + xPercent: -100 * (panels.length - 1), + ease: "none", + scrollTrigger: { + trigger: containerRef.current, + pin: true, + scrub: 1, + end: () => "+=" + (panelsRef.current?.scrollWidth ?? 0), + }, + }); + }, containerRef); + + return () => ctx.revert(); // CRITICAL: full cleanup + }, []); + + return ( + + + {/* .panel elements */} + + + ); +} +``` + +## 4. Parallax Tilt Card (Framer Motion) + +Mouse-tracking 3D perspective. Uses `useMotionValue` — never `useState`. + +```tsx +"use client"; +import { motion, useMotionValue, useTransform } from "framer-motion"; + +export function TiltCard({ children }: { children: React.ReactNode }) { + const x = useMotionValue(0.5); + const y = useMotionValue(0.5); + const rotateX = useTransform(y, [0, 1], [8, -8]); + const rotateY = useTransform(x, [0, 1], [-8, 8]); + + return ( + { + const rect = e.currentTarget.getBoundingClientRect(); + x.set((e.clientX - rect.left) / rect.width); + y.set((e.clientY - rect.top) / rect.height); + }} + onMouseLeave={() => { + x.set(0.5); + y.set(0.5); + }} + className="rounded-2xl bg-white shadow-lg" + > + {children} + + ); +} +``` + +## 5. Magnetic Button (Framer Motion) + +Cursor-attracted button. Pure `useMotionValue` — zero re-renders. + +```tsx +"use client"; +import { motion, useMotionValue, useSpring } from "framer-motion"; +import { useRef } from "react"; + +export function MagneticButton({ children }: { children: React.ReactNode }) { + const ref = useRef(null); + const x = useMotionValue(0); + const y = useMotionValue(0); + const springX = useSpring(x, { stiffness: 200, damping: 15 }); + const springY = useSpring(y, { stiffness: 200, damping: 15 }); + + return ( + { + const rect = ref.current!.getBoundingClientRect(); + const dx = e.clientX - (rect.left + rect.width / 2); + const dy = e.clientY - (rect.top + rect.height / 2); + x.set(dx * 0.3); + y.set(dy * 0.3); + }} + onMouseLeave={() => { + x.set(0); + y.set(0); + }} + > + {children} + + ); +} +``` + +## 6. Text Scramble / Decode Effect + +Matrix-style character reveal — pure JS, no library needed. + +```tsx +"use client"; +import { useEffect, useRef, useState } from "react"; + +const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +export function TextScramble({ text, className }: { text: string; className?: string }) { + const [display, setDisplay] = useState(text); + const iteration = useRef(0); + + useEffect(() => { + iteration.current = 0; + const id = setInterval(() => { + setDisplay( + text + .split("") + .map((char, i) => + i < iteration.current ? char : chars[Math.floor(Math.random() * chars.length)] + ) + .join("") + ); + iteration.current += 1 / 3; + if (iteration.current >= text.length) clearInterval(id); + }, 30); + return () => clearInterval(id); + }, [text]); + + return {display}; +} +``` + +## 7. SVG Path Draw on Scroll (CSS Scroll-Driven) + +Zero-JS scroll-linked path drawing using native CSS. + +```css +@supports (animation-timeline: scroll()) { + .draw-path { + stroke-dasharray: 1; + stroke-dashoffset: 1; + animation: draw linear; + animation-timeline: scroll(); + animation-range: entry 0% cover 60%; + } + + @keyframes draw { + to { + stroke-dashoffset: 0; + } + } +} +``` + +## 8. Horizontal Scroll Hijack (GSAP) + +Vertical scroll drives horizontal panning. + +```tsx +"use client"; +import { useRef, useEffect } from "react"; +import gsap from "gsap"; +import { ScrollTrigger } from "gsap/ScrollTrigger"; + +gsap.registerPlugin(ScrollTrigger); + +export function HorizontalScroll({ children }: { children: React.ReactNode }) { + const sectionRef = useRef(null); + const trackRef = useRef(null); + + useEffect(() => { + const ctx = gsap.context(() => { + const track = trackRef.current!; + const scrollWidth = track.scrollWidth - window.innerWidth; + gsap.to(track, { + x: -scrollWidth, + ease: "none", + scrollTrigger: { + trigger: sectionRef.current, + pin: true, + scrub: 0.8, + end: () => `+=${scrollWidth}`, + }, + }); + }, sectionRef); + return () => ctx.revert(); + }, []); + + return ( + + + {children} + + + ); +} +``` + +## 9. Particle Background (React Three Fiber) + +Isolated canvas layer. Purely decorative, pointer-events-none. + +```tsx +"use client"; +import { Canvas, useFrame } from "@react-three/fiber"; +import { useRef, useMemo } from "react"; +import * as THREE from "three"; + +function Particles({ count = 800 }) { + const mesh = useRef(null); + const positions = useMemo(() => { + const arr = new Float32Array(count * 3); + for (let i = 0; i < count * 3; i++) arr[i] = (Math.random() - 0.5) * 10; + return arr; + }, [count]); + + useFrame(({ clock }) => { + if (mesh.current) mesh.current.rotation.y = clock.getElapsedTime() * 0.05; + }); + + return ( + + + + + + + ); +} + +export function ParticleCanvas() { + return ( + + + + + + ); +} +``` + +## 10. Shared Layout Morph (Framer Motion) + +Card-to-modal expansion using `layoutId`. + +```tsx +"use client"; +import { motion, AnimatePresence } from "framer-motion"; +import { useState } from "react"; + +export function MorphCard({ id, preview, detail }: { + id: string; + preview: React.ReactNode; + detail: React.ReactNode; +}) { + const [open, setOpen] = useState(false); + return ( + <> + setOpen(true)} + className="cursor-pointer rounded-2xl bg-white p-6 shadow-md"> + {preview} + + + + {open && ( + <> + setOpen(false)} + /> + + {detail} + + > + )} + + > + ); +} +``` + +## Scroll Animation Patterns + +### Sticky Scroll Stack +Cards pin to top and stack over each other. +- Each card: `position: sticky; top: calc(var(--index) * 2rem)` +- Depth illusion: `scale(calc(1 - var(--index) * 0.03))` + +### Split-Screen Parallax +Two viewport halves scroll at different speeds. +- Left: `translateY` at 0.5x scroll speed (GSAP `scrub`) +- Mobile: collapse to single column, disable parallax + +### Zoom Parallax +Hero image scales 1 to 1.5 on scroll. +```tsx +scrollTrigger: { trigger: heroRef, start: "top top", end: "bottom top", scrub: true } +gsap.to(imageRef, { scale: 1.5, ease: "none" }); +``` + +### Text Mask Reveal +Large typography as window into video/image background. +- `background-clip: text` + `color: transparent` +- Animate `background-position` on scroll + +### Curtain Reveal +Hero splits in half, each side slides away on scroll. +- Two halves clipped with `clip-path: inset(0 50% 0 0)` and `inset(0 0 0 50%)` +- GSAP animates `xPercent: -100` and `xPercent: 100` diff --git a/skills/frontend-dev/references/troubleshooting.md b/skills/frontend-dev/references/troubleshooting.md new file mode 100644 index 0000000..464edad --- /dev/null +++ b/skills/frontend-dev/references/troubleshooting.md @@ -0,0 +1,85 @@ +# Troubleshooting + +## Quick reference + +| Error | Cause | Fix | +|-------|-------|-----| +| `MINIMAX_API_KEY is not set` | Key not set | `export MINIMAX_API_KEY="key"` | +| `401 Unauthorized` | Invalid/expired key | Check key validity | +| `429 Too Many Requests` | Rate limit | Add delays between requests | +| `TimeoutError` | Network or long text | Use async TTS for long text, check network | +| `invalid params, method t2a-v2 not have model` | Wrong model name | Use `speech-2.8-hd` (hyphens, not underscores) | +| `brotli: decoder process called...` | Encoding issue | Already fixed in utils.py (Accept-Encoding header) | + +## Environment + +### API key not set + +```bash +export MINIMAX_API_KEY="" + +# Verify +echo $MINIMAX_API_KEY +``` + +### FFmpeg not found + +```bash +# macOS +brew install ffmpeg + +# Ubuntu +sudo apt install ffmpeg + +# Verify +ffmpeg -version +``` + +### Missing Python packages + +```bash +pip install requests +``` + +## API errors + +### Authentication (401) + +- Verify API key is correct and not expired +- Check for extra spaces in key value + +### Rate limiting (429) + +Add delays between requests: +```python +import time +for text in texts: + result = tts(text) + time.sleep(1) +``` + +### Invalid model name + +Valid names (use hyphens, must include -hd or -turbo): +- `speech-2.8-hd` (recommended) +- `speech-2.8-turbo` +- `speech-2.6-hd` +- `speech-2.6-turbo` + +Wrong: `speech_01`, `speech_2.6`, `speech-01` + +## Audio issues + +### Poor quality + +Re-generate with higher settings: +```bash +python scripts/minimax_tts.py "text" -o out.mp3 --sample-rate 32000 --model speech-2.8-hd +``` + +### Invalid emotion + +Valid emotions: +- All models: happy, sad, angry, fearful, disgusted, surprised, calm +- speech-2.6 only: + fluent, whisper +- speech-2.8: auto-matched (leave empty, recommended) diff --git a/skills/frontend-dev/scripts/minimax_image.py b/skills/frontend-dev/scripts/minimax_image.py new file mode 100755 index 0000000..53e7010 --- /dev/null +++ b/skills/frontend-dev/scripts/minimax_image.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +""" +MiniMax Text-to-Image — synchronous generation. + +Usage: + python minimax_image.py "A cat in space" -o cat.png + python minimax_image.py "Mountain landscape" -o bg.png --ratio 16:9 + python minimax_image.py "Product icons" -o icons.png -n 4 --ratio 1:1 + +Env: MINIMAX_API_KEY (required) +""" + +import os +import sys +import json +import argparse +import requests + +API_KEY = os.getenv("MINIMAX_API_KEY") +API_BASE = "https://api.minimax.io/v1" + +ASPECT_RATIOS = ["1:1", "16:9", "4:3", "3:2", "2:3", "3:4", "9:16", "21:9"] + + +def _headers(): + if not API_KEY: + raise SystemExit("ERROR: MINIMAX_API_KEY is not set.\n export MINIMAX_API_KEY='your-key'") + return { + "Authorization": f"Bearer {API_KEY}", + "Content-Type": "application/json", + } + + +def generate_image( + prompt: str, + model: str = "image-01", + aspect_ratio: str = "1:1", + n: int = 1, + response_format: str = "url", + prompt_optimizer: bool = False, + seed: int = None, +) -> dict: + """Generate image(s). Returns API response dict.""" + payload = { + "model": model, + "prompt": prompt, + "aspect_ratio": aspect_ratio, + "n": n, + "response_format": response_format, + "prompt_optimizer": prompt_optimizer, + } + if seed is not None: + payload["seed"] = seed + + resp = requests.post( + f"{API_BASE}/image_generation", + headers=_headers(), + json=payload, + timeout=120, + ) + resp.raise_for_status() + data = resp.json() + + base_resp = data.get("base_resp", {}) + if base_resp.get("status_code", 0) != 0: + raise SystemExit(f"API Error [{base_resp.get('status_code')}]: {base_resp.get('status_msg')}") + + return data + + +def download_and_save(url: str, output_path: str): + """Download image from URL and save.""" + resp = requests.get(url, timeout=60) + resp.raise_for_status() + with open(output_path, "wb") as f: + f.write(resp.content) + return len(resp.content) + + +def main(): + p = argparse.ArgumentParser(description="MiniMax Text-to-Image") + p.add_argument("prompt", help="Image description (max 1500 chars)") + p.add_argument("-o", "--output", required=True, help="Output file path (.png/.jpg)") + p.add_argument("--model", default="image-01", help="Model (default: image-01)") + p.add_argument("--ratio", default="1:1", choices=ASPECT_RATIOS, help="Aspect ratio (default: 1:1)") + p.add_argument("-n", "--count", type=int, default=1, choices=range(1, 10), help="Number of images (1-9, default: 1)") + p.add_argument("--seed", type=int, default=None, help="Random seed for reproducibility") + p.add_argument("--optimize", action="store_true", help="Enable prompt auto-optimization") + p.add_argument("--base64", action="store_true", help="Use base64 response instead of URL") + args = p.parse_args() + + os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True) + + fmt = "base64" if args.base64 else "url" + result = generate_image( + prompt=args.prompt, + model=args.model, + aspect_ratio=args.ratio, + n=args.count, + response_format=fmt, + prompt_optimizer=args.optimize, + seed=args.seed, + ) + + meta = result.get("metadata", {}) + print(f"Generated: {meta.get('success_count', '?')} success, {meta.get('failed_count', '?')} failed") + + if args.base64: + images = result.get("data", {}).get("image_base64", []) + import base64 + for i, b64 in enumerate(images): + path = args.output if len(images) == 1 else _numbered_path(args.output, i) + raw = base64.b64decode(b64) + with open(path, "wb") as f: + f.write(raw) + print(f"OK: {len(raw)} bytes -> {path}") + else: + urls = result.get("data", {}).get("image_urls", []) + for i, url in enumerate(urls): + path = args.output if len(urls) == 1 else _numbered_path(args.output, i) + size = download_and_save(url, path) + print(f"OK: {size} bytes -> {path}") + + +def _numbered_path(path: str, index: int) -> str: + """Insert index before extension: out.png -> out-0.png""" + base, ext = os.path.splitext(path) + return f"{base}-{index}{ext}" + + +if __name__ == "__main__": + main() diff --git a/skills/frontend-dev/scripts/minimax_music.py b/skills/frontend-dev/scripts/minimax_music.py new file mode 100644 index 0000000..54d531a --- /dev/null +++ b/skills/frontend-dev/scripts/minimax_music.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +""" +MiniMax Music Generation (HTTP) +Self-contained: no external dependencies beyond `requests`. + +Usage: + python minimax_music.py --prompt "Indie folk, melancholic" --lyrics "[verse]\nStreetlights flicker" -o song.mp3 + python minimax_music.py --prompt "Upbeat pop, energetic" --auto-lyrics -o pop.mp3 + python minimax_music.py --prompt "Jazz piano, smooth, relaxing" --instrumental -o jazz.mp3 + +Env: MINIMAX_API_KEY (required) +""" + +import os +import sys +import json +import argparse +import requests + +API_KEY = os.getenv("MINIMAX_API_KEY") +API_BASE = os.getenv("MINIMAX_API_BASE", "https://api.minimax.io/v1") + + +def generate_music( + prompt: str = "", + lyrics: str = "", + model: str = "music-2.5+", + is_instrumental: bool = False, + lyrics_optimizer: bool = False, + sample_rate: int = 44100, + bitrate: int = 256000, + fmt: str = "mp3", + output_format: str = "hex", + timeout: int = 600, +) -> dict: + """Synchronous HTTP music generation. Returns dict with audio bytes and metadata.""" + if not API_KEY: + raise SystemExit("ERROR: MINIMAX_API_KEY is not set.\n export MINIMAX_API_KEY='your-key'") + + payload = { + "model": model, + "audio_setting": { + "sample_rate": sample_rate, + "bitrate": bitrate, + "format": fmt, + }, + "output_format": output_format, + } + + if prompt: + payload["prompt"] = prompt + if lyrics: + payload["lyrics"] = lyrics + if is_instrumental: + payload["is_instrumental"] = True + if lyrics_optimizer: + payload["lyrics_optimizer"] = True + + resp = requests.post( + f"{API_BASE}/music_generation", + headers={ + "Authorization": f"Bearer {API_KEY}", + "Content-Type": "application/json", + }, + json=payload, + timeout=timeout, + ) + resp.raise_for_status() + data = resp.json() + + # Check API-level error + base_resp = data.get("base_resp", {}) + if base_resp.get("status_code", 0) != 0: + raise SystemExit(f"API Error [{base_resp.get('status_code')}]: {base_resp.get('status_msg')}") + + status = data.get("data", {}).get("status") + if status != 2: + raise SystemExit(f"Generation incomplete (status={status}): {json.dumps(data, indent=2)}") + + audio_data = data.get("data", {}).get("audio", "") + if not audio_data: + raise SystemExit(f"No audio in response: {json.dumps(data, indent=2)}") + + extra = data.get("extra_info", {}) + + if output_format == "hex": + audio_bytes = bytes.fromhex(audio_data) + else: + # URL mode — audio_data is a URL string + audio_bytes = None + + return { + "audio_bytes": audio_bytes, + "audio_url": audio_data if output_format == "url" else None, + "duration": extra.get("music_duration"), + "sample_rate": extra.get("music_sample_rate"), + "channels": extra.get("music_channel"), + "bitrate": extra.get("bitrate"), + "size": extra.get("music_size"), + } + + +def main(): + p = argparse.ArgumentParser(description="MiniMax Music Generation (HTTP)") + p.add_argument("-o", "--output", required=True, help="Output file path") + p.add_argument("--prompt", default="", help="Music description: style, mood, scenario (max 2000 chars)") + p.add_argument("--lyrics", default="", help="Song lyrics with structure tags (max 3500 chars)") + p.add_argument("--lyrics-file", default="", help="Read lyrics from file instead of --lyrics") + p.add_argument("--model", default="music-2.5+", choices=["music-2.5+", "music-2.5"], help="Model (default: music-2.5+)") + p.add_argument("--instrumental", action="store_true", help="Generate instrumental only (no vocals)") + p.add_argument("--auto-lyrics", action="store_true", help="Auto-generate lyrics from prompt") + p.add_argument("--format", default="mp3", dest="fmt", choices=["mp3", "wav", "pcm"], help="Audio format (default: mp3)") + p.add_argument("--sample-rate", type=int, default=44100, choices=[16000, 24000, 32000, 44100], help="Sample rate (default: 44100)") + p.add_argument("--bitrate", type=int, default=256000, choices=[32000, 64000, 128000, 256000], help="Bitrate (default: 256000)") + args = p.parse_args() + + lyrics = args.lyrics + if args.lyrics_file: + with open(args.lyrics_file, "r") as f: + lyrics = f.read() + + os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True) + + result = generate_music( + prompt=args.prompt, + lyrics=lyrics, + model=args.model, + is_instrumental=args.instrumental, + lyrics_optimizer=args.auto_lyrics, + sample_rate=args.sample_rate, + bitrate=args.bitrate, + fmt=args.fmt, + ) + + if result["audio_bytes"]: + with open(args.output, "wb") as f: + f.write(result["audio_bytes"]) + size = len(result["audio_bytes"]) + else: + # URL mode — download + r = requests.get(result["audio_url"], timeout=120) + r.raise_for_status() + with open(args.output, "wb") as f: + f.write(r.content) + size = len(r.content) + + duration = result.get("duration", "?") + print(f"OK: {size} bytes -> {args.output} (duration: {duration}s)") + + +if __name__ == "__main__": + main() diff --git a/skills/frontend-dev/scripts/minimax_tts.py b/skills/frontend-dev/scripts/minimax_tts.py new file mode 100755 index 0000000..9f78d67 --- /dev/null +++ b/skills/frontend-dev/scripts/minimax_tts.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +""" +MiniMax Sync TTS (HTTP) +Self-contained: no external dependencies beyond `requests`. + +Usage: + python minimax_tts.py "Hello world" -o output.mp3 + python minimax_tts.py "你好世界" -o hi.mp3 -v female-shaonv --model speech-2.8-hd + python minimax_tts.py "Welcome" -o out.wav -v male-qn-jingying --speed 0.8 --format wav + +Env: MINIMAX_API_KEY (required) +""" + +import os +import sys +import json +import argparse +import requests + +API_KEY = os.getenv("MINIMAX_API_KEY") +API_BASE = os.getenv("MINIMAX_API_BASE", "https://api.minimax.io/v1") + + +def tts( + text: str, + voice_id: str = "male-qn-qingse", + model: str = "speech-2.8-hd", + speed: float = 1.0, + volume: float = 1.0, + pitch: int = 0, + emotion: str = "", + sample_rate: int = 32000, + bitrate: int = 128000, + fmt: str = "mp3", + language_boost: str = "auto", + timeout: int = 120, +) -> bytes: + """Synchronous HTTP TTS. Returns raw audio bytes.""" + if not API_KEY: + raise SystemExit("ERROR: MINIMAX_API_KEY is not set.\n export MINIMAX_API_KEY='your-key'") + + voice_setting = {"voice_id": voice_id, "speed": speed, "vol": volume, "pitch": pitch} + if emotion: + voice_setting["emotion"] = emotion + + payload = { + "model": model, + "text": text, + "stream": False, + "voice_setting": voice_setting, + "audio_setting": { + "sample_rate": sample_rate, + "bitrate": bitrate, + "format": fmt, + "channel": 1, + }, + "language_boost": language_boost, + "output_format": "hex", + } + + resp = requests.post( + f"{API_BASE}/t2a_v2", + headers={ + "Authorization": f"Bearer {API_KEY}", + "Content-Type": "application/json", + }, + json=payload, + timeout=timeout, + ) + resp.raise_for_status() + data = resp.json() + + # Check API-level error + base_resp = data.get("base_resp", {}) + if base_resp.get("status_code", 0) != 0: + raise SystemExit(f"API Error [{base_resp.get('status_code')}]: {base_resp.get('status_msg')}") + + audio_hex = data.get("data", {}).get("audio", "") + if not audio_hex: + raise SystemExit(f"No audio in response: {json.dumps(data, indent=2)}") + + return bytes.fromhex(audio_hex) + + +def main(): + p = argparse.ArgumentParser(description="MiniMax Sync TTS (HTTP)") + p.add_argument("text", help="Text to synthesize (max 10000 chars)") + p.add_argument("-o", "--output", required=True, help="Output file path") + p.add_argument("-v", "--voice", default="male-qn-qingse", help="Voice ID") + p.add_argument("--model", default="speech-2.8-hd", help="Model (default: speech-2.8-hd)") + p.add_argument("--speed", type=float, default=1.0, help="Speed 0.5-2.0") + p.add_argument("--volume", type=float, default=1.0, help="Volume 0.1-10") + p.add_argument("--pitch", type=int, default=0, help="Pitch -12 to 12") + p.add_argument("--emotion", default="", help="Emotion tag (happy/sad/angry/...)") + p.add_argument("--format", default="mp3", dest="fmt", help="Audio format (mp3/wav/flac)") + p.add_argument("--sample-rate", type=int, default=32000, help="Sample rate") + p.add_argument("--lang", default="auto", help="Language boost") + args = p.parse_args() + + os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True) + + audio = tts( + text=args.text, + voice_id=args.voice, + model=args.model, + speed=args.speed, + volume=args.volume, + pitch=args.pitch, + emotion=args.emotion, + fmt=args.fmt, + sample_rate=args.sample_rate, + language_boost=args.lang, + ) + + with open(args.output, "wb") as f: + f.write(audio) + + print(f"OK: {len(audio)} bytes -> {args.output}") + + +if __name__ == "__main__": + main() diff --git a/skills/frontend-dev/scripts/minimax_video.py b/skills/frontend-dev/scripts/minimax_video.py new file mode 100755 index 0000000..8fa997c --- /dev/null +++ b/skills/frontend-dev/scripts/minimax_video.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +""" +MiniMax Text-to-Video — async generation with polling and download. + +Usage: + python minimax_video.py "A cat playing piano" -o cat.mp4 + python minimax_video.py "Ocean waves [Truck left]" -o waves.mp4 --model MiniMax-Hailuo-2.3 --duration 10 + python minimax_video.py "City skyline at sunset [Push in]" -o city.mp4 --resolution 1080P + +Env: MINIMAX_API_KEY (required) +""" + +import os +import sys +import json +import time +import argparse +import requests + +API_KEY = os.getenv("MINIMAX_API_KEY") +API_BASE = "https://api.minimax.io/v1" + + +def _headers(): + if not API_KEY: + raise SystemExit("ERROR: MINIMAX_API_KEY is not set.\n export MINIMAX_API_KEY='your-key'") + return { + "Authorization": f"Bearer {API_KEY}", + "Content-Type": "application/json", + } + + +def _check_resp(data): + base_resp = data.get("base_resp", {}) + code = base_resp.get("status_code", 0) + if code != 0: + msg = base_resp.get("status_msg", "Unknown error") + raise SystemExit(f"API Error [{code}]: {msg}") + + +def create_task( + prompt: str, + model: str = "MiniMax-Hailuo-2.3", + duration: int = 6, + resolution: str = "768P", + prompt_optimizer: bool = True, +) -> str: + """Submit a video generation task. Returns task_id.""" + payload = { + "model": model, + "prompt": prompt, + "duration": duration, + "resolution": resolution, + "prompt_optimizer": prompt_optimizer, + } + + resp = requests.post( + f"{API_BASE}/video_generation", + headers=_headers(), + json=payload, + timeout=30, + ) + resp.raise_for_status() + data = resp.json() + _check_resp(data) + + task_id = data.get("task_id") + if not task_id: + raise SystemExit(f"No task_id in response: {json.dumps(data, indent=2)}") + return task_id + + +def poll_task(task_id: str, interval: int = 10, max_wait: int = 600) -> str: + """Poll task status until Success. Returns file_id.""" + elapsed = 0 + while elapsed < max_wait: + resp = requests.get( + f"{API_BASE}/query/video_generation", + headers=_headers(), + params={"task_id": task_id}, + timeout=30, + ) + resp.raise_for_status() + data = resp.json() + _check_resp(data) + + status = data.get("status", "") + file_id = data.get("file_id", "") + + if status == "Success": + if not file_id: + raise SystemExit("Task succeeded but no file_id returned") + print(f" Done! file_id={file_id}") + return file_id + elif status == "Fail": + raise SystemExit(f"Video generation failed: {json.dumps(data, indent=2)}") + else: + print(f" [{elapsed}s] Status: {status}...") + time.sleep(interval) + elapsed += interval + + raise SystemExit(f"Timeout after {max_wait}s. task_id={task_id}, check manually.") + + +def download_video(file_id: str, output_path: str): + """Retrieve download URL via file_id and save the video.""" + resp = requests.get( + f"{API_BASE}/files/retrieve", + headers=_headers(), + params={"file_id": file_id}, + timeout=30, + ) + resp.raise_for_status() + data = resp.json() + _check_resp(data) + + download_url = data.get("file", {}).get("download_url", "") + if not download_url: + raise SystemExit(f"No download_url in response: {json.dumps(data, indent=2)}") + + print(f" Downloading from {download_url[:80]}...") + video_resp = requests.get(download_url, timeout=300) + video_resp.raise_for_status() + + os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) + with open(output_path, "wb") as f: + f.write(video_resp.content) + + print(f"OK: {len(video_resp.content)} bytes -> {output_path}") + + +def generate( + prompt: str, + output_path: str, + model: str = "MiniMax-Hailuo-2.3", + duration: int = 6, + resolution: str = "768P", + prompt_optimizer: bool = True, + poll_interval: int = 10, + max_wait: int = 600, +): + """Full pipeline: create task -> poll -> download.""" + print(f"Creating video task...") + print(f" Model: {model} | Duration: {duration}s | Resolution: {resolution}") + print(f" Prompt: {prompt[:100]}{'...' if len(prompt) > 100 else ''}") + + task_id = create_task(prompt, model, duration, resolution, prompt_optimizer) + print(f" task_id={task_id}") + print(f"Waiting for generation...") + + file_id = poll_task(task_id, poll_interval, max_wait) + download_video(file_id, output_path) + + +def main(): + p = argparse.ArgumentParser(description="MiniMax Text-to-Video") + p.add_argument("prompt", help="Video description (max 2000 chars). Use [Camera Command] for camera control.") + p.add_argument("-o", "--output", required=True, help="Output file path (.mp4)") + p.add_argument("--model", default="MiniMax-Hailuo-2.3", + choices=["MiniMax-Hailuo-2.3", "MiniMax-Hailuo-02", "T2V-01-Director", "T2V-01"], + help="Model (default: MiniMax-Hailuo-2.3)") + p.add_argument("--duration", type=int, default=6, choices=[6, 10], help="Duration in seconds (default: 6)") + p.add_argument("--resolution", default="768P", choices=["720P", "768P", "1080P"], help="Resolution (default: 768P)") + p.add_argument("--no-optimize", action="store_true", help="Disable prompt auto-optimization") + p.add_argument("--poll-interval", type=int, default=10, help="Poll interval in seconds (default: 10)") + p.add_argument("--max-wait", type=int, default=600, help="Max wait time in seconds (default: 600)") + args = p.parse_args() + + generate( + prompt=args.prompt, + output_path=args.output, + model=args.model, + duration=args.duration, + resolution=args.resolution, + prompt_optimizer=not args.no_optimize, + poll_interval=args.poll_interval, + max_wait=args.max_wait, + ) + + +if __name__ == "__main__": + main() diff --git a/skills/frontend-dev/templates/generator_template.js b/skills/frontend-dev/templates/generator_template.js new file mode 100644 index 0000000..7f39639 --- /dev/null +++ b/skills/frontend-dev/templates/generator_template.js @@ -0,0 +1,223 @@ +/** + * ═══════════════════════════════════════════════════════════════════════════ + * P5.JS GENERATIVE ART - BEST PRACTICES + * ═══════════════════════════════════════════════════════════════════════════ + * + * This file shows STRUCTURE and PRINCIPLES for p5.js generative art. + * It does NOT prescribe what art you should create. + * + * Your algorithmic philosophy should guide what you build. + * These are just best practices for how to structure your code. + * + * ═══════════════════════════════════════════════════════════════════════════ + */ + +// ============================================================================ +// 1. PARAMETER ORGANIZATION +// ============================================================================ +// Keep all tunable parameters in one object +// This makes it easy to: +// - Connect to UI controls +// - Reset to defaults +// - Serialize/save configurations + +let params = { + // Define parameters that match YOUR algorithm + // Examples (customize for your art): + // - Counts: how many elements (particles, circles, branches, etc.) + // - Scales: size, speed, spacing + // - Probabilities: likelihood of events + // - Angles: rotation, direction + // - Colors: palette arrays + + seed: 12345, + // define colorPalette as an array -- choose whatever colors you'd like ['#d97757', '#6a9bcc', '#788c5d', '#b0aea5'] + // Add YOUR parameters here based on your algorithm +}; + +// ============================================================================ +// 2. SEEDED RANDOMNESS (Critical for reproducibility) +// ============================================================================ +// ALWAYS use seeded random for Art Blocks-style reproducible output + +function initializeSeed(seed) { + randomSeed(seed); + noiseSeed(seed); + // Now all random() and noise() calls will be deterministic +} + +// ============================================================================ +// 3. P5.JS LIFECYCLE +// ============================================================================ + +function setup() { + createCanvas(800, 800); + + // Initialize seed first + initializeSeed(params.seed); + + // Set up your generative system + // This is where you initialize: + // - Arrays of objects + // - Grid structures + // - Initial positions + // - Starting states + + // For static art: call noLoop() at the end of setup + // For animated art: let draw() keep running +} + +function draw() { + // Option 1: Static generation (runs once, then stops) + // - Generate everything in setup() + // - Call noLoop() in setup() + // - draw() doesn't do much or can be empty + + // Option 2: Animated generation (continuous) + // - Update your system each frame + // - Common patterns: particle movement, growth, evolution + // - Can optionally call noLoop() after N frames + + // Option 3: User-triggered regeneration + // - Use noLoop() by default + // - Call redraw() when parameters change +} + +// ============================================================================ +// 4. CLASS STRUCTURE (When you need objects) +// ============================================================================ +// Use classes when your algorithm involves multiple entities +// Examples: particles, agents, cells, nodes, etc. + +class Entity { + constructor() { + // Initialize entity properties + // Use random() here - it will be seeded + } + + update() { + // Update entity state + // This might involve: + // - Physics calculations + // - Behavioral rules + // - Interactions with neighbors + } + + display() { + // Render the entity + // Keep rendering logic separate from update logic + } +} + +// ============================================================================ +// 5. PERFORMANCE CONSIDERATIONS +// ============================================================================ + +// For large numbers of elements: +// - Pre-calculate what you can +// - Use simple collision detection (spatial hashing if needed) +// - Limit expensive operations (sqrt, trig) when possible +// - Consider using p5 vectors efficiently + +// For smooth animation: +// - Aim for 60fps +// - Profile if things are slow +// - Consider reducing particle counts or simplifying calculations + +// ============================================================================ +// 6. UTILITY FUNCTIONS +// ============================================================================ + +// Color utilities +function hexToRgb(hex) { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : null; +} + +function colorFromPalette(index) { + return params.colorPalette[index % params.colorPalette.length]; +} + +// Mapping and easing +function mapRange(value, inMin, inMax, outMin, outMax) { + return outMin + (outMax - outMin) * ((value - inMin) / (inMax - inMin)); +} + +function easeInOutCubic(t) { + return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; +} + +// Constrain to bounds +function wrapAround(value, max) { + if (value < 0) return max; + if (value > max) return 0; + return value; +} + +// ============================================================================ +// 7. PARAMETER UPDATES (Connect to UI) +// ============================================================================ + +function updateParameter(paramName, value) { + params[paramName] = value; + // Decide if you need to regenerate or just update + // Some params can update in real-time, others need full regeneration +} + +function regenerate() { + // Reinitialize your generative system + // Useful when parameters change significantly + initializeSeed(params.seed); + // Then regenerate your system +} + +// ============================================================================ +// 8. COMMON P5.JS PATTERNS +// ============================================================================ + +// Drawing with transparency for trails/fading +function fadeBackground(opacity) { + fill(250, 249, 245, opacity); // brand light with alpha + noStroke(); + rect(0, 0, width, height); +} + +// Using noise for organic variation +function getNoiseValue(x, y, scale = 0.01) { + return noise(x * scale, y * scale); +} + +// Creating vectors from angles +function vectorFromAngle(angle, magnitude = 1) { + return createVector(cos(angle), sin(angle)).mult(magnitude); +} + +// ============================================================================ +// 9. EXPORT FUNCTIONS +// ============================================================================ + +function exportImage() { + saveCanvas('generative-art-' + params.seed, 'png'); +} + +// ============================================================================ +// REMEMBER +// ============================================================================ +// +// These are TOOLS and PRINCIPLES, not a recipe. +// Your algorithmic philosophy should guide WHAT you create. +// This structure helps you create it WELL. +// +// Focus on: +// - Clean, readable code +// - Parameterized for exploration +// - Seeded for reproducibility +// - Performant execution +// +// The art itself is entirely up to you! +// +// ============================================================================ \ No newline at end of file diff --git a/skills/frontend-dev/templates/viewer.html b/skills/frontend-dev/templates/viewer.html new file mode 100644 index 0000000..719c6cc --- /dev/null +++ b/skills/frontend-dev/templates/viewer.html @@ -0,0 +1,599 @@ + + + + + + + Generative Art Viewer + + + + + + + + + + + + TITLE - EDIT + SUBHEADER - EDIT + + + + Seed + + + ← Prev + Next → + + ↻ Random + + + + + Parameters + + + + Particle Count + + + 5000 + + + + + + Flow Speed + + + 0.5 + + + + + + Noise Scale + + + 0.005 + + + + + + Trail Length + + + 8 + + + + + + + Colors + + + + Primary Color + + + #d97757 + + + + + + Secondary Color + + + #6a9bcc + + + + + + Accent Color + + + #788c5d + + + + + + + Actions + + Reset + + + + + + + + Initializing generative art... + + + + + + + \ No newline at end of file diff --git a/skills/fullstack-dev/SKILL.md b/skills/fullstack-dev/SKILL.md new file mode 100644 index 0000000..07abbf9 --- /dev/null +++ b/skills/fullstack-dev/SKILL.md @@ -0,0 +1,1037 @@ +--- +name: fullstack-dev +description: | + Full-stack backend architecture and frontend-backend integration guide. + TRIGGER when: building a full-stack app, creating REST API with frontend, scaffolding backend service, + building todo app, building CRUD app, building real-time app, building chat app, + Express + React, Next.js API, Node.js backend, Python backend, Go backend, + designing service layers, implementing error handling, managing config/auth, + setting up API clients, implementing auth flows, handling file uploads, + adding real-time features (SSE/WebSocket), hardening for production. + DO NOT TRIGGER when: pure frontend UI work, pure CSS/styling, database schema only. +license: MIT +metadata: + category: full-stack + version: "1.0.0" + sources: + - The Twelve-Factor App (12factor.net) + - Clean Architecture (Robert C. Martin) + - Domain-Driven Design (Eric Evans) + - Patterns of Enterprise Application Architecture (Martin Fowler) + - Martin Fowler (Testing Pyramid, Contract Tests) + - Google SRE Handbook (Release Engineering) + - ThoughtWorks Technology Radar +--- + +# Full-Stack Development Practices + +## MANDATORY WORKFLOW — Follow These Steps In Order + +**When this skill is triggered, you MUST follow this workflow before writing any code.** + +### Step 0: Gather Requirements + +Before scaffolding anything, ask the user to clarify (or infer from context): + +1. **Stack**: Language/framework for backend and frontend (e.g., Express + React, Django + Vue, Go + HTMX) +2. **Service type**: API-only, full-stack monolith, or microservice? +3. **Database**: SQL (PostgreSQL, SQLite, MySQL) or NoSQL (MongoDB, Redis)? +4. **Integration**: REST, GraphQL, tRPC, or gRPC? +5. **Real-time**: Needed? If yes — SSE, WebSocket, or polling? +6. **Auth**: Needed? If yes — JWT, session, OAuth, or third-party (Clerk, Auth.js)? + +If the user has already specified these in their request, skip asking and proceed. + +### Step 1: Architectural Decisions + +Based on requirements, make and state these decisions before coding: + +| Decision | Options | Reference | +|----------|---------|-----------| +| Project structure | Feature-first (recommended) vs layer-first | [Section 1](#1-project-structure--layering-critical) | +| API client approach | Typed fetch / React Query / tRPC / OpenAPI codegen | [Section 5](#5-api-client-patterns-medium) | +| Auth strategy | JWT + refresh / session / third-party | [Section 6](#6-authentication--middleware-high) | +| Real-time method | Polling / SSE / WebSocket | [Section 11](#11-real-time-patterns-medium) | +| Error handling | Typed error hierarchy + global handler | [Section 3](#3-error-handling--resilience-high) | + +Briefly explain each choice (1 sentence per decision). + +### Step 2: Scaffold with Checklist + +Use the appropriate checklist below. Ensure ALL checked items are implemented — do not skip any. + +### Step 3: Implement Following Patterns + +Write code following the patterns in this document. Reference specific sections as you implement each part. + +### Step 4: Test & Verify + +After implementation, run these checks before claiming completion: + +1. **Build check**: Ensure both backend and frontend compile without errors + ```bash + # Backend + cd server && npm run build + # Frontend + cd client && npm run build + ``` +2. **Start & smoke test**: Start the server, verify key endpoints return expected responses + ```bash + # Start server, then test + curl http://localhost:3000/health + curl http://localhost:3000/api/ + ``` +3. **Integration check**: Verify frontend can connect to backend (CORS, API base URL, auth flow) +4. **Real-time check** (if applicable): Open two browser tabs, verify changes sync + +If any check fails, fix the issue before proceeding. + +### Step 5: Handoff Summary + +Provide a brief summary to the user: + +- **What was built**: List of implemented features and endpoints +- **How to run**: Exact commands to start backend and frontend +- **What's missing / next steps**: Any deferred items, known limitations, or recommended improvements +- **Key files**: List the most important files the user should know about + +--- + +## Scope + +**USE this skill when:** +- Building a full-stack application (backend + frontend) +- Scaffolding a new backend service or API +- Designing service layers and module boundaries +- Implementing database access, caching, or background jobs +- Writing error handling, logging, or configuration management +- Reviewing backend code for architectural issues +- Hardening for production +- Setting up API clients, auth flows, file uploads, or real-time features + +**NOT for:** +- Pure frontend/UI concerns (use your frontend framework's docs) +- Pure database schema design without backend context + +--- + +## Quick Start — New Backend Service Checklist + +- [ ] Project scaffolded with **feature-first** structure +- [ ] Configuration **centralized**, env vars **validated at startup** (fail fast) +- [ ] **Typed error hierarchy** defined (not generic `Error`) +- [ ] **Global error handler** middleware +- [ ] **Structured JSON logging** with request ID propagation +- [ ] Database: **migrations** set up, **connection pooling** configured +- [ ] **Input validation** on all endpoints (Zod / Pydantic / Go validator) +- [ ] **Authentication middleware** in place +- [ ] **Health check** endpoints (`/health`, `/ready`) +- [ ] **Graceful shutdown** handling (SIGTERM) +- [ ] **CORS** configured (explicit origins, not `*`) +- [ ] **Security headers** (helmet or equivalent) +- [ ] `.env.example` committed (no real secrets) + +## Quick Start — Frontend-Backend Integration Checklist + +- [ ] **API client** configured (typed fetch wrapper, React Query, tRPC, or OpenAPI generated) +- [ ] **Base URL** from environment variable (not hardcoded) +- [ ] **Auth token** attached to requests automatically (interceptor / middleware) +- [ ] **Error handling** — API errors mapped to user-facing messages +- [ ] **Loading states** handled (skeleton/spinner, not blank screen) +- [ ] **Type safety** across the boundary (shared types, OpenAPI, or tRPC) +- [ ] **CORS** configured with explicit origins (not `*` in production) +- [ ] **Refresh token** flow implemented (httpOnly cookie + transparent retry on 401) + +--- + +## Quick Navigation + +| Need to… | Jump to | +|----------|---------| +| Organize project folders | [1. Project Structure](#1-project-structure--layering-critical) | +| Manage config + secrets | [2. Configuration](#2-configuration--environment-critical) | +| Handle errors properly | [3. Error Handling](#3-error-handling--resilience-high) | +| Write database code | [4. Database Access Patterns](#4-database-access-patterns-high) | +| Set up API client from frontend | [5. API Client Patterns](#5-api-client-patterns-medium) | +| Add auth middleware | [6. Auth & Middleware](#6-authentication--middleware-high) | +| Set up logging | [7. Logging & Observability](#7-logging--observability-medium-high) | +| Add background jobs | [8. Background Jobs](#8-background-jobs--async-medium) | +| Implement caching | [9. Caching](#9-caching-patterns-medium) | +| Upload files (presigned URL, multipart) | [10. File Upload Patterns](#10-file-upload-patterns-medium) | +| Add real-time features (SSE, WebSocket) | [11. Real-Time Patterns](#11-real-time-patterns-medium) | +| Handle API errors in frontend UI | [12. Cross-Boundary Error Handling](#12-cross-boundary-error-handling-medium) | +| Harden for production | [13. Production Hardening](#13-production-hardening-medium) | +| Design API endpoints | [API Design](references/api-design.md) | +| Design database schema | [Database Schema](references/db-schema.md) | +| Auth flow (JWT, refresh, Next.js SSR, RBAC) | [references/auth-flow.md](references/auth-flow.md) | +| CORS, env vars, environment management | [references/environment-management.md](references/environment-management.md) | + +--- + +## Core Principles (7 Iron Rules) + +``` +1. ✅ Organize by FEATURE, not by technical layer +2. ✅ Controllers never contain business logic +3. ✅ Services never import HTTP request/response types +4. ✅ All config from env vars, validated at startup, fail fast +5. ✅ Every error is typed, logged, and returns consistent format +6. ✅ All input validated at the boundary — trust nothing from client +7. ✅ Structured JSON logging with request ID — not console.log +``` + +--- + +## 1. Project Structure & Layering (CRITICAL) + +### Feature-First Organization + +``` +✅ Feature-first ❌ Layer-first +src/ src/ + orders/ controllers/ + order.controller.ts order.controller.ts + order.service.ts user.controller.ts + order.repository.ts services/ + order.dto.ts order.service.ts + order.test.ts user.service.ts + users/ repositories/ + user.controller.ts ... + user.service.ts + shared/ + database/ + middleware/ +``` + +### Three-Layer Architecture + +``` +Controller (HTTP) → Service (Business Logic) → Repository (Data Access) +``` + +| Layer | Responsibility | ❌ Never | +|-------|---------------|---------| +| Controller | Parse request, validate, call service, format response | Business logic, DB queries | +| Service | Business rules, orchestration, transaction mgmt | HTTP types (req/res), direct DB | +| Repository | Database queries, external API calls | Business logic, HTTP types | + +### Dependency Injection (All Languages) + +**TypeScript:** +```typescript +class OrderService { + constructor( + private readonly orderRepo: OrderRepository, // ✅ injected interface + private readonly emailService: EmailService, + ) {} +} +``` + +**Python:** +```python +class OrderService: + def __init__(self, order_repo: OrderRepository, email_service: EmailService): + self.order_repo = order_repo # ✅ injected + self.email_service = email_service +``` + +**Go:** +```go +type OrderService struct { + orderRepo OrderRepository // ✅ interface + emailService EmailService +} + +func NewOrderService(repo OrderRepository, email EmailService) *OrderService { + return &OrderService{orderRepo: repo, emailService: email} +} +``` + +--- + +## 2. Configuration & Environment (CRITICAL) + +### Centralized, Typed, Fail-Fast + +**TypeScript:** +```typescript +const config = { + port: parseInt(process.env.PORT || '3000', 10), + database: { url: requiredEnv('DATABASE_URL'), poolSize: intEnv('DB_POOL_SIZE', 10) }, + auth: { jwtSecret: requiredEnv('JWT_SECRET'), expiresIn: process.env.JWT_EXPIRES_IN || '1h' }, +} as const; + +function requiredEnv(name: string): string { + const value = process.env[name]; + if (!value) throw new Error(`Missing required env var: ${name}`); // fail fast + return value; +} +``` + +**Python:** +```python +from pydantic_settings import BaseSettings + +class Settings(BaseSettings): + database_url: str # required — app won't start without it + jwt_secret: str # required + port: int = 3000 # optional with default + db_pool_size: int = 10 + class Config: + env_file = ".env" + +settings = Settings() # fails fast if DATABASE_URL missing +``` + +### Rules + +``` +✅ All config via environment variables (Twelve-Factor) +✅ Validate required vars at startup — fail fast +✅ Type-cast at config layer, not at usage sites +✅ Commit .env.example with dummy values + +❌ Never hardcode secrets, URLs, or credentials +❌ Never commit .env files +❌ Never scatter process.env / os.environ throughout code +``` + +--- + +## 3. Error Handling & Resilience (HIGH) + +### Typed Error Hierarchy + +```typescript +// Base (TypeScript) +class AppError extends Error { + constructor( + message: string, + public readonly code: string, + public readonly statusCode: number, + public readonly isOperational: boolean = true, + ) { super(message); } +} +class NotFoundError extends AppError { + constructor(resource: string, id: string) { + super(`${resource} not found: ${id}`, 'NOT_FOUND', 404); + } +} +class ValidationError extends AppError { + constructor(public readonly errors: FieldError[]) { + super('Validation failed', 'VALIDATION_ERROR', 422); + } +} +``` + +```python +# Base (Python) +class AppError(Exception): + def __init__(self, message: str, code: str, status_code: int): + self.message, self.code, self.status_code = message, code, status_code + +class NotFoundError(AppError): + def __init__(self, resource: str, id: str): + super().__init__(f"{resource} not found: {id}", "NOT_FOUND", 404) +``` + +### Global Error Handler + +```typescript +// TypeScript (Express) +app.use((err, req, res, next) => { + if (err instanceof AppError && err.isOperational) { + return res.status(err.statusCode).json({ + title: err.code, status: err.statusCode, + detail: err.message, request_id: req.id, + }); + } + logger.error('Unexpected error', { error: err.message, stack: err.stack, request_id: req.id }); + res.status(500).json({ title: 'Internal Error', status: 500, request_id: req.id }); +}); +``` + +### Rules + +``` +✅ Typed, domain-specific error classes +✅ Global error handler catches everything +✅ Operational errors → structured response +✅ Programming errors → log + generic 500 +✅ Retry transient failures with exponential backoff + +❌ Never catch and ignore errors silently +❌ Never return stack traces to client +❌ Never throw generic Error('something') +``` + +--- + +## 4. Database Access Patterns (HIGH) + +### Migrations Always + +```bash +# TypeScript (Prisma) # Python (Alembic) # Go (golang-migrate) +npx prisma migrate dev alembic revision --autogenerate migrate -source file://migrations +npx prisma migrate deploy alembic upgrade head migrate -database $DB up +``` + +``` +✅ Schema changes via migrations, never manual SQL +✅ Migrations must be reversible +✅ Review migration SQL before production +❌ Never modify production schema manually +``` + +### N+1 Prevention + +```typescript +// ❌ N+1: 1 query + N queries +const orders = await db.order.findMany(); +for (const o of orders) { o.items = await db.item.findMany({ where: { orderId: o.id } }); } + +// ✅ Single JOIN query +const orders = await db.order.findMany({ include: { items: true } }); +``` + +### Transactions for Multi-Step Writes + +```typescript +await db.$transaction(async (tx) => { + const order = await tx.order.create({ data: orderData }); + await tx.inventory.decrement({ productId, quantity }); + await tx.payment.create({ orderId: order.id, amount }); +}); +``` + +### Connection Pooling + +Pool size = `(CPU cores × 2) + spindle_count` (start with 10-20). Always set connection timeout. Use PgBouncer for serverless. + +--- + +## 5. API Client Patterns (MEDIUM) + +The "glue layer" between frontend and backend. Choose the approach that fits your team and stack. + +### Option A: Typed Fetch Wrapper (Simple, No Dependencies) + +```typescript +// lib/api-client.ts +const BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'; + +class ApiError extends Error { + constructor(public status: number, public body: any) { + super(body?.detail || body?.message || `API error ${status}`); + } +} + +async function api(path: string, options: RequestInit = {}): Promise { + const token = getAuthToken(); // from cookie / memory / context + + const res = await fetch(`${BASE_URL}${path}`, { + ...options, + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + ...options.headers, + }, + }); + + if (!res.ok) { + const body = await res.json().catch(() => null); + throw new ApiError(res.status, body); + } + + if (res.status === 204) return undefined as T; + return res.json(); +} + +export const apiClient = { + get: (path: string) => api(path), + post: (path: string, data: unknown) => api(path, { method: 'POST', body: JSON.stringify(data) }), + put: (path: string, data: unknown) => api(path, { method: 'PUT', body: JSON.stringify(data) }), + patch: (path: string, data: unknown) => api(path, { method: 'PATCH', body: JSON.stringify(data) }), + delete: (path: string) => api(path, { method: 'DELETE' }), +}; +``` + +### Option B: React Query + Typed Client (Recommended for React) + +```typescript +// hooks/use-orders.ts +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { apiClient } from '@/lib/api-client'; + +interface Order { id: string; total: number; status: string; } +interface CreateOrderInput { items: { productId: string; quantity: number }[] } + +export function useOrders() { + return useQuery({ + queryKey: ['orders'], + queryFn: () => apiClient.get<{ data: Order[] }>('/api/orders'), + staleTime: 1000 * 60, // 1 min + }); +} + +export function useCreateOrder() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (data: CreateOrderInput) => + apiClient.post<{ data: Order }>('/api/orders', data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['orders'] }); + }, + }); +} + +// Usage in component: +function OrdersPage() { + const { data, isLoading, error } = useOrders(); + const createOrder = useCreateOrder(); + if (isLoading) return ; + if (error) return ; + // ... +} +``` + +### Option C: tRPC (Same Team Owns Both Sides) + +```typescript +// server: trpc/router.ts +export const appRouter = router({ + orders: router({ + list: publicProcedure.query(async () => { + return db.order.findMany({ include: { items: true } }); + }), + create: protectedProcedure + .input(z.object({ items: z.array(orderItemSchema) })) + .mutation(async ({ input, ctx }) => { + return orderService.create(ctx.user.id, input); + }), + }), +}); +export type AppRouter = typeof appRouter; + +// client: automatic type safety, no code generation +const { data } = trpc.orders.list.useQuery(); +const createOrder = trpc.orders.create.useMutation(); +``` + +### Option D: OpenAPI Generated Client (Public / Multi-Consumer APIs) + +```bash +npx openapi-typescript-codegen \ + --input http://localhost:3001/api/openapi.json \ + --output src/generated/api \ + --client axios +``` + +### Decision: Which API Client? + +| Approach | When | Type Safety | Effort | +|----------|------|-------------|--------| +| Typed fetch wrapper | Simple apps, small teams | Manual types | Low | +| React Query + fetch | React apps, server state | Manual types | Medium | +| tRPC | Same team, TypeScript both sides | Automatic | Low | +| OpenAPI generated | Public API, multi-consumer | Automatic | Medium | +| GraphQL codegen | GraphQL APIs | Automatic | Medium | + +--- + +## 6. Authentication & Middleware (HIGH) + +> **Full reference:** [references/auth-flow.md](references/auth-flow.md) — JWT bearer flow, automatic token refresh, Next.js server-side auth, RBAC pattern, backend middleware order. + +### Standard Middleware Order + +``` +Request → 1.RequestID → 2.Logging → 3.CORS → 4.RateLimit → 5.BodyParse + → 6.Auth → 7.Authz → 8.Validation → 9.Handler → 10.ErrorHandler → Response +``` + +### JWT Rules + +``` +✅ Short expiry access token (15min) + refresh token (server-stored) +✅ Minimal claims: userId, roles (not entire user object) +✅ Rotate signing keys periodically + +❌ Never store tokens in localStorage (XSS risk) +❌ Never pass tokens in URL query params +``` + +### RBAC Pattern + +```typescript +function authorize(...roles: Role[]) { + return (req, res, next) => { + if (!req.user) throw new UnauthorizedError(); + if (!roles.some(r => req.user.roles.includes(r))) throw new ForbiddenError(); + next(); + }; +} +router.delete('/users/:id', authenticate, authorize('admin'), deleteUser); +``` + +### Auth Token Automatic Refresh + +```typescript +// lib/api-client.ts — transparent refresh on 401 +async function apiWithRefresh(path: string, options: RequestInit = {}): Promise { + try { + return await api(path, options); + } catch (err) { + if (err instanceof ApiError && err.status === 401) { + const refreshed = await api<{ accessToken: string }>('/api/auth/refresh', { + method: 'POST', + credentials: 'include', // send httpOnly cookie + }); + setAuthToken(refreshed.accessToken); + return api(path, options); // retry + } + throw err; + } +} +``` + +--- + +## 7. Logging & Observability (MEDIUM-HIGH) + +### Structured JSON Logging + +```typescript +// ✅ Structured — parseable, filterable, alertable +logger.info('Order created', { + orderId: order.id, userId: user.id, total: order.total, + items: order.items.length, duration_ms: Date.now() - startTime, +}); +// Output: {"level":"info","msg":"Order created","orderId":"ord_123",...} + +// ❌ Unstructured — useless at scale +console.log(`Order created for user ${user.id} with total ${order.total}`); +``` + +### Log Levels + +| Level | When | Production? | +|-------|------|------------| +| error | Requires immediate attention | ✅ Always | +| warn | Unexpected but handled | ✅ Always | +| info | Normal operations, audit trail | ✅ Always | +| debug | Dev troubleshooting | ❌ Dev only | + +### Rules + +``` +✅ Request ID in every log entry (propagated via middleware) +✅ Log at layer boundaries (request in, response out, external call) +❌ Never log passwords, tokens, PII, or secrets +❌ Never use console.log in production code +``` + +--- + +## 8. Background Jobs & Async (MEDIUM) + +### Rules + +``` +✅ All jobs must be IDEMPOTENT (same job running twice = same result) +✅ Failed jobs → retry (max 3) → dead letter queue → alert +✅ Workers run as SEPARATE processes (not threads in API server) + +❌ Never put long-running tasks in request handlers +❌ Never assume job runs exactly once +``` + +### Idempotent Job Pattern + +```typescript +async function processPayment(data: { orderId: string }) { + const order = await orderRepo.findById(data.orderId); + if (order.paymentStatus === 'completed') return; // already processed + await paymentGateway.charge(order); + await orderRepo.updatePaymentStatus(order.id, 'completed'); +} +``` + +--- + +## 9. Caching Patterns (MEDIUM) + +### Cache-Aside (Lazy Loading) + +```typescript +async function getUser(id: string): Promise { + const cached = await redis.get(`user:${id}`); + if (cached) return JSON.parse(cached); + + const user = await userRepo.findById(id); + if (!user) throw new NotFoundError('User', id); + + await redis.set(`user:${id}`, JSON.stringify(user), 'EX', 900); // 15min TTL + return user; +} +``` + +### Rules + +``` +✅ ALWAYS set TTL — never cache without expiry +✅ Invalidate on write (delete cache key after update) +✅ Use cache for reads, never for authoritative state + +❌ Never cache without TTL (stale data is worse than slow data) +``` + +| Data Type | Suggested TTL | +|-----------|---------------| +| User profile | 5-15 min | +| Product catalog | 1-5 min | +| Config / feature flags | 30-60 sec | +| Session | Match session duration | + +--- + +## 10. File Upload Patterns (MEDIUM) + +### Option A: Presigned URL (Recommended for Large Files) + +``` +Client → GET /api/uploads/presign?filename=photo.jpg&type=image/jpeg +Server → { uploadUrl: "https://s3.../presigned", fileKey: "uploads/abc123.jpg" } +Client → PUT uploadUrl (direct to S3, bypasses your server) +Client → POST /api/photos { fileKey: "uploads/abc123.jpg" } (save reference) +``` + +**Backend:** +```typescript +app.get('/api/uploads/presign', authenticate, async (req, res) => { + const { filename, type } = req.query; + const key = `uploads/${crypto.randomUUID()}-${filename}`; + const url = await s3.getSignedUrl('putObject', { + Bucket: process.env.S3_BUCKET, Key: key, + ContentType: type, Expires: 300, // 5 min + }); + res.json({ uploadUrl: url, fileKey: key }); +}); +``` + +**Frontend:** +```typescript +async function uploadFile(file: File) { + const { uploadUrl, fileKey } = await apiClient.get( + `/api/uploads/presign?filename=${file.name}&type=${file.type}` + ); + await fetch(uploadUrl, { method: 'PUT', body: file, headers: { 'Content-Type': file.type } }); + return apiClient.post('/api/photos', { fileKey }); +} +``` + +### Option B: Multipart (Small Files < 10MB) + +```typescript +// Frontend +const formData = new FormData(); +formData.append('file', file); +formData.append('description', 'Profile photo'); +const res = await fetch('/api/upload', { method: 'POST', body: formData }); +// Note: do NOT set Content-Type header — browser sets boundary automatically +``` + +### Decision + +| Method | File Size | Server Load | Complexity | +|--------|-----------|-------------|------------| +| Presigned URL | Any (recommended > 5MB) | None (direct to storage) | Medium | +| Multipart | < 10MB | High (streams through server) | Low | +| Chunked / Resumable | > 100MB | Medium | High | + +--- + +## 11. Real-Time Patterns (MEDIUM) + +### Option A: Server-Sent Events (SSE) — One-Way Server → Client + +Best for: notifications, live feeds, streaming AI responses. + +**Backend (Express):** +```typescript +app.get('/api/events', authenticate, (req, res) => { + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + }); + const send = (event: string, data: unknown) => { + res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`); + }; + const unsubscribe = eventBus.subscribe(req.user.id, (event) => { + send(event.type, event.payload); + }); + req.on('close', () => unsubscribe()); +}); +``` + +**Frontend:** +```typescript +function useServerEvents(userId: string) { + useEffect(() => { + const source = new EventSource(`/api/events?userId=${userId}`); + source.addEventListener('notification', (e) => { + showToast(JSON.parse(e.data).message); + }); + source.onerror = () => { source.close(); setTimeout(() => /* reconnect */, 3000); }; + return () => source.close(); + }, [userId]); +} +``` + +### Option B: WebSocket — Bidirectional + +Best for: chat, collaborative editing, gaming. + +**Backend (ws library):** +```typescript +import { WebSocketServer } from 'ws'; +const wss = new WebSocketServer({ server: httpServer, path: '/ws' }); +wss.on('connection', (ws, req) => { + const userId = authenticateWs(req); + if (!userId) { ws.close(4001, 'Unauthorized'); return; } + ws.on('message', (raw) => handleMessage(userId, JSON.parse(raw.toString()))); + ws.on('close', () => cleanupUser(userId)); + const interval = setInterval(() => ws.ping(), 30000); + ws.on('pong', () => { /* alive */ }); + ws.on('close', () => clearInterval(interval)); +}); +``` + +**Frontend:** +```typescript +function useWebSocket(url: string) { + const [ws, setWs] = useState(null); + useEffect(() => { + const socket = new WebSocket(url); + socket.onopen = () => setWs(socket); + socket.onclose = () => setTimeout(() => /* reconnect */, 3000); + return () => socket.close(); + }, [url]); + const send = useCallback((data: unknown) => ws?.send(JSON.stringify(data)), [ws]); + return { ws, send }; +} +``` + +### Option C: Polling (Simplest, No Infrastructure) + +```typescript +function useOrderStatus(orderId: string) { + return useQuery({ + queryKey: ['order-status', orderId], + queryFn: () => apiClient.get(`/api/orders/${orderId}`), + refetchInterval: (query) => { + if (query.state.data?.status === 'completed') return false; + return 5000; + }, + }); +} +``` + +### Decision + +| Method | Direction | Complexity | When | +|--------|-----------|------------|------| +| Polling | Client → Server | Low | Simple status checks, < 10 clients | +| SSE | Server → Client | Medium | Notifications, feeds, AI streaming | +| WebSocket | Bidirectional | High | Chat, collaboration, gaming | + +--- + +## 12. Cross-Boundary Error Handling (MEDIUM) + +### API Error → User-Facing Message + +```typescript +// lib/error-handler.ts +export function getErrorMessage(error: unknown): string { + if (error instanceof ApiError) { + switch (error.status) { + case 401: return 'Please log in to continue.'; + case 403: return 'You don\'t have permission to do this.'; + case 404: return 'The item you\'re looking for doesn\'t exist.'; + case 409: return 'This conflicts with an existing item.'; + case 422: + const fields = error.body?.errors; + if (fields?.length) return fields.map((f: any) => f.message).join('. '); + return 'Please check your input.'; + case 429: return 'Too many requests. Please wait a moment.'; + default: return 'Something went wrong. Please try again.'; + } + } + if (error instanceof TypeError && error.message === 'Failed to fetch') { + return 'Cannot connect to server. Check your internet connection.'; + } + return 'An unexpected error occurred.'; +} +``` + +### React Query Global Error Handler + +```typescript +const queryClient = new QueryClient({ + defaultOptions: { + mutations: { onError: (error) => toast.error(getErrorMessage(error)) }, + queries: { + retry: (failureCount, error) => { + if (error instanceof ApiError && error.status < 500) return false; + return failureCount < 3; + }, + }, + }, +}); +``` + +### Rules + +``` +✅ Map every API error code to a human-readable message +✅ Show field-level validation errors next to form inputs +✅ Auto-retry on 5xx (max 3, with backoff), never on 4xx +✅ Redirect to login on 401 (after refresh attempt fails) +✅ Show "offline" banner when fetch fails with TypeError + +❌ Never show raw API error messages to users ("NullPointerException") +❌ Never silently swallow errors (show toast or log) +❌ Never retry 4xx errors (client is wrong, retrying won't help) +``` + +### Integration Decision Tree + +``` +Same team owns frontend + backend? +│ +├─ YES, both TypeScript +│ └─ tRPC (end-to-end type safety, zero codegen) +│ +├─ YES, different languages +│ └─ OpenAPI spec → generated client (type safety via codegen) +│ +├─ NO, public API +│ └─ REST + OpenAPI → generated SDKs for consumers +│ +└─ Complex data needs, multiple frontends + └─ GraphQL + codegen (flexible queries per client) + +Real-time needed? +│ +├─ Server → Client only (notifications, feeds, AI streaming) +│ └─ SSE (simplest, auto-reconnect, works through proxies) +│ +├─ Bidirectional (chat, collaboration) +│ └─ WebSocket (need heartbeat + reconnection logic) +│ +└─ Simple status polling (< 10 clients) + └─ React Query refetchInterval (no infrastructure needed) +``` + +--- + +## 13. Production Hardening (MEDIUM) + +### Health Checks + +```typescript +app.get('/health', (req, res) => res.json({ status: 'ok' })); // liveness +app.get('/ready', async (req, res) => { // readiness + const checks = { + database: await checkDb(), redis: await checkRedis(), + }; + const ok = Object.values(checks).every(c => c.status === 'ok'); + res.status(ok ? 200 : 503).json({ status: ok ? 'ok' : 'degraded', checks }); +}); +``` + +### Graceful Shutdown + +```typescript +process.on('SIGTERM', async () => { + logger.info('SIGTERM received'); + server.close(); // stop new connections + await drainConnections(); // finish in-flight + await closeDatabase(); + process.exit(0); +}); +``` + +### Security Checklist + +``` +✅ CORS: explicit origins (never '*' in production) +✅ Security headers (helmet / equivalent) +✅ Rate limiting on public endpoints +✅ Input validation on ALL endpoints (trust nothing) +✅ HTTPS enforced +❌ Never expose internal errors to clients +``` + +--- + +## Anti-Patterns + +| # | ❌ Don't | ✅ Do Instead | +|---|---------|--------------| +| 1 | Business logic in routes/controllers | Move to service layer | +| 2 | `process.env` scattered everywhere | Centralized typed config | +| 3 | `console.log` for logging | Structured JSON logger | +| 4 | Generic `Error('oops')` | Typed error hierarchy | +| 5 | Direct DB calls in controllers | Repository pattern | +| 6 | No input validation | Validate at boundary (Zod/Pydantic) | +| 7 | Catching errors silently | Log + rethrow or return error | +| 8 | No health check endpoints | `/health` + `/ready` | +| 9 | Hardcoded config/secrets | Environment variables | +| 10 | No graceful shutdown | Handle SIGTERM properly | +| 11 | Hardcode API URL in frontend | Environment variable (`NEXT_PUBLIC_API_URL`) | +| 12 | Store JWT in localStorage | Memory + httpOnly refresh cookie | +| 13 | Show raw API errors to users | Map to human-readable messages | +| 14 | Retry 4xx errors | Only retry 5xx (server failures) | +| 15 | Skip loading states | Skeleton/spinner while fetching | +| 16 | Upload large files through API server | Presigned URL → direct to S3 | +| 17 | Poll for real-time data | SSE or WebSocket | +| 18 | Duplicate types frontend + backend | Shared types, tRPC, or OpenAPI codegen | + +--- + +## Common Issues + +### Issue 1: "Where does this business rule go?" + +**Rule:** If it involves HTTP (request parsing, status codes, headers) → controller. If it involves business decisions (pricing, permissions, rules) → service. If it touches the database → repository. + +### Issue 2: "Service is getting too big" + +**Symptom:** One service file > 500 lines with 20+ methods. + +**Fix:** Split by sub-domain. `OrderService` → `OrderCreationService` + `OrderFulfillmentService` + `OrderQueryService`. Each focused on one workflow. + +### Issue 3: "Tests are slow because they hit the database" + +**Fix:** Unit tests mock the repository layer (fast). Integration tests use test containers or transaction rollback (real DB, still fast). Never mock the service layer in integration tests. + +--- + +## Reference Documents + +This skill includes deep-dive references for specialized topics. Read the relevant reference when you need detailed guidance. + +| Need to… | Reference | +|----------|-----------| +| Write backend tests (unit, integration, e2e, contract, performance) | [references/testing-strategy.md](references/testing-strategy.md) | +| Validate a release before deployment (6-gate checklist) | [references/release-checklist.md](references/release-checklist.md) | +| Choose a tech stack (language, framework, database, infra) | [references/technology-selection.md](references/technology-selection.md) | +| Build with Django / DRF (models, views, serializers, admin) | [references/django-best-practices.md](references/django-best-practices.md) | +| Design REST/GraphQL/gRPC endpoints (URLs, status codes, pagination) | [references/api-design.md](references/api-design.md) | +| Design database schema, indexes, migrations, multi-tenancy | [references/db-schema.md](references/db-schema.md) | +| Auth flow (JWT bearer, token refresh, Next.js SSR, RBAC, middleware order) | [references/auth-flow.md](references/auth-flow.md) | +| CORS config, env vars per environment, common CORS issues | [references/environment-management.md](references/environment-management.md) | diff --git a/skills/fullstack-dev/references/api-design.md b/skills/fullstack-dev/references/api-design.md new file mode 100644 index 0000000..bf73a25 --- /dev/null +++ b/skills/fullstack-dev/references/api-design.md @@ -0,0 +1,444 @@ +--- +name: fullstack-dev-api-design +description: "API design patterns and best practices. Use when creating endpoints, choosing methods/status codes, implementing pagination, or writing OpenAPI specs. Prevents common REST/GraphQL/gRPC mistakes." +license: MIT +metadata: + version: "2.0.0" + sources: + - Microsoft REST API Guidelines + - Google API Design Guide + - Zalando RESTful API Guidelines + - JSON:API Specification + - RFC 9457 (Problem Details for HTTP APIs) + - RFC 9110 (HTTP Semantics) +--- + +# API Design Guidelines + +Framework-agnostic API design guide for backend and full-stack engineers. 50+ rules across 10 categories, prioritized by impact. Covers REST, GraphQL, and gRPC. + +## Scope + +**USE this skill when:** +- Designing a new API or adding endpoints +- Reviewing API pull requests +- Choosing between REST / GraphQL / gRPC +- Writing OpenAPI specifications +- Migrating or versioning an existing API + +**NOT for:** +- Framework-specific implementation details (use your framework's own skill/docs) +- Frontend data fetching patterns (use React Query / SWR docs) +- Authentication implementation details (use your auth library's docs) +- Database schema design (→ `database-schema-design`) + +## Context Required + +Before applying this skill, gather: + +| Required | Optional | +|----------|----------| +| Target consumers (browser, mobile, service) | Existing API conventions in the project | +| Expected request volume (RPS estimate) | Current OpenAPI / Swagger spec | +| Authentication method (JWT, API key, OAuth) | Rate limiting requirements | +| Data model / domain entities | Caching strategy | + +--- + +## Quick Start Checklist + +New API endpoint? Run through this before writing code: + +- [ ] Resource named as **plural noun** (`/orders`, not `/getOrders`) +- [ ] URL in **kebab-case**, body fields in **camelCase** +- [ ] Correct **HTTP method** (GET=read, POST=create, PUT=replace, PATCH=partial, DELETE=remove) +- [ ] Correct **status code** (201 Created, 422 Validation, 404 Not Found…) +- [ ] Error response follows **RFC 9457** envelope +- [ ] **Pagination** on all list endpoints (default 20, max 100) +- [ ] **Authentication** required (Bearer token, not query param) +- [ ] **Request ID** in response header (`X-Request-Id`) +- [ ] **Rate limit** headers included +- [ ] Endpoint documented in **OpenAPI spec** + +--- + +## Quick Navigation + +| Need to… | Jump to | +|----------|---------| +| Name a resource URL | [1. Resource Modeling](#1-resource-modeling-critical) | +| Pick HTTP method + status code | [3. HTTP Methods & Status Codes](#3-http-methods--status-codes-critical) | +| Format error responses | [4. Error Handling](#4-error-handling-high) | +| Add pagination or filtering | [6. Pagination & Filtering](#6-pagination--filtering-high) | +| Choose API style (REST vs GraphQL vs gRPC) | [10. API Style Decision](#10-api-style-decision-tree) | +| Version an existing API | [7. Versioning](#7-versioning-medium-high) | +| Avoid common mistakes | [Anti-Patterns](#anti-patterns-checklist) | + +--- + +## 1. Resource Modeling (CRITICAL) + +### Core Rules + +``` +✅ /users — plural noun +✅ /users/{id}/orders — 1 level nesting +✅ /reviews?orderId={oid} — flatten deep nesting with query params + +❌ /getUsers — verb in URL +❌ /user — singular +❌ /users/{uid}/orders/{oid}/items/{iid}/reviews — 3+ levels deep +``` + +**Max nesting: 2 levels.** Beyond that, promote to top-level resource with filters. + +### Domain Alignment + +Resources map to **domain concepts**, not database tables: + +``` +✅ /checkout-sessions (domain aggregate) +✅ /shipping-labels (domain concept) + +❌ /tbl_order_header (database table leak) +❌ /join_user_role (internal schema leak) +``` + +--- + +## 2. URL & Naming (CRITICAL) + +| Context | Convention | Example | +|---------|-----------|---------| +| URL path | kebab-case | `/order-items` | +| JSON body fields | camelCase | `{ "firstName": "Jane" }` | +| Query params | camelCase or snake_case (be consistent) | `?sortBy=createdAt` | +| Headers | Train-Case | `X-Request-Id` | + +**Python exception:** If your entire stack is Python/snake_case, you MAY use `snake_case` in JSON — but be **consistent across all endpoints**. + +``` +✅ GET /users ❌ GET /users/ +✅ GET /reports/annual ❌ GET /reports/annual.json +✅ POST /users ❌ POST /users/create +``` + +--- + +## 3. HTTP Methods & Status Codes (CRITICAL) + +### Method Semantics + +| Method | Semantics | Idempotent | Safe | Request Body | +|--------|-----------|-----------|------|-------------| +| GET | Read | ✅ | ✅ | ❌ Never | +| POST | Create / Action | ❌ | ❌ | ✅ Always | +| PUT | Full replace | ✅ | ❌ | ✅ Always | +| PATCH | Partial update | ❌* | ❌ | ✅ Always | +| DELETE | Remove | ✅ | ❌ | ❌ Rarely | + +### Status Code Quick Reference + +**Success:** + +| Code | When | Response Body | +|------|------|--------------| +| 200 OK | GET, PUT, PATCH success | Resource / result | +| 201 Created | POST created resource | Created resource + `Location` header | +| 202 Accepted | Async operation started | Job ID / status URL | +| 204 No Content | DELETE success, PUT with no body | None | + +**Client Errors:** + +| Code | When | Key Distinction | +|------|------|-----------------| +| 400 Bad Request | Malformed syntax | Can't even parse | +| 401 Unauthorized | Missing / invalid auth | "Who are you?" | +| 403 Forbidden | Authenticated, no permission | "I know you, but no" | +| 404 Not Found | Resource doesn't exist | Also use to hide 403 | +| 409 Conflict | Duplicate, version mismatch | State conflict | +| 422 Unprocessable | Valid syntax, failed validation | Semantic errors | +| 429 Too Many Requests | Rate limit hit | Include `Retry-After` | + +**Server Errors:** 500 (unexpected), 502 (upstream fail), 503 (overloaded), 504 (upstream timeout) + +--- + +## 4. Error Handling (HIGH) + +### Standard Error Envelope (RFC 9457) + +Every error response uses this format: + +```json +{ + "type": "https://api.example.com/errors/insufficient-funds", + "title": "Insufficient Funds", + "status": 422, + "detail": "Account balance $10.00 is less than withdrawal $50.00.", + "instance": "/transactions/txn_abc123", + "request_id": "req_7f3a8b2c", + "errors": [ + { "field": "amount", "message": "Exceeds balance", "code": "INSUFFICIENT_BALANCE" } + ] +} +``` + +### Multi-Language Implementation + +**TypeScript (Express):** +```typescript +class AppError extends Error { + constructor( + public readonly title: string, + public readonly status: number, + public readonly detail: string, + public readonly code: string, + ) { super(detail); } +} + +// Middleware +app.use((err, req, res, next) => { + if (err instanceof AppError) { + return res.status(err.status).json({ + type: `https://api.example.com/errors/${err.code}`, + title: err.title, status: err.status, + detail: err.detail, request_id: req.id, + }); + } + res.status(500).json({ title: 'Internal Error', status: 500, request_id: req.id }); +}); +``` + +**Python (FastAPI):** +```python +from fastapi import Request +from fastapi.responses import JSONResponse + +class AppError(Exception): + def __init__(self, title: str, status: int, detail: str, code: str): + self.title, self.status, self.detail, self.code = title, status, detail, code + +@app.exception_handler(AppError) +async def app_error_handler(request: Request, exc: AppError): + return JSONResponse(status_code=exc.status, content={ + "type": f"https://api.example.com/errors/{exc.code}", + "title": exc.title, "status": exc.status, + "detail": exc.detail, "request_id": request.state.request_id, + }) +``` + +### Iron Rules + +``` +✅ Return RFC 9457 error envelope for ALL errors +✅ Include request_id in every error response +✅ Return per-field validation errors in `errors` array + +❌ Never expose stack traces in production +❌ Never return 200 for errors +❌ Never swallow errors silently +``` + +--- + +## 5. Authentication & Authorization (HIGH) + +``` +✅ Authorization: Bearer eyJhbGci... (header) +❌ GET /users?token=eyJhbGci... (URL — appears in logs) + +✅ 401 → "Who are you?" (missing/invalid credentials) +✅ 403 → "You can't do this" (authenticated, no permission) +✅ 404 → Hide resource existence (use instead of 403 when needed) +``` + +**Rate Limit Headers (always include):** +``` +X-RateLimit-Limit: 100 +X-RateLimit-Remaining: 42 +X-RateLimit-Reset: 1625097600 +Retry-After: 30 +``` + +--- + +## 6. Pagination & Filtering (HIGH) + +### Cursor vs Offset + +| Strategy | When | Pros | Cons | +|----------|------|------|------| +| **Cursor** (preferred) | Large/dynamic datasets | Consistent, no skips | Can't jump to page N | +| **Offset** | Small/stable datasets, admin UIs | Simple, page jumps | Drift on insert/delete | + +**Cursor pagination response:** +```json +{ + "data": [...], + "pagination": { "next_cursor": "eyJpZCI6MTIwfQ", "has_more": true } +} +``` + +**Offset pagination response:** +```json +{ + "data": [...], + "pagination": { "page": 3, "per_page": 20, "total": 256, "total_pages": 13 } +} +``` + +**Always enforce:** Default 20 items, max 100 items. + +### Standard Filter Patterns + +``` +GET /orders?status=shipped&created_after=2025-01-01&sort=-created_at&fields=id,status +``` + +| Pattern | Convention | +|---------|-----------| +| Exact match | `?status=shipped` | +| Range | `?price_gte=10&price_lte=100` | +| Date range | `?created_after=2025-01-01&created_before=2025-12-31` | +| Sort | `?sort=field` (asc), `?sort=-field` (desc) | +| Sparse fields | `?fields=id,name,email` | +| Search | `?q=search+term` | + +--- + +## 7. Versioning (MEDIUM-HIGH) + +| Strategy | Format | Best For | +|----------|--------|----------| +| **URL path** (recommended) | `/v1/users` | Public APIs | +| **Header** | `Api-Version: 2` | Internal APIs | +| **Query param** | `?version=2` | Legacy (avoid) | + +**Non-breaking changes (no version bump):** New optional response fields, new endpoints, new optional params. + +**Breaking changes (new version required):** Removing/renaming fields, changing types, stricter validation, removing endpoints. + +**Deprecation headers:** +``` +Sunset: Sat, 01 Mar 2026 00:00:00 GMT +Deprecation: true +Link: ; rel="successor-version" +``` + +--- + +## 8. Request / Response Design (MEDIUM) + +### Consistent Envelope + +```json +{ + "data": { "id": "ord_123", "status": "pending", "total": 99.50 }, + "meta": { "request_id": "req_abc123", "timestamp": "2025-06-15T10:30:00Z" } +} +``` + +### Key Rules + +| Rule | Correct | Wrong | +|------|---------|-------| +| Timestamps | `"2025-06-15T10:30:00Z"` (ISO 8601) | `"06/15/2025"` or `1718447400` | +| Public IDs | UUID `"550e8400-..."` | Auto-increment `42` | +| Null vs absent (PATCH) | `{ "nickname": null }` = clear field | Absent field = don't change | +| HATEOAS (public APIs) | `"links": { "cancel": "/orders/123/cancel" }` | No discoverability | + +--- + +## 9. Documentation — OpenAPI (MEDIUM) + +**Design-first workflow:** + +``` +1. Write OpenAPI 3.1 spec +2. Review spec with stakeholders +3. Generate server stubs + client SDKs +4. Implement handlers +5. Validate responses against spec in CI +``` + +Every endpoint documents: summary, all parameters, request body + examples, all response codes + schemas, auth requirements. + +--- + +## 10. API Style Decision Tree + +``` +What kind of API? +│ +├─ Browser + mobile clients, flexible queries +│ └─ GraphQL +│ Rules: DataLoader (no N+1), depth limit ≤7, Relay pagination +│ +├─ Standard CRUD, public consumers, caching important +│ └─ REST (this guide) +│ Rules: Resources, HTTP methods, status codes, OpenAPI +│ +├─ Service-to-service, high throughput, strong typing +│ └─ gRPC +│ Rules: Protobuf schemas, streaming for large data, deadlines +│ +├─ Full-stack TypeScript, same team owns client + server +│ └─ tRPC +│ Rules: Shared types, no code generation needed +│ +└─ Real-time bidirectional + └─ WebSocket / SSE + Rules: Heartbeat, reconnection, message ordering +``` + +--- + +## Anti-Patterns Checklist + +| # | ❌ Don't | ✅ Do Instead | +|---|---------|--------------| +| 1 | Verbs in URLs (`/getUser`) | HTTP methods + noun resources | +| 2 | Return 200 for errors | Correct 4xx/5xx status codes | +| 3 | Mix naming styles | One convention per context | +| 4 | Expose database IDs | UUIDs for public identifiers | +| 5 | No pagination on lists | Always paginate (default 20) | +| 6 | Swallow errors silently | Structured RFC 9457 errors | +| 7 | Token in URL query | Authorization header | +| 8 | Deep nesting (3+ levels) | Flatten with query params | +| 9 | Break changes without version | Maintain compatibility or version | +| 10 | No rate limiting | Implement + communicate via headers | +| 11 | No request ID | `X-Request-Id` on every response | +| 12 | Stack traces in production | Safe error message + internal log | + +--- + +## Common Issues + +### Issue 1: "Should this be a new resource or a sub-resource?" + +**Symptom:** URL path keeps growing (`/users/{id}/orders/{id}/items/{id}/reviews`) + +**Rule:** If the child entity makes sense on its own, promote it. If it only exists within the parent context, keep it nested (max 2 levels). + +``` +/reviews?orderId=123 ✅ (reviews exist independently) +/orders/{id}/items ✅ (items belong to orders, 1 level) +``` + +### Issue 2: "PUT or PATCH?" + +**Symptom:** Team can't agree on update semantics. + +**Rule:** +- PUT = client sends **complete** resource (missing fields → set to default/null) +- PATCH = client sends **only changed fields** (missing fields → unchanged) +- When unsure → **PATCH** (safer, less surprising) + +### Issue 3: "400 or 422?" + +**Symptom:** Inconsistent validation error codes. + +**Rule:** +- 400 = can't parse request at all (malformed JSON, wrong content-type) +- 422 = parsed OK, but values fail validation (invalid email, negative quantity) diff --git a/skills/fullstack-dev/references/auth-flow.md b/skills/fullstack-dev/references/auth-flow.md new file mode 100644 index 0000000..8c71351 --- /dev/null +++ b/skills/fullstack-dev/references/auth-flow.md @@ -0,0 +1,165 @@ +# Authentication Flow Patterns + +Complete auth flow across frontend and backend. Covers JWT bearer flow, automatic token refresh, Next.js server-side auth, RBAC, and backend middleware order. + +--- + +## JWT Bearer Flow (Most Common) + +``` +1. Login + Client → POST /api/auth/login { email, password } + Server → { accessToken (15min), refreshToken (7d, httpOnly cookie) } + +2. Authenticated Requests + Client → GET /api/orders Authorization: Bearer + Server → validates JWT → returns data + +3. Token Refresh (transparent) + Client → 401 received → POST /api/auth/refresh (cookie auto-sent) + Server → new accessToken + Client → retry original request with new token + +4. Logout + Client → POST /api/auth/logout + Server → invalidate refresh token → clear cookie +``` + +--- + +## Frontend: Automatic Token Refresh + +```typescript +// lib/api-client.ts — add to existing fetch wrapper +async function apiWithRefresh(path: string, options: RequestInit = {}): Promise { + try { + return await api(path, options); + } catch (err) { + if (err instanceof ApiError && err.status === 401) { + // Try refresh + const refreshed = await api<{ accessToken: string }>('/api/auth/refresh', { + method: 'POST', + credentials: 'include', // send httpOnly cookie + }); + setAuthToken(refreshed.accessToken); + // Retry original request + return api(path, options); + } + throw err; + } +} +``` + +--- + +## Next.js: Server-Side Auth (App Router) + +```typescript +// middleware.ts — protect routes server-side +import { NextResponse } from 'next/server'; +import type { NextRequest } from 'next/server'; + +export function middleware(request: NextRequest) { + const token = request.cookies.get('session')?.value; + if (!token && request.nextUrl.pathname.startsWith('/dashboard')) { + return NextResponse.redirect(new URL('/login', request.url)); + } + return NextResponse.next(); +} + +// app/dashboard/page.tsx — server component with auth +import { cookies } from 'next/headers'; + +export default async function Dashboard() { + const token = (await cookies()).get('session')?.value; + const user = await fetch(`${process.env.API_URL}/api/me`, { + headers: { Authorization: `Bearer ${token}` }, + }).then(r => r.json()); + + return ; +} +``` + +--- + +## Backend: Standard Middleware Order + +``` +Request → 1.RequestID → 2.Logging → 3.CORS → 4.RateLimit → 5.BodyParse + → 6.Auth → 7.Authz → 8.Validation → 9.Handler → 10.ErrorHandler → Response +``` + +--- + +## Backend: JWT Rules + +``` +✅ Short expiry access token (15min) + refresh token (server-stored) +✅ Minimal claims: userId, roles (not entire user object) +✅ Rotate signing keys periodically + +❌ Never store tokens in localStorage (XSS risk) +❌ Never pass tokens in URL query params +``` + +--- + +## Backend: RBAC Pattern + +```typescript +function authorize(...roles: Role[]) { + return (req, res, next) => { + if (!req.user) throw new UnauthorizedError(); + if (!roles.some(r => req.user.roles.includes(r))) throw new ForbiddenError(); + next(); + }; +} +router.delete('/users/:id', authenticate, authorize('admin'), deleteUser); +``` + +--- + +## Auth Decision Table + +| Method | When | Frontend | +|--------|------|----------| +| Session | Same-domain, SSR, Django templates | Django templates / htmx | +| JWT | Different domain, SPA, mobile | React, Vue, mobile apps | +| OAuth2 | Third-party login, API consumers | Any | + +--- + +## Iron Rules + +``` +✅ Access token: short-lived (15min), in memory +✅ Refresh token: httpOnly cookie (XSS-safe) +✅ Automatic transparent refresh on 401 +✅ Redirect to login when refresh fails + +❌ Never store tokens in localStorage (XSS risk) +❌ Never send tokens in URL query params (logged) +❌ Never trust client-side auth checks alone (server must validate) +``` + +--- + +## Common Issues + +### Issue 1: "Auth works on page load but breaks on navigation" + +**Cause:** Token stored in component state (lost on unmount). + +**Fix:** Store access token in a persistent location: +- React Context (survives navigation, lost on refresh) +- Cookie (survives refresh) +- React Query cache with `staleTime: Infinity` for session + +### Issue 2: "CORS error with auth requests" + +**Cause:** Missing `credentials: 'include'` on frontend or `credentials: true` on backend CORS config. + +**Fix:** +1. Frontend: `fetch(url, { credentials: 'include' })` +2. Backend: `cors({ origin: 'https://your-frontend.com', credentials: true })` +3. Backend: explicit origin (not `*`) when using credentials diff --git a/skills/fullstack-dev/references/db-schema.md b/skills/fullstack-dev/references/db-schema.md new file mode 100644 index 0000000..c3442af --- /dev/null +++ b/skills/fullstack-dev/references/db-schema.md @@ -0,0 +1,706 @@ +--- +name: fullstack-dev-db-schema +description: "Database schema design and migrations. Use when creating tables, defining ORM models, adding indexes, or designing relationships. Covers zero-downtime migrations and multi-tenancy." +license: MIT +metadata: + version: "1.0.0" + sources: + - PostgreSQL official documentation + - Use The Index, Luke (use-the-index-luke.com) + - Designing Data-Intensive Applications (Martin Kleppmann) + - Database Reliability Engineering (Laine Campbell & Charity Majors) +--- + +# Database Schema Design + +ORM-agnostic guide for relational database schema design. Covers data modeling, normalization, indexing, migrations, multi-tenancy, and common application patterns. Primarily PostgreSQL-focused but principles apply to MySQL/MariaDB. + +## Scope + +**USE this skill when:** +- Designing a schema for a new project or feature +- Deciding between normalization and denormalization +- Choosing which indexes to create +- Planning a zero-downtime migration on a live database +- Implementing multi-tenant data isolation +- Adding audit trails, soft delete, or versioning +- Diagnosing slow queries caused by schema problems + +**NOT for:** +- Choosing which database technology to use (→ `technology-selection`) +- PostgreSQL-specific query tuning (use PostgreSQL performance docs) +- ORM-specific configuration (→ `django-best-practices` or your ORM's docs) +- Application-layer caching (→ `fullstack-dev-practices`) + +## Context Required + +| Required | Optional | +|----------|----------| +| Database engine (PostgreSQL / MySQL) | Expected data volume (rows, growth rate) | +| Domain entities and relationships | Read/write ratio | +| Key access patterns (queries) | Multi-tenant requirements | + +--- + +## Quick Start Checklist + +Designing a new schema: + +- [ ] **Domain entities identified** — map 1 entity = 1 table (not 1 class = 1 table) +- [ ] **Primary keys**: UUID for public IDs, serial/bigserial for internal-only +- [ ] **Foreign keys** with explicit `ON DELETE` behavior +- [ ] **NOT NULL** by default — nullable only when business logic requires it +- [ ] **Timestamps**: `created_at` + `updated_at` on every table +- [ ] **Indexes** created for every WHERE, JOIN, ORDER BY column +- [ ] **No premature denormalization** — start normalized, denormalize when measured +- [ ] **Naming convention** consistent: `snake_case`, plural table names + +--- + +## Quick Navigation + +| Need to… | Jump to | +|----------|---------| +| Model entities and relationships | [1. Data Modeling](#1-data-modeling-critical) | +| Decide normalize vs denormalize | [2. Normalization](#2-normalization-vs-denormalization-critical) | +| Choose the right index | [3. Indexing](#3-indexing-strategy-critical) | +| Run migrations safely on live DB | [4. Migrations](#4-zero-downtime-migrations-high) | +| Design multi-tenant schema | [5. Multi-Tenancy](#5-multi-tenant-design-high) | +| Add soft delete / audit trails | [6. Common Patterns](#6-common-schema-patterns-medium) | +| Partition large tables | [7. Partitioning](#7-table-partitioning-medium) | +| See anti-patterns | [Anti-Patterns](#anti-patterns) | + +--- + +## Core Principles (7 Rules) + +``` +1. ✅ Start normalized (3NF) — denormalize only when you have measured evidence +2. ✅ Every table has a primary key, created_at, updated_at +3. ✅ UUID for public-facing IDs, serial for internal join keys +4. ✅ NOT NULL by default — null is a business decision, not a lazy default +5. ✅ Index every column used in WHERE, JOIN, ORDER BY +6. ✅ Foreign keys enforced in database (not just application code) +7. ✅ Migrations are additive — never drop/rename in production without a multi-step plan +``` + +--- + +## 1. Data Modeling (CRITICAL) + +### Table Naming + +```sql +-- ✅ Plural, snake_case +CREATE TABLE orders (...); +CREATE TABLE order_items (...); +CREATE TABLE user_profiles (...); + +-- ❌ Singular, mixed case +CREATE TABLE Order (...); +CREATE TABLE OrderItem (...); +CREATE TABLE tbl_usr_prof (...); -- cryptic abbreviation +``` + +### Primary Keys + +| Strategy | When | Pros | Cons | +|----------|------|------|------| +| `bigserial` (auto-increment) | Internal tables, FK joins | Compact, fast joins | Enumerable, not safe for public IDs | +| `uuid` (v4 random) | Public-facing resources | Non-guessable, globally unique | Larger (16 bytes), random I/O on B-Tree | +| `uuid` v7 (time-sorted) | Public + needs ordering | Non-guessable + insert-friendly | Newer, less ecosystem support | +| `text` slug | URL-friendly resources | Human-readable | Must enforce uniqueness, updates expensive | + +**Recommended default:** + +```sql +CREATE TABLE orders ( + id bigserial PRIMARY KEY, -- internal FK target + public_id uuid NOT NULL DEFAULT gen_random_uuid() UNIQUE, -- API-facing + -- ... + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now() +); +``` + +### Relationships + +```sql +-- One-to-Many: user → orders +CREATE TABLE orders ( + id bigserial PRIMARY KEY, + user_id bigint NOT NULL REFERENCES users(id) ON DELETE CASCADE, + -- ... +); +CREATE INDEX idx_orders_user_id ON orders(user_id); + +-- Many-to-Many: orders ↔ products (via junction table) +CREATE TABLE order_items ( + id bigserial PRIMARY KEY, + order_id bigint NOT NULL REFERENCES orders(id) ON DELETE CASCADE, + product_id bigint NOT NULL REFERENCES products(id) ON DELETE RESTRICT, + quantity int NOT NULL CHECK (quantity > 0), + unit_price numeric(10,2) NOT NULL, + UNIQUE (order_id, product_id) -- prevent duplicate line items +); + +-- One-to-One: user → profile +CREATE TABLE user_profiles ( + user_id bigint PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, + bio text, + avatar_url text, + -- ... +); +``` + +### ON DELETE Behavior + +| Behavior | When | Example | +|----------|------|---------| +| `CASCADE` | Child meaningless without parent | order_items when order deleted | +| `RESTRICT` | Prevent accidental deletion | products referenced by order_items | +| `SET NULL` | Preserve child, clear reference | orders.assigned_to when employee leaves | +| `SET DEFAULT` | Fallback to default value | Rare, for status columns | + +--- + +## 2. Normalization vs Denormalization (CRITICAL) + +### Start Normalized (3NF) + +**Normal forms in practice:** + +| Form | Rule | Example Violation | +|------|------|-------------------| +| 1NF | No repeating groups, atomic values | `tags = "go,python,rust"` in one column | +| 2NF | No partial dependencies (composite keys) | `order_items.product_name` depends on `product_id` alone | +| 3NF | No transitive dependencies | `orders.customer_city` depends on `customer_id`, not `order_id` | + +**1NF violation fix:** +```sql +-- ❌ Tags as comma-separated string +CREATE TABLE posts (id serial, tags text); -- tags = "go,python" + +-- ✅ Separate table (or array/JSONB if simple) +CREATE TABLE post_tags ( + post_id bigint REFERENCES posts(id) ON DELETE CASCADE, + tag_id bigint REFERENCES tags(id) ON DELETE CASCADE, + PRIMARY KEY (post_id, tag_id) +); + +-- ✅ Alternative: PostgreSQL array (if tags are just strings, no metadata) +CREATE TABLE posts (id serial, tags text[] NOT NULL DEFAULT '{}'); +CREATE INDEX idx_posts_tags ON posts USING GIN(tags); +``` + +### When to Denormalize + +**Denormalize ONLY when:** +1. You have **measured** a performance problem (EXPLAIN ANALYZE, not "I think it's slow") +2. The denormalized data is **read-heavy** (read:write ratio > 100:1) +3. You accept the **consistency maintenance cost** (triggers, application logic, or materialized views) + +**Safe denormalization patterns:** + +```sql +-- Pattern 1: Materialized view (computed, refreshable) +CREATE MATERIALIZED VIEW order_summary AS +SELECT o.id, o.user_id, o.total, + COUNT(oi.id) AS item_count, + u.email AS user_email +FROM orders o +JOIN order_items oi ON oi.order_id = o.id +JOIN users u ON u.id = o.user_id +GROUP BY o.id, u.email; + +REFRESH MATERIALIZED VIEW CONCURRENTLY order_summary; -- non-blocking + +-- Pattern 2: Cached aggregate column (application-maintained) +ALTER TABLE orders ADD COLUMN item_count int NOT NULL DEFAULT 0; +-- Update via trigger or application code on order_item insert/delete + +-- Pattern 3: JSONB snapshot (freeze-at-write-time) +-- Store a copy of the product details at the time of purchase +CREATE TABLE order_items ( + id bigserial PRIMARY KEY, + order_id bigint NOT NULL REFERENCES orders(id), + product_id bigint REFERENCES products(id), + quantity int NOT NULL, + unit_price numeric(10,2) NOT NULL, -- frozen price + product_snapshot jsonb NOT NULL -- frozen name, description, image +); +``` + +--- + +## 3. Indexing Strategy (CRITICAL) + +### Index Types (PostgreSQL) + +| Type | When | Example | +|------|------|---------| +| **B-Tree** (default) | Equality, range, ORDER BY | `WHERE status = 'active'`, `WHERE created_at > '2025-01-01'` | +| **Hash** | Equality only (rare, B-Tree usually better) | `WHERE id = 123` (large tables, Postgres 10+) | +| **GIN** | Arrays, JSONB, full-text search | `WHERE tags @> '{go}'`, `WHERE data->>'key' = 'val'` | +| **GiST** | Geometry, ranges, nearest-neighbor | PostGIS, tsrange, ltree | +| **BRIN** | Very large tables with natural ordering | Time-series data sorted by timestamp | + +### Index Decision Rules + +``` +Rule 1: Index every column in WHERE clauses +Rule 2: Index every column used in JOIN ON conditions +Rule 3: Index every column in ORDER BY (if queried with LIMIT) +Rule 4: Composite index for multi-column WHERE (leftmost prefix rule) +Rule 5: Partial index when filtering a subset (e.g., only active records) +Rule 6: Covering index (INCLUDE) to avoid table lookup +Rule 7: DON'T index low-cardinality columns alone (e.g., boolean) +``` + +### Composite Index: Column Order Matters + +```sql +-- Query: WHERE user_id = ? AND status = ? ORDER BY created_at DESC +-- ✅ Optimal: matches query pattern left-to-right +CREATE INDEX idx_orders_user_status_created +ON orders(user_id, status, created_at DESC); + +-- ❌ Wrong order: can't use for this query efficiently +CREATE INDEX idx_orders_created_user_status +ON orders(created_at DESC, user_id, status); +``` + +**Leftmost prefix rule:** Index on `(A, B, C)` supports queries on `(A)`, `(A, B)`, `(A, B, C)` but NOT `(B)`, `(C)`, or `(B, C)`. + +### Partial Index (Index Only What Matters) + +```sql +-- Only 5% of orders are 'pending', but queried frequently +CREATE INDEX idx_orders_pending +ON orders(created_at DESC) +WHERE status = 'pending'; + +-- Only active users matter for login +CREATE INDEX idx_users_active_email +ON users(email) +WHERE is_active = true; +``` + +### Covering Index (Avoid Table Lookup) + +```sql +-- Query only needs id and status, no need to read the table row +CREATE INDEX idx_orders_user_covering +ON orders(user_id) INCLUDE (status, total); + +-- Now this query is index-only: +SELECT status, total FROM orders WHERE user_id = 123; +``` + +### When NOT to Index + +``` +❌ Columns rarely used in WHERE/JOIN/ORDER BY +❌ Tables with < 1,000 rows (sequential scan is faster) +❌ Columns with very low cardinality alone (e.g., boolean is_active) +❌ Write-heavy tables where index maintenance cost > read benefit +❌ Duplicate indexes (check pg_stat_user_indexes for unused indexes) +``` + +--- + +## 4. Zero-Downtime Migrations (HIGH) + +### The Golden Rule + +``` +NEVER make destructive changes in one step. +Always: ADD → MIGRATE DATA → REMOVE OLD (in separate deploys). +``` + +### Safe Migration Patterns + +**Rename a column (3 deploys):** + +``` +Deploy 1: Add new column + ALTER TABLE users ADD COLUMN full_name text; + UPDATE users SET full_name = name; -- backfill + -- App writes to BOTH name and full_name + +Deploy 2: Switch reads to new column + -- App reads from full_name, still writes to both + +Deploy 3: Drop old column + ALTER TABLE users DROP COLUMN name; + -- App only uses full_name +``` + +**Add a NOT NULL column (2 deploys):** + +```sql +-- Deploy 1: Add nullable column, backfill +ALTER TABLE orders ADD COLUMN currency text; -- nullable first +UPDATE orders SET currency = 'USD' WHERE currency IS NULL; -- backfill + +-- Deploy 2: Add constraint (after all rows backfilled) +ALTER TABLE orders ALTER COLUMN currency SET NOT NULL; +ALTER TABLE orders ALTER COLUMN currency SET DEFAULT 'USD'; +``` + +**Add an index without locking:** + +```sql +-- ✅ CONCURRENTLY: no table lock, can run on live DB +CREATE INDEX CONCURRENTLY idx_orders_status ON orders(status); + +-- ❌ Without CONCURRENTLY: locks table for writes during build +CREATE INDEX idx_orders_status ON orders(status); +``` + +### Migration Safety Checklist + +``` +✅ Migration runs in < 30 seconds on production data size +✅ No exclusive table locks (use CONCURRENTLY for indexes) +✅ Rollback plan documented and tested +✅ Backfill runs in batches (not one giant UPDATE) +✅ New column added as nullable first, constraint added later +✅ Old column kept until all code references removed + +❌ Never rename/drop columns in one deploy +❌ Never ALTER TYPE on large tables without testing timing +❌ Never run data backfill in a transaction (OOM on large tables) +``` + +### Batch Backfill Template + +```sql +-- Backfill in batches of 10,000 (avoids long-running transactions) +DO $$ +DECLARE + batch_size int := 10000; + affected int; +BEGIN + LOOP + UPDATE orders + SET currency = 'USD' + WHERE id IN ( + SELECT id FROM orders WHERE currency IS NULL LIMIT batch_size + ); + GET DIAGNOSTICS affected = ROW_COUNT; + RAISE NOTICE 'Updated % rows', affected; + EXIT WHEN affected = 0; + PERFORM pg_sleep(0.1); -- brief pause to reduce load + END LOOP; +END $$; +``` + +--- + +## 5. Multi-Tenant Design (HIGH) + +### Three Approaches + +| Approach | Isolation | Complexity | When | +|----------|-----------|------------|------| +| **Row-level** (shared tables + `tenant_id`) | Low | Low | SaaS MVP, < 1,000 tenants | +| **Schema-per-tenant** | Medium | Medium | Regulated industries, moderate scale | +| **Database-per-tenant** | High | High | Enterprise, strict data isolation | + +### Row-Level Tenancy (Most Common) + +```sql +-- Every table has tenant_id +CREATE TABLE orders ( + id bigserial PRIMARY KEY, + tenant_id bigint NOT NULL REFERENCES tenants(id), + user_id bigint NOT NULL REFERENCES users(id), + total numeric(10,2) NOT NULL, + -- ... +); + +-- Composite index: tenant first (most queries filter by tenant) +CREATE INDEX idx_orders_tenant_user ON orders(tenant_id, user_id); +CREATE INDEX idx_orders_tenant_status ON orders(tenant_id, status); + +-- Row-Level Security (PostgreSQL) +ALTER TABLE orders ENABLE ROW LEVEL SECURITY; +CREATE POLICY tenant_isolation ON orders + USING (tenant_id = current_setting('app.tenant_id')::bigint); +``` + +**Application-level enforcement:** + +```typescript +// Middleware: set tenant context on every request +app.use((req, res, next) => { + const tenantId = req.headers['x-tenant-id']; + if (!tenantId) return res.status(400).json({ error: 'Missing tenant' }); + req.tenantId = tenantId; + next(); +}); + +// Repository: ALWAYS filter by tenant +async findOrders(tenantId: string, userId: string) { + return db.order.findMany({ + where: { tenantId, userId }, // ← tenant_id in EVERY query + }); +} +``` + +### Rules + +``` +✅ tenant_id in EVERY table that holds tenant data +✅ tenant_id as FIRST column in every composite index +✅ Application middleware enforces tenant context +✅ Use RLS (PostgreSQL) as defense-in-depth, not sole protection +✅ Test with 2+ tenants to verify isolation + +❌ Never allow cross-tenant queries in application code +❌ Never skip tenant_id in WHERE clauses (even in admin tools) +``` + +--- + +## 6. Common Schema Patterns (MEDIUM) + +### Soft Delete + +```sql +ALTER TABLE orders ADD COLUMN deleted_at timestamptz; + +-- All queries filter deleted records +CREATE VIEW active_orders AS +SELECT * FROM orders WHERE deleted_at IS NULL; + +-- Partial index: only index non-deleted rows +CREATE INDEX idx_orders_active_status +ON orders(status, created_at DESC) +WHERE deleted_at IS NULL; +``` + +**ORM integration:** + +```typescript +// Prisma middleware: auto-filter soft-deleted records +prisma.$use(async (params, next) => { + if (params.action === 'findMany' || params.action === 'findFirst') { + params.args.where = { ...params.args.where, deletedAt: null }; + } + return next(params); +}); +``` + +### Audit Trail + +```sql +-- Option A: Audit columns on every table +ALTER TABLE orders ADD COLUMN created_by bigint REFERENCES users(id); +ALTER TABLE orders ADD COLUMN updated_by bigint REFERENCES users(id); + +-- Option B: Separate audit log table (more detail) +CREATE TABLE audit_log ( + id bigserial PRIMARY KEY, + table_name text NOT NULL, + record_id bigint NOT NULL, + action text NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE')), + old_data jsonb, + new_data jsonb, + changed_by bigint REFERENCES users(id), + changed_at timestamptz NOT NULL DEFAULT now() +); +CREATE INDEX idx_audit_table_record ON audit_log(table_name, record_id); +CREATE INDEX idx_audit_changed_at ON audit_log(changed_at DESC); +``` + +### Enum Columns + +```sql +-- Option A: PostgreSQL enum type (strict, but ALTER TYPE is painful) +CREATE TYPE order_status AS ENUM ('pending', 'confirmed', 'shipped', 'delivered', 'cancelled'); +ALTER TABLE orders ADD COLUMN status order_status NOT NULL DEFAULT 'pending'; + +-- Option B: Text + CHECK constraint (easier to migrate) +ALTER TABLE orders ADD COLUMN status text NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending', 'confirmed', 'shipped', 'delivered', 'cancelled')); + +-- Option C: Lookup table (most flexible, best for UI-driven lists) +CREATE TABLE order_statuses ( + id serial PRIMARY KEY, + name text UNIQUE NOT NULL, + label text NOT NULL -- display name +); +``` + +**Recommendation:** Option B (text + CHECK) for most cases. Option C if statuses are managed by non-developers. + +### Polymorphic Associations + +```sql +-- ❌ Anti-pattern: polymorphic FK (no referential integrity) +CREATE TABLE comments ( + id bigserial PRIMARY KEY, + commentable_type text, -- 'Post' or 'Photo' + commentable_id bigint, -- no FK constraint possible! + body text +); + +-- ✅ Pattern A: Separate FK columns (nullable) +CREATE TABLE comments ( + id bigserial PRIMARY KEY, + post_id bigint REFERENCES posts(id) ON DELETE CASCADE, + photo_id bigint REFERENCES photos(id) ON DELETE CASCADE, + body text NOT NULL, + CHECK ( + (post_id IS NOT NULL AND photo_id IS NULL) OR + (post_id IS NULL AND photo_id IS NOT NULL) + ) +); + +-- ✅ Pattern B: Separate tables (cleanest, best for different schemas) +CREATE TABLE post_comments (..., post_id bigint REFERENCES posts(id)); +CREATE TABLE photo_comments (..., photo_id bigint REFERENCES photos(id)); +``` + +### JSONB Columns (Semi-Structured Data) + +```sql +-- Good uses: metadata, settings, flexible attributes +CREATE TABLE products ( + id bigserial PRIMARY KEY, + name text NOT NULL, + price numeric(10,2) NOT NULL, + attributes jsonb NOT NULL DEFAULT '{}' -- color, size, weight... +); + +-- Index for JSONB queries +CREATE INDEX idx_products_attrs ON products USING GIN(attributes); + +-- Query +SELECT * FROM products WHERE attributes->>'color' = 'red'; +SELECT * FROM products WHERE attributes @> '{"size": "XL"}'; +``` + +``` +✅ Use JSONB for truly flexible/optional data (metadata, settings, preferences) +✅ Index JSONB columns with GIN when queried + +❌ Never use JSONB for data that should be columns (email, status, price) +❌ Never use JSONB to avoid schema design (it's not MongoDB-in-Postgres) +``` + +--- + +## 7. Table Partitioning (MEDIUM) + +### When to Partition + +``` +✅ Table > 100M rows AND growing +✅ Most queries filter on the partition key (date range, tenant) +✅ Old data can be dropped/archived by partition (efficient DELETE) + +❌ Table < 10M rows (overhead not worth it) +❌ Queries don't filter on partition key (scans all partitions) +``` + +### Range Partitioning (Time-Series) + +```sql +CREATE TABLE events ( + id bigserial, + tenant_id bigint NOT NULL, + event_type text NOT NULL, + payload jsonb, + created_at timestamptz NOT NULL DEFAULT now() +) PARTITION BY RANGE (created_at); + +-- Monthly partitions +CREATE TABLE events_2025_01 PARTITION OF events + FOR VALUES FROM ('2025-01-01') TO ('2025-02-01'); +CREATE TABLE events_2025_02 PARTITION OF events + FOR VALUES FROM ('2025-02-01') TO ('2025-03-01'); + +-- Automate partition creation with pg_partman or cron +``` + +### List Partitioning (Multi-Tenant) + +```sql +CREATE TABLE orders ( + id bigserial, + tenant_id bigint NOT NULL, + total numeric(10,2) +) PARTITION BY LIST (tenant_id); + +CREATE TABLE orders_tenant_1 PARTITION OF orders FOR VALUES IN (1); +CREATE TABLE orders_tenant_2 PARTITION OF orders FOR VALUES IN (2); +``` + +--- + +## Anti-Patterns + +| # | ❌ Don't | ✅ Do Instead | +|---|---------|--------------| +| 1 | Premature denormalization | Start 3NF, denormalize when measured | +| 2 | Auto-increment IDs as public API identifiers | UUID for public, serial for internal | +| 3 | No foreign key constraints | FK enforced in database, always | +| 4 | Nullable by default | NOT NULL by default, nullable when required | +| 5 | No indexes on FK columns | Index every FK column | +| 6 | Single-step destructive migration | ADD → MIGRATE → REMOVE in separate deploys | +| 7 | `CREATE INDEX` without `CONCURRENTLY` | Always `CONCURRENTLY` on live tables | +| 8 | Polymorphic FK (`commentable_type + commentable_id`) | Separate FK columns or separate tables | +| 9 | JSONB for everything | JSONB for flexible data only, columns for structured | +| 10 | No `created_at` / `updated_at` | Timestamp pair on every table | +| 11 | Comma-separated values in one column | Separate table or PostgreSQL array | +| 12 | `text` without length validation | CHECK constraint or application validation | + +--- + +## Common Issues + +### Issue 1: "Query is slow but I already have an index" + +**Symptom:** `EXPLAIN ANALYZE` shows Sequential Scan despite existing index. + +**Causes:** +1. **Wrong index column order** — composite index `(A, B)` won't help `WHERE B = ?` +2. **Low selectivity** — index on boolean column (50% of rows match), planner prefers seq scan +3. **Stale statistics** — run `ANALYZE table_name;` +4. **Type mismatch** — comparing `varchar` column with `integer` parameter → no index use + +**Fix:** Check `EXPLAIN (ANALYZE, BUFFERS)`, verify index matches query pattern, run `ANALYZE`. + +### Issue 2: "Migration locks the table for minutes" + +**Symptom:** `ALTER TABLE` blocks all writes during execution. + +**Cause:** Adding NOT NULL constraint, changing column type, or creating index without `CONCURRENTLY`. + +**Fix:** +```sql +-- Add index without lock +CREATE INDEX CONCURRENTLY idx_name ON table(col); + +-- Add NOT NULL constraint without lock (Postgres 12+) +ALTER TABLE t ADD CONSTRAINT t_col_nn CHECK (col IS NOT NULL) NOT VALID; +ALTER TABLE t VALIDATE CONSTRAINT t_col_nn; -- non-blocking validation +``` + +### Issue 3: "How many indexes is too many?" + +**Rule of thumb:** +- Read-heavy table (reports, product catalog): 5-10 indexes is fine +- Write-heavy table (events, logs): 2-3 indexes max +- Monitor with `pg_stat_user_indexes` — drop indexes with `idx_scan = 0` + +```sql +-- Find unused indexes +SELECT schemaname, relname, indexrelname, idx_scan +FROM pg_stat_user_indexes +WHERE idx_scan = 0 AND indexrelname NOT LIKE '%pkey%' +ORDER BY pg_relation_size(indexrelid) DESC; +``` diff --git a/skills/fullstack-dev/references/django-best-practices.md b/skills/fullstack-dev/references/django-best-practices.md new file mode 100644 index 0000000..6c1ce6b --- /dev/null +++ b/skills/fullstack-dev/references/django-best-practices.md @@ -0,0 +1,466 @@ +# Django Best Practices + +Production-grade guide for Django 5.x and Django REST Framework. 40+ rules across 8 categories. + +## Core Principles (7 Rules) + +``` +1. ✅ Custom User model BEFORE first migration (can't change later) +2. ✅ One Django app per domain concept (users, orders, payments) +3. ✅ Fat models, thin views — business logic in models/managers, not views +4. ✅ Always use select_related/prefetch_related (prevent N+1) +5. ✅ Settings split by environment (base + dev + prod) +6. ✅ Test with pytest-django + factory_boy (not fixtures) +7. ✅ Never use runserver in production (Gunicorn + Nginx) +``` + +--- + +## 1. Project Structure (CRITICAL) + +### App-Per-Domain + +``` +myproject/ +├── config/ # Project config +│ ├── __init__.py +│ ├── settings/ +│ │ ├── base.py # Shared settings +│ │ ├── dev.py # DEBUG=True, SQLite ok +│ │ └── prod.py # DEBUG=False, Postgres, HTTPS +│ ├── urls.py +│ ├── wsgi.py +│ └── asgi.py +├── apps/ +│ ├── users/ # Custom User model +│ │ ├── models.py +│ │ ├── serializers.py +│ │ ├── views.py +│ │ ├── urls.py +│ │ ├── admin.py +│ │ ├── services.py # Business logic +│ │ ├── selectors.py # Complex queries +│ │ └── tests/ +│ │ ├── test_models.py +│ │ ├── test_views.py +│ │ └── factories.py +│ ├── orders/ +│ └── payments/ +├── manage.py +├── requirements/ +│ ├── base.txt +│ ├── dev.txt +│ └── prod.txt +└── docker-compose.yml +``` + +### Rules + +``` +✅ One app = one bounded context (users, orders, payments) +✅ Business logic in services.py / selectors.py, not views +✅ Each app has its own urls.py, admin.py, tests/ + +❌ Never put everything in one app +❌ Never import across app boundaries at the model level (use IDs) +❌ Never put business logic in views or serializers +``` + +--- + +## 2. Models & Migrations (CRITICAL) + +### Custom User Model (Day 1!) + +```python +# apps/users/models.py +from django.contrib.auth.models import AbstractUser +from django.db import models +import uuid + +class User(AbstractUser): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + email = models.EmailField(unique=True) + + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = ['username'] + + class Meta: + db_table = 'users' + +# config/settings/base.py +AUTH_USER_MODEL = 'users.User' +``` + +**This MUST be done before `migrate`. Cannot change after.** + +### Model Best Practices + +```python +class TimeStampedModel(models.Model): + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + class Meta: + abstract = True + +class Order(TimeStampedModel): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='orders') + status = models.CharField(max_length=20, choices=OrderStatus.choices, default=OrderStatus.PENDING, db_index=True) + total = models.DecimalField(max_digits=10, decimal_places=2) + + class Meta: + db_table = 'orders' + ordering = ['-created_at'] + indexes = [ + models.Index(fields=['user', 'status']), + ] + + def can_cancel(self) -> bool: + return self.status in [OrderStatus.PENDING, OrderStatus.CONFIRMED] + + def cancel(self): + if not self.can_cancel(): + raise ValueError(f"Cannot cancel order in {self.status} status") + self.status = OrderStatus.CANCELLED + self.save(update_fields=['status', 'updated_at']) +``` + +### Migration Rules + +``` +✅ Review migration SQL: python manage.py sqlmigrate app_name 0001 +✅ Name migrations descriptively: --name add_status_index_to_orders +✅ Separate data migrations from schema migrations +✅ Non-destructive first: add column → backfill → remove old column + +❌ Never edit or delete applied migrations +❌ Never use RunPython without reverse function +``` + +--- + +## 3. Views & Serializers — DRF (HIGH) + +### Service Layer Pattern + +```python +# apps/orders/services.py +from django.db import transaction + +class OrderService: + @staticmethod + @transaction.atomic + def create_order(user, items_data: list[dict]) -> Order: + total = sum(item['price'] * item['quantity'] for item in items_data) + order = Order.objects.create(user=user, total=total) + OrderItem.objects.bulk_create([ + OrderItem(order=order, **item) for item in items_data + ]) + return order + + @staticmethod + def cancel_order(order_id: str, user) -> Order: + order = Order.objects.select_for_update().get(id=order_id, user=user) + order.cancel() + return order +``` + +### Serializers + +```python +class OrderSerializer(serializers.ModelSerializer): + items = OrderItemSerializer(many=True, read_only=True) + class Meta: + model = Order + fields = ['id', 'status', 'total', 'items', 'created_at'] + read_only_fields = ['id', 'total', 'created_at'] + +class CreateOrderSerializer(serializers.Serializer): + """Input-only serializer — separate from output.""" + items = serializers.ListField( + child=serializers.DictField(), min_length=1, max_length=50, + ) + def validate_items(self, items): + for item in items: + if item.get('quantity', 0) < 1: + raise serializers.ValidationError("Quantity must be at least 1") + return items +``` + +### Views (Thin!) + +```python +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def create_order(request): + serializer = CreateOrderSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + order = OrderService.create_order(request.user, serializer.validated_data['items']) + return Response({'data': OrderSerializer(order).data}, status=status.HTTP_201_CREATED) +``` + +### Rules + +``` +✅ Separate input serializers from output serializers +✅ Views only: validate → call service → serialize → respond +✅ Use @transaction.atomic for multi-model writes + +❌ Never put business logic in views or serializers +❌ Never use ModelSerializer for write operations (too implicit) +``` + +--- + +## 4. Authentication (HIGH) + +| Method | When | Frontend | +|--------|------|----------| +| Session | Same-domain, SSR, Django templates | Django templates / htmx | +| JWT | Different domain, SPA, mobile | React, Vue, mobile apps | +| OAuth2 | Third-party login, API consumers | Any | + +### JWT Config (djangorestframework-simplejwt) + +```python +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), + 'ROTATE_REFRESH_TOKENS': True, + 'BLACKLIST_AFTER_ROTATION': True, +} +``` + +--- + +## 5. Performance Optimization (HIGH) + +### N+1 Query Prevention + +```python +# ❌ N+1: 1 query for orders + N queries for users +orders = Order.objects.all() +for o in orders: + print(o.user.email) # hits DB each iteration + +# ✅ select_related (FK/OneToOne — JOIN) +orders = Order.objects.select_related('user').all() + +# ✅ prefetch_related (ManyToMany/reverse FK — 2 queries) +orders = Order.objects.prefetch_related('items').all() + +# ✅ Combined +orders = Order.objects.select_related('user').prefetch_related('items').all() +``` + +### Query Optimization Toolkit + +```python +# Only fetch needed columns +User.objects.values('id', 'email') +User.objects.values_list('email', flat=True) + +# Annotate instead of Python loops +from django.db.models import Count, Sum +Order.objects.annotate(item_count=Count('items'), revenue=Sum('items__price')) + +# Bulk operations +OrderItem.objects.bulk_create([...]) +Order.objects.filter(status='pending').update(status='cancelled') + +# Database indexes +class Meta: + indexes = [ + models.Index(fields=['user', 'status']), + models.Index(fields=['-created_at']), + models.Index(fields=['email'], condition=Q(is_active=True)), + ] + +# Pagination +from rest_framework.pagination import CursorPagination +class OrderPagination(CursorPagination): + page_size = 20 + ordering = '-created_at' +``` + +### Caching + +```python +from django.core.cache import cache + +def get_product(product_id: str): + cache_key = f'product:{product_id}' + product = cache.get(cache_key) + if product is None: + product = Product.objects.get(id=product_id) + cache.set(cache_key, product, timeout=300) + return product +``` + +--- + +## 6. Testing (MEDIUM-HIGH) + +### pytest-django + factory_boy + +```python +# conftest.py +@pytest.fixture +def api_client(): + return APIClient() + +@pytest.fixture +def authenticated_client(api_client, user_factory): + user = user_factory() + api_client.force_authenticate(user=user) + return api_client +``` + +```python +# factories.py +class UserFactory(factory.django.DjangoModelFactory): + class Meta: + model = User + email = factory.Sequence(lambda n: f'user{n}@example.com') + username = factory.Sequence(lambda n: f'user{n}') + +class OrderFactory(factory.django.DjangoModelFactory): + class Meta: + model = 'orders.Order' + user = factory.SubFactory(UserFactory) + total = factory.Faker('pydecimal', left_digits=3, right_digits=2, positive=True) +``` + +```python +# test_views.py +@pytest.mark.django_db +class TestListOrders: + def test_returns_user_orders(self, authenticated_client): + OrderFactory.create_batch(3, user=authenticated_client.handler._force_user) + response = authenticated_client.get('/api/orders/') + assert response.status_code == 200 + assert len(response.data['data']) == 3 + + def test_requires_authentication(self, api_client): + response = api_client.get('/api/orders/') + assert response.status_code == 401 +``` + +--- + +## 7. Admin Customization (MEDIUM) + +```python +class OrderItemInline(admin.TabularInline): + model = OrderItem + extra = 0 + readonly_fields = ['price'] + +@admin.register(Order) +class OrderAdmin(admin.ModelAdmin): + list_display = ['id', 'user', 'status', 'total', 'created_at'] + list_filter = ['status', 'created_at'] + search_fields = ['user__email', 'id'] + readonly_fields = ['id', 'created_at', 'updated_at'] + inlines = [OrderItemInline] + date_hierarchy = 'created_at' + + def get_queryset(self, request): + return super().get_queryset(request).select_related('user') +``` + +--- + +## 8. Production Deployment (MEDIUM) + +### Security Settings + +```python +# settings/prod.py +DEBUG = False +ALLOWED_HOSTS = ['example.com', 'www.example.com'] +CSRF_TRUSTED_ORIGINS = ['https://example.com'] +SECURE_SSL_REDIRECT = True +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True +SECURE_HSTS_SECONDS = 31536000 +``` + +### Deployment Stack + +``` +Nginx → Gunicorn → Django + ↕ + PostgreSQL + Redis (cache) + ↕ + Celery (background tasks) +``` + +```bash +gunicorn config.wsgi:application \ + --bind 0.0.0.0:8000 \ + --workers 4 \ + --timeout 120 \ + --access-logfile - +``` + +### WhiteNoise for Static Files + +```python +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'whitenoise.middleware.WhiteNoiseMiddleware', # right after Security + ... +] +STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' +``` + +### Rules + +``` +✅ Gunicorn + Nginx (or Cloud Run / Railway) +✅ PostgreSQL (not SQLite) +✅ python manage.py check --deploy +✅ Sentry for error tracking + +❌ Never use runserver in production +❌ Never use DEBUG=True in production +❌ Never use SQLite in production +``` + +--- + +## Anti-Patterns + +| # | ❌ Don't | ✅ Do Instead | +|---|---------|--------------| +| 1 | Business logic in views | Service layer (`services.py`) | +| 2 | One giant app | App-per-domain | +| 3 | Default User model | Custom User before first migrate | +| 4 | No `select_related` | Always eager-load related objects | +| 5 | Django fixtures for tests | `factory_boy` factories | +| 6 | `settings.py` single file | Split: base + dev + prod | +| 7 | `runserver` in production | Gunicorn + Nginx | +| 8 | SQLite in production | PostgreSQL | +| 9 | `ModelSerializer` for writes | Explicit input serializer | +| 10 | Raw SQL in views | ORM querysets + `selectors.py` | + +--- + +## Common Issues + +### Issue 1: "Can't change User model after first migration" + +**Fix:** If starting fresh: delete all migrations + DB, set custom User, re-migrate. If data exists: complex migration (use `django-allauth` or incremental field migration). + +### Issue 2: "Serializer is too slow on large querysets" + +**Fix:** Missing `select_related` / `prefetch_related` → N+1 queries. +```python +queryset = Order.objects.select_related('user').prefetch_related('items') +``` + +### Issue 3: "Circular import between apps" + +**Fix:** Use string references: `models.ForeignKey('orders.Order', ...)` instead of importing the model class. For services, import inside the function. diff --git a/skills/fullstack-dev/references/environment-management.md b/skills/fullstack-dev/references/environment-management.md new file mode 100644 index 0000000..8709992 --- /dev/null +++ b/skills/fullstack-dev/references/environment-management.md @@ -0,0 +1,78 @@ +# Environment & CORS Management + +Patterns for managing environment variables, API URLs, and CORS configuration across frontend and backend stacks. + +--- + +## Standard Environment Pattern + +``` +# .env.local (gitignored, for local dev) +NEXT_PUBLIC_API_URL=http://localhost:3001 +NEXT_PUBLIC_WS_URL=ws://localhost:3001 + +# Staging (set in Vercel/CI) +NEXT_PUBLIC_API_URL=https://api-staging.example.com + +# Production (set in Vercel/CI) +NEXT_PUBLIC_API_URL=https://api.example.com +``` + +--- + +## Environment Variable Rules + +``` +✅ API base URL from environment variable — NEVER hardcoded +✅ Prefix client-side vars with NEXT_PUBLIC_ (Next.js) or VITE_ (Vite) +✅ Backend URL = server-only env var (for SSR calls, not exposed to browser) +✅ CORS on backend: explicit list of allowed origins per environment + +❌ Never use localhost URLs in production builds +❌ Never expose backend-only secrets with NEXT_PUBLIC_ prefix +❌ Never commit .env.local (commit .env.example with placeholders) +``` + +--- + +## CORS Configuration + +```typescript +// Backend: environment-aware CORS +const ALLOWED_ORIGINS = { + development: ['http://localhost:3000', 'http://localhost:5173'], + staging: ['https://staging.example.com'], + production: ['https://example.com', 'https://www.example.com'], +}; + +app.use(cors({ + origin: ALLOWED_ORIGINS[process.env.NODE_ENV || 'development'], + credentials: true, // needed for cookies (auth) + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], +})); +``` + +--- + +## Common Issues + +### Issue 1: "CORS error in browser but works in Postman" + +**Cause:** CORS is a browser security feature. Postman/curl skip it. + +**Fix:** +1. Backend must return `Access-Control-Allow-Origin: https://your-frontend.com` +2. For cookies/auth: `credentials: true` on both sides +3. Check that preflight `OPTIONS` request returns correct headers + +### Issue 2: "Environment variable undefined in browser" + +**Cause:** Missing `NEXT_PUBLIC_` or `VITE_` prefix for client-side access. + +**Fix:** Client-side vars MUST have the framework prefix. Rebuild after adding new env vars (they are embedded at build time). + +### Issue 3: "Works locally, fails in staging" + +**Cause:** Different origins, missing CORS config for staging domain. + +**Fix:** Add staging origin to `ALLOWED_ORIGINS`, verify env vars are set in deployment platform. diff --git a/skills/fullstack-dev/references/release-checklist.md b/skills/fullstack-dev/references/release-checklist.md new file mode 100644 index 0000000..e34d329 --- /dev/null +++ b/skills/fullstack-dev/references/release-checklist.md @@ -0,0 +1,278 @@ +# Release & Acceptance Checklist + +6-gate release checklist for backend and full-stack applications. Prevents "it works on my machine" and "we forgot to check X" failures. + +**Iron Law: NO RELEASE WITHOUT ALL GATES PASSING.** + +--- + +## Release Gates Overview + +``` +Feature Complete + ↓ +Gate 1: Functional Acceptance → Does it do what it should? + ↓ +Gate 2: Non-Functional Acceptance → Is it fast, reliable, observable? + ↓ +Gate 3: Security Review → Is it safe? + ↓ +Gate 4: Deployment Readiness → Can we deploy and rollback safely? + ↓ +Gate 5: Release Execution → Deploy with canary + monitoring + ↓ +Gate 6: Post-Release Validation → Did it actually work in production? +``` + +--- + +## Gate 1: Functional Acceptance + +**Question: Does it do what the requirements say?** + +- [ ] All acceptance criteria from ticket/PRD have passing tests +- [ ] Happy path works end-to-end +- [ ] Edge cases tested (empty inputs, max lengths, Unicode) +- [ ] Error cases tested (invalid input, not found, timeout) +- [ ] Data integrity verified (CRUD cycle produces correct state) +- [ ] Backward compatibility confirmed (existing clients not broken) +- [ ] API contract matches OpenAPI spec +- [ ] Idempotency verified (retries don't create duplicates) + +### Evidence Template + +| Requirement | Test | Status | Notes | +|-------------|------|--------|-------| +| User can create order | `orders.api.test:creates order` | ✅ PASS | | +| Empty cart → error | `orders.api.test:rejects empty` | ✅ PASS | | +| Payment failure handled | `payments.test:handles decline` | ✅ PASS | | + +--- + +## Gate 2: Non-Functional Acceptance + +**Question: Is it fast, reliable, and observable?** + +### Performance + +- [ ] Response time within budget (p95 < ___ms) — measured, not assumed +- [ ] No N+1 queries (checked with query logging) +- [ ] New queries use indexes (`EXPLAIN ANALYZE`) +- [ ] Pagination works on large datasets +- [ ] Caching effective (hit rate > 80%) +- [ ] Connection pool healthy under load + +### Reliability + +- [ ] Graceful degradation when dependencies fail (circuit breaker) +- [ ] Retry logic works for transient failures +- [ ] All external calls have timeouts +- [ ] Rate limiting returns 429 correctly +- [ ] Health check endpoints verified (`/health`, `/ready`) + +### Observability + +- [ ] Structured logging with request ID (not `console.log`) +- [ ] Metrics exposed (request count, latency, error rate) +- [ ] Alerts configured (error spike, latency spike) +- [ ] Request tracing works end-to-end +- [ ] Dashboard updated for new feature + +### Evidence + +| Metric | Target | Actual | Status | +|--------|--------|--------|--------| +| p95 response | < 500ms | ___ms | ✅/❌ | +| p99 response | < 1000ms | ___ms | ✅/❌ | +| Error rate (load) | < 0.1% | ___% | ✅/❌ | +| Throughput | > ___ RPS | ___ RPS | ✅/❌ | + +--- + +## Gate 3: Security Review + +**Question: Does this introduce vulnerabilities?** + +### Input & Output + +- [ ] All input validated server-side (never trust client) +- [ ] SQL injection prevented (parameterized queries only) +- [ ] XSS prevented (output encoding) +- [ ] File upload validated (type, size, name sanitized) +- [ ] Rate limiting on sensitive endpoints (login, reset, APIs) + +### Auth & Data + +- [ ] Protected endpoints require valid credentials +- [ ] Users can only access their own resources +- [ ] Admin routes require admin role +- [ ] Tokens expire (short-lived access + refresh) +- [ ] Passwords hashed (bcrypt/argon2, not MD5/SHA) +- [ ] Sensitive data not logged (passwords, tokens, PII) +- [ ] Secrets in env vars (not hardcoded) +- [ ] Error messages don't leak internals + +### Dependencies + +- [ ] No known vulnerabilities (`npm audit` / `pip audit` / `govulncheck`) +- [ ] Dependencies pinned in lockfile +- [ ] Unused dependencies removed + +--- + +## Gate 4: Deployment Readiness + +**Question: Can we deploy safely and roll back if needed?** + +### Code + +- [ ] All tests pass in CI (not "it passed locally") +- [ ] Linter clean, build succeeds +- [ ] Code reviewed and approved +- [ ] No unresolved TODO/FIXME/HACK + +### Database + +- [ ] Migration tested on staging with production-like data +- [ ] Down migration works (tested!) +- [ ] Migration is non-destructive (additive only) +- [ ] Migration timing estimated on production data size +- [ ] Backfill plan documented (if needed) + +### Configuration + +- [ ] New env vars documented in `.env.example` +- [ ] Env vars set in staging and verified +- [ ] Env vars set in production +- [ ] Feature flags configured (if applicable) + +### Rollback Plan Template + +```markdown +## Rollback Plan: [Feature] + +### When to rollback +- Error rate > 1% sustained 5 minutes +- p99 latency > 3000ms sustained 10 minutes +- Critical business function broken + +### Steps +1. Revert deploy: [command] +2. Rollback migration (if applied): [command] +3. Invalidate cache: [command] +4. Notify team: #incidents channel +5. Verify rollback: [verification steps] + +### Estimated time: [X minutes] +### Data recovery: [procedure if data was modified] +``` + +--- + +## Gate 5: Release Execution + +### Deployment Sequence + +``` +1. 📢 ANNOUNCE in release channel + +2. 🗄️ DATABASE — Apply migration + - Run migration + - Verify completion + - Check data integrity + +3. 🚀 DEPLOY — Roll out code + - Canary first (10% traffic) + - Monitor 5 minutes + - If OK → 50% → monitor → 100% + - If NOT OK → STOP immediately + +4. 🔍 SMOKE TEST + - Health check → 200 + - Login works + - Core operation works + - No error spikes + +5. ✅ ANNOUNCE "Release complete. Monitoring 30 min." +``` + +### Canary Decision Table + +| Metric | Baseline | Canary OK | STOP | ROLLBACK | +|--------|----------|-----------|------|----------| +| Error rate | 0.05% | < 0.1% | 0.5% | > 1% | +| p95 latency | 300ms | < 500ms | 700ms | > 1000ms | + +--- + +## Gate 6: Post-Release Validation + +### Immediate (0-30 min) + +- [ ] Health checks green on all instances +- [ ] Error rate within normal range +- [ ] Latency normal (p95, p99) +- [ ] Core user journey manually tested +- [ ] Logs clean — no unexpected errors +- [ ] Alerts silent + +### Short-term (1-24 hours) + +- [ ] No customer complaints +- [ ] Business metrics stable (conversion, revenue, signups) +- [ ] Memory/CPU stable (no creeping usage) +- [ ] Queue backlogs clear +- [ ] Database performance stable + +### Post-Release Report Template + +```markdown +## Release Report: [Feature] +- Deployed: [timestamp] by @[engineer] +- Duration: [minutes] + +| Check | Status | Notes | +|-------|--------|-------| +| Health checks | ✅ | All healthy | +| Error rate | ✅ | 0.03% (baseline: 0.05%) | +| p95 latency | ✅ | 310ms (baseline: 300ms) | +| Core flow | ✅ | Order creation verified | + +Issues found: None / [details] +Rollback used: No / Yes: [reason] +``` + +--- + +## Release Readiness Score + +Score each gate **0-2**: (0 = not checked, 1 = partially, 2 = fully verified with evidence) + +| Gate | Score | +|------|-------| +| 1. Functional Acceptance | /2 | +| 2. Non-Functional Acceptance | /2 | +| 3. Security Review | /2 | +| 4. Deployment Readiness | /2 | +| 5. Release Execution Plan | /2 | +| 6. Post-Release Validation Plan | /2 | +| **Total** | **/12** | + +**Decision:** +- **12/12** → Ship it ✅ +- **10-11** → Ship with documented exceptions + owner assigned +- **< 10** → Do NOT release. Fix gaps first. + +--- + +## Common Rationalizations + +| ❌ Excuse | ✅ Reality | +|----------|-----------| +| "It's a small change" | Small changes cause outages every day | +| "We tested locally" | Local ≠ production | +| "We'll fix it if it breaks" | You'll fix it at 3 AM. Prevent now. | +| "Deadline is today" | Broken code costs more than late code | +| "CI passed" | CI doesn't check everything. Run the checklist. | +| "We can always rollback" | Only if you planned and tested rollback | +| "We did this last time fine" | Survivorship bias. Checklist every time. | diff --git a/skills/fullstack-dev/references/technology-selection.md b/skills/fullstack-dev/references/technology-selection.md new file mode 100644 index 0000000..038c5c7 --- /dev/null +++ b/skills/fullstack-dev/references/technology-selection.md @@ -0,0 +1,254 @@ +# Technology Selection Framework + +Structured decision framework for backend and full-stack technology choices. Prevents analysis paralysis while ensuring rigorous evaluation. + +**Iron Law: NO TECHNOLOGY CHOICE WITHOUT EXPLICIT TRADE-OFF ANALYSIS.** + +"I like it" and "it's trending" are not engineering arguments. + +--- + +## Phase 1: Requirements Before Technology + +### Non-Functional Requirements (Quantify!) + +| Dimension | Question | Bad Answer | Good Answer | +|-----------|----------|-----------|-------------| +| Scale | How many concurrent users? | "Lots" | "1K concurrent, 500 RPS peak" | +| Latency | Acceptable p99 response time? | "Fast" | "< 200ms API, < 2s reports" | +| Availability | Required uptime? | "Always up" | "99.9% (8.7h downtime/year)" | +| Data volume | Expected storage growth? | "A lot" | "100GB/year, 10M rows" | +| Consistency | Strong vs eventual? | "Consistent" | "Strong for payments, eventual for feeds" | +| Compliance | Regulatory? | "Some" | "GDPR data residency EU, SOC 2 Type II" | + +### Team Constraints + +- Team size and seniority level +- What the team already knows well +- Can you hire for this stack? (check job market) +- Timeline pressure (days vs months to production) +- Budget for licenses, infrastructure, training + +--- + +## Phase 2: Evaluation Matrix + +Score each option 1-5 on weighted criteria: + +| Criterion | Weight | Option A | Option B | Option C | +|-----------|--------|----------|----------|----------| +| Meets functional requirements | 5× | _ | _ | _ | +| Meets non-functional requirements | 5× | _ | _ | _ | +| Team expertise / learning curve | 4× | _ | _ | _ | +| Ecosystem maturity (libs, tools) | 3× | _ | _ | _ | +| Community & long-term viability | 3× | _ | _ | _ | +| Operational complexity | 3× | _ | _ | _ | +| Hiring pool availability | 2× | _ | _ | _ | +| Cost (license + infra + training) | 2× | _ | _ | _ | +| **Weighted Total** | | _ | _ | _ | + +**Rules:** +- Any option scoring **1 on a 5× criterion** → automatically disqualified +- Options within **10%** of each other → choose what team knows best +- Options within **15%** → run a **time-boxed PoC** (2-5 days max) + +--- + +## Phase 3: Decision Trees + +### Backend Language / Framework + +``` +What type of project? +│ +├─ REST/GraphQL API, rapid development +│ ├─ Team knows TypeScript → Node.js +│ │ ├─ Full-featured, enterprise patterns → NestJS +│ │ ├─ Lightweight, flexible → Fastify / Hono / Express +│ │ └─ Full-stack with React → Next.js API routes +│ ├─ Team knows Python +│ │ ├─ High-perf async API → FastAPI +│ │ ├─ Full-stack, admin-heavy → Django +│ │ └─ Lightweight → Flask / Litestar +│ └─ Team knows Java/Kotlin +│ ├─ Enterprise, large team → Spring Boot +│ └─ Lightweight, fast startup → Quarkus / Ktor +│ +├─ High concurrency, systems-level +│ ├─ Microservices, network → Go +│ ├─ Extreme perf, safety → Rust (Axum / Actix) +│ └─ Fault tolerance → Elixir (Phoenix) +│ +├─ Real-time (WebSocket, streaming) +│ ├─ Node.js ecosystem → Socket.io / ws +│ ├─ Scalable pub/sub → Elixir Phoenix +│ └─ Low-latency → Go / Rust +│ +└─ ML / data-intensive + └─ Python (FastAPI + ML libs) +``` + +### Database + +``` +What data model? +│ +├─ Structured, relational, ACID +│ ├─ General purpose → PostgreSQL ← DEFAULT CHOICE +│ ├─ Read-heavy, MySQL ecosystem → MySQL / MariaDB +│ └─ Embedded / serverless edge → SQLite / Turso / D1 +│ +├─ Semi-structured, flexible schema +│ ├─ Document-oriented → MongoDB +│ ├─ Serverless document → DynamoDB / Firestore +│ └─ Search-heavy → Elasticsearch / OpenSearch +│ +├─ Key-value / cache +│ ├─ In-memory + data structures → Redis / Valkey +│ └─ Planet-scale KV → DynamoDB / Cassandra +│ +├─ Time-series → TimescaleDB / ClickHouse / InfluxDB +├─ Graph → Neo4j / Apache AGE (Postgres extension) +└─ Vector (AI embeddings) → pgvector / Pinecone / Qdrant +``` + +**Default:** Start with PostgreSQL. It handles 80% of use cases. + +### Caching Strategy + +| Pattern | Technology | When | +|---------|-----------|------| +| Application cache | Redis / Valkey | Sessions, frequent reads, rate limiting | +| HTTP cache | CDN (Cloudflare/Vercel) | Static assets, public API responses | +| Query cache | Materialized views | Complex aggregations, dashboards | +| In-process cache | LRU (in-memory) | Config, small lookup tables | +| Edge cache | Cloudflare KV / Vercel KV | Global low-latency reads | + +### Message Queue / Event Streaming + +| Pattern | Technology | When | +|---------|-----------|------| +| Task queue (background jobs) | BullMQ / Celery / SQS | Email, exports, payments | +| Event streaming (replay, audit) | Kafka / Redpanda | Event sourcing, real-time pipelines | +| Lightweight pub/sub | Redis Streams / NATS | Simple notifications, broadcasting | +| Request-reply (sync over async) | NATS / RabbitMQ RPC | Internal service calls | + +### Hosting / Deployment + +| Model | Technology | When | +|-------|-----------|------| +| Serverless (auto-scale) | Vercel / Cloudflare Workers / Lambda | Variable traffic, pay-per-use | +| Container (predictable) | Cloud Run / Render / Railway / Fly.io | Steady traffic, simple ops | +| Kubernetes (large scale) | EKS / GKE / AKS | 10+ services, team has K8s expertise | +| VPS (full control) | DigitalOcean / Hetzner / EC2 | Predictable workload, cost-sensitive | + +--- + +## Phase 4: Decision Documentation + +### ADR (Architecture Decision Record) Template + +```markdown +# ADR-{NNN}: {Title} + +## Status: Proposed | Accepted | Deprecated | Superseded by ADR-{NNN} + +## Context +What problem are we solving? What forces are at play? + +## Decision +What did we choose and why? + +## Evaluation +| Criterion | Weight | Chosen | Runner-up | +|-----------|--------|--------|-----------| + +## Consequences +- Positive: ... +- Negative: ... +- Risks: ... + +## Alternatives Rejected +- Option B: rejected because... +- Option C: rejected because... +``` + +--- + +## Common Stack Templates + +### A: Startup / MVP (Speed) + +| Layer | Choice | Why | +|-------|--------|-----| +| Language | TypeScript | One language front + back | +| Framework | Next.js (full-stack) or NestJS (API) | Fast iteration | +| Database | PostgreSQL (Supabase / Neon) | Managed, generous free tier | +| Auth | Better Auth / Clerk | No auth code to maintain | +| Cache | Redis (Upstash) | Serverless-friendly | +| Hosting | Vercel / Railway | Zero-config deploys | + +### B: SaaS / Business App (Balance) + +| Layer | Choice | Why | +|-------|--------|-----| +| Language | TypeScript or Python | Team preference | +| Framework | NestJS or FastAPI | Structured, testable | +| Database | PostgreSQL | Reliable, feature-rich | +| Queue | BullMQ (Redis) | Simple background jobs | +| Auth | OAuth 2.0 + JWT | Standard, flexible | +| Hosting | AWS ECS / Cloud Run | Scalable containers | +| Monitoring | Datadog / Grafana + Prometheus | Full observability | + +### C: High-Performance (Scale) + +| Layer | Choice | Why | +|-------|--------|-----| +| Language | Go or Rust | Max throughput, low latency | +| Database | PostgreSQL + Redis + ClickHouse | OLTP + cache + analytics | +| Queue | Kafka / Redpanda | High-throughput streaming | +| Hosting | Kubernetes (EKS/GKE) | Fine-grained scaling | +| Monitoring | Prometheus + Grafana + Jaeger | Metrics + tracing | + +### D: AI / ML Application + +| Layer | Choice | Why | +|-------|--------|-----| +| Language | Python (API) + TypeScript (frontend) | ML libs + modern UI | +| Framework | FastAPI + Next.js | Async + SSR | +| Database | PostgreSQL + pgvector | Relational + embeddings | +| Queue | Celery + Redis | ML job processing | +| Hosting | Modal / AWS GPU / Replicate | GPU access | + +--- + +## Anti-Patterns + +| # | ❌ Don't | ✅ Do Instead | +|---|---------|--------------| +| 1 | "X is trending on HN" | Evaluate against YOUR requirements | +| 2 | Resume-Driven Development | Choose what team can maintain | +| 3 | "Must scale to 1M users" (day 1) | Build for 10× current need, not 1000× | +| 4 | Evaluate for weeks | Time-box to 3-5 days, then decide | +| 5 | No decision documentation | Write ADR for every major choice | +| 6 | Ignore operational cost | Include deploy, monitor, debug cost | +| 7 | "We'll rewrite later" | Assume you won't. Choose carefully. | +| 8 | Microservices by default | Start monolith, extract when needed | +| 9 | Different DB per service (day 1) | One database, split when justified | +| 10 | "It worked at Google" | You're not Google. Scale to YOUR context. | + +--- + +## Common Issues + +### Issue 1: "Team can't agree on a framework" + +**Fix:** Time-box to 3 days. Fill the evaluation matrix. If scores within 10%, pick what the majority knows. Document in ADR. Move on. + +### Issue 2: "We picked X but it doesn't fit" + +**Fix:** Sunk cost fallacy check. If < 2 weeks invested, switch now. If > 2 weeks, document pain points and plan phased migration. + +### Issue 3: "Do we need microservices?" + +**Fix:** Almost certainly no. Start with a well-structured monolith. Extract to services only when: (a) different scaling needs, (b) different team ownership, (c) different deployment cadence. diff --git a/skills/fullstack-dev/references/testing-strategy.md b/skills/fullstack-dev/references/testing-strategy.md new file mode 100644 index 0000000..1552a13 --- /dev/null +++ b/skills/fullstack-dev/references/testing-strategy.md @@ -0,0 +1,404 @@ +# Backend Testing Strategy + +Comprehensive testing guide for backend and full-stack applications. Covers the full testing pyramid with deep focus on API integration tests, database testing, contract testing, and performance testing. + +## Quick Start Checklist + +- [ ] **Test runner configured** (Jest/Vitest, Pytest, Go test) +- [ ] **Test database** ready (Docker container or in-memory) +- [ ] **Database isolation** per test (transaction rollback or truncation) +- [ ] **Test factories** for common entities (user, order, product) +- [ ] **Auth helper** to generate tokens for tests +- [ ] **CI pipeline** runs tests with real database service +- [ ] **Coverage threshold** enforced (≥ 80%) + +--- + +## The Testing Pyramid + +``` + ╱╲ E2E (few, slow) — full flows across services + ╱ ╲ + ╱────╲ Integration (moderate) — API + DB + external + ╱ ╲ + ╱────────╲ Unit (many, fast) — pure business logic + ╱__________╲ +``` + +| Level | What | Speed | Count | +|-------|------|-------|-------| +| Unit | Pure functions, business logic, no I/O | < 10ms | 70%+ of tests | +| Integration | API routes + real database + mocked externals | 50-500ms | ~20% | +| E2E | Full user flow across deployed services | 1-30s | ~10% | +| Contract | API compatibility between services | < 100ms | Per API boundary | +| Performance | Load, stress, soak | Minutes | Per critical path | + +--- + +## 1. API Integration Testing (CRITICAL) + +### What to Test for Every Endpoint + +| Aspect | Tests to Write | +|--------|---------------| +| Happy path | Correct input → expected response + correct DB state | +| Auth | No token → 401, bad token → 401, expired → 401 | +| Authorization | Wrong role → 403, not owner → 403 | +| Validation | Missing fields → 422, bad types → 422, boundary values | +| Not found | Invalid ID → 404, deleted resource → 404 | +| Conflict | Duplicate create → 409, stale update → 409 | +| Idempotency | Same request twice → same result | +| Side effects | DB state changed, events emitted, cache invalidated | +| Error format | All errors match RFC 9457 envelope | + +### TypeScript (Jest + Supertest) + +```typescript +describe('POST /api/orders', () => { + let token: string; + let product: Product; + + beforeAll(async () => { + await resetDatabase(); + const user = await createTestUser({ role: 'customer' }); + token = await getAuthToken(user); + product = await createTestProduct({ price: 29.99, stock: 10 }); + }); + + it('creates order → 201 + correct DB state', async () => { + const res = await request(app) + .post('/api/orders') + .set('Authorization', `Bearer ${token}`) + .send({ items: [{ productId: product.id, quantity: 2 }] }); + + expect(res.status).toBe(201); + expect(res.body.data.total).toBe(59.98); + + const updated = await db.product.findUnique({ where: { id: product.id } }); + expect(updated!.stock).toBe(8); + }); + + it('rejects without auth → 401', async () => { + const res = await request(app).post('/api/orders').send({ items: [] }); + expect(res.status).toBe(401); + }); + + it('rejects empty items → 422', async () => { + const res = await request(app) + .post('/api/orders') + .set('Authorization', `Bearer ${token}`) + .send({ items: [] }); + expect(res.status).toBe(422); + expect(res.body.errors[0].field).toBe('items'); + }); +}); +``` + +### Python (Pytest + FastAPI TestClient) + +```python +@pytest.fixture +def client(db_session): + def override_get_db(): + yield db_session + app.dependency_overrides[get_db] = override_get_db + yield TestClient(app) + app.dependency_overrides.clear() + +def test_create_order_success(client, auth_headers, test_product): + response = client.post("/api/orders", json={ + "items": [{"product_id": test_product.id, "quantity": 2}] + }, headers=auth_headers) + assert response.status_code == 201 + assert response.json()["data"]["total"] == 59.98 + +def test_create_order_no_auth(client): + response = client.post("/api/orders", json={"items": []}) + assert response.status_code == 401 + +def test_create_order_empty_items(client, auth_headers): + response = client.post("/api/orders", json={"items": []}, headers=auth_headers) + assert response.status_code == 422 +``` + +--- + +## 2. Database Testing (HIGH) + +### Test Isolation Strategies + +| Strategy | Speed | Realism | When | +|----------|-------|---------|------| +| **Transaction rollback** | ⚡ Fastest | Medium | Default for unit + integration | +| **Truncation** | Fast | High | When rollback isn't possible | +| **Test containers** | Slow startup | Highest | CI pipeline, full integration | + +**Transaction rollback (recommended default):** +```typescript +let tx: Transaction; +beforeEach(async () => { tx = await db.beginTransaction(); }); +afterEach(async () => { await tx.rollback(); }); +``` + +**Docker test containers (CI):** +```yaml +# docker-compose.test.yml +services: + test-db: + image: postgres:16-alpine + tmpfs: /var/lib/postgresql/data # RAM disk for speed + environment: + POSTGRES_DB: myapp_test +``` + +### Test Factories (Not Raw SQL) + +```typescript +// factories/user.factory.ts +import { faker } from '@faker-js/faker'; + +export function buildUser(overrides: Partial = {}): CreateUserDTO { + return { + email: faker.internet.email(), + firstName: faker.person.firstName(), + role: 'customer', + ...overrides, + }; +} +export async function createUser(overrides = {}) { + return db.user.create({ data: buildUser(overrides) }); +} +``` + +```python +# factories/user_factory.py +import factory +from faker import Faker + +class UserFactory(factory.Factory): + class Meta: + model = User + email = factory.LazyAttribute(lambda _: Faker().email()) + first_name = factory.LazyAttribute(lambda _: Faker().first_name()) + role = "customer" +``` + +--- + +## 3. External Service Testing (HIGH) + +### HTTP-Level Mocking (Not Function Mocking) + +**TypeScript (nock):** +```typescript +import nock from 'nock'; + +it('processes payment successfully', async () => { + nock('https://api.stripe.com') + .post('/v1/charges') + .reply(200, { id: 'ch_123', status: 'succeeded', amount: 5000 }); + + const result = await paymentService.charge({ amount: 50.00, currency: 'usd' }); + expect(result.status).toBe('succeeded'); +}); + +it('handles payment timeout', async () => { + nock('https://api.stripe.com').post('/v1/charges').delay(10000).reply(200); + await expect(paymentService.charge({ amount: 50, currency: 'usd' })) + .rejects.toThrow('timeout'); +}); +``` + +**Python (responses):** +```python +import responses + +@responses.activate +def test_payment_success(): + responses.post("https://api.stripe.com/v1/charges", + json={"id": "ch_123", "status": "succeeded"}, status=200) + result = payment_service.charge(amount=50.00, currency="usd") + assert result.status == "succeeded" +``` + +### Test Containers for Infrastructure + +```typescript +import { PostgreSqlContainer } from '@testcontainers/postgresql'; +import { RedisContainer } from '@testcontainers/redis'; + +beforeAll(async () => { + const pg = await new PostgreSqlContainer('postgres:16').start(); + process.env.DATABASE_URL = pg.getConnectionUri(); + await runMigrations(); +}, 60000); +``` + +--- + +## 4. Contract Testing (MEDIUM-HIGH) + +### Consumer-Driven Contracts (Pact) + +**Consumer (OrderService calls UserService):** +```typescript +it('can fetch user by ID', async () => { + await pact.addInteraction() + .given('user usr_123 exists') + .uponReceiving('GET /users/usr_123') + .withRequest('GET', '/api/users/usr_123') + .willRespondWith(200, (b) => { + b.jsonBody({ data: { id: MatchersV3.string(), email: MatchersV3.email() } }); + }) + .executeTest(async (mockserver) => { + const user = await new UserClient(mockserver.url).getUser('usr_123'); + expect(user.id).toBeDefined(); + }); +}); +``` + +**Provider verifies in CI:** +```typescript +await new Verifier({ + providerBaseUrl: 'http://localhost:3001', + pactBrokerUrl: process.env.PACT_BROKER_URL, + provider: 'UserService', +}).verifyProvider(); +``` + +--- + +## 5. Performance Testing (MEDIUM) + +### k6 Load Test + +```javascript +import http from 'k6/http'; +import { check, sleep } from 'k6'; + +export const options = { + stages: [ + { duration: '30s', target: 20 }, // ramp up + { duration: '1m', target: 100 }, // sustain + { duration: '30s', target: 0 }, // ramp down + ], + thresholds: { + http_req_duration: ['p(95)<500', 'p(99)<1000'], + http_req_failed: ['rate<0.01'], + }, +}; + +export default function () { + const res = http.get(`${__ENV.BASE_URL}/api/orders`); + check(res, { 'status 200': (r) => r.status === 200 }); + sleep(1); +} +``` + +### Performance Budgets + +| Metric | Target | Action if Exceeded | +|--------|--------|--------------------| +| p95 response time | < 500ms | Optimize queries/caching | +| p99 response time | < 1000ms | Check outlier queries | +| Error rate | < 0.1% | Investigate spikes | +| DB query time | < 100ms each | Add indexes | + +### When to Run + +| Trigger | Test Type | +|---------|-----------| +| Before major release | Full load test | +| New DB query/index | Query benchmark | +| Infrastructure change | Baseline comparison | +| Weekly (CI) | Smoke load test | + +--- + +## Test File Organization + +``` +tests/ + unit/ # Pure logic, mocked dependencies + order.service.test.ts + integration/ # API + real DB + orders.api.test.ts + auth.api.test.ts + contracts/ # Consumer-driven contracts + user-service.consumer.pact.ts + performance/ # Load tests + load-test.js + fixtures/ + factories/ # Test data factories + user.factory.ts + seeds/ + test-data.ts + helpers/ + setup.ts # Global test config + auth.helper.ts # Token generation + db.helper.ts # DB cleanup +``` + +--- + +## Anti-Patterns + +| # | ❌ Don't | ✅ Do Instead | +|---|---------|--------------| +| 1 | Test only happy paths | Test errors, auth, validation, edge cases | +| 2 | Mock everything (no real DB) | Use test containers or test DB | +| 3 | Tests depend on execution order | Each test sets up / tears down own state | +| 4 | Hardcode test data | Use factories (faker + overrides) | +| 5 | Test implementation details | Test behavior: input → output | +| 6 | Share mutable state | Isolate per test (transaction rollback) | +| 7 | Skip migration testing in CI | Run migrations from scratch in CI | +| 8 | No performance test before release | Load test every major release | +| 9 | Test against production data | Generated test data only | +| 10 | Test suite > 10 minutes | Parallelize, RAM disk, optimize setup | + +--- + +## Common Issues + +### Issue 1: "Tests pass alone but fail together" + +**Cause:** Shared database state between tests. Missing cleanup. + +**Fix:** +```typescript +beforeEach(async () => { await db.raw('TRUNCATE orders, users CASCADE'); }); +// OR use transaction rollback per test +``` + +### Issue 2: "Jest did not exit one second after test run" + +**Cause:** Unclosed database connections or HTTP servers. + +**Fix:** +```typescript +afterAll(async () => { + await db.destroy(); + await server.close(); +}); +``` + +### Issue 3: "Async callback was not invoked within timeout" + +**Cause:** Missing `async/await` or unhandled promise. + +**Fix:** +```typescript +// ❌ Promise not awaited +it('should work', () => { request(app).get('/users'); }); + +// ✅ Properly awaited +it('should work', async () => { await request(app).get('/users'); }); +``` + +### Issue 4: "Integration tests too slow in CI" + +**Fix:** +1. Use `tmpfs` for PostgreSQL data dir (RAM disk) +2. Run migrations once in `beforeAll`, truncate in `beforeEach` +3. Parallelize test suites with `--maxWorkers` +4. Skip performance tests on feature branches (only main)