diff --git a/skills/claude-web-remote/LICENSE.txt b/skills/claude-web-remote/LICENSE.txt new file mode 100644 index 000000000..3728a12c7 --- /dev/null +++ b/skills/claude-web-remote/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 lunaticfluker / freeformsystems.ai + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/claude-web-remote/SKILL.md b/skills/claude-web-remote/SKILL.md new file mode 100644 index 000000000..6e9aa6fae --- /dev/null +++ b/skills/claude-web-remote/SKILL.md @@ -0,0 +1,189 @@ +--- +name: claude-web-remote +description: Set up remote browser access to Claude Code and dev site preview from any device (iPad, phone, another computer) with QR codes. Use when the user wants to access Claude Code remotely, work from a tablet or phone, or set up browser-based terminal access. +license: MIT +compatibility: Designed for Claude Code on macOS and Linux +--- + +# Claude Web Remote — Browser Access Setup + +Set up remote access to Claude Code + dev site preview from any browser, with scannable QR codes and basic authentication. + +## Prerequisites + +Check and install if missing: + +**macOS:** +```bash +brew install ttyd cloudflared qrencode +``` + +**Linux:** +```bash +# ttyd: https://github.com/tsl0922/ttyd/releases +# cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/ +sudo apt install qrencode +``` + +## Steps + +1. **Detect the project type** — look at package.json, docker-compose.yml, requirements.txt, manage.py, etc. to find the dev server command and port. + +2. **Generate a random password** for the ttyd session to prevent unauthorized access. + +3. **Create `claude-remote.sh`** in the project root with this template, adapting SITE_PORT and the dev server command: + +```bash +#!/bin/bash +# Claude Web Remote — access Claude Code + site preview from anywhere +# Uses ttyd (web terminal) + Cloudflare quick tunnels + QR codes +# Auth: ttyd basic auth protects terminal access + +set -euo pipefail + +CLAUDE_PORT=7682 +SITE_PORT=3000 # Adapt: match your dev server port +CLAUDE_TUNNEL_LOG=$(mktemp) +SITE_TUNNEL_LOG=$(mktemp) +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# --- Authentication --- +# Generate random credentials for this session +TTYD_USER="claude" +TTYD_PASS=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16 || true) + +PIDS=() +cleanup() { + echo "" + echo "Shutting down..." + for pid in "${PIDS[@]}"; do + kill "$pid" 2>/dev/null || true + done + rm -f "$CLAUDE_TUNNEL_LOG" "$SITE_TUNNEL_LOG" + wait 2>/dev/null + echo "All processes stopped." +} +trap cleanup EXIT INT TERM + +# --- Claude Code terminal (with basic auth) --- +echo "Starting Claude Code web terminal on port $CLAUDE_PORT..." +ttyd -W -p $CLAUDE_PORT \ + -c "${TTYD_USER}:${TTYD_PASS}" \ + -t fontSize=14 \ + -t 'theme={"background":"#1a1a2e","foreground":"#e0e0e0"}' \ + bash -c "cd $SCRIPT_DIR && claude" & +PIDS+=($!) +sleep 2 + +if ! kill -0 "${PIDS[-1]}" 2>/dev/null; then + echo "Failed to start ttyd" + exit 1 +fi + +# --- Dev server --- +echo "Starting dev server on port $SITE_PORT..." +# Adapt: change this to your dev server start command +cd "$SCRIPT_DIR" && npm run dev & +PIDS+=($!) +cd "$SCRIPT_DIR" +echo "Waiting for dev server to start..." +sleep 8 + +# --- Tunnels --- +echo "Creating Cloudflare tunnels..." +cloudflared tunnel --url http://localhost:$CLAUDE_PORT > "$CLAUDE_TUNNEL_LOG" 2>&1 & +PIDS+=($!) + +cloudflared tunnel --url http://localhost:$SITE_PORT > "$SITE_TUNNEL_LOG" 2>&1 & +PIDS+=($!) + +# Wait for both tunnel URLs +echo "Waiting for tunnel URLs..." +get_tunnel_url() { + local logfile="$1" + for i in $(seq 1 30); do + local url + url=$(grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' "$logfile" 2>/dev/null | head -1 || true) + if [ -n "$url" ]; then + echo "$url" + return 0 + fi + sleep 1 + done + echo "FAILED" + return 1 +} + +CLAUDE_URL=$(get_tunnel_url "$CLAUDE_TUNNEL_LOG") +SITE_URL=$(get_tunnel_url "$SITE_TUNNEL_LOG") + +echo "" +echo "================================================" +echo " CLAUDE WEB REMOTE" +echo "================================================" +echo "" + +if [ "$CLAUDE_URL" != "FAILED" ]; then + echo " Claude Code: $CLAUDE_URL" + echo " Login: $TTYD_USER / $TTYD_PASS" +else + echo " Claude Code: FAILED (check $CLAUDE_TUNNEL_LOG)" +fi + +if [ "$SITE_URL" != "FAILED" ]; then + echo " Site Preview: $SITE_URL" +else + echo " Site Preview: FAILED (check $SITE_TUNNEL_LOG)" +fi + +echo "" +echo "================================================" + +if [ "$CLAUDE_URL" != "FAILED" ]; then + echo "" + echo "--- Claude Code QR ---" + qrencode -t ANSIUTF8 "$CLAUDE_URL" +fi + +if [ "$SITE_URL" != "FAILED" ]; then + echo "" + echo "--- Site Preview QR ---" + qrencode -t ANSIUTF8 "$SITE_URL" +fi + +echo "" +echo "Press Ctrl+C to stop" +echo "" + +wait +``` + +4. **Make executable and run:** +```bash +chmod +x claude-remote.sh +./claude-remote.sh +``` + +5. **Show the user** the tunnel URLs, login credentials, and QR codes from the output. Remind them that the credentials are for the ttyd terminal login prompt. + +## Adaptation Guide + +- **Next.js**: `SITE_PORT=3000`, dev command = `npm run dev` +- **Vite/React**: `SITE_PORT=5173`, dev command = `npm run dev` +- **Flask**: `SITE_PORT=5000`, dev command = `python app.py` (use 5050 if 5000 is taken by AirPlay on macOS) +- **Django**: `SITE_PORT=8000`, dev command = `python manage.py runserver` +- **Static HTML**: Replace dev server block with `python3 -m http.server $SITE_PORT --directory ./public &` + +## Security + +- **Terminal auth**: ttyd is launched with `-c user:pass` flag for basic authentication. A random 16-character password is generated each session. +- **Tunnel URLs**: Cloudflare quick tunnels use random URLs that are hard to guess and expire when the script stops. +- **No Cloudflare account needed**: Quick tunnels are free and anonymous. +- **For stronger security**: Swap to a named Cloudflare tunnel with Access policies to gate access behind email verification. + +## Notes + +- Access via any browser (Safari, Chrome on iPad/phone works fine) +- Tunnel URLs are random and temporary — they die when the script stops +- `-W` flag on ttyd enables writable/interactive mode +- For persistent sessions, wrap in `tmux` before running diff --git a/skills/fal-ai/LICENSE.txt b/skills/fal-ai/LICENSE.txt new file mode 100644 index 000000000..3728a12c7 --- /dev/null +++ b/skills/fal-ai/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 lunaticfluker / freeformsystems.ai + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/fal-ai/SKILL.md b/skills/fal-ai/SKILL.md new file mode 100644 index 000000000..8c2432f52 --- /dev/null +++ b/skills/fal-ai/SKILL.md @@ -0,0 +1,267 @@ +--- +name: fal-ai +description: Generate images, videos, and audio using the fal.ai API. Use when the user works with fal.ai, fal_client, @fal-ai/client, Flux Pro, Kling video, or mentions AI image/video generation with fal. +license: MIT +compatibility: Designed for Claude Code on macOS and Linux +--- + +# fal.ai — Image, Video & Audio Generation + +Generate images, videos, and audio using fal.ai's model library (600+ models). Covers Python and JavaScript SDKs, model selection, prompt engineering, and production patterns. + +## Setup + +**Python:** +```bash +pip install fal-client +``` +```python +import fal_client +# Requires FAL_KEY environment variable +``` + +**JavaScript/TypeScript:** +```bash +npm install @fal-ai/client +``` +```javascript +import * as fal from "@fal-ai/client"; +// Requires FAL_KEY environment variable +``` + +## Models — Quick Reference + +### Image Generation +| Model | ID | Speed | Best For | +|-------|-----|-------|----------| +| Flux Pro v1.1 | `fal-ai/flux-pro/v1.1` | ~3-6s | Production images | +| Flux Pro Ultra | `fal-ai/flux-pro/v1.1-ultra` | ~8-12s | Maximum detail | +| Flux Schnell | `fal-ai/flux/schnell` | ~1-4s | Fast prototyping | +| Flux Dev | `fal-ai/flux/dev` | ~3-5s | Balanced quality/speed | + +### Video Generation +| Model | ID | Best For | +|-------|-----|----------| +| Kling 1.6 T2V | `fal-ai/kling-video/v1.6/standard/text-to-video` | Text to video | +| Kling 1.6 I2V | `fal-ai/kling-video/v1.6/standard/image-to-video` | Animate a still image | +| Kling 2.6 Pro | `fal-ai/kling-video/v2.6/pro/image-to-video` | Professional quality | + +### Audio +| Model | ID | Best For | +|-------|-----|----------| +| ElevenLabs TTS | `fal-ai/elevenlabs/tts/multilingual-v2` | Text to speech | + +## Image Generation + +### Python +```python +result = fal_client.subscribe( + "fal-ai/flux-pro/v1.1", + arguments={ + "prompt": "dark figure dissolving into void, motion blur, crushed blacks, cinematic B&W", + "image_size": "square_hd", + "num_images": 1, + "num_inference_steps": 28, + "guidance_scale": 3.5, + "enable_safety_checker": False, + }, +) +image_url = result["images"][0]["url"] +``` + +### JavaScript +```javascript +const result = await fal.run("fal-ai/flux-pro/v1.1", { + input: { + prompt: "dark figure dissolving into void, motion blur, crushed blacks, cinematic B&W", + image_size: "square_hd", + num_images: 1, + num_inference_steps: 28, + guidance_scale: 3.5, + enable_safety_checker: false, + }, +}); +const imageUrl = result.images[0].url; +``` + +### Image Size Options +- Presets: `square_hd`, `square`, `portrait_4_3`, `portrait_16_9`, `landscape_4_3`, `landscape_16_9` +- Custom: `{ "width": 1080, "height": 1350 }` (must be multiples of 8) + +### Parameters +| Param | Default | Range | Effect | +|-------|---------|-------|--------| +| `num_inference_steps` | 28 | 10-50 | Quality (higher = slower + better) | +| `guidance_scale` | 3.5 | 1-10 | Prompt adherence (lower = creative) | +| `num_images` | 1 | 1-4 | Batch size | +| `seed` | random | any int | Reproducibility | + +## Video Generation + +### Image-to-Video (recommended — better quality) +```python +result = fal_client.subscribe( + "fal-ai/kling-video/v1.6/standard/image-to-video", + arguments={ + "prompt": "slow camera drift, subtle motion blur, atmospheric", + "image_url": "https://v3.fal.media/files/...", + "duration": "5", + "aspect_ratio": "9:16", + }, +) +video_url = result["video"]["url"] +``` + +### Text-to-Video +```python +result = fal_client.subscribe( + "fal-ai/kling-video/v1.6/standard/text-to-video", + arguments={ + "prompt": "dark figure walking through fog, cinematic slow motion", + "duration": "5", + "aspect_ratio": "16:9", + }, +) +``` + +### Duration & Aspect +- Duration: `"5"` or `"10"` seconds (5s produces more focused results) +- Aspect: `"16:9"`, `"9:16"`, `"1:1"` + +## File Upload + +Upload local files to fal CDN for use in image-to-video or image-to-image: + +```python +cdn_url = fal_client.upload_file("path/to/image.png") +# Returns: https://v3.fal.media/files/{prefix}/{id}.{ext} +``` + +```javascript +const cdnUrl = await fal.storage.upload(file); +``` + +## Async / Queue Pattern + +For long-running tasks (video generation), use the queue: + +### Python +```python +# Submit to queue +handle = fal_client.submit( + "fal-ai/kling-video/v1.6/standard/image-to-video", + arguments={...}, +) + +# Check status +status = fal_client.status("fal-ai/kling-video/v1.6/standard/image-to-video", handle.request_id) + +# Get result when done +result = fal_client.result("fal-ai/kling-video/v1.6/standard/image-to-video", handle.request_id) +``` + +### JavaScript +```javascript +const handle = await fal.queue.submit("fal-ai/kling-video/v1.6/standard/image-to-video", { + input: {...}, +}); + +// Stream status updates +for await (const status of fal.queue.streamStatus(handle.request_id)) { + console.log(status); +} + +const result = await fal.queue.result(handle.request_id); +``` + +## Webhook Pattern + +```python +fal_client.subscribe( + "fal-ai/flux-pro/v1.1", + arguments={...}, + webhook_url="https://your-server.com/webhook/fal", +) +# Server receives POST with: +# Header: X-Fal-Webhook-Signature (verify for security) +# Body: { "request_id": "...", "status": "OK", "result": {...} } +``` + +## Timeout & Error Handling + +### Python — Timeout Wrapper +```python +import concurrent.futures + +def fal_with_timeout(model, arguments, timeout=300): + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(fal_client.subscribe, model, arguments=arguments) + try: + return future.result(timeout=timeout) + except concurrent.futures.TimeoutError: + raise TimeoutError(f"fal.ai {model} timed out after {timeout}s") +``` + +### Error Types +```python +try: + result = fal_client.subscribe(model, arguments=args) +except fal_client.ValidationError: + # Content policy violation or bad params — don't retry + pass +except fal_client.RateLimitError: + # Too many requests — exponential backoff + import time, random + time.sleep((2 ** attempt) + random.uniform(0, 1)) +except fal_client.FalClientTimeoutError: + # Timeout — may be transient, retry + pass +``` + +The platform auto-retries up to 10 times on 503/504/connection errors. + +## Rate Limits + +- New accounts: 2 concurrent requests +- Scales up to 40 as you purchase credits +- Requests exceeding the limit are queued automatically + +## Prompt Engineering + +### Structure +``` +[Subject] + [Environment/Mood] + [Style] + [Technical/Camera] +``` + +### Example +``` +dark figure dissolving into void, motion blur and ghosting, +crushed blacks and high contrast | moody electronic aesthetic | +wide frame from waist up | 28mm lens, cinematic, B&W editorial +``` + +### Tips +- Keep under 100 words — every word should serve a purpose +- Be specific about lighting: "golden hour", "dramatic side lighting", "soft diffused" +- Style modifiers: "cinematic", "photorealistic", "editorial", "minimalist" +- Camera language: "28mm lens", "macro", "aerial view", "shallow depth of field" +- Flux models don't support `negative_prompt` — describe what you want, not what to avoid + +## Response Structure + +### Image +```json +{ + "images": [{ "url": "https://v3.fal.media/...", "width": 1024, "height": 1024 }], + "seed": 12345 +} +``` + +### Video +```json +{ + "video": { "url": "https://v3.fal.media/...", "content_type": "video/mp4" } +} +``` + +CDN URLs persist and are reusable across requests. diff --git a/skills/shopify-liquid/LICENSE.txt b/skills/shopify-liquid/LICENSE.txt new file mode 100644 index 000000000..3728a12c7 --- /dev/null +++ b/skills/shopify-liquid/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 lunaticfluker / freeformsystems.ai + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/shopify-liquid/SKILL.md b/skills/shopify-liquid/SKILL.md new file mode 100644 index 000000000..95db9f5c9 --- /dev/null +++ b/skills/shopify-liquid/SKILL.md @@ -0,0 +1,219 @@ +--- +name: shopify-liquid +description: Build and modify Shopify Online Store 2.0 themes using Liquid, section schemas, template JSON, and Shopify CLI. Use when the user works with .liquid files, Shopify themes, section schemas, Shopify CLI commands, or mentions Shopify theme development. +license: MIT +compatibility: Designed for Claude Code on macOS and Linux +--- + +# Shopify Liquid — Theme Development + +Build and modify Shopify Online Store 2.0 themes with Liquid templating, section schemas, and safe deployment workflows. + +## Theme Structure + +``` +theme/ +├── layout/theme.liquid # Base HTML wrapper (required) +├── templates/*.json # Page composition (section order + saved settings) +├── sections/*.liquid # Reusable modules with schema +├── snippets/*.liquid # Reusable fragments ({% render 'name' %}) +├── assets/ # CSS, JS, images ({{ 'file' | asset_url }}) +├── config/settings_schema.json # Global theme settings for Shopify Admin +��── locales/*.json # i18n translations +``` + +## Section Anatomy + +Every section follows this pattern: + +```liquid +
+ {% if section.settings.eyebrow != blank %} + {{ section.settings.eyebrow }} + {% endif %} + +

{{ section.settings.heading }}

+ + {% for block in section.blocks %} +
+

{{ block.settings.title }}

+

{{ block.settings.text }}

+
+ {% endfor %} +
+ +{% schema %} +{ + "name": "My Section", + "settings": [ + { "type": "text", "id": "eyebrow", "label": "Eyebrow" }, + { "type": "text", "id": "heading", "label": "Heading", "default": "Section Title" } + ], + "blocks": [ + { + "type": "item", + "name": "Item", + "settings": [ + { "type": "text", "id": "title", "label": "Title" }, + { "type": "textarea", "id": "text", "label": "Text" } + ] + } + ], + "presets": [ + { "name": "My Section" } + ] +} +{% endschema %} +``` + +## Schema Setting Types + +| Type | Usage | Notes | +|------|-------|-------| +| `text` | Single line input | | +| `textarea` | Multi-line input | | +| `richtext` | HTML editor | Returns HTML string | +| `image_picker` | Image from file library | Use with `image_url` filter | +| `product` | Product selector | Returns product object | +| `collection` | Collection selector | Returns collection object | +| `select` | Dropdown | Requires `options` array | +| `range` | Numeric slider | Requires `min`, `max`, `step` | +| `checkbox` | Boolean toggle | | +| `color` | Color picker | Returns hex string | +| `url` | URL input | | +| `header` | Section divider | Label only, no `id` | + +Access: `section.settings.field_id` (section level), `block.settings.field_id` (block level) + +## Template JSON + +Templates define which sections appear on a page and their order: + +```json +{ + "sections": { + "hero": { + "type": "hero-banner", + "settings": { + "heading": "Welcome" + } + }, + "features": { + "type": "features-grid", + "settings": {} + } + }, + "order": ["hero", "features"] +} +``` + +**CRITICAL:** Template JSON contains merchant-saved data (images, text overrides). Never push templates without pulling first — it erases merchant customizations. + +## Layout File Pattern + +```liquid + + + + + + {{ page_title }}{% if current_tags %} — {{ current_tags | join: ', ' }}{% endif %} + {{ 'theme.css' | asset_url | stylesheet_tag }} + {{ content_for_header }} + + + {% section 'header' %} +
{{ content_for_layout }}
+ {% section 'footer' %} + + + +``` + +- `content_for_header` — required (Shopify analytics, editor JS) +- `content_for_layout` — where template sections render + +## Common Liquid Filters + +**Assets:** +- `{{ 'file.css' | asset_url }}` — CDN URL for theme asset +- `{{ image | image_url: width: 800 }}` — resized image URL +- `{{ 'key' | t }}` — i18n translation from locales + +**Display:** +- `{{ price | money }}` — format as currency +- `{{ string | strip }}` — trim whitespace +- `{{ value | default: 'fallback' }}` — fallback if empty +- `{{ string | escape }}` — HTML escape +- `{{ string | truncate: 100 }}` — truncate with ellipsis + +**Whitespace:** Use `{%- tag -%}` (hyphens) to strip surrounding whitespace. + +## Shopify CLI Workflow + +```bash +# Install +brew install shopify-cli + +# Local dev (live preview, no live store affected) +shopify theme dev --store mystore.myshopify.com + +# Pull remote changes (merchant edits) BEFORE pushing +shopify theme pull --store mystore.myshopify.com + +# Safe push (sections + assets only, never delete remote files) +shopify theme push --store mystore.myshopify.com \ + --allow-live \ + --nodelete \ + --only "sections/*:assets/*:layout/*:snippets/*:locales/*:config/settings_schema.json" + +# Push specific template (only when intentional) +shopify theme push --only "templates/page.about.json" + +# Create new unpublished theme +shopify theme push --store mystore.myshopify.com --theme-name "New Theme" +``` + +**Flags:** +- `--allow-live` — update the live (published) theme +- `--nodelete` — don't remove files missing locally +- `--only` — colon-separated glob patterns (not comma) + +## Common Gotchas + +1. **image_picker crashes** — `image_url` filter on nil crashes silently. Always guard: `{% if section.settings.image != blank %}` + +2. **Schema defaults ignored** — If template JSON has ANY saved settings for a section, ALL schema defaults are ignored. Design accordingly. + +3. **CDN caching** — Shopify uses Cloudflare. Pushes don't purge cache. Merchant must save in theme editor to force refresh. + +4. **Block shopify_attributes** — Always add `{{ block.shopify_attributes }}` to block wrapper divs. Without it, blocks can't be reordered/edited in the theme editor. + +5. **Locale key fallback** — Missing translation keys render as the key string itself (e.g., `pages.home.hero.title` appears literally). + +6. **Form handling** — Use `{%- form 'contact' -%}` for native Shopify forms. Submissions appear in Admin > Customers. Use `name="contact[body]"` for custom fields. + +7. **Reveal animations in editor** — Sections with `opacity: 0` on load (reveal-on-scroll) won't show in the theme editor. Add: `{% if request.design_mode %}style="opacity:1"{% endif %}` + +## i18n Pattern + +Locale file (`locales/en.default.json`): +```json +{ + "sections": { + "hero": { + "eyebrow": "WELCOME", + "heading": "Your Brand Here" + } + } +} +``` + +Usage: `{{ 'sections.hero.eyebrow' | t }}` + +Locale-aware links: +```liquid +{%- assign base = request.locale.root_url -%} +{%- if base == '/' -%}{%- assign base = '' -%}{%- endif -%} +Shop +```