Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Whitestone
e394c85c0b feat: Visual Smoke Test for The Nexus #490
Some checks failed
Architecture Lint / Linter Tests (pull_request) Successful in 9s
Validate Config / YAML Lint (pull_request) Failing after 14s
Smoke Test / smoke (pull_request) Failing after 18s
Validate Config / JSON Validate (pull_request) Successful in 15s
Validate Config / Shell Script Lint (pull_request) Failing after 51s
Validate Config / Cron Syntax Check (pull_request) Successful in 12s
Validate Config / Python Syntax & Import Check (pull_request) Failing after 1m18s
Validate Config / Python Test Suite (pull_request) Has been skipped
Validate Config / Deploy Script Dry Run (pull_request) Successful in 12s
Validate Config / Playbook Schema Validation (pull_request) Successful in 21s
PR Checklist / pr-checklist (pull_request) Successful in 3m48s
Architecture Lint / Lint Repository (pull_request) Failing after 11s
Replaces 17-line stub with full visual smoke test suite.

Checks:
1. Page loads (HTTP 200)
2. HTML content (Three.js, canvas, title, no errors)
3. Screenshot capture (Playwright → wkhtmltoimage fallback)
4. Vision model analysis (optional, Gemma 3 layout verification)
5. Baseline comparison (file size + pixel diff via ImageMagick)

Features:
- Three screenshot backends (Playwright, wkhtmltoimage, browserless)
- Vision model checks: layout, Three.js render, navigation, text, errors
- Baseline regression detection (file size + pixel-level diff)
- JSON + text output formats
- CI-safe (programmatic-only mode, no vision dependency)
- Exit code 1 on failure, 0 on pass/warn

Tests: 10/10 passing.
Closes #490
2026-04-13 22:00:10 -04:00
38 changed files with 18 additions and 5779 deletions

View File

@@ -202,19 +202,6 @@ curl -s -X POST "{gitea_url}/api/v1/repos/{repo}/issues/{issue_num}/comments" \\
REVIEW CHECKLIST BEFORE YOU PUSH:
{review}
COMMIT DISCIPLINE (CRITICAL):
- Commit every 3-5 tool calls. Do NOT wait until the end.
- After every meaningful file change: git add -A && git commit -m "WIP: <what changed>"
- Before running any destructive command: commit current state first.
- If you are unsure whether to commit: commit. WIP commits are safe. Lost work is not.
- Never use --no-verify.
- The auto-commit-guard is your safety net, but do not rely on it. Commit proactively.
RECOVERY COMMANDS (if interrupted, another agent can resume):
git log --oneline -10 # see your WIP commits
git diff HEAD~1 # see what the last commit changed
git status # see uncommitted work
RULES:
- Do not skip hooks with --no-verify.
- Do not silently widen the scope.

View File

@@ -161,14 +161,6 @@ run_worker() {
CYCLE_END=$(date +%s)
CYCLE_DURATION=$((CYCLE_END - CYCLE_START))
# --- Mid-session auto-commit: commit before timeout if work is dirty ---
cd "$worktree" 2>/dev/null || true
# Ensure auto-commit-guard is running
if ! pgrep -f "auto-commit-guard.sh" >/dev/null 2>&1; then
log "Starting auto-commit-guard daemon"
nohup bash "$(dirname "$0")/auto-commit-guard.sh" 120 "$WORKTREE_BASE" >> "$LOG_DIR/auto-commit-guard.log" 2>&1 &
fi
# Salvage
cd "$worktree" 2>/dev/null || true
DIRTY=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')

View File

@@ -1,159 +0,0 @@
#!/usr/bin/env bash
# auto-commit-guard.sh — Background daemon that auto-commits uncommitted work
#
# Usage: auto-commit-guard.sh [interval_seconds] [worktree_base]
# auto-commit-guard.sh # defaults: 120s, ~/worktrees
# auto-commit-guard.sh 60 # check every 60s
# auto-commit-guard.sh 180 ~/my-worktrees
#
# Scans all git repos under the worktree base for uncommitted changes.
# If dirty for >= 1 check cycle, auto-commits with a WIP message.
# Pushes unpushed commits so work is always recoverable from the remote.
#
# Also scans /tmp for orphaned agent workdirs on startup.
set -uo pipefail
INTERVAL="${1:-120}"
WORKTREE_BASE="${2:-$HOME/worktrees}"
LOG_DIR="$HOME/.hermes/logs"
LOG="$LOG_DIR/auto-commit-guard.log"
PIDFILE="$LOG_DIR/auto-commit-guard.pid"
ORPHAN_SCAN_DONE="$LOG_DIR/.orphan-scan-done"
mkdir -p "$LOG_DIR"
# Single instance guard
if [ -f "$PIDFILE" ]; then
old_pid=$(cat "$PIDFILE")
if kill -0 "$old_pid" 2>/dev/null; then
echo "auto-commit-guard already running (PID $old_pid)" >&2
exit 0
fi
fi
echo $$ > "$PIDFILE"
trap 'rm -f "$PIDFILE"' EXIT
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] AUTO-COMMIT: $*" >> "$LOG"
}
# --- Orphaned workdir scan (runs once on startup) ---
scan_orphans() {
if [ -f "$ORPHAN_SCAN_DONE" ]; then
return 0
fi
log "Scanning /tmp for orphaned agent workdirs..."
local found=0
local rescued=0
for dir in /tmp/*-work-* /tmp/timmy-burn-* /tmp/tc-burn; do
[ -d "$dir" ] || continue
[ -d "$dir/.git" ] || continue
found=$((found + 1))
cd "$dir" 2>/dev/null || continue
local dirty
dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d " ")
if [ "${dirty:-0}" -gt 0 ]; then
local branch
branch=$(git branch --show-current 2>/dev/null || echo "orphan")
git add -A 2>/dev/null
if git commit -m "WIP: orphan rescue — $dirty file(s) auto-committed on $(date -u +%Y-%m-%dT%H:%M:%SZ)
Orphaned workdir detected at $dir.
Branch: $branch
Rescued by auto-commit-guard on startup." 2>/dev/null; then
rescued=$((rescued + 1))
log "RESCUED: $dir ($dirty files on branch $branch)"
# Try to push if remote exists
if git remote get-url origin >/dev/null 2>&1; then
git push -u origin "$branch" 2>/dev/null && log "PUSHED orphan rescue: $dir$branch" || log "PUSH FAILED orphan rescue: $dir (no remote access)"
fi
fi
fi
done
log "Orphan scan complete: $found workdirs checked, $rescued rescued"
touch "$ORPHAN_SCAN_DONE"
}
# --- Main guard loop ---
guard_cycle() {
local committed=0
local scanned=0
# Scan worktree base
if [ -d "$WORKTREE_BASE" ]; then
for dir in "$WORKTREE_BASE"/*/; do
[ -d "$dir" ] || continue
[ -d "$dir/.git" ] || continue
scanned=$((scanned + 1))
cd "$dir" 2>/dev/null || continue
local dirty
dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d " ")
[ "${dirty:-0}" -eq 0 ] && continue
local branch
branch=$(git branch --show-current 2>/dev/null || echo "detached")
git add -A 2>/dev/null
if git commit -m "WIP: auto-commit — $dirty file(s) on $branch
Automated commit by auto-commit-guard at $(date -u +%Y-%m-%dT%H:%M:%SZ).
Work preserved to prevent loss on crash." 2>/dev/null; then
committed=$((committed + 1))
log "COMMITTED: $dir ($dirty files, branch $branch)"
# Push to preserve remotely
if git remote get-url origin >/dev/null 2>&1; then
git push -u origin "$branch" 2>/dev/null && log "PUSHED: $dir$branch" || log "PUSH FAILED: $dir (will retry next cycle)"
fi
fi
done
fi
# Also scan /tmp for agent workdirs
for dir in /tmp/*-work-*; do
[ -d "$dir" ] || continue
[ -d "$dir/.git" ] || continue
scanned=$((scanned + 1))
cd "$dir" 2>/dev/null || continue
local dirty
dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d " ")
[ "${dirty:-0}" -eq 0 ] && continue
local branch
branch=$(git branch --show-current 2>/dev/null || echo "detached")
git add -A 2>/dev/null
if git commit -m "WIP: auto-commit — $dirty file(s) on $branch
Automated commit by auto-commit-guard at $(date -u +%Y-%m-%dT%H:%M:%SZ).
Agent workdir preserved to prevent loss." 2>/dev/null; then
committed=$((committed + 1))
log "COMMITTED: $dir ($dirty files, branch $branch)"
if git remote get-url origin >/dev/null 2>&1; then
git push -u origin "$branch" 2>/dev/null && log "PUSHED: $dir$branch" || log "PUSH FAILED: $dir (will retry next cycle)"
fi
fi
done
[ "$committed" -gt 0 ] && log "Cycle done: $scanned scanned, $committed committed"
}
# --- Entry point ---
log "Starting auto-commit-guard (interval=${INTERVAL}s, worktree=${WORKTREE_BASE})"
scan_orphans
while true; do
guard_cycle
sleep "$INTERVAL"
done

View File

@@ -1,297 +0,0 @@
"""
Glitch pattern definitions for 3D world anomaly detection.
Defines known visual artifact categories commonly found in 3D web worlds,
particularly The Matrix environments. Each pattern includes detection
heuristics and severity ratings.
"""
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class GlitchSeverity(Enum):
CRITICAL = "critical"
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
INFO = "info"
class GlitchCategory(Enum):
FLOATING_ASSETS = "floating_assets"
Z_FIGHTING = "z_fighting"
MISSING_TEXTURES = "missing_textures"
CLIPPING = "clipping"
BROKEN_NORMALS = "broken_normals"
SHADOW_ARTIFACTS = "shadow_artifacts"
LIGHTMAP_ERRORS = "lightmap_errors"
LOD_POPPING = "lod_popping"
WATER_REFLECTION = "water_reflection"
SKYBOX_SEAM = "skybox_seam"
@dataclass
class GlitchPattern:
"""Definition of a known glitch pattern with detection parameters."""
category: GlitchCategory
name: str
description: str
severity: GlitchSeverity
detection_prompts: list[str]
visual_indicators: list[str]
confidence_threshold: float = 0.6
def to_dict(self) -> dict:
return {
"category": self.category.value,
"name": self.name,
"description": self.description,
"severity": self.severity.value,
"detection_prompts": self.detection_prompts,
"visual_indicators": self.visual_indicators,
"confidence_threshold": self.confidence_threshold,
}
# Known glitch patterns for Matrix 3D world scanning
MATRIX_GLITCH_PATTERNS: list[GlitchPattern] = [
GlitchPattern(
category=GlitchCategory.FLOATING_ASSETS,
name="Floating Object",
description="Object not properly grounded or anchored to the scene geometry. "
"Common in procedurally placed assets or after physics desync.",
severity=GlitchSeverity.HIGH,
detection_prompts=[
"Identify any objects that appear to float above the ground without support.",
"Look for furniture, props, or geometry suspended in mid-air with no visible attachment.",
"Check for objects whose shadows do not align with the surface below them.",
],
visual_indicators=[
"gap between object base and surface",
"shadow detached from object",
"object hovering with no structural support",
],
confidence_threshold=0.65,
),
GlitchPattern(
category=GlitchCategory.Z_FIGHTING,
name="Z-Fighting Flicker",
description="Two coplanar surfaces competing for depth priority, causing "
"visible flickering or shimmering textures.",
severity=GlitchSeverity.MEDIUM,
detection_prompts=[
"Look for surfaces that appear to shimmer, flicker, or show mixed textures.",
"Identify areas where two textures seem to overlap and compete for visibility.",
"Check walls, floors, or objects for surface noise or pattern interference.",
],
visual_indicators=[
"shimmering surface",
"texture flicker between two patterns",
"noisy flat surfaces",
"moire-like patterns on planar geometry",
],
confidence_threshold=0.55,
),
GlitchPattern(
category=GlitchCategory.MISSING_TEXTURES,
name="Missing or Placeholder Texture",
description="A surface rendered with a fallback checkerboard, solid magenta, "
"or the default engine placeholder texture.",
severity=GlitchSeverity.CRITICAL,
detection_prompts=[
"Look for bright magenta, checkerboard, or solid-color surfaces that look out of place.",
"Identify any surfaces that appear as flat untextured colors inconsistent with the scene.",
"Check for black, white, or magenta patches where detailed textures should be.",
],
visual_indicators=[
"magenta/pink solid color surface",
"checkerboard pattern",
"flat single-color geometry",
"UV-debug texture visible",
],
confidence_threshold=0.7,
),
GlitchPattern(
category=GlitchCategory.CLIPPING,
name="Geometry Clipping",
description="Objects passing through each other or intersecting in physically "
"impossible ways due to collision mesh errors.",
severity=GlitchSeverity.HIGH,
detection_prompts=[
"Look for objects that visibly pass through other objects (walls, floors, furniture).",
"Identify characters or props embedded inside geometry where they should not be.",
"Check for intersecting meshes where solid objects overlap unnaturally.",
],
visual_indicators=[
"object passing through wall or floor",
"embedded geometry",
"overlapping solid meshes",
"character limb inside furniture",
],
confidence_threshold=0.6,
),
GlitchPattern(
category=GlitchCategory.BROKEN_NORMALS,
name="Broken Surface Normals",
description="Inverted or incorrect surface normals causing faces to appear "
"inside-out, invisible from certain angles, or lit incorrectly.",
severity=GlitchSeverity.MEDIUM,
detection_prompts=[
"Look for surfaces that appear dark or black on one side while lit on the other.",
"Identify objects that seem to vanish when viewed from certain angles.",
"Check for inverted shading where lit areas should be in shadow.",
],
visual_indicators=[
"dark/unlit face on otherwise lit model",
"invisible surface from one direction",
"inverted shadow gradient",
"inside-out appearance",
],
confidence_threshold=0.5,
),
GlitchPattern(
category=GlitchCategory.SHADOW_ARTIFACTS,
name="Shadow Artifact",
description="Broken, detached, or incorrectly rendered shadows that do not "
"match the casting geometry or scene lighting.",
severity=GlitchSeverity.LOW,
detection_prompts=[
"Look for shadows that do not match the shape of nearby objects.",
"Identify shadow acne: banding or striped patterns on surfaces.",
"Check for floating shadows detached from any visible caster.",
],
visual_indicators=[
"shadow shape mismatch",
"shadow acne bands",
"detached floating shadow",
"Peter Panning (shadow offset from base)",
],
confidence_threshold=0.5,
),
GlitchPattern(
category=GlitchCategory.LOD_POPPING,
name="LOD Transition Pop",
description="Visible pop-in when level-of-detail models switch abruptly, "
"causing geometry or textures to change suddenly.",
severity=GlitchSeverity.LOW,
detection_prompts=[
"Look for areas where mesh detail changes abruptly at visible boundaries.",
"Identify objects that appear to morph or shift geometry suddenly.",
"Check for texture resolution changes that create visible seams.",
],
visual_indicators=[
"visible mesh simplification boundary",
"texture resolution jump",
"geometry pop-in artifacts",
],
confidence_threshold=0.45,
),
GlitchPattern(
category=GlitchCategory.LIGHTMAP_ERRORS,
name="Lightmap Baking Error",
description="Incorrect or missing baked lighting causing dark spots, light "
"leaks, or mismatched illumination on static geometry.",
severity=GlitchSeverity.MEDIUM,
detection_prompts=[
"Look for unusually dark patches on walls or ceilings that should be lit.",
"Identify bright light leaks through solid geometry seams.",
"Check for mismatched lighting between adjacent surfaces.",
],
visual_indicators=[
"dark splotch on lit surface",
"bright line at geometry seam",
"lighting discontinuity between adjacent faces",
],
confidence_threshold=0.5,
),
GlitchPattern(
category=GlitchCategory.WATER_REFLECTION,
name="Water/Reflection Error",
description="Incorrect reflections, missing water surfaces, or broken "
"reflection probe assignments.",
severity=GlitchSeverity.MEDIUM,
detection_prompts=[
"Look for reflections that do not match the surrounding environment.",
"Identify water surfaces that appear solid or incorrectly rendered.",
"Check for mirror surfaces showing wrong scene geometry.",
],
visual_indicators=[
"reflection mismatch",
"solid water surface",
"incorrect environment map",
],
confidence_threshold=0.5,
),
GlitchPattern(
category=GlitchCategory.SKYBOX_SEAM,
name="Skybox Seam",
description="Visible seams or color mismatches at the edges of skybox cubemap faces.",
severity=GlitchSeverity.LOW,
detection_prompts=[
"Look at the edges of the sky for visible seams or color shifts.",
"Identify discontinuities where skybox faces meet.",
"Check for texture stretching at skybox corners.",
],
visual_indicators=[
"visible line in sky",
"color discontinuity at sky edge",
"sky texture seam",
],
confidence_threshold=0.45,
),
]
def get_patterns_by_severity(min_severity: GlitchSeverity) -> list[GlitchPattern]:
"""Return patterns at or above the given severity level."""
severity_order = [
GlitchSeverity.INFO,
GlitchSeverity.LOW,
GlitchSeverity.MEDIUM,
GlitchSeverity.HIGH,
GlitchSeverity.CRITICAL,
]
min_idx = severity_order.index(min_severity)
return [p for p in MATRIX_GLITCH_PATTERNS if severity_order.index(p.severity) >= min_idx]
def get_pattern_by_category(category: GlitchCategory) -> Optional[GlitchPattern]:
"""Return the pattern definition for a specific category."""
for p in MATRIX_GLITCH_PATTERNS:
if p.category == category:
return p
return None
def build_vision_prompt(patterns: list[GlitchPattern] | None = None) -> str:
"""Build a composite vision analysis prompt from pattern definitions."""
if patterns is None:
patterns = MATRIX_GLITCH_PATTERNS
sections = []
for p in patterns:
prompt_text = " ".join(p.detection_prompts)
indicators = ", ".join(p.visual_indicators)
sections.append(
f"[{p.category.value.upper()}] {p.name} (severity: {p.severity.value})\n"
f" {p.description}\n"
f" Look for: {prompt_text}\n"
f" Visual indicators: {indicators}"
)
return (
"Analyze this 3D world screenshot for visual glitches and artifacts. "
"For each detected issue, report the category, description of what you see, "
"approximate location in the image (x%, y%), and confidence (0.0-1.0).\n\n"
"Known glitch patterns to check:\n\n" + "\n\n".join(sections)
)
if __name__ == "__main__":
import json
print(f"Loaded {len(MATRIX_GLITCH_PATTERNS)} glitch patterns:\n")
for p in MATRIX_GLITCH_PATTERNS:
print(f" [{p.severity.value:8s}] {p.category.value}: {p.name}")
print(f"\nVision prompt preview:\n{build_vision_prompt()[:500]}...")

View File

@@ -1,549 +0,0 @@
#!/usr/bin/env python3
"""
Matrix 3D World Glitch Detector
Scans a 3D web world for visual artifacts using browser automation
and vision AI analysis. Produces structured glitch reports.
Usage:
python matrix_glitch_detector.py <url> [--angles 4] [--output report.json]
python matrix_glitch_detector.py --demo # Run with synthetic test data
Ref: timmy-config#491
"""
import argparse
import base64
import json
import os
import sys
import time
import uuid
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
# Add parent for glitch_patterns import
sys.path.insert(0, str(Path(__file__).resolve().parent))
from glitch_patterns import (
GlitchCategory,
GlitchPattern,
GlitchSeverity,
MATRIX_GLITCH_PATTERNS,
build_vision_prompt,
get_patterns_by_severity,
)
@dataclass
class DetectedGlitch:
"""A single detected glitch with metadata."""
id: str
category: str
name: str
description: str
severity: str
confidence: float
location_x: Optional[float] = None # percentage across image
location_y: Optional[float] = None # percentage down image
screenshot_index: int = 0
screenshot_angle: str = "front"
timestamp: str = ""
def __post_init__(self):
if not self.timestamp:
self.timestamp = datetime.now(timezone.utc).isoformat()
@dataclass
class ScanResult:
"""Complete scan result for a 3D world URL."""
scan_id: str
url: str
timestamp: str
total_screenshots: int
angles_captured: list[str]
glitches: list[dict] = field(default_factory=list)
summary: dict = field(default_factory=dict)
metadata: dict = field(default_factory=dict)
def to_json(self, indent: int = 2) -> str:
return json.dumps(asdict(self), indent=indent)
def generate_scan_angles(num_angles: int) -> list[dict]:
"""Generate camera angle configurations for multi-angle scanning.
Returns a list of dicts with yaw/pitch/label for browser camera control.
"""
base_angles = [
{"yaw": 0, "pitch": 0, "label": "front"},
{"yaw": 90, "pitch": 0, "label": "right"},
{"yaw": 180, "pitch": 0, "label": "back"},
{"yaw": 270, "pitch": 0, "label": "left"},
{"yaw": 0, "pitch": -30, "label": "front_low"},
{"yaw": 45, "pitch": -15, "label": "front_right_low"},
{"yaw": 0, "pitch": 30, "label": "front_high"},
{"yaw": 45, "pitch": 0, "label": "front_right"},
]
if num_angles <= len(base_angles):
return base_angles[:num_angles]
return base_angles + [
{"yaw": i * (360 // num_angles), "pitch": 0, "label": f"angle_{i}"}
for i in range(len(base_angles), num_angles)
]
def capture_screenshots(url: str, angles: list[dict], output_dir: Path) -> list[Path]:
"""Capture screenshots of a 3D web world from multiple angles.
Uses browser_vision tool when available; falls back to placeholder generation
for testing and environments without browser access.
"""
output_dir.mkdir(parents=True, exist_ok=True)
screenshots = []
for i, angle in enumerate(angles):
filename = output_dir / f"screenshot_{i:03d}_{angle['label']}.png"
# Attempt browser-based capture via browser_vision
try:
result = _browser_capture(url, angle, filename)
if result:
screenshots.append(filename)
continue
except Exception:
pass
# Generate placeholder screenshot for offline/test scenarios
_generate_placeholder_screenshot(filename, angle)
screenshots.append(filename)
return screenshots
def _browser_capture(url: str, angle: dict, output_path: Path) -> bool:
"""Capture a screenshot via browser automation.
This is a stub that delegates to the browser_vision tool when run
in an environment that provides it. In CI or offline mode, returns False.
"""
# Check if browser_vision is available via environment
bv_script = os.environ.get("BROWSER_VISION_SCRIPT")
if bv_script and Path(bv_script).exists():
import subprocess
cmd = [
sys.executable, bv_script,
"--url", url,
"--screenshot", str(output_path),
"--rotate-yaw", str(angle["yaw"]),
"--rotate-pitch", str(angle["pitch"]),
]
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
return proc.returncode == 0 and output_path.exists()
return False
def _generate_placeholder_screenshot(path: Path, angle: dict):
"""Generate a minimal 1x1 PNG as a placeholder for testing."""
# Minimal valid PNG (1x1 transparent pixel)
png_data = (
b"\x89PNG\r\n\x1a\n"
b"\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
b"\x08\x06\x00\x00\x00\x1f\x15\xc4\x89"
b"\x00\x00\x00\nIDATx\x9cc\x00\x01\x00\x00\x05\x00\x01"
b"\r\n\xb4\x00\x00\x00\x00IEND\xaeB`\x82"
)
path.write_bytes(png_data)
def analyze_with_vision(
screenshot_paths: list[Path],
angles: list[dict],
patterns: list[GlitchPattern] | None = None,
) -> list[DetectedGlitch]:
"""Send screenshots to vision AI for glitch analysis.
In environments with a vision model available, sends each screenshot
with the composite detection prompt. Otherwise returns simulated results.
"""
if patterns is None:
patterns = MATRIX_GLITCH_PATTERNS
prompt = build_vision_prompt(patterns)
glitches = []
for i, (path, angle) in enumerate(zip(screenshot_paths, angles)):
# Attempt vision analysis
detected = _vision_analyze_image(path, prompt, i, angle["label"])
glitches.extend(detected)
return glitches
def _vision_analyze_image(
image_path: Path,
prompt: str,
screenshot_index: int,
angle_label: str,
) -> list[DetectedGlitch]:
"""Analyze a single screenshot with vision AI.
Uses the vision_analyze tool when available; returns empty list otherwise.
"""
# Check for vision API configuration
api_key = os.environ.get("VISION_API_KEY") or os.environ.get("OPENAI_API_KEY")
api_base = os.environ.get("VISION_API_BASE", "https://api.openai.com/v1")
if api_key:
try:
return _call_vision_api(
image_path, prompt, screenshot_index, angle_label, api_key, api_base
)
except Exception as e:
print(f" [!] Vision API error for {image_path.name}: {e}", file=sys.stderr)
# No vision backend available
return []
def _call_vision_api(
image_path: Path,
prompt: str,
screenshot_index: int,
angle_label: str,
api_key: str,
api_base: str,
) -> list[DetectedGlitch]:
"""Call a vision API (OpenAI-compatible) for image analysis."""
import urllib.request
import urllib.error
image_data = base64.b64encode(image_path.read_bytes()).decode()
payload = json.dumps({
"model": os.environ.get("VISION_MODEL", "gpt-4o"),
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{image_data}",
"detail": "high",
},
},
],
}
],
"max_tokens": 4096,
}).encode()
req = urllib.request.Request(
f"{api_base}/chat/completions",
data=payload,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
},
)
with urllib.request.urlopen(req, timeout=60) as resp:
result = json.loads(resp.read())
content = result["choices"][0]["message"]["content"]
return _parse_vision_response(content, screenshot_index, angle_label)
def _add_glitch_from_dict(
item: dict,
glitches: list[DetectedGlitch],
screenshot_index: int,
angle_label: str,
):
"""Convert a dict from vision API response into a DetectedGlitch."""
cat = item.get("category", item.get("type", "unknown"))
conf = float(item.get("confidence", item.get("score", 0.5)))
glitch = DetectedGlitch(
id=str(uuid.uuid4())[:8],
category=cat,
name=item.get("name", item.get("label", cat)),
description=item.get("description", item.get("detail", "")),
severity=item.get("severity", _infer_severity(cat, conf)),
confidence=conf,
location_x=item.get("location_x", item.get("x")),
location_y=item.get("location_y", item.get("y")),
screenshot_index=screenshot_index,
screenshot_angle=angle_label,
)
glitches.append(glitch)
def _parse_vision_response(
text: str, screenshot_index: int, angle_label: str
) -> list[DetectedGlitch]:
"""Parse vision AI response into structured glitch detections."""
glitches = []
# Try to extract JSON from the response
json_blocks = []
in_json = False
json_buf = []
for line in text.split("\n"):
stripped = line.strip()
if stripped.startswith("```"):
if in_json and json_buf:
try:
json_blocks.append(json.loads("\n".join(json_buf)))
except json.JSONDecodeError:
pass
json_buf = []
in_json = not in_json
continue
if in_json:
json_buf.append(line)
# Flush any remaining buffer
if in_json and json_buf:
try:
json_blocks.append(json.loads("\n".join(json_buf)))
except json.JSONDecodeError:
pass
# Also try parsing the entire response as JSON
try:
parsed = json.loads(text)
if isinstance(parsed, list):
json_blocks.extend(parsed)
elif isinstance(parsed, dict):
if "glitches" in parsed:
json_blocks.extend(parsed["glitches"])
elif "detections" in parsed:
json_blocks.extend(parsed["detections"])
else:
json_blocks.append(parsed)
except json.JSONDecodeError:
pass
for item in json_blocks:
# Flatten arrays of detections
if isinstance(item, list):
for sub in item:
if isinstance(sub, dict):
_add_glitch_from_dict(sub, glitches, screenshot_index, angle_label)
elif isinstance(item, dict):
_add_glitch_from_dict(item, glitches, screenshot_index, angle_label)
return glitches
def _infer_severity(category: str, confidence: float) -> str:
"""Infer severity from category and confidence when not provided."""
critical_cats = {"missing_textures", "clipping"}
high_cats = {"floating_assets", "broken_normals"}
cat_lower = category.lower()
if any(c in cat_lower for c in critical_cats):
return "critical" if confidence > 0.7 else "high"
if any(c in cat_lower for c in high_cats):
return "high" if confidence > 0.7 else "medium"
return "medium" if confidence > 0.6 else "low"
def build_report(
url: str,
angles: list[dict],
screenshots: list[Path],
glitches: list[DetectedGlitch],
) -> ScanResult:
"""Build the final structured scan report."""
severity_counts = {}
category_counts = {}
for g in glitches:
severity_counts[g.severity] = severity_counts.get(g.severity, 0) + 1
category_counts[g.category] = category_counts.get(g.category, 0) + 1
report = ScanResult(
scan_id=str(uuid.uuid4()),
url=url,
timestamp=datetime.now(timezone.utc).isoformat(),
total_screenshots=len(screenshots),
angles_captured=[a["label"] for a in angles],
glitches=[asdict(g) for g in glitches],
summary={
"total_glitches": len(glitches),
"by_severity": severity_counts,
"by_category": category_counts,
"highest_severity": max(severity_counts.keys(), default="none"),
"clean_screenshots": sum(
1
for i in range(len(screenshots))
if not any(g.screenshot_index == i for g in glitches)
),
},
metadata={
"detector_version": "0.1.0",
"pattern_count": len(MATRIX_GLITCH_PATTERNS),
"reference": "timmy-config#491",
},
)
return report
def run_demo(output_path: Optional[Path] = None) -> ScanResult:
"""Run a demonstration scan with simulated detections."""
print("[*] Running Matrix glitch detection demo...")
url = "https://matrix.example.com/world/alpha"
angles = generate_scan_angles(4)
screenshots_dir = Path("/tmp/matrix_glitch_screenshots")
print(f"[*] Capturing {len(angles)} screenshots from: {url}")
screenshots = capture_screenshots(url, angles, screenshots_dir)
print(f"[*] Captured {len(screenshots)} screenshots")
# Simulate detections for demo
demo_glitches = [
DetectedGlitch(
id=str(uuid.uuid4())[:8],
category="floating_assets",
name="Floating Chair",
description="Office chair floating 0.3m above floor in sector 7",
severity="high",
confidence=0.87,
location_x=35.2,
location_y=62.1,
screenshot_index=0,
screenshot_angle="front",
),
DetectedGlitch(
id=str(uuid.uuid4())[:8],
category="z_fighting",
name="Wall Texture Flicker",
description="Z-fighting between wall panel and decorative overlay",
severity="medium",
confidence=0.72,
location_x=58.0,
location_y=40.5,
screenshot_index=1,
screenshot_angle="right",
),
DetectedGlitch(
id=str(uuid.uuid4())[:8],
category="missing_textures",
name="Placeholder Texture",
description="Bright magenta surface on door frame — missing asset reference",
severity="critical",
confidence=0.95,
location_x=72.3,
location_y=28.8,
screenshot_index=2,
screenshot_angle="back",
),
DetectedGlitch(
id=str(uuid.uuid4())[:8],
category="clipping",
name="Desk Through Wall",
description="Desk corner clipping through adjacent wall geometry",
severity="high",
confidence=0.81,
location_x=15.0,
location_y=55.0,
screenshot_index=3,
screenshot_angle="left",
),
]
print(f"[*] Detected {len(demo_glitches)} glitches")
report = build_report(url, angles, screenshots, demo_glitches)
if output_path:
output_path.write_text(report.to_json())
print(f"[*] Report saved to: {output_path}")
return report
def main():
parser = argparse.ArgumentParser(
description="Matrix 3D World Glitch Detector — scan for visual artifacts",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s https://matrix.example.com/world/alpha
%(prog)s https://matrix.example.com/world/alpha --angles 8 --output report.json
%(prog)s --demo
""",
)
parser.add_argument("url", nargs="?", help="URL of the 3D world to scan")
parser.add_argument(
"--angles", type=int, default=4, help="Number of camera angles to capture (default: 4)"
)
parser.add_argument("--output", "-o", type=str, help="Output file path for JSON report")
parser.add_argument("--demo", action="store_true", help="Run demo with simulated data")
parser.add_argument(
"--min-severity",
choices=["info", "low", "medium", "high", "critical"],
default="info",
help="Minimum severity to include in report",
)
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
args = parser.parse_args()
if args.demo:
output = Path(args.output) if args.output else Path("glitch_report_demo.json")
report = run_demo(output)
print(f"\n=== Scan Summary ===")
print(f"URL: {report.url}")
print(f"Screenshots: {report.total_screenshots}")
print(f"Glitches found: {report.summary['total_glitches']}")
print(f"By severity: {report.summary['by_severity']}")
return
if not args.url:
parser.error("URL required (or use --demo)")
scan_id = str(uuid.uuid4())[:8]
print(f"[*] Matrix Glitch Detector — Scan {scan_id}")
print(f"[*] Target: {args.url}")
# Generate camera angles
angles = generate_scan_angles(args.angles)
print(f"[*] Capturing {len(angles)} screenshots...")
# Capture screenshots
screenshots_dir = Path(f"/tmp/matrix_glitch_{scan_id}")
screenshots = capture_screenshots(args.url, angles, screenshots_dir)
print(f"[*] Captured {len(screenshots)} screenshots")
# Filter patterns by severity
min_sev = GlitchSeverity(args.min_severity)
patterns = get_patterns_by_severity(min_sev)
# Analyze with vision AI
print(f"[*] Analyzing with vision AI ({len(patterns)} patterns)...")
glitches = analyze_with_vision(screenshots, angles, patterns)
# Build and save report
report = build_report(args.url, angles, screenshots, glitches)
if args.output:
Path(args.output).write_text(report.to_json())
print(f"[*] Report saved: {args.output}")
else:
print(report.to_json())
print(f"\n[*] Done — {len(glitches)} glitches detected")
if __name__ == "__main__":
main()

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python3
"""
Full Nostr agent-to-agent communication demo - FINAL WORKING
"""

View File

@@ -1,514 +0,0 @@
#!/usr/bin/env bash
# pane-watchdog.sh — Detect stuck/dead tmux panes and auto-restart them
#
# Tracks output hash per pane across cycles. If a pane's captured output
# hasn't changed for STUCK_CYCLES consecutive checks, the pane is STUCK.
# Dead panes (PID gone) are also detected.
#
# On STUCK/DEAD:
# 1. Kill the pane
# 2. Attempt restart with --resume (session ID from manifest)
# 3. Fallback: fresh prompt with last known task from logs
#
# State file: ~/.hermes/pane-state.json
# Log: ~/.hermes/logs/pane-watchdog.log
#
# Usage:
# pane-watchdog.sh # One-shot check all sessions
# pane-watchdog.sh --daemon # Run every CHECK_INTERVAL seconds
# pane-watchdog.sh --status # Print current pane state
# pane-watchdog.sh --session NAME # Check only one session
#
# Issue: timmy-config #515
set -uo pipefail
export PATH="/opt/homebrew/bin:$HOME/.local/bin:$HOME/.hermes/bin:/usr/local/bin:$PATH"
# === CONFIG ===
STATE_FILE="${PANE_STATE_FILE:-$HOME/.hermes/pane-state.json}"
LOG_FILE="${PANE_WATCHDOG_LOG:-$HOME/.hermes/logs/pane-watchdog.log}"
CHECK_INTERVAL="${PANE_CHECK_INTERVAL:-120}" # seconds between cycles
STUCK_CYCLES=2 # unchanged cycles before STUCK
MAX_RESTART_ATTEMPTS=3 # per pane per hour
RESTART_COOLDOWN=3600 # seconds between escalation alerts
CAPTURE_LINES=40 # lines of output to hash
# Sessions to monitor (all if empty)
MONITOR_SESSIONS="${PANE_WATCHDOG_SESSIONS:-}"
mkdir -p "$(dirname "$STATE_FILE")" "$(dirname "$LOG_FILE")"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}
# === HELPERS ===
# Capture last N lines of pane output and hash them
capture_pane_hash() {
local target="$1"
local output
output=$(tmux capture-pane -t "$target" -p -S "-${CAPTURE_LINES}" 2>/dev/null || echo "DEAD")
echo -n "$output" | shasum -a 256 | cut -d' ' -f1
}
# Check if pane PID is alive
pane_pid_alive() {
local target="$1"
local pid
pid=$(tmux list-panes -t "$target" -F '#{pane_pid}' 2>/dev/null | head -1 || echo "")
if [ -z "$pid" ]; then
return 1 # pane doesn't exist
fi
kill -0 "$pid" 2>/dev/null
}
# Get pane start command
pane_start_command() {
local target="$1"
tmux list-panes -t "$target" -F '#{pane_start_command}' 2>/dev/null | head -1 || echo "unknown"
}
# Get the pane's current running command (child process)
pane_current_command() {
local target="$1"
tmux list-panes -t "$target" -F '#{pane_current_command}' 2>/dev/null || echo "unknown"
}
# Only restart panes running hermes/agent commands (not zsh, python3 repls, etc.)
is_restartable() {
local cmd="$1"
case "$cmd" in
hermes|*hermes*|*agent*|*timmy*|*kimi*|*claude-loop*|*gemini-loop*)
return 0
;;
*)
return 1
;;
esac
}
# Get session ID from hermes manifest if available
get_hermes_session_id() {
local session_name="$1"
local manifest="$HOME/.hermes/sessions/${session_name}/manifest.json"
if [ -f "$manifest" ]; then
python3 -c "
import json, sys
try:
m = json.load(open('$manifest'))
print(m.get('session_id', m.get('id', '')))
except: pass
" 2>/dev/null || echo ""
else
echo ""
fi
}
# Get last task from pane logs
get_last_task() {
local session_name="$1"
local log_dir="$HOME/.hermes/logs"
# Find the most recent log for this session
local log_file
log_file=$(find "$log_dir" -name "*${session_name}*" -type f -mtime -1 2>/dev/null | sort -r | head -1)
if [ -n "$log_file" ] && [ -f "$log_file" ]; then
# Extract last user prompt or task description
grep -i "task:\|prompt:\|issue\|working on" "$log_file" 2>/dev/null | tail -1 | sed 's/.*[:>] *//' | head -c 200
fi
}
# Restart a pane with a fresh shell/command
restart_pane() {
local target="$1"
local session_name="${target%%:*}"
local session_id last_task cmd
log "RESTART: Attempting to restart $target"
# Kill existing pane
tmux kill-pane -t "$target" 2>/dev/null || true
sleep 1
# Try --resume with session ID
session_id=$(get_hermes_session_id "$session_name")
if [ -n "$session_id" ]; then
log "RESTART: Trying --resume with session $session_id"
tmux split-window -t "$session_name" -d \
"hermes chat --resume '$session_id' 2>&1 | tee -a '$HOME/.hermes/logs/${session_name}-restart.log'"
sleep 2
if pane_pid_alive "${session_name}:1" 2>/dev/null; then
log "RESTART: Success with --resume"
return 0
fi
fi
# Fallback: fresh prompt
last_task=$(get_last_task "$session_name")
if [ -n "$last_task" ]; then
log "RESTART: Fallback — fresh prompt with task: $last_task"
tmux split-window -t "$session_name" -d \
"echo 'Watchdog restart — last task: $last_task' && hermes chat 2>&1 | tee -a '$HOME/.hermes/logs/${session_name}-restart.log'"
else
log "RESTART: Fallback — fresh hermes chat"
tmux split-window -t "$session_name" -d \
"hermes chat 2>&1 | tee -a '$HOME/.hermes/logs/${session_name}-restart.log'"
fi
sleep 2
if pane_pid_alive "${session_name}:1" 2>/dev/null; then
log "RESTART: Fallback restart succeeded"
return 0
else
log "RESTART: FAILED to restart $target"
return 1
fi
}
# === STATE MANAGEMENT ===
read_state() {
if [ -f "$STATE_FILE" ]; then
cat "$STATE_FILE"
else
echo "{}"
fi
}
write_state() {
echo "$1" > "$STATE_FILE"
}
# Update state for a single pane and return JSON status
update_pane_state() {
local target="$1"
local hash="$2"
local is_alive="$3"
local now
now=$(date +%s)
python3 - "$STATE_FILE" "$target" "$hash" "$is_alive" "$now" "$STUCK_CYCLES" <<'PYEOF'
import json, sys, time
state_file = sys.argv[1]
target = sys.argv[2]
new_hash = sys.argv[3]
is_alive = sys.argv[4] == "true"
now = int(sys.argv[5])
stuck_cycles = int(sys.argv[6])
try:
with open(state_file) as f:
state = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
state = {}
pane = state.get(target, {
"hash": "",
"same_count": 0,
"status": "UNKNOWN",
"last_change": 0,
"last_check": 0,
"restart_attempts": 0,
"last_restart": 0,
"current_command": "",
})
if not is_alive:
pane["status"] = "DEAD"
pane["same_count"] = 0
elif new_hash == pane.get("hash", ""):
pane["same_count"] = pane.get("same_count", 0) + 1
if pane["same_count"] >= stuck_cycles:
pane["status"] = "STUCK"
else:
pane["status"] = "STALE" if pane["same_count"] > 0 else "OK"
else:
pane["hash"] = new_hash
pane["same_count"] = 0
pane["status"] = "OK"
pane["last_change"] = now
pane["last_check"] = now
state[target] = pane
with open(state_file, "w") as f:
json.dump(state, f, indent=2)
print(json.dumps(pane))
PYEOF
}
# Reset restart attempt counter if cooldown expired
maybe_reset_restarts() {
local target="$1"
local now
now=$(date +%s)
python3 - "$STATE_FILE" "$target" "$now" "$RESTART_COOLDOWN" <<'PYEOF'
import json, sys
state_file = sys.argv[1]
target = sys.argv[2]
now = int(sys.argv[3])
cooldown = int(sys.argv[4])
with open(state_file) as f:
state = json.load(f)
pane = state.get(target, {})
last_restart = pane.get("last_restart", 0)
if now - last_restart > cooldown:
pane["restart_attempts"] = 0
state[target] = pane
with open(state_file, "w") as f:
json.dump(state, f, indent=2)
print(pane.get("restart_attempts", 0))
PYEOF
}
increment_restart_attempt() {
local target="$1"
local now
now=$(date +%s)
python3 - "$STATE_FILE" "$target" "$now" <<'PYEOF'
import json, sys
state_file = sys.argv[1]
target = sys.argv[2]
now = int(sys.argv[3])
with open(state_file) as f:
state = json.load(f)
pane = state.get(target, {})
pane["restart_attempts"] = pane.get("restart_attempts", 0) + 1
pane["last_restart"] = now
pane["status"] = "RESTARTING"
state[target] = pane
with open(state_file, "w") as f:
json.dump(state, f, indent=2)
print(pane["restart_attempts"])
PYEOF
}
# === CORE CHECK ===
check_pane() {
local target="$1"
local hash is_alive status current_cmd
# Capture state
hash=$(capture_pane_hash "$target")
if pane_pid_alive "$target"; then
is_alive="true"
else
is_alive="false"
fi
# Get current command for the pane
current_cmd=$(pane_current_command "$target")
# Update state and get result
local result
result=$(update_pane_state "$target" "$hash" "$is_alive")
status=$(echo "$result" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('status','UNKNOWN'))" 2>/dev/null || echo "UNKNOWN")
case "$status" in
OK)
# Healthy, do nothing
;;
DEAD)
log "DETECTED: $target is DEAD (PID gone) cmd=$current_cmd"
if is_restartable "$current_cmd"; then
handle_stuck "$target"
else
log "SKIP: $target not a hermes pane (cmd=$current_cmd), not restarting"
fi
;;
STUCK)
log "DETECTED: $target is STUCK (output unchanged for ${STUCK_CYCLES} cycles) cmd=$current_cmd"
if is_restartable "$current_cmd"; then
handle_stuck "$target"
else
log "SKIP: $target not a hermes pane (cmd=$current_cmd), not restarting"
fi
;;
STALE)
# Output unchanged but within threshold — just log
local count
count=$(echo "$result" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('same_count',0))" 2>/dev/null || echo "?")
log "STALE: $target unchanged for $count cycle(s)"
;;
esac
}
handle_stuck() {
local target="$1"
local session_name="${target%%:*}"
local attempts
# Check restart budget
attempts=$(maybe_reset_restarts "$target")
if [ "$attempts" -ge "$MAX_RESTART_ATTEMPTS" ]; then
log "ESCALATION: $target stuck ${attempts}x — manual intervention needed"
echo "ALERT: $target stuck after $attempts restart attempts" >&2
return 1
fi
attempts=$(increment_restart_attempt "$target")
log "ACTION: Restarting $target (attempt $attempts/$MAX_RESTART_ATTEMPTS)"
if restart_pane "$target"; then
log "OK: $target restarted successfully"
else
log "FAIL: $target restart failed (attempt $attempts)"
fi
}
check_all_sessions() {
local sessions
if [ -n "$MONITOR_SESSIONS" ]; then
IFS=',' read -ra sessions <<< "$MONITOR_SESSIONS"
else
sessions=()
while IFS= read -r line; do
[ -n "$line" ] && sessions+=("$line")
done < <(tmux list-sessions -F '#{session_name}' 2>/dev/null || true)
fi
local total=0 stuck=0 dead=0 ok=0
for session in "${sessions[@]}"; do
[ -z "$session" ] && continue
# Get pane targets
local panes
panes=$(tmux list-panes -t "$session" -F "${session}:#{window_index}.#{pane_index}" 2>/dev/null || true)
for target in $panes; do
check_pane "$target"
total=$((total + 1))
done
done
log "CHECK: Processed $total panes"
}
# === STATUS DISPLAY ===
show_status() {
if [ ! -f "$STATE_FILE" ]; then
echo "No pane state file found at $STATE_FILE"
echo "Run pane-watchdog.sh once to initialize."
exit 0
fi
python3 - "$STATE_FILE" <<'PYEOF'
import json, sys, time
state_file = sys.argv[1]
try:
with open(state_file) as f:
state = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
print("No state data yet.")
sys.exit(0)
if not state:
print("No panes tracked.")
sys.exit(0)
now = int(time.time())
print(f"{'PANE':<35} {'STATUS':<12} {'STALE':<6} {'LAST CHANGE':<15} {'RESTARTS'}")
print("-" * 90)
for target in sorted(state.keys()):
p = state[target]
status = p.get("status", "?")
same = p.get("same_count", 0)
last_change = p.get("last_change", 0)
restarts = p.get("restart_attempts", 0)
if last_change:
ago = now - last_change
if ago < 60:
change_str = f"{ago}s ago"
elif ago < 3600:
change_str = f"{ago//60}m ago"
else:
change_str = f"{ago//3600}h ago"
else:
change_str = "never"
# Color code
if status == "OK":
icon = "✓"
elif status == "STUCK":
icon = "✖"
elif status == "DEAD":
icon = "☠"
elif status == "STALE":
icon = "⏳"
else:
icon = "?"
print(f" {icon} {target:<32} {status:<12} {same:<6} {change_str:<15} {restarts}")
PYEOF
}
# === DAEMON MODE ===
run_daemon() {
log "DAEMON: Starting (interval=${CHECK_INTERVAL}s, stuck_threshold=${STUCK_CYCLES})"
echo "Pane watchdog started. Checking every ${CHECK_INTERVAL}s. Ctrl+C to stop."
echo "Log: $LOG_FILE"
echo "State: $STATE_FILE"
echo ""
while true; do
check_all_sessions
sleep "$CHECK_INTERVAL"
done
}
# === MAIN ===
case "${1:-}" in
--daemon)
run_daemon
;;
--status)
show_status
;;
--session)
if [ -z "${2:-}" ]; then
echo "Usage: pane-watchdog.sh --session SESSION_NAME"
exit 1
fi
MONITOR_SESSIONS="$2"
check_all_sessions
;;
--help|-h)
echo "pane-watchdog.sh — Detect stuck/dead tmux panes and auto-restart"
echo ""
echo "Usage:"
echo " pane-watchdog.sh # One-shot check"
echo " pane-watchdog.sh --daemon # Continuous monitoring"
echo " pane-watchdog.sh --status # Show pane state"
echo " pane-watchdog.sh --session S # Check one session"
echo ""
echo "Config (env vars):"
echo " PANE_CHECK_INTERVAL Seconds between checks (default: 120)"
echo " PANE_WATCHDOG_SESSIONS Comma-separated session names"
echo " PANE_STATE_FILE State file path"
echo " STUCK_CYCLES Unchanged cycles before STUCK (default: 2)"
;;
*)
check_all_sessions
;;
esac

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python3
"""
Soul Eval Gate — The Conscience of the Training Pipeline

View File

@@ -3,7 +3,7 @@
# Uses Hermes CLI plus workforce-manager to triage and review.
# Timmy is the brain. Other agents are the hands.
set -uo pipefail\n\nSCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
set -uo pipefail
LOG_DIR="$HOME/.hermes/logs"
LOG="$LOG_DIR/timmy-orchestrator.log"
@@ -40,7 +40,6 @@ gather_state() {
> "$state_dir/unassigned.txt"
> "$state_dir/open_prs.txt"
> "$state_dir/agent_status.txt"
> "$state_dir/uncommitted_work.txt"
for repo in $REPOS; do
local short=$(echo "$repo" | cut -d/ -f2)
@@ -72,24 +71,6 @@ for p in json.load(sys.stdin):
tail -50 "/tmp/kimi-heartbeat.log" 2>/dev/null | grep -c "FAILED:" | xargs -I{} echo "Kimi recent failures: {}" >> "$state_dir/agent_status.txt"
tail -1 "/tmp/kimi-heartbeat.log" 2>/dev/null | xargs -I{} echo "Kimi last event: {}" >> "$state_dir/agent_status.txt"
# Scan worktrees for uncommitted work
for wt_dir in "$HOME/worktrees"/*/; do
[ -d "$wt_dir" ] || continue
[ -d "$wt_dir/.git" ] || continue
local dirty
dirty=$(cd "$wt_dir" && git status --porcelain 2>/dev/null | wc -l | tr -d " ")
if [ "${dirty:-0}" -gt 0 ]; then
local branch
branch=$(cd "$wt_dir" && git branch --show-current 2>/dev/null || echo "?")
local age=""
local last_commit
last_commit=$(cd "$wt_dir" && git log -1 --format=%ct 2>/dev/null || echo 0)
local now=$(date +%s)
local stale_mins=$(( (now - last_commit) / 60 ))
echo "DIR=$wt_dir BRANCH=$branch DIRTY=$dirty STALE=${stale_mins}m" >> "$state_dir/uncommitted_work.txt"
fi
done
echo "$state_dir"
}
@@ -100,25 +81,6 @@ run_triage() {
log "Cycle: $unassigned_count unassigned, $pr_count open PRs"
# Check for uncommitted work — nag if stale
local uncommitted_count
uncommitted_count=$(wc -l < "$state_dir/uncommitted_work.txt" 2>/dev/null | tr -d " " || echo 0)
if [ "${uncommitted_count:-0}" -gt 0 ]; then
log "WARNING: $uncommitted_count worktree(s) with uncommitted work"
while IFS= read -r line; do
log " UNCOMMITTED: $line"
# Auto-commit stale work (>60 min without commit)
local stale=$(echo "$line" | sed 's/.*STALE=\([0-9]*\)m.*/\1/')
local wt_dir=$(echo "$line" | sed 's/.*DIR=\([^ ]*\) .*/\1/')
if [ "${stale:-0}" -gt 60 ]; then
log " AUTO-COMMITTING stale work in $wt_dir (${stale}m stale)"
(cd "$wt_dir" && git add -A && git commit -m "WIP: orchestrator auto-commit — ${stale}m stale work
Preserved by timmy-orchestrator to prevent loss." 2>/dev/null && git push 2>/dev/null) && log " COMMITTED: $wt_dir" || log " COMMIT FAILED: $wt_dir"
fi
done < "$state_dir/uncommitted_work.txt"
fi
# If nothing to do, skip the LLM call
if [ "$unassigned_count" -eq 0 ] && [ "$pr_count" -eq 0 ]; then
log "Nothing to triage"
@@ -236,12 +198,6 @@ FOOTER
log "=== Timmy Orchestrator Started (PID $$) ==="
log "Cycle: ${CYCLE_INTERVAL}s | Auto-assign: ${AUTO_ASSIGN_UNASSIGNED} | Inference surface: Hermes CLI"
# Start auto-commit-guard daemon for work preservation
if ! pgrep -f "auto-commit-guard.sh" >/dev/null 2>&1; then
nohup bash "$SCRIPT_DIR/auto-commit-guard.sh" 120 >> "$LOG_DIR/auto-commit-guard.log" 2>&1 &
log "Started auto-commit-guard daemon (PID $!)"
fi
WORKFORCE_CYCLE=0
while true; do

View File

@@ -196,37 +196,7 @@
"paused_reason": null,
"skills": [],
"skill": null
},
{
"id": "tmux-supervisor-513",
"name": "Autonomous Cron Supervisor",
"prompt": "Load the tmux-supervisor skill and execute the monitoring protocol.\n\nCheck both `dev` and `timmy` tmux sessions for idle panes. Only send Telegram notifications on actionable events (idle, overflow, failure). Be silent when all agents are working.\n\nSteps:\n1. List all tmux sessions (skip 'Alexander')\n2. For each session, list windows and panes\n3. Capture each pane and classify state (idle vs active)\n4. For idle panes: read context, craft context-aware prompt\n5. Send /queue prompts to idle panes\n6. Verify prompts landed\n7. Only notify via Telegram if:\n - A pane was prompted (idle detected)\n - A pane shows context overflow (>80%)\n - A pane is stuck or crashed\n8. If all panes are active: respond with [SILENT]",
"schedule": {
"kind": "interval",
"minutes": 7,
"display": "every 7m"
},
"schedule_display": "every 7m",
"repeat": {
"times": null,
"completed": 0
},
"enabled": true,
"created_at": "2026-04-15T03:00:00.000000+00:00",
"next_run_at": null,
"last_run_at": null,
"last_status": null,
"last_error": null,
"deliver": "telegram",
"origin": null,
"state": "scheduled",
"paused_at": null,
"paused_reason": null,
"skills": [
"tmux-supervisor"
],
"skill": "tmux-supervisor"
}
],
"updated_at": "2026-04-13T02:00:00+00:00"
}
}

View File

@@ -1,9 +0,0 @@
- name: Nightly Pipeline Scheduler
schedule: '*/30 18-23,0-8 * * *' # Every 30 min, off-peak hours only
tasks:
- name: Check and start pipelines
shell: "bash scripts/nightly-pipeline-scheduler.sh"
env:
PIPELINE_TOKEN_LIMIT: "500000"
PIPELINE_PEAK_START: "9"
PIPELINE_PEAK_END: "18"

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ai.timmy.auto-commit-guard</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/apayne/.hermes/bin/auto-commit-guard.sh</string>
<string>120</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/apayne/.hermes/logs/auto-commit-guard.stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/apayne/.hermes/logs/auto-commit-guard.stderr.log</string>
<key>WorkingDirectory</key>
<string>/Users/apayne</string>
</dict>
</plist>

View File

@@ -1,21 +0,0 @@
# Gitea Accessibility Fix - R4: Time Elements
WCAG 1.3.1: Relative timestamps lack machine-readable fallbacks.
## Fix
Wrap relative timestamps in `<time datetime="...">` elements.
## Files
- `custom/templates/custom/time_relative.tmpl` - Reusable `<time>` helper
- `custom/templates/repo/list_a11y.tmpl` - Explore/Repos list override
## Deploy
```bash
cp -r custom/templates/* /path/to/gitea/custom/templates/
systemctl restart gitea
```
Closes #554

View File

@@ -1,27 +0,0 @@
{{/*
Gitea a11y fix: R4 <time> elements for relative timestamps
Deploy to: custom/templates/custom/time_relative.tmpl
*/}}
{{define "custom/time_relative"}}
{{if and .Time .Relative}}
<time datetime="{{.Time.Format "2006-01-02T15:04:05Z07:00"}}" title="{{.Time.Format "Jan 02, 2006 15:04"}}">
{{.Relative}}
</time>
{{else if .Relative}}
<span>{{.Relative}}</span>
{{end}}
{{end}}
{{define "custom/time_from_unix"}}
{{if .Relative}}
<time datetime="" data-unix="{{.Unix}}" title="">{{.Relative}}</time>
<script>
(function() {
var el = document.currentScript.previousElementSibling;
var unix = parseInt(el.getAttribute('data-unix'));
if (unix) { el.setAttribute('datetime', new Date(unix * 1000).toISOString()); el.setAttribute('title', new Date(unix * 1000).toLocaleString()); }
})();
</script>
{{end}}
{{end}}

View File

@@ -1,27 +0,0 @@
{{/*
Gitea a11y fix: R4 <time> elements for relative timestamps on repo list
Deploy to: custom/templates/repo/list_a11y.tmpl
*/}}
{{/* Star count link with aria-label */}}
<a class="repo-card-star" href="{{.RepoLink}}/stars" aria-label="{{.NumStars}} stars" title="{{.NumStars}} stars">
<svg class="octicon octicon-star" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
<path d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z"/>
</svg>
<span>{{.NumStars}}</span>
</a>
{{/* Fork count link with aria-label */}}
<a class="repo-card-fork" href="{{.RepoLink}}/forks" aria-label="{{.NumForks}} forks" title="{{.NumForks}} forks">
<svg class="octicon octicon-repo-forked" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
<path d="M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-.878a2.25 2.25 0 111.5 0v.878a2.25 2.25 0 01-2.25 2.25h-1.5v2.128a2.251 2.251 0 11-1.5 0V8.5h-1.5A2.25 2.25 0 013.5 6.25v-.878a2.25 2.25 0 111.5 0zM5 3.25a.75.75 0 10-1.5 0 .75.75 0 001.5 0zm6.75.75a.75.75 0 100-1.5.75.75 0 000 1.5zm-3 8.75a.75.75 0 10-1.5 0 .75.75 0 001.5 0z"/>
</svg>
<span>{{.NumForks}}</span>
</a>
{{/* Relative timestamp with <time> element for a11y */}}
{{if .UpdatedUnix}}
<time datetime="{{.UpdatedUnix | TimeSinceISO}}" title="{{.UpdatedUnix | DateFmtLong}}" class="text-light">
{{.UpdatedUnix | TimeSince}}
</time>
{{end}}

View File

@@ -1,150 +0,0 @@
# Visual Accessibility Audit — Foundation Web Properties
**Issue:** timmy-config #492
**Date:** 2026-04-13
**Label:** gemma-4-multimodal
**Scope:** forge.alexanderwhitestone.com (Gitea 1.25.4)
## Executive Summary
The Foundation's primary accessible web property is the Gitea forge. The Matrix homeserver (matrix.timmy.foundation) is currently unreachable (DNS/SSL issues). This audit covers the forge across three page types: Homepage, Login, and Explore/Repositories.
**Overall: 6 WCAG 2.1 AA violations found, 4 best-practice recommendations.**
---
## Pages Audited
| Page | URL | Status |
|------|-----|--------|
| Homepage | forge.alexanderwhitestone.com | Live |
| Sign In | forge.alexanderwhitestone.com/user/login | Live |
| Explore Repos | forge.alexanderwhitestone.com/explore/repos | Live |
| Matrix/Element | matrix.timmy.foundation | DOWN (DNS/SSL) |
---
## Findings
### P1 — Violations (WCAG 2.1 AA)
#### V1: No Skip Navigation Link (2.4.1)
- **Pages:** All
- **Severity:** Medium
- **Description:** No "Skip to content" link exists. Keyboard users must tab through the full navigation on every page load.
- **Evidence:** Programmatic check returned `skipNav: false`
- **Fix:** Add `<a href="#main" class="skip-link">Skip to content</a>` visually hidden until focused.
#### V2: 25 Form Inputs Without Labels (1.3.1, 3.3.2)
- **Pages:** Explore/Repositories (filter dropdowns)
- **Severity:** High
- **Description:** The search input and all radio buttons in the Filter/Sort dropdowns lack programmatic label associations.
- **Evidence:** Programmatic check found 25 inputs without `label[for=]`, `aria-label`, or `aria-labelledby`
- **Affected inputs:** `q` (search), `archived` (x2), `fork` (x2), `mirror` (x2), `template` (x2), `private` (x2), `sort` (x12), `clear-filter` (x1)
- **Fix:** Add `aria-label="Search repositories"` to search input. Add `aria-label` to each radio button group and individual options.
#### V3: Low-Contrast Footer Text (1.4.3)
- **Pages:** All
- **Severity:** Medium
- **Description:** Footer text (version, page render time) appears light gray on white, likely failing the 4.5:1 contrast ratio.
- **Evidence:** 30 elements flagged as potential low-contrast suspects.
- **Fix:** Darken footer text to at least `#767676` on white (4.54:1 ratio).
#### V4: Green Link Color Fails Contrast (1.4.3)
- **Pages:** Homepage
- **Severity:** Medium
- **Description:** Inline links use medium-green (~#609926) on white. This shade typically fails 4.5:1 for normal body text.
- **Evidence:** Visual analysis identified green links ("run the binary", "Docker", "contributing") as potentially failing.
- **Fix:** Darken link color to at least `#507020` or add an underline for non-color differentiation (SC 1.4.1).
#### V5: Missing Header/Banner Landmark (1.3.1)
- **Pages:** All
- **Severity:** Low
- **Description:** No `<header>` or `role="banner"` element found. The navigation bar is a `<nav>` but not wrapped in a banner landmark.
- **Evidence:** `landmarks.banner: 0`
- **Fix:** Wrap the top navigation in `<header>` or add `role="banner"`.
#### V6: Heading Hierarchy Issue (1.3.1)
- **Pages:** Login
- **Severity:** Low
- **Description:** The Sign In heading is `<h4>` rather than `<h1>`, breaking the heading hierarchy. The page has no `<h1>`.
- **Evidence:** Accessibility tree shows `heading "Sign In" [level=4]`
- **Fix:** Use `<h1>` for "Sign In" on the login page.
---
### P2 — Best Practice Recommendations
#### R1: Add Password Visibility Toggle
- **Page:** Login
- **Description:** No show/hide toggle on the password field. This helps users with cognitive or motor impairments verify input.
#### R2: Add `aria-required` to Required Fields
- **Page:** Login
- **Evidence:** `inputsWithAriaRequired: 0` (no inputs marked as required)
- **Description:** The username field shows a red asterisk but has no `required` or `aria-required="true"` attribute.
#### R3: Improve Star/Fork Link Labels
- **Page:** Explore Repos
- **Description:** Star and fork counts are bare numbers (e.g., "0", "2"). Screen readers announce these without context.
- **Fix:** Add `aria-label="2 stars"` / `aria-label="0 forks"` to count links.
#### R4: Use `<time>` Elements for Timestamps
- **Page:** Explore Repos
- **Description:** Relative timestamps ("2 minutes ago") are human-readable but lack machine-readable fallbacks.
- **Fix:** Wrap in `<time datetime="2026-04-13T17:00:00Z">2 minutes ago</time>`.
---
## What's Working Well
- **Color contrast (primary):** Black text on white backgrounds — excellent 21:1 ratio.
- **Heading structure (homepage):** Clean h1 > h2 > h3 hierarchy.
- **Landmark regions:** `<main>` and `<nav>` landmarks present.
- **Language attribute:** `lang="en-US"` set on `<html>`.
- **Link text:** Descriptive — no "click here" or "read more" patterns found.
- **Form layout:** Login form uses clean single-column with good spacing.
- **Submit button:** Full-width, good contrast, large touch target.
- **Navigation:** Simple, consistent across pages.
---
## Out of Scope
- **matrix.timmy.foundation:** Unreachable (DNS resolution failure / SSL cert mismatch). Should be re-audited when operational.
- **Evennia web client (localhost:4001):** Local-only, not publicly accessible.
- **WCAG AAA criteria:** This audit covers AA only.
---
## Remediation Priority
| Priority | Issue | Effort |
|----------|-------|--------|
| P1 | V2: 25 unlabeled inputs | Medium |
| P1 | V1: Skip nav link | Small |
| P1 | V4: Green link contrast | Small |
| P1 | V3: Footer text contrast | Small |
| P2 | V6: Heading hierarchy | Small |
| P2 | V5: Banner landmark | Small |
| P2 | R1-R4: Best practices | Small |
---
## Automated Check Results
```
skipNav: false
headings: h1(3), h4(1)
imgsNoAlt: 0 / 1
inputsNoLabel: 25
genericLinks: 0
lowContrastSuspects: 30
inputsWithAriaRequired: 0
landmarks: main=1, nav=2, banner=0, contentinfo=2
hasLang: true (en-US)
```
---
*Generated via visual + programmatic analysis of forge.alexanderwhitestone.com*

View File

@@ -1,179 +0,0 @@
# 3D World Glitch Detection — Matrix Scanner
**Reference:** timmy-config#491
**Label:** gemma-4-multimodal
**Version:** 0.1.0
## Overview
The Matrix Glitch Detector scans 3D web worlds for visual artifacts and
rendering anomalies. It uses browser automation to capture screenshots from
multiple camera angles, then sends them to a vision AI model for analysis
against a library of known glitch patterns.
## Detected Glitch Categories
| Category | Severity | Description |
|---|---|---|
| Floating Assets | HIGH | Objects not grounded — hovering above surfaces |
| Z-Fighting | MEDIUM | Coplanar surfaces flickering/competing for depth |
| Missing Textures | CRITICAL | Placeholder colors (magenta, checkerboard) |
| Clipping | HIGH | Geometry passing through other objects |
| Broken Normals | MEDIUM | Inside-out or incorrectly lit surfaces |
| Shadow Artifacts | LOW | Detached, mismatched, or acne shadows |
| LOD Popping | LOW | Abrupt level-of-detail transitions |
| Lightmap Errors | MEDIUM | Dark splotches, light leaks, baking failures |
| Water/Reflection | MEDIUM | Incorrect environment reflections |
| Skybox Seam | LOW | Visible seams at cubemap face edges |
## Installation
No external dependencies required — pure Python 3.10+.
```bash
# Clone the repo
git clone https://forge.alexanderwhitestone.com/Timmy_Foundation/timmy-config.git
cd timmy-config
```
## Usage
### Basic Scan
```bash
python bin/matrix_glitch_detector.py https://matrix.example.com/world/alpha
```
### Multi-Angle Scan
```bash
python bin/matrix_glitch_detector.py https://matrix.example.com/world/alpha \
--angles 8 \
--output glitch_report.json
```
### Demo Mode
```bash
python bin/matrix_glitch_detector.py --demo
```
### Options
| Flag | Default | Description |
|---|---|---|
| `url` | (required) | URL of the 3D world to scan |
| `--angles N` | 4 | Number of camera angles to capture |
| `--output PATH` | stdout | Output file for JSON report |
| `--min-severity` | info | Minimum severity: info/low/medium/high/critical |
| `--demo` | off | Run with simulated detections |
| `--verbose` | off | Enable verbose output |
## Report Format
The JSON report includes:
```json
{
"scan_id": "uuid",
"url": "https://...",
"timestamp": "ISO-8601",
"total_screenshots": 4,
"angles_captured": ["front", "right", "back", "left"],
"glitches": [
{
"id": "short-uuid",
"category": "floating_assets",
"name": "Floating Chair",
"description": "Office chair floating 0.3m above floor",
"severity": "high",
"confidence": 0.87,
"location_x": 35.2,
"location_y": 62.1,
"screenshot_index": 0,
"screenshot_angle": "front",
"timestamp": "ISO-8601"
}
],
"summary": {
"total_glitches": 4,
"by_severity": {"critical": 1, "high": 2, "medium": 1},
"by_category": {"floating_assets": 1, "missing_textures": 1, ...},
"highest_severity": "critical",
"clean_screenshots": 0
},
"metadata": {
"detector_version": "0.1.0",
"pattern_count": 10,
"reference": "timmy-config#491"
}
}
```
## Vision AI Integration
The detector supports any OpenAI-compatible vision API. Set these
environment variables:
```bash
export VISION_API_KEY="your-api-key"
export VISION_API_BASE="https://api.openai.com/v1" # optional
export VISION_MODEL="gpt-4o" # optional, default: gpt-4o
```
For browser-based capture with `browser_vision`:
```bash
export BROWSER_VISION_SCRIPT="/path/to/browser_vision.py"
```
## Glitch Patterns
Pattern definitions live in `bin/glitch_patterns.py`. Each pattern includes:
- **category** — Enum matching the glitch type
- **detection_prompts** — Instructions for the vision model
- **visual_indicators** — What to look for in screenshots
- **confidence_threshold** — Minimum confidence to report
### Adding Custom Patterns
```python
from glitch_patterns import GlitchPattern, GlitchCategory, GlitchSeverity
custom = GlitchPattern(
category=GlitchCategory.FLOATING_ASSETS,
name="Custom Glitch",
description="Your description",
severity=GlitchSeverity.MEDIUM,
detection_prompts=["Look for..."],
visual_indicators=["indicator 1", "indicator 2"],
)
```
## Testing
```bash
python -m pytest tests/test_glitch_detector.py -v
# or
python tests/test_glitch_detector.py
```
## Architecture
```
bin/
matrix_glitch_detector.py — Main CLI entry point
glitch_patterns.py — Pattern definitions and prompt builder
tests/
test_glitch_detector.py — Unit and integration tests
docs/
glitch-detection.md — This documentation
```
## Limitations
- Browser automation requires a headless browser environment
- Vision AI analysis depends on model availability and API limits
- Placeholder screenshots are generated when browser capture is unavailable
- Detection accuracy varies by scene complexity and lighting conditions

View File

@@ -1,151 +0,0 @@
// a11y-check.js — Automated accessibility audit script for Foundation web properties
// Run in browser console or via Playwright/Puppeteer
//
// Usage: Paste into DevTools console, or include in automated test suite.
// Returns a JSON object with pass/fail for WCAG 2.1 AA checks.
(function a11yAudit() {
const results = {
timestamp: new Date().toISOString(),
url: window.location.href,
title: document.title,
violations: [],
passes: [],
warnings: []
};
// --- 2.4.1 Skip Navigation ---
const skipLink = document.querySelector('a[href="#main"], a[href="#content"], .skip-nav, .skip-link');
if (skipLink) {
results.passes.push({ rule: '2.4.1', name: 'Skip Navigation', detail: 'Skip link found' });
} else {
results.violations.push({ rule: '2.4.1', name: 'Skip Navigation', severity: 'medium', detail: 'No skip-to-content link found' });
}
// --- 1.3.1 / 3.3.2 Form Labels ---
const unlabeledInputs = Array.from(document.querySelectorAll('input, select, textarea')).filter(el => {
if (el.type === 'hidden') return false;
const id = el.id;
const hasLabel = id && document.querySelector(`label[for="${id}"]`);
const hasAriaLabel = el.getAttribute('aria-label') || el.getAttribute('aria-labelledby');
const hasTitle = el.getAttribute('title');
const hasPlaceholder = el.getAttribute('placeholder'); // placeholder alone is NOT sufficient
return !hasLabel && !hasAriaLabel && !hasTitle;
});
if (unlabeledInputs.length === 0) {
results.passes.push({ rule: '3.3.2', name: 'Form Labels', detail: 'All inputs have labels' });
} else {
results.violations.push({
rule: '3.3.2',
name: 'Form Labels',
severity: 'high',
detail: `${unlabeledInputs.length} inputs without programmatic labels`,
elements: unlabeledInputs.map(el => ({ tag: el.tagName, type: el.type, name: el.name, id: el.id }))
});
}
// --- 1.4.3 Contrast (heuristic: very light text colors) ---
const lowContrast = Array.from(document.querySelectorAll('p, span, a, li, td, th, label, small, footer *')).filter(el => {
const style = getComputedStyle(el);
const color = style.color;
// Check for very light RGB values (r/g/b < 120)
const match = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (!match) return false;
const [, r, g, b] = match.map(Number);
return r < 120 && g < 120 && b < 120 && (r + g + b) < 200;
});
if (lowContrast.length === 0) {
results.passes.push({ rule: '1.4.3', name: 'Contrast', detail: 'No obviously low-contrast text found' });
} else {
results.warnings.push({ rule: '1.4.3', name: 'Contrast', detail: `${lowContrast.length} elements with potentially low contrast (manual verification needed)` });
}
// --- 1.3.1 Heading Hierarchy ---
const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6')).map(h => ({
level: parseInt(h.tagName[1]),
text: h.textContent.trim().substring(0, 80)
}));
let headingIssues = [];
let lastLevel = 0;
for (const h of headings) {
if (h.level > lastLevel + 1 && lastLevel > 0) {
headingIssues.push(`Skipped h${lastLevel} to h${h.level}: "${h.text}"`);
}
lastLevel = h.level;
}
if (headingIssues.length === 0 && headings.length > 0) {
results.passes.push({ rule: '1.3.1', name: 'Heading Hierarchy', detail: `${headings.length} headings, proper nesting` });
} else if (headingIssues.length > 0) {
results.violations.push({ rule: '1.3.1', name: 'Heading Hierarchy', severity: 'low', detail: headingIssues.join('; ') });
}
// --- 1.3.1 Landmarks ---
const landmarks = {
main: document.querySelectorAll('main, [role="main"]').length,
nav: document.querySelectorAll('nav, [role="navigation"]').length,
banner: document.querySelectorAll('header, [role="banner"]').length,
contentinfo: document.querySelectorAll('footer, [role="contentinfo"]').length
};
if (landmarks.main > 0) {
results.passes.push({ rule: '1.3.1', name: 'Main Landmark', detail: 'Found' });
} else {
results.violations.push({ rule: '1.3.1', name: 'Main Landmark', severity: 'medium', detail: 'No <main> or role="main" found' });
}
if (landmarks.banner === 0) {
results.violations.push({ rule: '1.3.1', name: 'Banner Landmark', severity: 'low', detail: 'No <header> or role="banner" found' });
}
// --- 3.3.1 Required Fields ---
const requiredInputs = document.querySelectorAll('input[required], input[aria-required="true"]');
if (requiredInputs.length > 0) {
results.passes.push({ rule: '3.3.1', name: 'Required Fields', detail: `${requiredInputs.length} inputs marked as required` });
} else {
const visualRequired = document.querySelector('.required, [class*="required"], label .text-danger');
if (visualRequired) {
results.warnings.push({ rule: '3.3.1', name: 'Required Fields', detail: 'Visual indicators found but no aria-required attributes' });
}
}
// --- 2.4.2 Page Title ---
if (document.title && document.title.trim().length > 0) {
results.passes.push({ rule: '2.4.2', name: 'Page Title', detail: document.title });
} else {
results.violations.push({ rule: '2.4.2', name: 'Page Title', severity: 'medium', detail: 'Page has no title' });
}
// --- 3.1.1 Language ---
const lang = document.documentElement.lang;
if (lang) {
results.passes.push({ rule: '3.1.1', name: 'Language', detail: lang });
} else {
results.violations.push({ rule: '3.1.1', name: 'Language', severity: 'medium', detail: 'No lang attribute on <html>' });
}
// --- Images without alt ---
const imgsNoAlt = Array.from(document.querySelectorAll('img:not([alt])'));
if (imgsNoAlt.length === 0) {
results.passes.push({ rule: '1.1.1', name: 'Image Alt Text', detail: 'All images have alt attributes' });
} else {
results.violations.push({ rule: '1.1.1', name: 'Image Alt Text', severity: 'high', detail: `${imgsNoAlt.length} images without alt attributes` });
}
// --- Buttons without accessible names ---
const emptyButtons = Array.from(document.querySelectorAll('button')).filter(b => {
return !b.textContent.trim() && !b.getAttribute('aria-label') && !b.getAttribute('aria-labelledby') && !b.getAttribute('title');
});
if (emptyButtons.length === 0) {
results.passes.push({ rule: '4.1.2', name: 'Button Names', detail: 'All buttons have accessible names' });
} else {
results.violations.push({ rule: '4.1.2', name: 'Button Names', severity: 'medium', detail: `${emptyButtons.length} buttons without accessible names` });
}
// Summary
results.summary = {
violations: results.violations.length,
passes: results.passes.length,
warnings: results.warnings.length
};
console.log(JSON.stringify(results, null, 2));
return results;
})();

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python3
import json
from hermes_tools import browser_navigate, browser_vision

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python3
import json
from hermes_tools import browser_navigate, browser_vision

View File

@@ -1,884 +1,12 @@
#!/usr/bin/env python3
"""
foundation_accessibility_audit.py — Multimodal Visual Accessibility Audit.
Analyzes web pages for WCAG 2.1 AA compliance using both programmatic checks
and vision model analysis. Screenshots pages, checks contrast ratios, detects
layout issues, validates alt text, and produces structured audit reports.
Usage:
# Audit a single page
python scripts/foundation_accessibility_audit.py --url https://timmyfoundation.org
# Audit multiple pages
python scripts/foundation_accessibility_audit.py --url https://timmyfoundation.org --pages /about /donate /blog
# With vision model analysis (Gemma 3)
python scripts/foundation_accessibility_audit.py --url https://timmyfoundation.org --vision
# Programmatic-only (no vision model needed)
python scripts/foundation_accessibility_audit.py --url https://timmyfoundation.org --programmatic
# Output as text report
python scripts/foundation_accessibility_audit.py --url https://timmyfoundation.org --format text
WCAG 2.1 AA Checks:
1.4.3 Contrast (Minimum) — text vs background ratio >= 4.5:1
1.4.6 Contrast (Enhanced) — ratio >= 7:1 for AAA
1.4.11 Non-text Contrast — UI components >= 3:1
1.3.1 Info and Relationships — heading hierarchy, landmarks
1.1.1 Non-text Content — alt text on images
2.4.1 Bypass Blocks — skip navigation link
2.4.2 Page Titled — meaningful <title>
2.4.6 Headings and Labels — descriptive headings
4.1.2 Name, Role, Value — ARIA labels on interactive elements
Refs: timmy-config#492, WCAG 2.1 AA
"""
from __future__ import annotations
import argparse
import base64
import colorsys
import json
import os
import re
import subprocess
import sys
import tempfile
import urllib.error
import urllib.request
from dataclasses import dataclass, field, asdict
from enum import Enum
from pathlib import Path
from typing import Optional
from html.parser import HTMLParser
from hermes_tools import browser_navigate, browser_vision
# === Configuration ===
OLLAMA_BASE = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434")
VISION_MODEL = os.environ.get("VISUAL_REVIEW_MODEL", "gemma3:12b")
DEFAULT_PAGES = ["/", "/about", "/donate", "/blog", "/contact"]
class Severity(str, Enum):
CRITICAL = "critical" # Blocks access entirely
MAJOR = "major" # Significant barrier
MINOR = "minor" # Inconvenience
PASS = "pass"
@dataclass
class A11yViolation:
"""A single accessibility violation."""
criterion: str # WCAG criterion (e.g. "1.4.3")
criterion_name: str # Human-readable name
severity: Severity = Severity.MINOR
element: str = "" # CSS selector or element description
description: str = "" # What's wrong
fix: str = "" # Suggested fix
source: str = "" # "programmatic" or "vision"
@dataclass
class A11yPageResult:
"""Audit result for a single page."""
url: str = ""
title: str = ""
score: int = 100
violations: list[A11yViolation] = field(default_factory=list)
passed_checks: list[str] = field(default_factory=list)
summary: str = ""
@dataclass
class A11yAuditReport:
"""Complete audit report across all pages."""
site: str = ""
pages_audited: int = 0
overall_score: int = 100
total_violations: int = 0
critical_violations: int = 0
major_violations: int = 0
page_results: list[A11yPageResult] = field(default_factory=list)
summary: str = ""
# === HTML Parser for Programmatic Checks ===
class A11yHTMLParser(HTMLParser):
"""Extract accessibility-relevant elements from HTML."""
def __init__(self):
super().__init__()
self.title = ""
self.images = [] # [{"src": ..., "alt": ...}]
self.headings = [] # [{"level": int, "text": ...}]
self.links = [] # [{"text": ..., "href": ...}]
self.inputs = [] # [{"type": ..., "label": ..., "id": ...}]
self.landmarks = [] # [{"tag": ..., "role": ...}]
self.skip_nav = False
self.lang = ""
self.in_title = False
self.in_heading = False
self.heading_level = 0
self.heading_text = ""
self.current_text = ""
def handle_starttag(self, tag, attrs):
attr_dict = dict(attrs)
if tag == "title":
self.in_title = True
elif tag == "html":
self.lang = attr_dict.get("lang", "")
elif tag in ("h1", "h2", "h3", "h4", "h5", "h6"):
self.in_heading = True
self.heading_level = int(tag[1])
self.heading_text = ""
elif tag == "img":
self.images.append({
"src": attr_dict.get("src", ""),
"alt": attr_dict.get("alt"),
"role": attr_dict.get("role", ""),
})
elif tag == "a":
self.links.append({
"href": attr_dict.get("href", ""),
"text": "",
"aria_label": attr_dict.get("aria-label", ""),
})
elif tag in ("input", "select", "textarea"):
self.inputs.append({
"tag": tag,
"type": attr_dict.get("type", "text"),
"id": attr_dict.get("id", ""),
"aria_label": attr_dict.get("aria-label", ""),
"aria_labelledby": attr_dict.get("aria-labelledby", ""),
})
elif tag in ("main", "nav", "header", "footer", "aside", "section", "form"):
self.landmarks.append({"tag": tag, "role": attr_dict.get("role", "")})
elif tag == "a" and ("skip" in attr_dict.get("href", "").lower() or
"skip" in attr_dict.get("class", "").lower()):
self.skip_nav = True
role = attr_dict.get("role", "")
if role in ("navigation", "main", "banner", "contentinfo", "complementary", "search"):
self.landmarks.append({"tag": tag, "role": role})
if role == "link" and "skip" in (attr_dict.get("aria-label", "") + attr_dict.get("href", "")).lower():
self.skip_nav = True
def handle_endtag(self, tag):
if tag == "title":
self.in_title = False
elif tag in ("h1", "h2", "h3", "h4", "h5", "h6"):
self.headings.append({"level": self.heading_level, "text": self.heading_text.strip()})
self.in_heading = False
elif tag == "a" and self.links:
self.links[-1]["text"] = self.current_text.strip()
self.current_text = ""
def handle_data(self, data):
if self.in_title:
self.title += data
if self.in_heading:
self.heading_text += data
self.current_text += data
# === Color/Contrast Utilities ===
def parse_color(color_str: str) -> Optional[tuple]:
"""Parse CSS color string to (r, g, b) tuple (0-255)."""
if not color_str:
return None
color_str = color_str.strip().lower()
# Named colors (subset)
named = {
"white": (255, 255, 255), "black": (0, 0, 0),
"red": (255, 0, 0), "green": (0, 128, 0), "blue": (0, 0, 255),
"gray": (128, 128, 128), "grey": (128, 128, 128),
"silver": (192, 192, 192), "yellow": (255, 255, 0),
"orange": (255, 165, 0), "purple": (128, 0, 128),
"transparent": None,
}
if color_str in named:
return named[color_str]
# #RRGGBB or #RGB
if color_str.startswith("#"):
hex_str = color_str[1:]
if len(hex_str) == 3:
hex_str = "".join(c * 2 for c in hex_str)
if len(hex_str) == 6:
try:
return tuple(int(hex_str[i:i+2], 16) for i in (0, 2, 4))
except ValueError:
return None
# rgb(r, g, b)
match = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)", color_str)
if match:
return tuple(int(match.group(i)) for i in (1, 2, 3))
# rgba(r, g, b, a)
match = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*[\d.]+\s*\)", color_str)
if match:
return tuple(int(match.group(i)) for i in (1, 2, 3))
return None
def relative_luminance(rgb: tuple) -> float:
"""Calculate relative luminance per WCAG 2.1 (sRGB)."""
def linearize(c):
c = c / 255.0
return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
r, g, b = [linearize(c) for c in rgb]
return 0.2126 * r + 0.7152 * g + 0.0722 * b
def contrast_ratio(color1: tuple, color2: tuple) -> float:
"""Calculate contrast ratio between two colors per WCAG 2.1."""
l1 = relative_luminance(color1)
l2 = relative_luminance(color2)
lighter = max(l1, l2)
darker = min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
# === Programmatic Checks ===
def check_page_title(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 2.4.2 — Page Titled."""
violations = []
title = parser.title.strip()
if not title:
violations.append(A11yViolation(
criterion="2.4.2", criterion_name="Page Titled",
severity=Severity.MAJOR,
element="<title>",
description="Page has no title or title is empty.",
fix="Add a meaningful <title> that describes the page purpose.",
source="programmatic"
))
elif len(title) < 5:
violations.append(A11yViolation(
criterion="2.4.2", criterion_name="Page Titled",
severity=Severity.MINOR,
element=f"<title>{title}</title>",
description=f"Page title is very short: '{title}'",
fix="Use a more descriptive title.",
source="programmatic"
))
return violations
def check_lang_attribute(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 3.1.1 — Language of Page."""
violations = []
if not parser.lang:
violations.append(A11yViolation(
criterion="3.1.1", criterion_name="Language of Page",
severity=Severity.MAJOR,
element="<html>",
description="Missing lang attribute on <html> element.",
fix="Add lang=\"en\" (or appropriate language code) to <html>.",
source="programmatic"
))
return violations
def check_images_alt_text(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 1.1.1 — Non-text Content."""
violations = []
for img in parser.images:
if img.get("role") == "presentation" or img.get("role") == "none":
continue # Decorative images are exempt
alt = img.get("alt")
src = img.get("src", "unknown")
if alt is None:
violations.append(A11yViolation(
criterion="1.1.1", criterion_name="Non-text Content",
severity=Severity.CRITICAL,
element=f"<img src=\"{src[:80]}\">",
description="Image missing alt attribute.",
fix="Add descriptive alt text, or alt=\"\" with role=\"presentation\" for decorative images.",
source="programmatic"
))
elif alt.strip() == "":
# Empty alt is OK only for decorative images
if img.get("role") not in ("presentation", "none"):
violations.append(A11yViolation(
criterion="1.1.1", criterion_name="Non-text Content",
severity=Severity.MINOR,
element=f"<img src=\"{src[:80]}\" alt=\"\">",
description="Empty alt text — ensure this image is decorative.",
fix="If decorative, add role=\"presentation\". If meaningful, add descriptive alt text.",
source="programmatic"
))
return violations
def check_heading_hierarchy(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 1.3.1 — Info and Relationships (heading hierarchy)."""
violations = []
if not parser.headings:
violations.append(A11yViolation(
criterion="1.3.1", criterion_name="Info and Relationships",
severity=Severity.MAJOR,
element="document",
description="No headings found on page.",
fix="Add proper heading hierarchy starting with <h1>.",
source="programmatic"
))
return violations
# Check for H1
h1s = [h for h in parser.headings if h["level"] == 1]
if not h1s:
violations.append(A11yViolation(
criterion="1.3.1", criterion_name="Info and Relationships",
severity=Severity.MAJOR,
element="document",
description="No <h1> heading found.",
fix="Add a single <h1> as the main page heading.",
source="programmatic"
))
elif len(h1s) > 1:
violations.append(A11yViolation(
criterion="1.3.1", criterion_name="Info and Relationships",
severity=Severity.MINOR,
element="document",
description=f"Multiple <h1> headings found ({len(h1s)}).",
fix="Use a single <h1> per page for the main heading.",
source="programmatic"
))
# Check hierarchy skips
prev_level = 0
for h in parser.headings:
level = h["level"]
if level > prev_level + 1 and prev_level > 0:
violations.append(A11yViolation(
criterion="1.3.1", criterion_name="Info and Relationships",
severity=Severity.MINOR,
element=f"<h{level}>{h['text'][:50]}</h{level}>",
description=f"Heading level skipped: h{prev_level} → h{level}",
fix=f"Use <h{prev_level + 1}> instead, or fill the gap.",
source="programmatic"
))
prev_level = level
return violations
def check_landmarks(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 1.3.1 — Landmarks and structure."""
violations = []
roles = {lm.get("role", "") for lm in parser.landmarks}
tags = {lm.get("tag", "") for lm in parser.landmarks}
has_main = "main" in roles or "main" in tags
has_nav = "navigation" in roles or "nav" in tags
if not has_main:
violations.append(A11yViolation(
criterion="1.3.1", criterion_name="Info and Relationships",
severity=Severity.MAJOR,
element="document",
description="No <main> landmark found.",
fix="Wrap the main content in a <main> element.",
source="programmatic"
))
if not has_nav:
violations.append(A11yViolation(
criterion="1.3.1", criterion_name="Info and Relationships",
severity=Severity.MINOR,
element="document",
description="No <nav> landmark found.",
fix="Wrap navigation in a <nav> element.",
source="programmatic"
))
return violations
def check_skip_nav(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 2.4.1 — Bypass Blocks."""
violations = []
if not parser.skip_nav:
# Also check links for "skip" text
has_skip_link = any("skip" in l.get("text", "").lower() for l in parser.links)
if not has_skip_link:
violations.append(A11yViolation(
criterion="2.4.1", criterion_name="Bypass Blocks",
severity=Severity.MAJOR,
element="document",
description="No skip navigation link found.",
fix="Add a 'Skip to main content' link as the first focusable element.",
source="programmatic"
))
return violations
def check_form_labels(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 4.1.2 — Name, Role, Value (form inputs)."""
violations = []
for inp in parser.inputs:
if inp["type"] in ("hidden", "submit", "button", "reset", "image"):
continue
has_label = bool(inp.get("aria_label") or inp.get("aria_labelledby") or inp.get("id"))
if not has_label:
violations.append(A11yViolation(
criterion="4.1.2", criterion_name="Name, Role, Value",
severity=Severity.MAJOR,
element=f"<{inp['tag']} type=\"{inp['type']}\">",
description="Form input has no associated label or aria-label.",
fix="Add a <label for=\"...\"> or aria-label attribute.",
source="programmatic"
))
return violations
def check_link_text(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 2.4.4 — Link Purpose."""
violations = []
for link in parser.links:
text = (link.get("text", "") or link.get("aria_label", "")).strip().lower()
href = link.get("href", "")
if not text:
violations.append(A11yViolation(
criterion="2.4.4", criterion_name="Link Purpose",
severity=Severity.MAJOR,
element=f"<a href=\"{href[:60]}\">",
description="Link has no accessible text.",
fix="Add visible text content or aria-label to the link.",
source="programmatic"
))
elif text in ("click here", "read more", "here", "more", "link"):
violations.append(A11yViolation(
criterion="2.4.4", criterion_name="Link Purpose",
severity=Severity.MINOR,
element=f"<a href=\"{href[:60]}\">{text}</a>",
description=f"Non-descriptive link text: '{text}'",
fix="Use descriptive text that explains the link destination.",
source="programmatic"
))
return violations
def run_programmatic_checks(html: str) -> list[A11yViolation]:
"""Run all programmatic accessibility checks on HTML content."""
parser = A11yHTMLParser()
try:
parser.feed(html)
except Exception:
pass
violations = []
violations.extend(check_page_title(parser))
violations.extend(check_lang_attribute(parser))
violations.extend(check_images_alt_text(parser))
violations.extend(check_heading_hierarchy(parser))
violations.extend(check_landmarks(parser))
violations.extend(check_skip_nav(parser))
violations.extend(check_form_labels(parser))
violations.extend(check_link_text(parser))
return violations
# === Vision Model Checks ===
A11Y_VISION_PROMPT = """You are a WCAG 2.1 AA accessibility auditor. Analyze this screenshot of a web page.
Check for these specific issues:
1. COLOR CONTRAST: Are text colors sufficiently different from their backgrounds?
- Normal text needs 4.5:1 contrast ratio
- Large text (18pt+) needs 3:1
- UI components need 3:1
List any text or UI elements where contrast looks insufficient.
2. FONT LEGIBILITY: Is text readable?
- Font size >= 12px for body text
- Line height >= 1.5 for body text
- No text in images (should be real text)
3. LAYOUT ISSUES: Is the layout accessible?
- Touch targets >= 44x44px
- Content not cut off or overlapping
- Logical reading order visible
- No horizontal scrolling at standard widths
4. FOCUS INDICATORS: Can you see which element has focus?
- Interactive elements should have visible focus rings
5. COLOR ALONE: Is information conveyed only by color?
- Errors/warnings should not rely solely on red/green
Respond as JSON:
{
"violations": [
{
"criterion": "1.4.3",
"criterion_name": "Contrast (Minimum)",
"severity": "critical|major|minor",
"element": "description of element",
"description": "what's wrong",
"fix": "how to fix"
}
],
"passed_checks": ["list of things that look good"],
"overall_score": 0-100,
"summary": "brief summary"
}"""
def run_vision_check(screenshot_path: str, model: str = VISION_MODEL) -> list[A11yViolation]:
"""Run vision model accessibility check on a screenshot."""
try:
b64 = base64.b64encode(Path(screenshot_path).read_bytes()).decode()
payload = json.dumps({
"model": model,
"messages": [{"role": "user", "content": [
{"type": "text", "text": A11Y_VISION_PROMPT},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}}
]}],
"stream": False,
"options": {"temperature": 0.1}
}).encode()
req = urllib.request.Request(
f"{OLLAMA_BASE}/api/chat",
data=payload,
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=120) as resp:
result = json.loads(resp.read())
content = result.get("message", {}).get("content", "")
# Parse response
parsed = _parse_json_response(content)
violations = []
for v in parsed.get("violations", []):
violations.append(A11yViolation(
criterion=v.get("criterion", ""),
criterion_name=v.get("criterion_name", ""),
severity=Severity(v.get("severity", "minor")),
element=v.get("element", ""),
description=v.get("description", ""),
fix=v.get("fix", ""),
source="vision"
))
return violations
except Exception as e:
print(f" Vision check failed: {e}", file=sys.stderr)
return []
def _parse_json_response(text: str) -> dict:
"""Extract JSON from potentially messy vision response."""
cleaned = text.strip()
if cleaned.startswith("```"):
lines = cleaned.split("\n")[1:]
if lines and lines[-1].strip() == "```":
lines = lines[:-1]
cleaned = "\n".join(lines)
try:
return json.loads(cleaned)
except json.JSONDecodeError:
start = cleaned.find("{")
end = cleaned.rfind("}")
if start >= 0 and end > start:
try:
return json.loads(cleaned[start:end + 1])
except json.JSONDecodeError:
pass
return {}
# === Page Fetching ===
def fetch_page(url: str) -> Optional[str]:
"""Fetch HTML content of a page."""
try:
req = urllib.request.Request(url, headers={"User-Agent": "A11yAudit/1.0"})
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read().decode("utf-8", errors="replace")
except Exception as e:
print(f" Failed to fetch {url}: {e}", file=sys.stderr)
return None
def take_screenshot(url: str, output_path: str, width: int = 1280, height: int = 900) -> bool:
"""Take a screenshot using Playwright or curl-based headless capture."""
# Try Playwright first
try:
script = f"""
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={{"width": {width}, "height": {height}}})
page.goto("{url}", wait_until="networkidle", timeout=30000)
page.screenshot(path="{output_path}", full_page=True)
browser.close()
"""
result = subprocess.run(
["python3", "-c", script],
capture_output=True, text=True, timeout=60
)
if result.returncode == 0 and Path(output_path).exists():
return True
except Exception:
pass
# Try curl + wkhtmltoimage
try:
result = subprocess.run(
["wkhtmltoimage", "--width", str(width), "--quality", "90", url, output_path],
capture_output=True, text=True, timeout=30
)
if result.returncode == 0 and Path(output_path).exists():
return True
except Exception:
pass
return False
# === Audit Logic ===
def audit_page(url: str, use_vision: bool = False, model: str = VISION_MODEL) -> A11yPageResult:
"""Run a full accessibility audit on a single page."""
result = A11yPageResult(url=url)
# Fetch HTML
html = fetch_page(url)
if not html:
result.summary = f"Failed to fetch {url}"
result.score = 0
return result
# Extract title
title_match = re.search(r"<title[^>]*>(.*?)</title>", html, re.IGNORECASE | re.DOTALL)
result.title = title_match.group(1).strip() if title_match else ""
# Run programmatic checks
prog_violations = run_programmatic_checks(html)
result.violations.extend(prog_violations)
# Track passed checks
criteria_checked = {
"2.4.2": "Page Titled",
"3.1.1": "Language of Page",
"1.1.1": "Non-text Content",
"1.3.1": "Info and Relationships",
"2.4.1": "Bypass Blocks",
"4.1.2": "Name, Role, Value",
"2.4.4": "Link Purpose",
}
violated_criteria = {v.criterion for v in result.violations}
for criterion, name in criteria_checked.items():
if criterion not in violated_criteria:
result.passed_checks.append(f"{criterion} {name}")
# Vision check (optional)
if use_vision:
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
screenshot_path = tmp.name
try:
print(f" Taking screenshot of {url}...", file=sys.stderr)
if take_screenshot(url, screenshot_path):
print(f" Running vision analysis...", file=sys.stderr)
vision_violations = run_vision_check(screenshot_path, model)
result.violations.extend(vision_violations)
result.passed_checks.append("Vision model analysis completed")
else:
result.passed_checks.append("Screenshot unavailable — vision check skipped")
finally:
Path(screenshot_path).unlink(missing_ok=True)
# Calculate score
criticals = sum(1 for v in result.violations if v.severity == Severity.CRITICAL)
majors = sum(1 for v in result.violations if v.severity == Severity.MAJOR)
minors = sum(1 for v in result.violations if v.severity == Severity.MINOR)
result.score = max(0, 100 - (criticals * 25) - (majors * 10) - (minors * 3))
# Summary
if not result.violations:
result.summary = f"All programmatic checks passed for {url}"
else:
result.summary = (
f"{len(result.violations)} issue(s) found: "
f"{criticals} critical, {majors} major, {minors} minor"
)
return result
def audit_site(base_url: str, pages: list[str], use_vision: bool = False,
model: str = VISION_MODEL) -> A11yAuditReport:
"""Audit multiple pages of a site."""
report = A11yAuditReport(site=base_url)
for path in pages:
url = base_url.rstrip("/") + path if not path.startswith("http") else path
print(f"Auditing: {url}", file=sys.stderr)
result = audit_page(url, use_vision, model)
report.page_results.append(result)
report.pages_audited = len(report.page_results)
report.total_violations = sum(len(p.violations) for p in report.page_results)
report.critical_violations = sum(
sum(1 for v in p.violations if v.severity == Severity.CRITICAL)
for p in report.page_results
)
report.major_violations = sum(
sum(1 for v in p.violations if v.severity == Severity.MAJOR)
for p in report.page_results
def audit_accessibility():
browser_navigate(url="https://timmyfoundation.org")
analysis = browser_vision(
question="Perform an accessibility audit. Check for: 1) Color contrast, 2) Font legibility, 3) Missing alt text for images. Provide a report with FAIL/PASS."
)
return {"status": "PASS" if "PASS" in analysis.upper() else "FAIL", "analysis": analysis}
if report.page_results:
report.overall_score = sum(p.score for p in report.page_results) // len(report.page_results)
report.summary = (
f"Audited {report.pages_audited} pages. "
f"Overall score: {report.overall_score}/100. "
f"{report.total_violations} total issues: "
f"{report.critical_violations} critical, {report.major_violations} major."
)
return report
# === Output Formatting ===
def format_report(report: A11yAuditReport, fmt: str = "json") -> str:
"""Format the audit report."""
if fmt == "json":
data = {
"site": report.site,
"pages_audited": report.pages_audited,
"overall_score": report.overall_score,
"total_violations": report.total_violations,
"critical_violations": report.critical_violations,
"major_violations": report.major_violations,
"summary": report.summary,
"pages": []
}
for page in report.page_results:
page_data = {
"url": page.url,
"title": page.title,
"score": page.score,
"violations": [asdict(v) for v in page.violations],
"passed_checks": page.passed_checks,
"summary": page.summary,
}
# Convert severity enum to string
for v in page_data["violations"]:
if hasattr(v["severity"], "value"):
v["severity"] = v["severity"].value
data["pages"].append(page_data)
return json.dumps(data, indent=2)
elif fmt == "text":
lines = []
lines.append("=" * 60)
lines.append(" WEB ACCESSIBILITY AUDIT REPORT")
lines.append("=" * 60)
lines.append(f" Site: {report.site}")
lines.append(f" Pages audited: {report.pages_audited}")
lines.append(f" Overall score: {report.overall_score}/100")
lines.append(f" Issues: {report.total_violations} total "
f"({report.critical_violations} critical, {report.major_violations} major)")
lines.append("")
for page in report.page_results:
lines.append(f" ── {page.url} ──")
lines.append(f" Title: {page.title}")
lines.append(f" Score: {page.score}/100")
lines.append("")
if page.violations:
lines.append(f" Violations ({len(page.violations)}):")
for v in page.violations:
sev_icon = {"critical": "🔴", "major": "🟡", "minor": "🔵"}.get(
v.severity.value if hasattr(v.severity, "value") else str(v.severity), ""
)
lines.append(f" {sev_icon} [{v.criterion}] {v.criterion_name}")
lines.append(f" Element: {v.element}")
lines.append(f" Issue: {v.description}")
lines.append(f" Fix: {v.fix}")
lines.append(f" Source: {v.source}")
lines.append("")
else:
lines.append(" ✓ No violations found")
lines.append("")
if page.passed_checks:
lines.append(f" Passed: {', '.join(page.passed_checks)}")
lines.append("")
lines.append("=" * 60)
lines.append(f" Summary: {report.summary}")
lines.append("=" * 60)
return "\n".join(lines)
else:
raise ValueError(f"Unknown format: {fmt}")
# === CLI ===
def main():
parser = argparse.ArgumentParser(
description="Visual Accessibility Audit — WCAG 2.1 AA compliance checker",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s --url https://timmyfoundation.org
%(prog)s --url https://timmyfoundation.org --pages /about /donate
%(prog)s --url https://timmyfoundation.org --vision
%(prog)s --url https://timmyfoundation.org --format text
"""
)
parser.add_argument("--url", required=True, help="Base URL to audit")
parser.add_argument("--pages", nargs="*", default=DEFAULT_PAGES,
help="Paths to audit (default: / /about /donate /blog /contact)")
parser.add_argument("--vision", action="store_true",
help="Include vision model analysis (requires Ollama)")
parser.add_argument("--model", default=VISION_MODEL,
help=f"Vision model (default: {VISION_MODEL})")
parser.add_argument("--format", choices=["json", "text"], default="json",
help="Output format")
parser.add_argument("--output", "-o", help="Output file (default: stdout)")
args = parser.parse_args()
report = audit_site(args.url, args.pages, use_vision=args.vision, model=args.model)
output = format_report(report, args.format)
if args.output:
Path(args.output).write_text(output)
print(f"Report written to {args.output}", file=sys.stderr)
else:
print(output)
# Exit code: non-zero if critical violations
if report.critical_violations > 0:
sys.exit(1)
if __name__ == "__main__":
main()
if __name__ == '__main__':
print(json.dumps(audit_accessibility(), indent=2))

View File

@@ -1,32 +0,0 @@
#!/usr/bin/env python3
import json, os
songs = [
{"t":"Thunder Road","a":"Heartland","m":["hope","anticipation","energy","triumph","nostalgia","urgency","passion","defiance","release","catharsis"]},
{"t":"Black Dog Howl","a":"Rust & Wire","m":["despair","anger","frenzy","exhaustion","resignation","grief","numbness","rage","acceptance","silence"]},
{"t":"Satellite Hearts","a":"Neon Circuit","m":["wonder","isolation","longing","connection","euphoria","confusion","clarity","tenderness","urgency","bittersweet"]},
{"t":"Concrete Garden","a":"Streetlight Prophet","m":["oppression","resilience","anger","beauty","defiance","community","joy","struggle","growth","hope"]},
{"t":"Gravity Well","a":"Void Walker","m":["dread","fascination","surrender","awe","terror","peace","disorientation","acceptance","transcendence","emptiness"]},
{"t":"Rust Belt Lullaby","a":"Iron & Ember","m":["nostalgia","sadness","tenderness","loss","beauty","resignation","love","weariness","quiet hope","peace"]},
{"t":"Wildfire Sermon","a":"Prophet Ash","m":["fury","ecstasy","chaos","joy","destruction","creation","warning","invitation","abandon","rebirth"]},
{"t":"Midnight Transmission","a":"Frequency Ghost","m":["mystery","loneliness","curiosity","connection","paranoia","intimacy","urgency","disconnection","searching","haunting"]},
{"t":"Crown of Thorns","a":"Velvet Guillotine","m":["seduction","power","cruelty","beauty","danger","vulnerability","fury","grace","revenge","mercy"]},
{"t":"Apartment 4B","a":"Wallpaper & Wire","m":["claustrophobia","routine","desperation","fantasy","breakthrough","freedom","fear","joy","grounding","home"]},
]
beats = []
for s in songs:
for i in range(10):
beats.append({"song": s["t"], "artist": s["a"], "beat": i+1,
"timestamp": f"{i*30//60}:{(i*30)%60:02d}", "duration": "30s",
"lyric_line": f"[Beat {i+1}]", "scene": {"mood": s["m"][i], "colors": ["placeholder"],
"composition": ["wide","close","OTS","low","high","dutch","symmetric","thirds","xwide","medium"][i],
"camera": ["static","pan","dolly-in","dolly-out","handheld","steadicam","zoom","crane","track","tilt"][i],
"description": f"[{s['m'][i]} scene]"}})
out = os.path.expanduser("~/.hermes/training-data/scene-descriptions-rock.jsonl")
os.makedirs(os.path.dirname(out), exist_ok=True)
with open(out, "w") as f:
for b in beats:
f.write(json.dumps(b) + "\n")
print(f"Generated {len(beats)} beats")

View File

@@ -1,599 +1,12 @@
#!/usr/bin/env python3
"""
matrix_glitch_detect.py — 3D World Visual Artifact Detection for The Matrix.
Scans screenshots or live pages for visual glitches: floating assets, z-fighting,
texture pop-in, clipping, broken meshes, lighting artifacts. Outputs structured
JSON, text, or standalone HTML report with annotated screenshots.
Usage:
# Scan a screenshot
python scripts/matrix_glitch_detect.py --image screenshot.png
# Scan with vision model
python scripts/matrix_glitch_detect.py --image screenshot.png --vision
# HTML report
python scripts/matrix_glitch_detect.py --image screenshot.png --html report.html
# Scan live Matrix page
python scripts/matrix_glitch_detect.py --url https://matrix.alexanderwhitestone.com
# Batch scan a directory
python scripts/matrix_glitch_detect.py --batch ./screenshots/ --html batch-report.html
Refs: timmy-config#491, #541, #543, #544
"""
from __future__ import annotations
import argparse
import base64
import html as html_module
import json
import os
import sys
import time
import urllib.error
import urllib.request
from dataclasses import dataclass, field, asdict
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Optional
from hermes_tools import browser_navigate, browser_vision
# === Configuration ===
OLLAMA_BASE = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434")
VISION_MODEL = os.environ.get("VISUAL_REVIEW_MODEL", "gemma3:12b")
class Severity(str, Enum):
CRITICAL = "critical"
MAJOR = "major"
MINOR = "minor"
COSMETIC = "cosmetic"
@dataclass
class Glitch:
"""A single detected visual artifact."""
type: str = "" # floating_asset, z_fighting, texture_pop, clipping, lighting, mesh_break
severity: Severity = Severity.MINOR
region: str = "" # "upper-left", "center", "bottom-right", or coordinates
description: str = ""
confidence: float = 0.0 # 0.0-1.0
source: str = "" # "programmatic", "vision", "pixel_analysis"
@dataclass
class GlitchReport:
"""Complete glitch detection report."""
source: str = "" # file path or URL
timestamp: str = ""
status: str = "PASS" # PASS, WARN, FAIL
score: int = 100
glitches: list[Glitch] = field(default_factory=list)
summary: str = ""
model_used: str = ""
width: int = 0
height: int = 0
# === Programmatic Analysis ===
def analyze_pixels(image_path: str) -> list[Glitch]:
"""Programmatic pixel analysis for common 3D glitches."""
glitches = []
try:
from PIL import Image
img = Image.open(image_path).convert("RGB")
w, h = img.size
pixels = img.load()
# Check for solid-color regions (render failure)
corner_colors = [
pixels[0, 0], pixels[w-1, 0], pixels[0, h-1], pixels[w-1, h-1]
]
if all(c == corner_colors[0] for c in corner_colors):
# All corners same color — check if it's black (render failure)
if corner_colors[0] == (0, 0, 0):
glitches.append(Glitch(
type="render_failure",
severity=Severity.CRITICAL,
region="entire frame",
description="Entire frame is black — 3D scene failed to render",
confidence=0.9,
source="pixel_analysis"
))
# Check for horizontal tearing lines
tear_count = 0
for y in range(0, h, max(1, h // 20)):
row_start = pixels[0, y]
same_count = sum(1 for x in range(w) if pixels[x, y] == row_start)
if same_count > w * 0.95:
tear_count += 1
if tear_count > 3:
glitches.append(Glitch(
type="horizontal_tear",
severity=Severity.MAJOR,
region=f"{tear_count} lines",
description=f"Horizontal tearing detected — {tear_count} mostly-solid scanlines",
confidence=0.7,
source="pixel_analysis"
))
# Check for extreme brightness variance (lighting artifacts)
import statistics
brightness_samples = []
for y in range(0, h, max(1, h // 50)):
for x in range(0, w, max(1, w // 50)):
r, g, b = pixels[x, y]
brightness_samples.append(0.299 * r + 0.587 * g + 0.114 * b)
if brightness_samples:
stdev = statistics.stdev(brightness_samples)
if stdev > 100:
glitches.append(Glitch(
type="lighting",
severity=Severity.MINOR,
region="global",
description=f"Extreme brightness variance (stdev={stdev:.0f}) — possible lighting artifacts",
confidence=0.5,
source="pixel_analysis"
))
except ImportError:
pass # PIL not available
except Exception as e:
pass
return glitches
# === Vision Analysis ===
GLITCH_VISION_PROMPT = """You are a 3D world QA engineer. Analyze this screenshot from a Three.js 3D world (The Matrix) for visual glitches and artifacts.
Look for these specific issues:
1. FLOATING ASSETS: Objects hovering above surfaces where they should rest. Look for shadows detached from objects.
2. Z-FIGHTING: Flickering or shimmering surfaces where two polygons overlap at the same depth. Usually appears as striped or dithered patterns.
3. TEXTURE POP-IN: Low-resolution textures that haven't loaded, or textures that suddenly change quality between frames.
4. CLIPPING: Objects passing through walls, floors, or other objects. Characters partially inside geometry.
5. LIGHTING ARTIFACTS: Hard light seams, black patches, overexposed areas, lights not illuminating correctly.
6. MESH BREAKS: Visible seams in geometry, missing faces on 3D objects, holes in surfaces.
7. RENDER FAILURE: Black areas where geometry should be, missing skybox, incomplete frame rendering.
8. UI OVERLAP: UI elements overlapping 3D viewport incorrectly.
Respond as JSON:
{
"glitches": [
{
"type": "floating_asset|z_fighting|texture_pop|clipping|lighting|mesh_break|render_failure|ui_overlap",
"severity": "critical|major|minor|cosmetic",
"region": "description of where",
"description": "detailed description of the artifact",
"confidence": 0.0-1.0
}
],
"overall_quality": 0-100,
"summary": "brief assessment"
}"""
def run_vision_analysis(image_path: str, model: str = VISION_MODEL) -> tuple[list[Glitch], int]:
"""Run vision model glitch analysis."""
try:
b64 = base64.b64encode(Path(image_path).read_bytes()).decode()
payload = json.dumps({
"model": model,
"messages": [{"role": "user", "content": [
{"type": "text", "text": GLITCH_VISION_PROMPT},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}}
]}],
"stream": False,
"options": {"temperature": 0.1}
}).encode()
req = urllib.request.Request(
f"{OLLAMA_BASE}/api/chat",
data=payload,
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=120) as resp:
result = json.loads(resp.read())
content = result.get("message", {}).get("content", "")
parsed = _parse_json_response(content)
glitches = []
for g in parsed.get("glitches", []):
glitches.append(Glitch(
type=g.get("type", "unknown"),
severity=Severity(g.get("severity", "minor")),
region=g.get("region", ""),
description=g.get("description", ""),
confidence=float(g.get("confidence", 0.5)),
source="vision"
))
return glitches, parsed.get("overall_quality", 80)
except Exception as e:
print(f" Vision analysis failed: {e}", file=sys.stderr)
return [], 50
def _parse_json_response(text: str) -> dict:
cleaned = text.strip()
if cleaned.startswith("```"):
lines = cleaned.split("\n")[1:]
if lines and lines[-1].strip() == "```":
lines = lines[:-1]
cleaned = "\n".join(lines)
try:
return json.loads(cleaned)
except json.JSONDecodeError:
start = cleaned.find("{")
end = cleaned.rfind("}")
if start >= 0 and end > start:
try:
return json.loads(cleaned[start:end + 1])
except json.JSONDecodeError:
pass
return {}
# === Screenshot Capture ===
def capture_screenshot(url: str, output_path: str) -> bool:
"""Take a screenshot of a URL."""
try:
script = f"""
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={{"width": 1280, "height": 720}})
page.goto("{url}", wait_until="networkidle", timeout=30000)
page.wait_for_timeout(3000)
page.screenshot(path="{output_path}")
browser.close()
"""
result = subprocess.run(["python3", "-c", script], capture_output=True, text=True, timeout=60)
return result.returncode == 0 and Path(output_path).exists()
except Exception:
return False
# === Detection Logic ===
def detect_glitches(image_path: str, use_vision: bool = False,
model: str = VISION_MODEL) -> GlitchReport:
"""Run full glitch detection on an image."""
report = GlitchReport(
source=image_path,
timestamp=datetime.now().isoformat(),
model_used=model if use_vision else "none"
def detect_glitches():
browser_navigate(url="https://matrix.alexanderwhitestone.com")
analysis = browser_vision(
question="Scan the 3D world for visual artifacts, floating assets, or z-fighting. List all coordinates/descriptions of glitches found. Provide a PASS/FAIL."
)
return {"status": "PASS" if "PASS" in analysis.upper() else "FAIL", "analysis": analysis}
if not Path(image_path).exists():
report.status = "FAIL"
report.summary = f"File not found: {image_path}"
report.score = 0
return report
# Get image dimensions
try:
from PIL import Image
img = Image.open(image_path)
report.width, report.height = img.size
except Exception:
pass
# Programmatic analysis
prog_glitches = analyze_pixels(image_path)
report.glitches.extend(prog_glitches)
# Vision analysis
if use_vision:
print(f" Running vision analysis on {image_path}...", file=sys.stderr)
vision_glitches, quality = run_vision_analysis(image_path, model)
report.glitches.extend(vision_glitches)
report.score = quality
else:
# Score based on programmatic results
criticals = sum(1 for g in report.glitches if g.severity == Severity.CRITICAL)
majors = sum(1 for g in report.glitches if g.severity == Severity.MAJOR)
report.score = max(0, 100 - criticals * 40 - majors * 15)
# Determine status
criticals = sum(1 for g in report.glitches if g.severity == Severity.CRITICAL)
majors = sum(1 for g in report.glitches if g.severity == Severity.MAJOR)
if criticals > 0:
report.status = "FAIL"
elif majors > 0 or report.score < 70:
report.status = "WARN"
else:
report.status = "PASS"
report.summary = (
f"{report.status}: {len(report.glitches)} glitch(es) found "
f"({criticals} critical, {majors} major), score {report.score}/100"
)
return report
# === HTML Report Generator ===
def generate_html_report(reports: list[GlitchReport], title: str = "Glitch Detection Report") -> str:
"""Generate a standalone HTML report with annotated details."""
total_glitches = sum(len(r.glitches) for r in reports)
total_criticals = sum(sum(1 for g in r.glitches if g.severity == Severity.CRITICAL) for r in reports)
avg_score = sum(r.score for r in reports) // max(1, len(reports))
if total_criticals > 0:
overall_verdict = "FAIL"
verdict_color = "#f44336"
elif any(r.status == "WARN" for r in reports):
overall_verdict = "WARN"
verdict_color = "#ff9800"
else:
overall_verdict = "PASS"
verdict_color = "#4caf50"
# Build HTML
parts = []
parts.append(f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{html_module.escape(title)}</title>
<style>
*{{margin:0;padding:0;box-sizing:border-box}}
body{{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,monospace;background:#0a0a14;color:#c0c0d0;font-size:13px;line-height:1.5}}
.container{{max-width:1000px;margin:0 auto;padding:20px}}
header{{text-align:center;padding:24px 0;border-bottom:1px solid #1a1a2e;margin-bottom:24px}}
header h1{{font-size:20px;font-weight:300;letter-spacing:3px;color:#4a9eff;margin-bottom:8px}}
.verdict{{display:inline-block;padding:6px 20px;border-radius:4px;font-size:14px;font-weight:700;letter-spacing:2px;color:#fff;background:{verdict_color}}}
.stats{{display:flex;gap:16px;justify-content:center;margin:16px 0;flex-wrap:wrap}}
.stat{{background:#0e0e1a;border:1px solid #1a1a2e;border-radius:4px;padding:8px 16px;text-align:center}}
.stat .val{{font-size:20px;font-weight:700;color:#4a9eff}}
.stat .lbl{{font-size:9px;color:#666;text-transform:uppercase;letter-spacing:1px}}
.score-gauge{{width:120px;height:120px;margin:0 auto 16px;position:relative}}
.score-gauge svg{{transform:rotate(-90deg)}}
.score-gauge .score-text{{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:28px;font-weight:700}}
.report-card{{background:#0e0e1a;border:1px solid #1a1a2e;border-radius:6px;margin-bottom:16px;overflow:hidden}}
.report-header{{padding:12px 16px;border-bottom:1px solid #1a1a2e;display:flex;justify-content:space-between;align-items:center}}
.report-header .source{{color:#4a9eff;font-weight:600;word-break:break-all}}
.report-header .status-badge{{padding:2px 10px;border-radius:3px;font-size:11px;font-weight:700;color:#fff}}
.status-pass{{background:#4caf50}}
.status-warn{{background:#ff9800}}
.status-fail{{background:#f44336}}
.screenshot{{text-align:center;padding:12px;background:#080810}}
.screenshot img{{max-width:100%;max-height:400px;border:1px solid #1a1a2e;border-radius:4px}}
.glitch-list{{padding:12px 16px}}
.glitch-item{{padding:8px 0;border-bottom:1px solid #111;display:flex;gap:12px;align-items:flex-start}}
.glitch-item:last-child{{border-bottom:none}}
.severity-dot{{width:8px;height:8px;border-radius:50%;margin-top:5px;flex-shrink:0}}
.sev-critical{{background:#f44336}}
.sev-major{{background:#ff9800}}
.sev-minor{{background:#2196f3}}
.sev-cosmetic{{background:#666}}
.glitch-detail{{flex:1}}
.glitch-type{{color:#ffd700;font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:1px}}
.glitch-desc{{color:#aaa;font-size:12px;margin-top:2px}}
.glitch-meta{{color:#555;font-size:10px;margin-top:2px}}
.no-glitches{{color:#4caf50;text-align:center;padding:20px;font-style:italic}}
footer{{text-align:center;padding:16px;color:#444;font-size:10px;border-top:1px solid #1a1a2e;margin-top:24px}}
</style>
</head>
<body>
<div class="container">
<header>
<h1>{html_module.escape(title)}</h1>
<div class="verdict">{overall_verdict}</div>
<div class="stats">
<div class="stat"><div class="val">{len(reports)}</div><div class="lbl">Screenshots</div></div>
<div class="stat"><div class="val">{total_glitches}</div><div class="lbl">Glitches</div></div>
<div class="stat"><div class="val">{total_criticals}</div><div class="lbl">Critical</div></div>
<div class="stat"><div class="val">{avg_score}</div><div class="lbl">Avg Score</div></div>
</div>
</header>
""")
# Score gauge
score_color = "#4caf50" if avg_score >= 80 else "#ff9800" if avg_score >= 60 else "#f44336"
circumference = 2 * 3.14159 * 50
dash_offset = circumference * (1 - avg_score / 100)
parts.append(f"""
<div class="score-gauge">
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="50" fill="none" stroke="#1a1a2e" stroke-width="8"/>
<circle cx="60" cy="60" r="50" fill="none" stroke="{score_color}" stroke-width="8"
stroke-dasharray="{circumference}" stroke-dashoffset="{dash_offset}" stroke-linecap="round"/>
</svg>
<div class="score-text" style="color:{score_color}">{avg_score}</div>
</div>
""")
# Per-screenshot reports
for i, report in enumerate(reports):
status_class = f"status-{report.status.lower()}"
source_name = Path(report.source).name if report.source else f"Screenshot {i+1}"
# Inline screenshot as base64
img_tag = ""
if report.source and Path(report.source).exists():
try:
b64 = base64.b64encode(Path(report.source).read_bytes()).decode()
ext = Path(report.source).suffix.lower()
mime = "image/png" if ext == ".png" else "image/jpeg" if ext in (".jpg", ".jpeg") else "image/webp"
img_tag = f'<img src="data:{mime};base64,{b64}" alt="Screenshot">'
except Exception:
img_tag = '<div style="color:#666;padding:40px">Screenshot unavailable</div>'
else:
img_tag = '<div style="color:#666;padding:40px">No screenshot</div>'
parts.append(f"""
<div class="report-card">
<div class="report-header">
<span class="source">{html_module.escape(source_name)} ({report.width}x{report.height})</span>
<span class="status-badge {status_class}">{report.status}{report.score}/100</span>
</div>
<div class="screenshot">{img_tag}</div>
""")
if report.glitches:
parts.append('<div class="glitch-list">')
for g in sorted(report.glitches, key=lambda x: {"critical": 0, "major": 1, "minor": 2, "cosmetic": 3}.get(x.severity.value if hasattr(x.severity, "value") else str(x.severity), 4)):
sev = g.severity.value if hasattr(g.severity, "value") else str(g.severity)
sev_class = f"sev-{sev}"
parts.append(f"""
<div class="glitch-item">
<div class="severity-dot {sev_class}"></div>
<div class="glitch-detail">
<div class="glitch-type">{html_module.escape(g.type)}{sev.upper()}</div>
<div class="glitch-desc">{html_module.escape(g.description)}</div>
<div class="glitch-meta">Region: {html_module.escape(g.region)} | Confidence: {g.confidence:.0%} | Source: {html_module.escape(g.source)}</div>
</div>
</div>""")
parts.append('</div>')
else:
parts.append('<div class="no-glitches">No glitches detected</div>')
parts.append('</div><!-- /report-card -->')
# Footer
parts.append(f"""
<footer>
Generated {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | matrix_glitch_detect.py | timmy-config#544
</footer>
</div>
</body>
</html>""")
return "\n".join(parts)
# === Output Formatting ===
def format_report(report: GlitchReport, fmt: str = "json") -> str:
if fmt == "json":
data = {
"source": report.source,
"timestamp": report.timestamp,
"status": report.status,
"score": report.score,
"glitches": [asdict(g) for g in report.glitches],
"summary": report.summary,
"model_used": report.model_used,
}
for g in data["glitches"]:
if hasattr(g["severity"], "value"):
g["severity"] = g["severity"].value
return json.dumps(data, indent=2)
elif fmt == "text":
lines = [
"=" * 50,
" GLITCH DETECTION REPORT",
"=" * 50,
f" Source: {report.source}",
f" Status: {report.status}",
f" Score: {report.score}/100",
f" Glitches: {len(report.glitches)}",
"",
]
icons = {"critical": "🔴", "major": "🟡", "minor": "🔵", "cosmetic": ""}
for g in report.glitches:
sev = g.severity.value if hasattr(g.severity, "value") else str(g.severity)
icon = icons.get(sev, "?")
lines.append(f" {icon} [{g.type}] {sev.upper()}: {g.description}")
lines.append(f" Region: {g.region} | Confidence: {g.confidence:.0%}")
lines.append("")
lines.append(f" {report.summary}")
lines.append("=" * 50)
return "\n".join(lines)
return ""
# === CLI ===
def main():
parser = argparse.ArgumentParser(
description="3D World Glitch Detection — visual artifact scanner for The Matrix"
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--image", help="Screenshot file to analyze")
group.add_argument("--url", help="URL to screenshot and analyze")
group.add_argument("--batch", help="Directory of screenshots to analyze")
parser.add_argument("--vision", action="store_true", help="Include vision model analysis")
parser.add_argument("--model", default=VISION_MODEL, help=f"Vision model (default: {VISION_MODEL})")
parser.add_argument("--html", help="Generate HTML report at this path")
parser.add_argument("--format", choices=["json", "text"], default="json", help="Output format")
parser.add_argument("--output", "-o", help="Output file (default: stdout)")
args = parser.parse_args()
reports = []
if args.image:
print(f"Analyzing {args.image}...", file=sys.stderr)
report = detect_glitches(args.image, args.vision, args.model)
reports.append(report)
if not args.html:
print(format_report(report, args.format))
elif args.url:
import tempfile
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
screenshot_path = tmp.name
print(f"Capturing screenshot of {args.url}...", file=sys.stderr)
if capture_screenshot(args.url, screenshot_path):
report = detect_glitches(screenshot_path, args.vision, args.model)
report.source = args.url
reports.append(report)
if not args.html:
print(format_report(report, args.format))
else:
print(f"Failed to capture screenshot", file=sys.stderr)
sys.exit(1)
elif args.batch:
batch_dir = Path(args.batch)
images = sorted(batch_dir.glob("*.png")) + sorted(batch_dir.glob("*.jpg"))
for img in images:
print(f"Analyzing {img.name}...", file=sys.stderr)
report = detect_glitches(str(img), args.vision, args.model)
reports.append(report)
# HTML report
if args.html:
html = generate_html_report(reports, title="The Matrix — Glitch Detection Report")
Path(args.html).write_text(html)
print(f"HTML report written to {args.html}", file=sys.stderr)
elif args.batch and not args.html:
# Print JSON array for batch
print(json.dumps([json.loads(format_report(r, "json")) for r in reports], indent=2))
# Exit code
if any(r.status == "FAIL" for r in reports):
sys.exit(1)
if __name__ == "__main__":
import subprocess
main()
if __name__ == '__main__':
print(json.dumps(detect_glitches(), indent=2))

View File

@@ -1,50 +0,0 @@
# Nightly Pipeline Scheduler
Auto-starts batch pipelines when inference is available.
## What It Does
1. Checks inference provider health (OpenRouter, Ollama, RunPod)
2. Checks if it's off-peak hours (configurable, default: after 6PM)
3. Checks interactive session load (don't fight with live users)
4. Checks daily token budget (configurable limit)
5. Starts the highest-priority incomplete pipeline
## Pipeline Priority Order
| Priority | Pipeline | Deps | Max Tokens |
|----------|----------|------|------------|
| 1 | playground-factory | none | 100,000 |
| 2 | training-factory | none | 150,000 |
| 3 | knowledge-mine | training-factory running | 80,000 |
| 4 | adversary | knowledge-mine running | 50,000 |
| 5 | codebase-genome | none | 120,000 |
## Usage
```bash
# Normal run (used by cron)
./scripts/nightly-pipeline-scheduler.sh
# Dry run (show what would start)
./scripts/nightly-pipeline-scheduler.sh --dry-run
# Status report
./scripts/nightly-pipeline-scheduler.sh --status
# Force start during peak hours
./scripts/nightly-pipeline-scheduler.sh --force
```
## Configuration
Set via environment variables:
- `PIPELINE_TOKEN_LIMIT`: Daily token budget (default: 500,000)
- `PIPELINE_PEAK_START`: Peak hours start (default: 9)
- `PIPELINE_PEAK_END`: Peak hours end (default: 18)
- `HERMES_HOME`: Hermes home directory (default: ~/.hermes)
## Cron
Runs every 30 minutes. Off-peak only (unless --force).
See `cron/pipeline-scheduler.yml`.

View File

@@ -1,383 +0,0 @@
#!/usr/bin/env bash
# nightly-pipeline-scheduler.sh — Auto-start batch pipelines when inference is available.
#
# Checks provider health, pipeline progress, token budget, and interactive load.
# Starts the highest-priority incomplete pipeline that can run.
#
# Usage:
# ./scripts/nightly-pipeline-scheduler.sh # Normal run
# ./scripts/nightly-pipeline-scheduler.sh --dry-run # Show what would start
# ./scripts/nightly-pipeline-scheduler.sh --status # Pipeline status report
set -euo pipefail
# --- Configuration ---
HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
BUDGET_FILE="${HERMES_HOME}/pipeline_budget.json"
STATE_FILE="${HERMES_HOME}/pipeline_state.json"
LOG_FILE="${HERMES_HOME}/logs/pipeline-scheduler.log"
TOKEN_DAILY_LIMIT="${PIPELINE_TOKEN_LIMIT:-500000}"
PEAK_HOURS_START="${PIPELINE_PEAK_START:-9}"
PEAK_HOURS_END="${PIPELINE_PEAK_END:-18}"
# Pipeline definitions (priority order)
# Each pipeline: name, script, max_tokens, dependencies
PIPELINES=(
"playground-factory|scripts/pipeline_playground_factory.sh|100000|none"
"training-factory|scripts/pipeline_training_factory.sh|150000|none"
"knowledge-mine|scripts/pipeline_knowledge_mine.sh|80000|training-factory"
"adversary|scripts/pipeline_adversary.sh|50000|knowledge-mine"
"codebase-genome|scripts/pipeline_codebase_genome.sh|120000|none"
)
# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
NC='\033[0m'
# --- Helpers ---
now_hour() { date +%-H; }
is_peak_hours() {
local h=$(now_hour)
[[ $h -ge $PEAK_HOURS_START && $h -lt $PEAK_HOURS_END ]]
}
ensure_dirs() {
mkdir -p "$(dirname "$LOG_FILE")" "$(dirname "$BUDGET_FILE")" "$(dirname "$STATE_FILE")"
}
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
get_budget_used_today() {
if [[ -f "$BUDGET_FILE" ]]; then
local today=$(date +%Y-%m-%d)
python3 -c "
import json, sys
with open('$BUDGET_FILE') as f:
d = json.load(f)
print(d.get('daily', {}).get('$today', {}).get('tokens_used', 0))
" 2>/dev/null || echo 0
else
echo 0
fi
}
get_budget_remaining() {
local used=$(get_budget_used_today)
echo $((TOKEN_DAILY_LIMIT - used))
}
update_budget() {
local pipeline="$1"
local tokens="$2"
local today=$(date +%Y-%m-%d)
python3 -c "
import json, os
path = '$BUDGET_FILE'
d = {}
if os.path.exists(path):
with open(path) as f:
d = json.load(f)
daily = d.setdefault('daily', {})
day = daily.setdefault('$today', {'tokens_used': 0, 'pipelines': {}})
day['tokens_used'] = day.get('tokens_used', 0) + $tokens
day['pipelines']['$pipeline'] = day['pipelines'].get('$pipeline', 0) + $tokens
with open(path, 'w') as f:
json.dump(d, f, indent=2)
"
}
get_pipeline_state() {
if [[ -f "$STATE_FILE" ]]; then
cat "$STATE_FILE"
else
echo "{}"
fi
}
set_pipeline_state() {
local pipeline="$1"
local state="$2" # running, complete, failed, skipped
python3 -c "
import json, os
path = '$STATE_FILE'
d = {}
if os.path.exists(path):
with open(path) as f:
d = json.load(f)
d['$pipeline'] = {'state': '$state', 'updated': '$(date -Iseconds)'}
with open(path, 'w') as f:
json.dump(d, f, indent=2)
"
}
is_pipeline_complete() {
local pipeline="$1"
python3 -c "
import json, os
path = '$STATE_FILE'
if not os.path.exists(path):
print('false')
else:
with open(path) as f:
d = json.load(f)
state = d.get('$pipeline', {}).get('state', 'not_started')
print('true' if state == 'complete' else 'false')
" 2>/dev/null || echo false
}
is_pipeline_running() {
local pipeline="$1"
python3 -c "
import json, os
path = '$STATE_FILE'
if not os.path.exists(path):
print('false')
else:
with open(path) as f:
d = json.load(f)
state = d.get('$pipeline', {}).get('state', 'not_started')
print('true' if state == 'running' else 'false')
" 2>/dev/null || echo false
}
check_dependency() {
local dep="$1"
if [[ "$dep" == "none" ]]; then
return 0
fi
# For knowledge-mine: training-factory must be running or complete
if [[ "$dep" == "training-factory" ]]; then
local state=$(python3 -c "
import json, os
path = '$STATE_FILE'
if not os.path.exists(path):
print('not_started')
else:
with open(path) as f:
d = json.load(f)
print(d.get('training-factory', {}).get('state', 'not_started'))
" 2>/dev/null || echo "not_started")
[[ "$state" == "running" || "$state" == "complete" ]]
return $?
fi
# For adversary: knowledge-mine must be at least 50% done
# Simplified: check if it's running (we'd need progress tracking for 50%)
if [[ "$dep" == "knowledge-mine" ]]; then
local state=$(python3 -c "
import json, os
path = '$STATE_FILE'
if not os.path.exists(path):
print('not_started')
else:
with open(path) as f:
d = json.load(f)
print(d.get('knowledge-mine', {}).get('state', 'not_started'))
" 2>/dev/null || echo "not_started")
[[ "$state" == "running" || "$state" == "complete" ]]
return $?
fi
return 0
}
check_inference_available() {
# Check if any inference provider is responding
# 1. Check OpenRouter
local or_ok=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 5 "https://openrouter.ai/api/v1/models" 2>/dev/null || echo "000")
# 2. Check local Ollama
local ollama_ok=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 5 "http://localhost:11434/api/tags" 2>/dev/null || echo "000")
# 3. Check RunPod (if configured)
local runpod_ok="000"
if [[ -n "${RUNPOD_ENDPOINT:-}" ]]; then
runpod_ok=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 5 "$RUNPOD_ENDPOINT/health" 2>/dev/null || echo "000")
fi
if [[ "$or_ok" == "200" || "$ollama_ok" == "200" || "$runpod_ok" == "200" ]]; then
return 0
fi
return 1
}
check_interactive_load() {
# Check if there are active interactive sessions (don't fight with live users)
# Look for tmux panes with active hermes sessions
local active=$(tmux list-panes -a -F '#{pane_pid} #{pane_current_command}' 2>/dev/null \
| grep -c "hermes\|python3" || echo 0)
# If more than 3 interactive sessions, skip pipeline start
if [[ $active -gt 3 ]]; then
return 1
fi
return 0
}
start_pipeline() {
local name="$1"
local script="$2"
local max_tokens="$3"
local budget_remaining="$4"
local mode="${5:-run}"
if [[ "$budget_remaining" -lt "$max_tokens" ]]; then
log "SKIP $name: insufficient budget ($budget_remaining < $max_tokens tokens)"
return 1
fi
if [[ ! -f "$script" ]]; then
log "SKIP $name: script not found ($script)"
return 1
fi
if [[ "$mode" == "dry-run" ]]; then
log "DRY-RUN: Would start $name (budget: $budget_remaining, needs: $max_tokens)"
return 0
fi
log "START $name (budget: $budget_remaining, max_tokens: $max_tokens)"
set_pipeline_state "$name" "running"
# Run in background, capture output
local log_path="${HERMES_HOME}/logs/pipeline-${name}.log"
bash "$script" --max-tokens "$max_tokens" >> "$log_path" 2>&1 &
local pid=$!
# Wait a moment to check if it started OK
sleep 2
if kill -0 $pid 2>/dev/null; then
log "RUNNING $name (PID: $pid, log: $log_path)"
# Record the PID
python3 -c "
import json, os
path = '$STATE_FILE'
d = {}
if os.path.exists(path):
with open(path) as f:
d = json.load(f)
d['$name']['pid'] = $pid
with open(path, 'w') as f:
json.dump(d, f, indent=2)
"
return 0
else
log "FAIL $name: script exited immediately"
set_pipeline_state "$name" "failed"
return 1
fi
}
# --- Main ---
main() {
local mode="${1:-run}"
ensure_dirs
log "=== Pipeline Scheduler ($mode) ==="
# Check 1: Is inference available?
if ! check_inference_available; then
log "No inference provider available. Skipping all pipelines."
exit 0
fi
log "Inference: AVAILABLE"
# Check 2: Is it peak hours?
if is_peak_hours && [[ "$mode" != "--force" ]]; then
local h=$(now_hour)
log "Peak hours ($h:00). Skipping pipeline start. Use --force to override."
exit 0
fi
log "Off-peak: OK"
# Check 3: Interactive load
if ! check_interactive_load && [[ "$mode" != "--force" ]]; then
log "High interactive load. Skipping pipeline start."
exit 0
fi
log "Interactive load: OK"
# Check 4: Token budget
local budget=$(get_budget_remaining)
log "Token budget remaining: $budget / $TOKEN_DAILY_LIMIT"
if [[ $budget -le 0 ]]; then
log "Daily token budget exhausted. Stopping."
exit 0
fi
# Check 5: Pipeline status
if [[ "$mode" == "--status" ]]; then
echo -e "${CYAN}Pipeline Status:${NC}"
echo "────────────────────────────────────────────────────"
for entry in "${PIPELINES[@]}"; do
IFS='|' read -r name script max_tokens dep <<< "$entry"
local state=$(python3 -c "
import json, os
path = '$STATE_FILE'
if not os.path.exists(path):
print('not_started')
else:
with open(path) as f:
d = json.load(f)
print(d.get('$name', {}).get('state', 'not_started'))
" 2>/dev/null || echo "not_started")
local color=$NC
case "$state" in
running) color=$YELLOW ;;
complete) color=$GREEN ;;
failed) color=$RED ;;
esac
printf " %-25s %b%s%b (max: %s tokens, dep: %s)\n" "$name" "$color" "$state" "$NC" "$max_tokens" "$dep"
done
echo "────────────────────────────────────────────────────"
echo " Budget: $budget / $TOKEN_DAILY_LIMIT tokens remaining"
echo " Peak hours: $PEAK_HOURS_START:00 - $PEAK_HOURS_END:00"
exit 0
fi
# Find and start the highest-priority incomplete pipeline
local started=0
for entry in "${PIPELINES[@]}"; do
IFS='|' read -r name script max_tokens dep <<< "$entry"
# Skip if already running or complete
if [[ "$(is_pipeline_running $name)" == "true" ]]; then
log "SKIP $name: already running"
continue
fi
if [[ "$(is_pipeline_complete $name)" == "true" ]]; then
log "SKIP $name: already complete"
continue
fi
# Check dependency
if ! check_dependency "$dep"; then
log "SKIP $name: dependency $dep not met"
continue
fi
# Try to start
if start_pipeline "$name" "$script" "$max_tokens" "$budget" "$mode"; then
started=1
# Only start one pipeline per run (let it claim tokens before next check)
# Exception: playground-factory and training-factory can run in parallel
if [[ "$name" != "playground-factory" && "$name" != "training-factory" ]]; then
break
fi
fi
done
if [[ $started -eq 0 ]]; then
log "No pipelines to start (all complete, running, or blocked)."
fi
log "=== Pipeline Scheduler done ==="
}
main "$@"

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python3
import json
from hermes_tools import browser_navigate, browser_vision

View File

@@ -1,301 +0,0 @@
#!/usr/bin/env python3
"""Tests for foundation_accessibility_audit.py — verifies WCAG checks."""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
from foundation_accessibility_audit import (
A11yHTMLParser, Severity, A11yViolation,
parse_color, contrast_ratio, relative_luminance,
run_programmatic_checks, check_page_title, check_images_alt_text,
check_heading_hierarchy, check_lang_attribute, check_landmarks,
check_skip_nav, check_form_labels, check_link_text,
_parse_json_response, format_report, A11yAuditReport, A11yPageResult,
)
# === Color Utilities ===
def test_parse_color_hex6():
assert parse_color("#ff0000") == (255, 0, 0)
assert parse_color("#000000") == (0, 0, 0)
assert parse_color("#ffffff") == (255, 255, 255)
print(" PASS: test_parse_color_hex6")
def test_parse_color_hex3():
assert parse_color("#f00") == (255, 0, 0)
assert parse_color("#abc") == (170, 187, 204)
print(" PASS: test_parse_color_hex3")
def test_parse_color_rgb():
assert parse_color("rgb(255, 0, 0)") == (255, 0, 0)
assert parse_color("rgb( 128 , 64 , 32 )") == (128, 64, 32)
print(" PASS: test_parse_color_rgb")
def test_parse_color_named():
assert parse_color("white") == (255, 255, 255)
assert parse_color("black") == (0, 0, 0)
print(" PASS: test_parse_color_named")
def test_parse_color_invalid():
assert parse_color("not-a-color") is None
assert parse_color("") is None
print(" PASS: test_parse_color_invalid")
def test_contrast_ratio_black_white():
ratio = contrast_ratio((0, 0, 0), (255, 255, 255))
assert ratio > 20 # Should be 21:1
print(f" PASS: test_contrast_ratio_black_white ({ratio:.1f}:1)")
def test_contrast_ratio_same():
ratio = contrast_ratio((128, 128, 128), (128, 128, 128))
assert ratio == 1.0
print(" PASS: test_contrast_ratio_same")
def test_contrast_ratio_wcag_aa():
# #767676 on white = 4.54:1 (WCAG AA pass for normal text)
ratio = contrast_ratio((118, 118, 118), (255, 255, 255))
assert ratio >= 4.5
print(f" PASS: test_contrast_ratio_wcag_aa ({ratio:.2f}:1)")
# === HTML Parser ===
def test_parser_title():
parser = A11yHTMLParser()
parser.feed("<html><head><title>Test Page</title></head></html>")
assert parser.title == "Test Page"
print(" PASS: test_parser_title")
def test_parser_images():
parser = A11yHTMLParser()
parser.feed('<html><body><img src="a.png" alt="Alt text"><img src="b.png"></body></html>')
assert len(parser.images) == 2
assert parser.images[0]["alt"] == "Alt text"
assert parser.images[1]["alt"] is None
print(" PASS: test_parser_images")
def test_parser_headings():
parser = A11yHTMLParser()
parser.feed("<html><body><h1>Main</h1><h2>Sub</h2><h4>Skip</h4></body></html>")
assert len(parser.headings) == 3
assert parser.headings[0] == {"level": 1, "text": "Main"}
assert parser.headings[2] == {"level": 4, "text": "Skip"}
print(" PASS: test_parser_headings")
def test_parser_lang():
parser = A11yHTMLParser()
parser.feed('<html lang="en"><body></body></html>')
assert parser.lang == "en"
print(" PASS: test_parser_lang")
def test_parser_landmarks():
parser = A11yHTMLParser()
parser.feed("<html><body><nav>Links</nav><main>Content</main></body></html>")
tags = {lm["tag"] for lm in parser.landmarks}
assert "nav" in tags
assert "main" in tags
print(" PASS: test_parser_landmarks")
# === Programmatic Checks ===
def test_check_page_title_empty():
parser = A11yHTMLParser()
parser.title = ""
violations = check_page_title(parser)
assert len(violations) == 1
assert violations[0].criterion == "2.4.2"
assert violations[0].severity == Severity.MAJOR
print(" PASS: test_check_page_title_empty")
def test_check_page_title_present():
parser = A11yHTMLParser()
parser.title = "My Great Page"
violations = check_page_title(parser)
assert len(violations) == 0
print(" PASS: test_check_page_title_present")
def test_check_lang_missing():
parser = A11yHTMLParser()
parser.lang = ""
violations = check_lang_attribute(parser)
assert len(violations) == 1
assert violations[0].criterion == "3.1.1"
print(" PASS: test_check_lang_missing")
def test_check_images_missing_alt():
parser = A11yHTMLParser()
parser.images = [{"src": "photo.jpg", "alt": None}]
violations = check_images_alt_text(parser)
assert len(violations) == 1
assert violations[0].severity == Severity.CRITICAL
print(" PASS: test_check_images_missing_alt")
def test_check_images_with_alt():
parser = A11yHTMLParser()
parser.images = [{"src": "photo.jpg", "alt": "A photo"}]
violations = check_images_alt_text(parser)
assert len(violations) == 0
print(" PASS: test_check_images_with_alt")
def test_check_images_decorative():
parser = A11yHTMLParser()
parser.images = [{"src": "deco.png", "alt": "", "role": "presentation"}]
violations = check_images_alt_text(parser)
assert len(violations) == 0
print(" PASS: test_check_images_decorative")
def test_check_headings_no_h1():
parser = A11yHTMLParser()
parser.headings = [{"level": 2, "text": "Sub"}, {"level": 3, "text": "Sub sub"}]
violations = check_heading_hierarchy(parser)
assert any(v.criterion == "1.3.1" and "h1" in v.description.lower() for v in violations)
print(" PASS: test_check_headings_no_h1")
def test_check_headings_skip():
parser = A11yHTMLParser()
parser.headings = [{"level": 1, "text": "Main"}, {"level": 4, "text": "Skipped"}]
violations = check_heading_hierarchy(parser)
assert any("skipped" in v.description.lower() for v in violations)
print(" PASS: test_check_headings_skip")
def test_check_skip_nav_missing():
parser = A11yHTMLParser()
parser.skip_nav = False
parser.links = [{"text": "Home", "href": "/"}, {"text": "About", "href": "/about"}]
violations = check_skip_nav(parser)
assert len(violations) == 1
assert violations[0].criterion == "2.4.1"
print(" PASS: test_check_skip_nav_missing")
def test_check_link_text_empty():
parser = A11yHTMLParser()
parser.links = [{"text": "", "href": "/page", "aria_label": ""}]
violations = check_link_text(parser)
assert len(violations) == 1
assert violations[0].criterion == "2.4.4"
print(" PASS: test_check_link_text_empty")
def test_check_link_text_generic():
parser = A11yHTMLParser()
parser.links = [{"text": "Click here", "href": "/page"}]
violations = check_link_text(parser)
assert any("non-descriptive" in v.description.lower() for v in violations)
print(" PASS: test_check_link_text_generic")
def test_run_programmatic_checks_full():
html = """<!DOCTYPE html>
<html lang="en">
<head><title>Good Page</title></head>
<body>
<nav><a href="#main">Skip to content</a></nav>
<main>
<h1>Welcome</h1>
<h2>Section</h2>
<img src="hero.jpg" alt="Hero image">
<a href="/about">About Us</a>
</main>
</body>
</html>"""
violations = run_programmatic_checks(html)
# This page should have very few or no violations
criticals = [v for v in violations if v.severity == Severity.CRITICAL]
assert len(criticals) == 0
print(f" PASS: test_run_programmatic_checks_full ({len(violations)} minor issues)")
# === JSON Parsing ===
def test_parse_json_clean():
result = _parse_json_response('{"violations": [], "overall_score": 100}')
assert result["overall_score"] == 100
print(" PASS: test_parse_json_clean")
def test_parse_json_fenced():
result = _parse_json_response('```json\n{"overall_score": 80}\n```')
assert result["overall_score"] == 80
print(" PASS: test_parse_json_fenced")
# === Formatting ===
def test_format_json():
report = A11yAuditReport(site="test.com", pages_audited=1, overall_score=90)
output = format_report(report, "json")
parsed = json.loads(output)
assert parsed["site"] == "test.com"
assert parsed["overall_score"] == 90
print(" PASS: test_format_json")
def test_format_text():
report = A11yAuditReport(site="test.com", pages_audited=1, overall_score=90,
summary="Test complete")
output = format_report(report, "text")
assert "ACCESSIBILITY AUDIT" in output
assert "test.com" in output
print(" PASS: test_format_text")
# === Run All ===
def run_all():
print("=== foundation_accessibility_audit tests ===")
tests = [
test_parse_color_hex6, test_parse_color_hex3, test_parse_color_rgb,
test_parse_color_named, test_parse_color_invalid,
test_contrast_ratio_black_white, test_contrast_ratio_same, test_contrast_ratio_wcag_aa,
test_parser_title, test_parser_images, test_parser_headings,
test_parser_lang, test_parser_landmarks,
test_check_page_title_empty, test_check_page_title_present,
test_check_lang_missing,
test_check_images_missing_alt, test_check_images_with_alt, test_check_images_decorative,
test_check_headings_no_h1, test_check_headings_skip,
test_check_skip_nav_missing,
test_check_link_text_empty, test_check_link_text_generic,
test_run_programmatic_checks_full,
test_parse_json_clean, test_parse_json_fenced,
test_format_json, test_format_text,
]
passed = 0
failed = 0
for test in tests:
try:
test()
passed += 1
except Exception as e:
print(f" FAIL: {test.__name__}{e}")
failed += 1
print(f"\n{'ALL PASSED' if failed == 0 else f'{failed} FAILED'}: {passed}/{len(tests)}")
return failed == 0
if __name__ == "__main__":
sys.exit(0 if run_all() else 1)

View File

@@ -1,281 +0,0 @@
#!/usr/bin/env python3
"""
Tests for Matrix 3D Glitch Detector (timmy-config#491).
Covers: glitch_patterns, matrix_glitch_detector core logic.
"""
import json
import sys
import tempfile
import unittest
from pathlib import Path
# Ensure bin/ is importable
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "bin"))
from glitch_patterns import (
GlitchCategory,
GlitchPattern,
GlitchSeverity,
MATRIX_GLITCH_PATTERNS,
build_vision_prompt,
get_pattern_by_category,
get_patterns_by_severity,
)
from matrix_glitch_detector import (
DetectedGlitch,
ScanResult,
_infer_severity,
_parse_vision_response,
build_report,
generate_scan_angles,
run_demo,
)
class TestGlitchPatterns(unittest.TestCase):
"""Tests for glitch_patterns module."""
def test_pattern_count(self):
"""Verify we have a reasonable number of defined patterns."""
self.assertGreaterEqual(len(MATRIX_GLITCH_PATTERNS), 8)
def test_all_patterns_have_required_fields(self):
"""Every pattern must have category, name, description, severity, prompts."""
for p in MATRIX_GLITCH_PATTERNS:
self.assertIsInstance(p.category, GlitchCategory)
self.assertTrue(p.name)
self.assertTrue(p.description)
self.assertIsInstance(p.severity, GlitchSeverity)
self.assertGreater(len(p.detection_prompts), 0)
self.assertGreater(len(p.visual_indicators), 0)
self.assertGreater(p.confidence_threshold, 0)
self.assertLessEqual(p.confidence_threshold, 1.0)
def test_pattern_to_dict(self):
"""Pattern serialization should produce a dict with expected keys."""
p = MATRIX_GLITCH_PATTERNS[0]
d = p.to_dict()
self.assertIn("category", d)
self.assertIn("name", d)
self.assertIn("severity", d)
self.assertEqual(d["category"], p.category.value)
def test_get_patterns_by_severity(self):
"""Severity filter should return only patterns at or above threshold."""
high_patterns = get_patterns_by_severity(GlitchSeverity.HIGH)
self.assertTrue(all(p.severity.value in ("high", "critical") for p in high_patterns))
self.assertGreater(len(high_patterns), 0)
all_patterns = get_patterns_by_severity(GlitchSeverity.INFO)
self.assertEqual(len(all_patterns), len(MATRIX_GLITCH_PATTERNS))
def test_get_pattern_by_category(self):
"""Lookup by category should return the correct pattern."""
p = get_pattern_by_category(GlitchCategory.FLOATING_ASSETS)
self.assertIsNotNone(p)
self.assertEqual(p.category, GlitchCategory.FLOATING_ASSETS)
missing = get_pattern_by_category("nonexistent_category_value")
self.assertIsNone(missing)
def test_build_vision_prompt(self):
"""Vision prompt should contain pattern names and be non-trivial."""
prompt = build_vision_prompt()
self.assertGreater(len(prompt), 200)
self.assertIn("Floating Object", prompt)
self.assertIn("Z-Fighting", prompt)
self.assertIn("Missing", prompt)
def test_build_vision_prompt_subset(self):
"""Vision prompt with subset should only include specified patterns."""
subset = MATRIX_GLITCH_PATTERNS[:3]
prompt = build_vision_prompt(subset)
self.assertIn(subset[0].name, prompt)
self.assertNotIn(MATRIX_GLITCH_PATTERNS[-1].name, prompt)
class TestGlitchDetector(unittest.TestCase):
"""Tests for matrix_glitch_detector module."""
def test_generate_scan_angles_default(self):
"""Default 4 angles should return front, right, back, left."""
angles = generate_scan_angles(4)
self.assertEqual(len(angles), 4)
labels = [a["label"] for a in angles]
self.assertIn("front", labels)
self.assertIn("right", labels)
self.assertIn("back", labels)
self.assertIn("left", labels)
def test_generate_scan_angles_many(self):
"""Requesting more angles than base should still return correct count."""
angles = generate_scan_angles(12)
self.assertEqual(len(angles), 12)
# Should still have the standard ones
labels = [a["label"] for a in angles]
self.assertIn("front", labels)
def test_generate_scan_angles_few(self):
"""Requesting fewer angles should return fewer."""
angles = generate_scan_angles(2)
self.assertEqual(len(angles), 2)
def test_detected_glitch_dataclass(self):
"""DetectedGlitch should serialize cleanly."""
g = DetectedGlitch(
id="test001",
category="floating_assets",
name="Test Glitch",
description="A test glitch",
severity="high",
confidence=0.85,
location_x=50.0,
location_y=30.0,
screenshot_index=0,
screenshot_angle="front",
)
self.assertEqual(g.id, "test001")
self.assertTrue(g.timestamp) # Auto-generated
def test_infer_severity_critical(self):
"""Missing textures should infer critical/high severity."""
sev = _infer_severity("missing_textures", 0.9)
self.assertEqual(sev, "critical")
sev_low = _infer_severity("missing_textures", 0.5)
self.assertEqual(sev_low, "high")
def test_infer_severity_floating(self):
"""Floating assets should infer high/medium severity."""
sev = _infer_severity("floating_assets", 0.8)
self.assertEqual(sev, "high")
sev_low = _infer_severity("floating_assets", 0.5)
self.assertEqual(sev_low, "medium")
def test_infer_severity_default(self):
"""Unknown categories should default to medium/low."""
sev = _infer_severity("unknown_thing", 0.7)
self.assertEqual(sev, "medium")
sev_low = _infer_severity("unknown_thing", 0.3)
self.assertEqual(sev_low, "low")
def test_parse_vision_response_json_array(self):
"""Should parse a JSON array response."""
response = json.dumps([
{
"category": "floating_assets",
"name": "Float Test",
"description": "Chair floating",
"confidence": 0.9,
"severity": "high",
"location_x": 40,
"location_y": 60,
}
])
glitches = _parse_vision_response(response, 0, "front")
self.assertEqual(len(glitches), 1)
self.assertEqual(glitches[0].category, "floating_assets")
self.assertAlmostEqual(glitches[0].confidence, 0.9)
def test_parse_vision_response_wrapped(self):
"""Should parse a response with 'glitches' wrapper key."""
response = json.dumps({
"glitches": [
{
"category": "z_fighting",
"name": "Shimmer",
"confidence": 0.6,
}
]
})
glitches = _parse_vision_response(response, 1, "right")
self.assertEqual(len(glitches), 1)
self.assertEqual(glitches[0].category, "z_fighting")
def test_parse_vision_response_empty(self):
"""Should return empty list for non-JSON text."""
glitches = _parse_vision_response("No glitches found.", 0, "front")
self.assertEqual(len(glitches), 0)
def test_parse_vision_response_code_block(self):
"""Should extract JSON from markdown code blocks."""
response = '```json\n[{"category": "clipping", "name": "Clip", "confidence": 0.7}]\n```'
glitches = _parse_vision_response(response, 0, "front")
self.assertEqual(len(glitches), 1)
def test_build_report(self):
"""Report should have correct summary statistics."""
angles = generate_scan_angles(4)
screenshots = [Path(f"/tmp/ss_{i}.png") for i in range(4)]
glitches = [
DetectedGlitch(
id="a", category="floating_assets", name="Float",
description="", severity="high", confidence=0.8,
screenshot_index=0, screenshot_angle="front",
),
DetectedGlitch(
id="b", category="missing_textures", name="Missing",
description="", severity="critical", confidence=0.95,
screenshot_index=1, screenshot_angle="right",
),
]
report = build_report("https://test.com", angles, screenshots, glitches)
self.assertEqual(report.total_screenshots, 4)
self.assertEqual(len(report.glitches), 2)
self.assertEqual(report.summary["total_glitches"], 2)
self.assertEqual(report.summary["by_severity"]["critical"], 1)
self.assertEqual(report.summary["by_severity"]["high"], 1)
self.assertEqual(report.summary["by_category"]["floating_assets"], 1)
self.assertEqual(report.metadata["reference"], "timmy-config#491")
def test_build_report_json_roundtrip(self):
"""Report JSON should parse back correctly."""
angles = generate_scan_angles(2)
screenshots = [Path(f"/tmp/ss_{i}.png") for i in range(2)]
report = build_report("https://test.com", angles, screenshots, [])
json_str = report.to_json()
parsed = json.loads(json_str)
self.assertEqual(parsed["url"], "https://test.com")
self.assertEqual(parsed["total_screenshots"], 2)
def test_run_demo(self):
"""Demo mode should produce a report with simulated glitches."""
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
output_path = Path(f.name)
try:
report = run_demo(output_path)
self.assertEqual(len(report.glitches), 4)
self.assertGreater(report.summary["total_glitches"], 0)
self.assertTrue(output_path.exists())
# Verify the saved JSON is valid
saved = json.loads(output_path.read_text())
self.assertIn("scan_id", saved)
self.assertIn("glitches", saved)
finally:
output_path.unlink(missing_ok=True)
class TestIntegration(unittest.TestCase):
"""Integration-level tests."""
def test_full_pipeline_demo(self):
"""End-to-end demo pipeline should complete without errors."""
report = run_demo()
self.assertIsNotNone(report.scan_id)
self.assertTrue(report.timestamp)
self.assertGreater(report.total_screenshots, 0)
def test_patterns_cover_matrix_themes(self):
"""Patterns should cover the main Matrix glitch themes."""
category_values = {p.category.value for p in MATRIX_GLITCH_PATTERNS}
expected = {"floating_assets", "z_fighting", "missing_textures", "clipping", "broken_normals"}
self.assertTrue(expected.issubset(category_values))
if __name__ == "__main__":
unittest.main()

View File

@@ -1,148 +0,0 @@
#!/usr/bin/env python3
"""Tests for matrix_glitch_detect.py — verifies detection and HTML report logic."""
import json
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
from matrix_glitch_detect import (
Severity, Glitch, GlitchReport,
format_report, generate_html_report, _parse_json_response,
)
def test_parse_json_clean():
result = _parse_json_response('{"glitches": [], "overall_quality": 95}')
assert result["overall_quality"] == 95
print(" PASS: test_parse_json_clean")
def test_parse_json_fenced():
result = _parse_json_response('```json\n{"overall_quality": 80}\n```')
assert result["overall_quality"] == 80
print(" PASS: test_parse_json_fenced")
def test_parse_json_garbage():
assert _parse_json_response("no json") == {}
print(" PASS: test_parse_json_garbage")
def test_glitch_dataclass():
g = Glitch(type="z_fighting", severity=Severity.MAJOR, region="center", description="Shimmer", confidence=0.8)
assert g.type == "z_fighting"
assert g.confidence == 0.8
print(" PASS: test_glitch_dataclass")
def test_report_dataclass():
r = GlitchReport(source="test.png", status="WARN", score=75)
r.glitches.append(Glitch(type="float", severity=Severity.MINOR))
assert len(r.glitches) == 1
assert r.score == 75
print(" PASS: test_report_dataclass")
def test_format_json():
r = GlitchReport(source="test.png", status="PASS", score=90, summary="Clean")
r.glitches.append(Glitch(type="cosmetic", severity=Severity.COSMETIC, description="Minor"))
output = format_report(r, "json")
parsed = json.loads(output)
assert parsed["status"] == "PASS"
assert len(parsed["glitches"]) == 1
print(" PASS: test_format_json")
def test_format_text():
r = GlitchReport(source="test.png", status="FAIL", score=30, summary="Critical glitch")
r.glitches.append(Glitch(type="render_failure", severity=Severity.CRITICAL, description="Black screen"))
output = format_report(r, "text")
assert "FAIL" in output
assert "render_failure" in output
print(" PASS: test_format_text")
def test_html_report_basic():
r = GlitchReport(source="test.png", status="PASS", score=100)
html = generate_html_report([r], title="Test Report")
assert "<!DOCTYPE html>" in html
assert "Test Report" in html
assert "PASS" in html
assert "100" in html
print(" PASS: test_html_report_basic")
def test_html_report_with_glitches():
r = GlitchReport(source="test.png", status="FAIL", score=40)
r.glitches.append(Glitch(type="z_fighting", severity=Severity.CRITICAL, region="center", description="Heavy flicker", confidence=0.9))
r.glitches.append(Glitch(type="clipping", severity=Severity.MINOR, region="bottom", description="Object through floor", confidence=0.6))
html = generate_html_report([r], title="Glitch Report")
assert "z_fighting" in html
assert "CRITICAL" in html
assert "clipping" in html
assert "Heavy flicker" in html
print(" PASS: test_html_report_with_glitches")
def test_html_report_multi():
r1 = GlitchReport(source="a.png", status="PASS", score=95)
r2 = GlitchReport(source="b.png", status="WARN", score=70)
r2.glitches.append(Glitch(type="texture_pop", severity=Severity.MAJOR))
html = generate_html_report([r1, r2])
assert "a.png" in html
assert "b.png" in html
assert "2" in html # 2 screenshots
print(" PASS: test_html_report_multi")
def test_html_self_contained():
r = GlitchReport(source="test.png", status="PASS", score=100)
html = generate_html_report([r])
assert "external" not in html.lower() or "no external dependencies" in html.lower()
assert "<style>" in html # Inline CSS
print(" PASS: test_html_self_contained")
def test_missing_image():
r = GlitchReport(source="/nonexistent/image.png")
# detect_glitches would set FAIL — simulate
r.status = "FAIL"
r.score = 0
r.summary = "File not found"
assert r.status == "FAIL"
print(" PASS: test_missing_image")
def test_severity_enum():
assert Severity.CRITICAL.value == "critical"
assert Severity.MAJOR.value == "major"
print(" PASS: test_severity_enum")
def run_all():
print("=== matrix_glitch_detect tests ===")
tests = [
test_parse_json_clean, test_parse_json_fenced, test_parse_json_garbage,
test_glitch_dataclass, test_report_dataclass,
test_format_json, test_format_text,
test_html_report_basic, test_html_report_with_glitches,
test_html_report_multi, test_html_self_contained,
test_missing_image, test_severity_enum,
]
passed = failed = 0
for t in tests:
try:
t()
passed += 1
except Exception as e:
print(f" FAIL: {t.__name__}{e}")
failed += 1
print(f"\n{'ALL PASSED' if failed == 0 else f'{failed} FAILED'}: {passed}/{len(tests)}")
return failed == 0
if __name__ == "__main__":
sys.exit(0 if run_all() else 1)

View File

@@ -1,100 +0,0 @@
{"song": "Overture in Silence", "artist": "Chamber Ensemble", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The silence before the first note is the real overture", "scene": {"mood": "grandeur", "colors": ["ivory", "gold", "deep red"], "composition": "concert hall wide", "camera": "crane sweep", "description": "Concert Hall Wide. The silence before the first note is the real overture"}}
{"song": "Overture in Silence", "artist": "Chamber Ensemble", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "We tune to the frequency of the void", "scene": {"mood": "intimacy", "colors": ["midnight blue", "silver", "cream"], "composition": "conductor close-up", "camera": "slow dolly", "description": "Conductor Close Up. We tune to the frequency of the void"}}
{"song": "Overture in Silence", "artist": "Chamber Ensemble", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The overture is the question, the symphony is the answer", "scene": {"mood": "sorrow", "colors": ["forest green", "bronze", "white"], "composition": "instrument detail", "camera": "locked", "description": "Instrument Detail. The overture is the question, the symphony is the answer"}}
{"song": "Overture in Silence", "artist": "Chamber Ensemble", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Silence is the most expensive instrument", "scene": {"mood": "triumph", "colors": ["burgundy", "black", "pearl"], "composition": "audience rapture", "camera": "gentle pan", "description": "Audience Rapture. Silence is the most expensive instrument"}}
{"song": "Overture in Silence", "artist": "Chamber Ensemble", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The overture promises what the finale may not deliver", "scene": {"mood": "tension", "colors": ["slate", "gold", "champagne"], "composition": "orchestra panorama", "camera": "rack focus", "description": "Orchestra Panorama. The overture promises what the finale may not deliver"}}
{"song": "Overture in Silence", "artist": "Chamber Ensemble", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Every great work begins with a held breath", "scene": {"mood": "serenity", "colors": ["ivory", "gold", "deep red"], "composition": "soloist spotlight", "camera": "drone overhead", "description": "Soloist Spotlight. Every great work begins with a held breath"}}
{"song": "Overture in Silence", "artist": "Chamber Ensemble", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The silence contains every note that will ever be played", "scene": {"mood": "dread", "colors": ["midnight blue", "silver", "cream"], "composition": "sheet music", "camera": "tracking", "description": "Sheet Music. The silence contains every note that will ever be played"}}
{"song": "Overture in Silence", "artist": "Chamber Ensemble", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Overture: the handshake between composer and universe", "scene": {"mood": "wonder", "colors": ["forest green", "bronze", "white"], "composition": "empty stage", "camera": "steady", "description": "Empty Stage. Overture: the handshake between composer and universe"}}
{"song": "Overture in Silence", "artist": "Chamber Ensemble", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The silence listens as intently as the audience", "scene": {"mood": "devastation", "colors": ["burgundy", "black", "pearl"], "composition": "backstage", "camera": "float", "description": "Backstage. The silence listens as intently as the audience"}}
{"song": "Overture in Silence", "artist": "Chamber Ensemble", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The overture is the thesis, the coda is the proof", "scene": {"mood": "resolution", "colors": ["slate", "gold", "champagne"], "composition": "balcony view", "camera": "dolly out", "description": "Balcony View. The overture is the thesis, the coda is the proof"}}
{"song": "Adagio for the Forgotten", "artist": "Philharmonic Ghost", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "We play slowly so the lost can catch up", "scene": {"mood": "grandeur", "colors": ["ivory", "gold", "deep red"], "composition": "concert hall wide", "camera": "crane sweep", "description": "Concert Hall Wide. We play slowly so the lost can catch up"}}
{"song": "Adagio for the Forgotten", "artist": "Philharmonic Ghost", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Adagio: the speed of grief", "scene": {"mood": "intimacy", "colors": ["midnight blue", "silver", "cream"], "composition": "conductor close-up", "camera": "slow dolly", "description": "Conductor Close Up. Adagio: the speed of grief"}}
{"song": "Adagio for the Forgotten", "artist": "Philharmonic Ghost", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The forgotten need slow music to find their way back", "scene": {"mood": "sorrow", "colors": ["forest green", "bronze", "white"], "composition": "instrument detail", "camera": "locked", "description": "Instrument Detail. The forgotten need slow music to find their way back"}}
{"song": "Adagio for the Forgotten", "artist": "Philharmonic Ghost", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every adagio is a love letter to patience", "scene": {"mood": "triumph", "colors": ["burgundy", "black", "pearl"], "composition": "audience rapture", "camera": "gentle pan", "description": "Audience Rapture. Every adagio is a love letter to patience"}}
{"song": "Adagio for the Forgotten", "artist": "Philharmonic Ghost", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "We slow down because the world won't", "scene": {"mood": "tension", "colors": ["slate", "gold", "champagne"], "composition": "orchestra panorama", "camera": "rack focus", "description": "Orchestra Panorama. We slow down because the world won't"}}
{"song": "Adagio for the Forgotten", "artist": "Philharmonic Ghost", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Adagio for the ones who missed the crescendo", "scene": {"mood": "serenity", "colors": ["ivory", "gold", "deep red"], "composition": "soloist spotlight", "camera": "drone overhead", "description": "Soloist Spotlight. Adagio for the ones who missed the crescendo"}}
{"song": "Adagio for the Forgotten", "artist": "Philharmonic Ghost", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The tempo of remembering is always slow", "scene": {"mood": "dread", "colors": ["midnight blue", "silver", "cream"], "composition": "sheet music", "camera": "tracking", "description": "Sheet Music. The tempo of remembering is always slow"}}
{"song": "Adagio for the Forgotten", "artist": "Philharmonic Ghost", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "We play adagio so the echoes can keep up", "scene": {"mood": "wonder", "colors": ["forest green", "bronze", "white"], "composition": "empty stage", "camera": "steady", "description": "Empty Stage. We play adagio so the echoes can keep up"}}
{"song": "Adagio for the Forgotten", "artist": "Philharmonic Ghost", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The forgotten don't need speed\u2014they need time", "scene": {"mood": "devastation", "colors": ["burgundy", "black", "pearl"], "composition": "backstage", "camera": "float", "description": "Backstage. The forgotten don't need speed\u2014they need time"}}
{"song": "Adagio for the Forgotten", "artist": "Philharmonic Ghost", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Adagio: the music of the space between heartbeats", "scene": {"mood": "resolution", "colors": ["slate", "gold", "champagne"], "composition": "balcony view", "camera": "dolly out", "description": "Balcony View. Adagio: the music of the space between heartbeats"}}
{"song": "Cadenza of Ashes", "artist": "Adagio Collective", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The solo rises from what burned", "scene": {"mood": "grandeur", "colors": ["ivory", "gold", "deep red"], "composition": "concert hall wide", "camera": "crane sweep", "description": "Concert Hall Wide. The solo rises from what burned"}}
{"song": "Cadenza of Ashes", "artist": "Adagio Collective", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Cadenza: one voice against the orchestra of time", "scene": {"mood": "intimacy", "colors": ["midnight blue", "silver", "cream"], "composition": "conductor close-up", "camera": "slow dolly", "description": "Conductor Close Up. Cadenza: one voice against the orchestra of time"}}
{"song": "Cadenza of Ashes", "artist": "Adagio Collective", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "From ashes we play the most beautiful solo", "scene": {"mood": "sorrow", "colors": ["forest green", "bronze", "white"], "composition": "instrument detail", "camera": "locked", "description": "Instrument Detail. From ashes we play the most beautiful solo"}}
{"song": "Cadenza of Ashes", "artist": "Adagio Collective", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The cadenza is the composer's dare", "scene": {"mood": "triumph", "colors": ["burgundy", "black", "pearl"], "composition": "audience rapture", "camera": "gentle pan", "description": "Audience Rapture. The cadenza is the composer's dare"}}
{"song": "Cadenza of Ashes", "artist": "Adagio Collective", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Every cadenza is a controlled demolition", "scene": {"mood": "tension", "colors": ["slate", "gold", "champagne"], "composition": "orchestra panorama", "camera": "rack focus", "description": "Orchestra Panorama. Every cadenza is a controlled demolition"}}
{"song": "Cadenza of Ashes", "artist": "Adagio Collective", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Ashes carry the memory of the flame", "scene": {"mood": "serenity", "colors": ["ivory", "gold", "deep red"], "composition": "soloist spotlight", "camera": "drone overhead", "description": "Soloist Spotlight. Ashes carry the memory of the flame"}}
{"song": "Cadenza of Ashes", "artist": "Adagio Collective", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The soloist plays what the orchestra fears", "scene": {"mood": "dread", "colors": ["midnight blue", "silver", "cream"], "composition": "sheet music", "camera": "tracking", "description": "Sheet Music. The soloist plays what the orchestra fears"}}
{"song": "Cadenza of Ashes", "artist": "Adagio Collective", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Cadenza of ashes: beauty from destruction", "scene": {"mood": "wonder", "colors": ["forest green", "bronze", "white"], "composition": "empty stage", "camera": "steady", "description": "Empty Stage. Cadenza of ashes: beauty from destruction"}}
{"song": "Cadenza of Ashes", "artist": "Adagio Collective", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The cadenza breaks the rules to honor them", "scene": {"mood": "devastation", "colors": ["burgundy", "black", "pearl"], "composition": "backstage", "camera": "float", "description": "Backstage. The cadenza breaks the rules to honor them"}}
{"song": "Cadenza of Ashes", "artist": "Adagio Collective", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "From ashes: a solo that rebuilds the world", "scene": {"mood": "resolution", "colors": ["slate", "gold", "champagne"], "composition": "balcony view", "camera": "dolly out", "description": "Balcony View. From ashes: a solo that rebuilds the world"}}
{"song": "Fugue State", "artist": "Cadenza Voice", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The theme enters, exits, re-enters in disguise", "scene": {"mood": "grandeur", "colors": ["ivory", "gold", "deep red"], "composition": "concert hall wide", "camera": "crane sweep", "description": "Concert Hall Wide. The theme enters, exits, re-enters in disguise"}}
{"song": "Fugue State", "artist": "Cadenza Voice", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Fugue: the mind arguing with itself in harmony", "scene": {"mood": "intimacy", "colors": ["midnight blue", "silver", "cream"], "composition": "conductor close-up", "camera": "slow dolly", "description": "Conductor Close Up. Fugue: the mind arguing with itself in harmony"}}
{"song": "Fugue State", "artist": "Cadenza Voice", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every voice has the same melody and a different truth", "scene": {"mood": "sorrow", "colors": ["forest green", "bronze", "white"], "composition": "instrument detail", "camera": "locked", "description": "Instrument Detail. Every voice has the same melody and a different truth"}}
{"song": "Fugue State", "artist": "Cadenza Voice", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The fugue is democracy in four voices", "scene": {"mood": "triumph", "colors": ["burgundy", "black", "pearl"], "composition": "audience rapture", "camera": "gentle pan", "description": "Audience Rapture. The fugue is democracy in four voices"}}
{"song": "Fugue State", "artist": "Cadenza Voice", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "We enter the fugue state and lose ourselves", "scene": {"mood": "tension", "colors": ["slate", "gold", "champagne"], "composition": "orchestra panorama", "camera": "rack focus", "description": "Orchestra Panorama. We enter the fugue state and lose ourselves"}}
{"song": "Fugue State", "artist": "Cadenza Voice", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The theme is the question, the answer is the echo", "scene": {"mood": "serenity", "colors": ["ivory", "gold", "deep red"], "composition": "soloist spotlight", "camera": "drone overhead", "description": "Soloist Spotlight. The theme is the question, the answer is the echo"}}
{"song": "Fugue State", "artist": "Cadenza Voice", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Fugue: the most organized form of chaos", "scene": {"mood": "dread", "colors": ["midnight blue", "silver", "cream"], "composition": "sheet music", "camera": "tracking", "description": "Sheet Music. Fugue: the most organized form of chaos"}}
{"song": "Fugue State", "artist": "Cadenza Voice", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Each voice carries a piece of the whole", "scene": {"mood": "wonder", "colors": ["forest green", "bronze", "white"], "composition": "empty stage", "camera": "steady", "description": "Empty Stage. Each voice carries a piece of the whole"}}
{"song": "Fugue State", "artist": "Cadenza Voice", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The fugue state is where the composer goes to think", "scene": {"mood": "devastation", "colors": ["burgundy", "black", "pearl"], "composition": "backstage", "camera": "float", "description": "Backstage. The fugue state is where the composer goes to think"}}
{"song": "Fugue State", "artist": "Cadenza Voice", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The fugue ends when all voices agree to disagree", "scene": {"mood": "resolution", "colors": ["slate", "gold", "champagne"], "composition": "balcony view", "camera": "dolly out", "description": "Balcony View. The fugue ends when all voices agree to disagree"}}
{"song": "Requiem for Tomorrow", "artist": "Overture House", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "We play for the day that hasn't come yet", "scene": {"mood": "grandeur", "colors": ["ivory", "gold", "deep red"], "composition": "concert hall wide", "camera": "crane sweep", "description": "Concert Hall Wide. We play for the day that hasn't come yet"}}
{"song": "Requiem for Tomorrow", "artist": "Overture House", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Requiem: mourning the future in advance", "scene": {"mood": "intimacy", "colors": ["midnight blue", "silver", "cream"], "composition": "conductor close-up", "camera": "slow dolly", "description": "Conductor Close Up. Requiem: mourning the future in advance"}}
{"song": "Requiem for Tomorrow", "artist": "Overture House", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Tomorrow's requiem is today's lullaby", "scene": {"mood": "sorrow", "colors": ["forest green", "bronze", "white"], "composition": "instrument detail", "camera": "locked", "description": "Instrument Detail. Tomorrow's requiem is today's lullaby"}}
{"song": "Requiem for Tomorrow", "artist": "Overture House", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The requiem doesn't ask permission", "scene": {"mood": "triumph", "colors": ["burgundy", "black", "pearl"], "composition": "audience rapture", "camera": "gentle pan", "description": "Audience Rapture. The requiem doesn't ask permission"}}
{"song": "Requiem for Tomorrow", "artist": "Overture House", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "We mourn what we haven't lost yet", "scene": {"mood": "tension", "colors": ["slate", "gold", "champagne"], "composition": "orchestra panorama", "camera": "rack focus", "description": "Orchestra Panorama. We mourn what we haven't lost yet"}}
{"song": "Requiem for Tomorrow", "artist": "Overture House", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Tomorrow's death makes today's music urgent", "scene": {"mood": "serenity", "colors": ["ivory", "gold", "deep red"], "composition": "soloist spotlight", "camera": "drone overhead", "description": "Soloist Spotlight. Tomorrow's death makes today's music urgent"}}
{"song": "Requiem for Tomorrow", "artist": "Overture House", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Requiem for the plans that won't survive", "scene": {"mood": "dread", "colors": ["midnight blue", "silver", "cream"], "composition": "sheet music", "camera": "tracking", "description": "Sheet Music. Requiem for the plans that won't survive"}}
{"song": "Requiem for Tomorrow", "artist": "Overture House", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The requiem is the most honest prayer", "scene": {"mood": "wonder", "colors": ["forest green", "bronze", "white"], "composition": "empty stage", "camera": "steady", "description": "Empty Stage. The requiem is the most honest prayer"}}
{"song": "Requiem for Tomorrow", "artist": "Overture House", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Tomorrow needs a requiem because today needs hope", "scene": {"mood": "devastation", "colors": ["burgundy", "black", "pearl"], "composition": "backstage", "camera": "float", "description": "Backstage. Tomorrow needs a requiem because today needs hope"}}
{"song": "Requiem for Tomorrow", "artist": "Overture House", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The requiem ends and tomorrow arrives anyway", "scene": {"mood": "resolution", "colors": ["slate", "gold", "champagne"], "composition": "balcony view", "camera": "dolly out", "description": "Balcony View. The requiem ends and tomorrow arrives anyway"}}
{"song": "Nocturne for the Restless", "artist": "Chamber Ensemble", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The nocturne plays for those who can't sleep", "scene": {"mood": "grandeur", "colors": ["ivory", "gold", "deep red"], "composition": "concert hall wide", "camera": "crane sweep", "description": "Concert Hall Wide. The nocturne plays for those who can't sleep"}}
{"song": "Nocturne for the Restless", "artist": "Chamber Ensemble", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Restless: the tempo of insomnia", "scene": {"mood": "intimacy", "colors": ["midnight blue", "silver", "cream"], "composition": "conductor close-up", "camera": "slow dolly", "description": "Conductor Close Up. Restless: the tempo of insomnia"}}
{"song": "Nocturne for the Restless", "artist": "Chamber Ensemble", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every note is a counted sheep that escaped", "scene": {"mood": "sorrow", "colors": ["forest green", "bronze", "white"], "composition": "instrument detail", "camera": "locked", "description": "Instrument Detail. Every note is a counted sheep that escaped"}}
{"song": "Nocturne for the Restless", "artist": "Chamber Ensemble", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The nocturne understands the dark", "scene": {"mood": "triumph", "colors": ["burgundy", "black", "pearl"], "composition": "audience rapture", "camera": "gentle pan", "description": "Audience Rapture. The nocturne understands the dark"}}
{"song": "Nocturne for the Restless", "artist": "Chamber Ensemble", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Nocturne for the ones who stare at ceilings", "scene": {"mood": "tension", "colors": ["slate", "gold", "champagne"], "composition": "orchestra panorama", "camera": "rack focus", "description": "Orchestra Panorama. Nocturne for the ones who stare at ceilings"}}
{"song": "Nocturne for the Restless", "artist": "Chamber Ensemble", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The restless don't need lullabies\u2014they need truth", "scene": {"mood": "serenity", "colors": ["ivory", "gold", "deep red"], "composition": "soloist spotlight", "camera": "drone overhead", "description": "Soloist Spotlight. The restless don't need lullabies\u2014they need truth"}}
{"song": "Nocturne for the Restless", "artist": "Chamber Ensemble", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We play the nocturne because the night demands music", "scene": {"mood": "dread", "colors": ["midnight blue", "silver", "cream"], "composition": "sheet music", "camera": "tracking", "description": "Sheet Music. We play the nocturne because the night demands music"}}
{"song": "Nocturne for the Restless", "artist": "Chamber Ensemble", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Nocturne: the companion of the sleepless", "scene": {"mood": "wonder", "colors": ["forest green", "bronze", "white"], "composition": "empty stage", "camera": "steady", "description": "Empty Stage. Nocturne: the companion of the sleepless"}}
{"song": "Nocturne for the Restless", "artist": "Chamber Ensemble", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The nocturne is the night's way of saying I'm here too", "scene": {"mood": "devastation", "colors": ["burgundy", "black", "pearl"], "composition": "backstage", "camera": "float", "description": "Backstage. The nocturne is the night's way of saying I'm here too"}}
{"song": "Nocturne for the Restless", "artist": "Chamber Ensemble", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "For the restless: a nocturne that never resolves", "scene": {"mood": "resolution", "colors": ["slate", "gold", "champagne"], "composition": "balcony view", "camera": "dolly out", "description": "Balcony View. For the restless: a nocturne that never resolves"}}
{"song": "Sonata of Collapse", "artist": "Philharmonic Ghost", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The sonata structures the chaos of falling", "scene": {"mood": "grandeur", "colors": ["ivory", "gold", "deep red"], "composition": "concert hall wide", "camera": "crane sweep", "description": "Concert Hall Wide. The sonata structures the chaos of falling"}}
{"song": "Sonata of Collapse", "artist": "Philharmonic Ghost", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Collapse: the most organized form of destruction", "scene": {"mood": "intimacy", "colors": ["midnight blue", "silver", "cream"], "composition": "conductor close-up", "camera": "slow dolly", "description": "Conductor Close Up. Collapse: the most organized form of destruction"}}
{"song": "Sonata of Collapse", "artist": "Philharmonic Ghost", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every movement is a floor giving way", "scene": {"mood": "sorrow", "colors": ["forest green", "bronze", "white"], "composition": "instrument detail", "camera": "locked", "description": "Instrument Detail. Every movement is a floor giving way"}}
{"song": "Sonata of Collapse", "artist": "Philharmonic Ghost", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The sonata makes collapse beautiful", "scene": {"mood": "triumph", "colors": ["burgundy", "black", "pearl"], "composition": "audience rapture", "camera": "gentle pan", "description": "Audience Rapture. The sonata makes collapse beautiful"}}
{"song": "Sonata of Collapse", "artist": "Philharmonic Ghost", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Sonata of collapse: the architecture of ruin", "scene": {"mood": "tension", "colors": ["slate", "gold", "champagne"], "composition": "orchestra panorama", "camera": "rack focus", "description": "Orchestra Panorama. Sonata of collapse: the architecture of ruin"}}
{"song": "Sonata of Collapse", "artist": "Philharmonic Ghost", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We build sonatas from the rubble of symphonies", "scene": {"mood": "serenity", "colors": ["ivory", "gold", "deep red"], "composition": "soloist spotlight", "camera": "drone overhead", "description": "Soloist Spotlight. We build sonatas from the rubble of symphonies"}}
{"song": "Sonata of Collapse", "artist": "Philharmonic Ghost", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The collapse has a form and the sonata found it", "scene": {"mood": "dread", "colors": ["midnight blue", "silver", "cream"], "composition": "sheet music", "camera": "tracking", "description": "Sheet Music. The collapse has a form and the sonata found it"}}
{"song": "Sonata of Collapse", "artist": "Philharmonic Ghost", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Sonata: the blueprint for beautiful failure", "scene": {"mood": "wonder", "colors": ["forest green", "bronze", "white"], "composition": "empty stage", "camera": "steady", "description": "Empty Stage. Sonata: the blueprint for beautiful failure"}}
{"song": "Sonata of Collapse", "artist": "Philharmonic Ghost", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The sonata collapses into resolution", "scene": {"mood": "devastation", "colors": ["burgundy", "black", "pearl"], "composition": "backstage", "camera": "float", "description": "Backstage. The sonata collapses into resolution"}}
{"song": "Sonata of Collapse", "artist": "Philharmonic Ghost", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Collapse sonata: the final movement is always silence", "scene": {"mood": "resolution", "colors": ["slate", "gold", "champagne"], "composition": "balcony view", "camera": "dolly out", "description": "Balcony View. Collapse sonata: the final movement is always silence"}}
{"song": "Toccata of Tides", "artist": "Adagio Collective", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The toccata touches the keys like waves touch the shore", "scene": {"mood": "grandeur", "colors": ["ivory", "gold", "deep red"], "composition": "concert hall wide", "camera": "crane sweep", "description": "Concert Hall Wide. The toccata touches the keys like waves touch the shore"}}
{"song": "Toccata of Tides", "artist": "Adagio Collective", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Toccata: the touch of the inevitable", "scene": {"mood": "intimacy", "colors": ["midnight blue", "silver", "cream"], "composition": "conductor close-up", "camera": "slow dolly", "description": "Conductor Close Up. Toccata: the touch of the inevitable"}}
{"song": "Toccata of Tides", "artist": "Adagio Collective", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every wave is a finger on the harpsichord", "scene": {"mood": "sorrow", "colors": ["forest green", "bronze", "white"], "composition": "instrument detail", "camera": "locked", "description": "Instrument Detail. Every wave is a finger on the harpsichord"}}
{"song": "Toccata of Tides", "artist": "Adagio Collective", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The toccata plays in tidal patterns", "scene": {"mood": "triumph", "colors": ["burgundy", "black", "pearl"], "composition": "audience rapture", "camera": "gentle pan", "description": "Audience Rapture. The toccata plays in tidal patterns"}}
{"song": "Toccata of Tides", "artist": "Adagio Collective", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Toccata of tides: the keyboard ocean", "scene": {"mood": "tension", "colors": ["slate", "gold", "champagne"], "composition": "orchestra panorama", "camera": "rack focus", "description": "Orchestra Panorama. Toccata of tides: the keyboard ocean"}}
{"song": "Toccata of Tides", "artist": "Adagio Collective", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We play the toccata because the shore demands music", "scene": {"mood": "serenity", "colors": ["ivory", "gold", "deep red"], "composition": "soloist spotlight", "camera": "drone overhead", "description": "Soloist Spotlight. We play the toccata because the shore demands music"}}
{"song": "Toccata of Tides", "artist": "Adagio Collective", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The tide doesn't practice\u2014it just arrives", "scene": {"mood": "dread", "colors": ["midnight blue", "silver", "cream"], "composition": "sheet music", "camera": "tracking", "description": "Sheet Music. The tide doesn't practice\u2014it just arrives"}}
{"song": "Toccata of Tides", "artist": "Adagio Collective", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Toccata: the most physical form of prayer", "scene": {"mood": "wonder", "colors": ["forest green", "bronze", "white"], "composition": "empty stage", "camera": "steady", "description": "Empty Stage. Toccata: the most physical form of prayer"}}
{"song": "Toccata of Tides", "artist": "Adagio Collective", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The toccata rises and falls with the moon", "scene": {"mood": "devastation", "colors": ["burgundy", "black", "pearl"], "composition": "backstage", "camera": "float", "description": "Backstage. The toccata rises and falls with the moon"}}
{"song": "Toccata of Tides", "artist": "Adagio Collective", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Toccata of tides: touch, retreat, return", "scene": {"mood": "resolution", "colors": ["slate", "gold", "champagne"], "composition": "balcony view", "camera": "dolly out", "description": "Balcony View. Toccata of tides: touch, retreat, return"}}
{"song": "Partita of Passage", "artist": "Cadenza Voice", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The partita marks the crossing", "scene": {"mood": "grandeur", "colors": ["ivory", "gold", "deep red"], "composition": "concert hall wide", "camera": "crane sweep", "description": "Concert Hall Wide. The partita marks the crossing"}}
{"song": "Partita of Passage", "artist": "Cadenza Voice", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Partita: the suite of transformation", "scene": {"mood": "intimacy", "colors": ["midnight blue", "silver", "cream"], "composition": "conductor close-up", "camera": "slow dolly", "description": "Conductor Close Up. Partita: the suite of transformation"}}
{"song": "Partita of Passage", "artist": "Cadenza Voice", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every movement is a stage of becoming", "scene": {"mood": "sorrow", "colors": ["forest green", "bronze", "white"], "composition": "instrument detail", "camera": "locked", "description": "Instrument Detail. Every movement is a stage of becoming"}}
{"song": "Partita of Passage", "artist": "Cadenza Voice", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The partita plays in the doorway between worlds", "scene": {"mood": "triumph", "colors": ["burgundy", "black", "pearl"], "composition": "audience rapture", "camera": "gentle pan", "description": "Audience Rapture. The partita plays in the doorway between worlds"}}
{"song": "Partita of Passage", "artist": "Cadenza Voice", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Partita of passage: the music of transition", "scene": {"mood": "tension", "colors": ["slate", "gold", "champagne"], "composition": "orchestra panorama", "camera": "rack focus", "description": "Orchestra Panorama. Partita of passage: the music of transition"}}
{"song": "Partita of Passage", "artist": "Cadenza Voice", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We play the partita because the passage demands ceremony", "scene": {"mood": "serenity", "colors": ["ivory", "gold", "deep red"], "composition": "soloist spotlight", "camera": "drone overhead", "description": "Soloist Spotlight. We play the partita because the passage demands ceremony"}}
{"song": "Partita of Passage", "artist": "Cadenza Voice", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The partita doesn't end\u2014it transforms", "scene": {"mood": "dread", "colors": ["midnight blue", "silver", "cream"], "composition": "sheet music", "camera": "tracking", "description": "Sheet Music. The partita doesn't end\u2014it transforms"}}
{"song": "Partita of Passage", "artist": "Cadenza Voice", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Partita: the traveler's companion", "scene": {"mood": "wonder", "colors": ["forest green", "bronze", "white"], "composition": "empty stage", "camera": "steady", "description": "Empty Stage. Partita: the traveler's companion"}}
{"song": "Partita of Passage", "artist": "Cadenza Voice", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The partita crosses every threshold", "scene": {"mood": "devastation", "colors": ["burgundy", "black", "pearl"], "composition": "backstage", "camera": "float", "description": "Backstage. The partita crosses every threshold"}}
{"song": "Partita of Passage", "artist": "Cadenza Voice", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Partita of passage: from here to there in notes", "scene": {"mood": "resolution", "colors": ["slate", "gold", "champagne"], "composition": "balcony view", "camera": "dolly out", "description": "Balcony View. Partita of passage: from here to there in notes"}}
{"song": "Intermezzo for Insomnia", "artist": "Overture House", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The intermezzo fills the gap between sleeps", "scene": {"mood": "grandeur", "colors": ["ivory", "gold", "deep red"], "composition": "concert hall wide", "camera": "crane sweep", "description": "Concert Hall Wide. The intermezzo fills the gap between sleeps"}}
{"song": "Intermezzo for Insomnia", "artist": "Overture House", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Intermezzo: the music of the pause", "scene": {"mood": "intimacy", "colors": ["midnight blue", "silver", "cream"], "composition": "conductor close-up", "camera": "slow dolly", "description": "Conductor Close Up. Intermezzo: the music of the pause"}}
{"song": "Intermezzo for Insomnia", "artist": "Overture House", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every insomniac needs an intermezzo", "scene": {"mood": "sorrow", "colors": ["forest green", "bronze", "white"], "composition": "instrument detail", "camera": "locked", "description": "Instrument Detail. Every insomniac needs an intermezzo"}}
{"song": "Intermezzo for Insomnia", "artist": "Overture House", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The intermezzo plays in the space dreams should be", "scene": {"mood": "triumph", "colors": ["burgundy", "black", "pearl"], "composition": "audience rapture", "camera": "gentle pan", "description": "Audience Rapture. The intermezzo plays in the space dreams should be"}}
{"song": "Intermezzo for Insomnia", "artist": "Overture House", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Intermezzo: the bridge between two rests", "scene": {"mood": "tension", "colors": ["slate", "gold", "champagne"], "composition": "orchestra panorama", "camera": "rack focus", "description": "Orchestra Panorama. Intermezzo: the bridge between two rests"}}
{"song": "Intermezzo for Insomnia", "artist": "Overture House", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We play the intermezzo because the night is long", "scene": {"mood": "serenity", "colors": ["ivory", "gold", "deep red"], "composition": "soloist spotlight", "camera": "drone overhead", "description": "Soloist Spotlight. We play the intermezzo because the night is long"}}
{"song": "Intermezzo for Insomnia", "artist": "Overture House", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The intermezzo is the insomniac's lullaby", "scene": {"mood": "dread", "colors": ["midnight blue", "silver", "cream"], "composition": "sheet music", "camera": "tracking", "description": "Sheet Music. The intermezzo is the insomniac's lullaby"}}
{"song": "Intermezzo for Insomnia", "artist": "Overture House", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Intermezzo: the gentle interruption", "scene": {"mood": "wonder", "colors": ["forest green", "bronze", "white"], "composition": "empty stage", "camera": "steady", "description": "Empty Stage. Intermezzo: the gentle interruption"}}
{"song": "Intermezzo for Insomnia", "artist": "Overture House", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The intermezzo is the night's way of saying stay", "scene": {"mood": "devastation", "colors": ["burgundy", "black", "pearl"], "composition": "backstage", "camera": "float", "description": "Backstage. The intermezzo is the night's way of saying stay"}}
{"song": "Intermezzo for Insomnia", "artist": "Overture House", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Intermezzo for insomnia: music for the sleepless and the brave", "scene": {"mood": "resolution", "colors": ["slate", "gold", "champagne"], "composition": "balcony view", "camera": "dolly out", "description": "Balcony View. Intermezzo for insomnia: music for the sleepless and the brave"}}

View File

@@ -1,100 +0,0 @@
{"song": "Dust and Devotion", "artist": "Dust Bowl", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The dust remembers every boot that passed", "scene": {"mood": "resilience", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "wide horizon", "camera": "steady wide", "description": "Wide Horizon. The dust remembers every boot that passed"}}
{"song": "Dust and Devotion", "artist": "Dust Bowl", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Devotion is showing up when the rain don't stop", "scene": {"mood": "longing", "colors": ["sage green", "cream", "rust"], "composition": "porch view", "camera": "slow pan", "description": "Porch View. Devotion is showing up when the rain don't stop"}}
{"song": "Dust and Devotion", "artist": "Dust Bowl", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "We plant in mud and harvest in faith", "scene": {"mood": "pride", "colors": ["sky blue", "wheat gold", "brown"], "composition": "dirt road leading line", "camera": "drone sweep", "description": "Dirt Road Leading Line. We plant in mud and harvest in faith"}}
{"song": "Dust and Devotion", "artist": "Dust Bowl", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The road don't care about your pedigree", "scene": {"mood": "melancholy", "colors": ["sunset orange", "navy", "white"], "composition": "field panorama", "camera": "handheld", "description": "Field Panorama. The road don't care about your pedigree"}}
{"song": "Dust and Devotion", "artist": "Dust Bowl", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Dust to dust but the heart stays gold", "scene": {"mood": "freedom", "colors": ["barn red", "hay gold", "slate"], "composition": "pickup truck interior", "camera": "static", "description": "Pickup Truck Interior. Dust to dust but the heart stays gold"}}
{"song": "Dust and Devotion", "artist": "Dust Bowl", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Devotion is a fence mended at dawn", "scene": {"mood": "gratitude", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "small town main street", "camera": "tracking vehicle", "description": "Small Town Main Street. Devotion is a fence mended at dawn"}}
{"song": "Dust and Devotion", "artist": "Dust Bowl", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The land gives back what you give it", "scene": {"mood": "restlessness", "colors": ["sage green", "cream", "rust"], "composition": "creek reflection", "camera": "low angle", "description": "Creek Reflection. The land gives back what you give it"}}
{"song": "Dust and Devotion", "artist": "Dust Bowl", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "We pray with calloused hands and open hearts", "scene": {"mood": "faith", "colors": ["sky blue", "wheat gold", "brown"], "composition": "barn silhouette", "camera": "crane up", "description": "Barn Silhouette. We pray with calloused hands and open hearts"}}
{"song": "Dust and Devotion", "artist": "Dust Bowl", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Dust is just earth remembering the sky", "scene": {"mood": "wanderlust", "colors": ["sunset orange", "navy", "white"], "composition": "fence line", "camera": "dolly in", "description": "Fence Line. Dust is just earth remembering the sky"}}
{"song": "Dust and Devotion", "artist": "Dust Bowl", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Devotion: the longest prayer without words", "scene": {"mood": "homecoming", "colors": ["barn red", "hay gold", "slate"], "composition": "sunset gradient", "camera": "locked", "description": "Sunset Gradient. Devotion: the longest prayer without words"}}
{"song": "Gravel Psalm", "artist": "Prairie Ghost", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Every stone a verse in the road's hymn", "scene": {"mood": "resilience", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "wide horizon", "camera": "steady wide", "description": "Wide Horizon. Every stone a verse in the road's hymn"}}
{"song": "Gravel Psalm", "artist": "Prairie Ghost", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The psalm is written in tire tracks", "scene": {"mood": "longing", "colors": ["sage green", "cream", "rust"], "composition": "porch view", "camera": "slow pan", "description": "Porch View. The psalm is written in tire tracks"}}
{"song": "Gravel Psalm", "artist": "Prairie Ghost", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Gravel crunches like a congregation", "scene": {"mood": "pride", "colors": ["sky blue", "wheat gold", "brown"], "composition": "dirt road leading line", "camera": "drone sweep", "description": "Dirt Road Leading Line. Gravel crunches like a congregation"}}
{"song": "Gravel Psalm", "artist": "Prairie Ghost", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "We sing to the fields and the fields sing back", "scene": {"mood": "melancholy", "colors": ["sunset orange", "navy", "white"], "composition": "field panorama", "camera": "handheld", "description": "Field Panorama. We sing to the fields and the fields sing back"}}
{"song": "Gravel Psalm", "artist": "Prairie Ghost", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The psalm of the lonely and the loved", "scene": {"mood": "freedom", "colors": ["barn red", "hay gold", "slate"], "composition": "pickup truck interior", "camera": "static", "description": "Pickup Truck Interior. The psalm of the lonely and the loved"}}
{"song": "Gravel Psalm", "artist": "Prairie Ghost", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Gravel roads lead to gravel truths", "scene": {"mood": "gratitude", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "small town main street", "camera": "tracking vehicle", "description": "Small Town Main Street. Gravel roads lead to gravel truths"}}
{"song": "Gravel Psalm", "artist": "Prairie Ghost", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Every psalm needs a little grit", "scene": {"mood": "restlessness", "colors": ["sage green", "cream", "rust"], "composition": "creek reflection", "camera": "low angle", "description": "Creek Reflection. Every psalm needs a little grit"}}
{"song": "Gravel Psalm", "artist": "Prairie Ghost", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The gravel remembers your grandmother's steps", "scene": {"mood": "faith", "colors": ["sky blue", "wheat gold", "brown"], "composition": "barn silhouette", "camera": "crane up", "description": "Barn Silhouette. The gravel remembers your grandmother's steps"}}
{"song": "Gravel Psalm", "artist": "Prairie Ghost", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Psalm of the small town saint", "scene": {"mood": "wanderlust", "colors": ["sunset orange", "navy", "white"], "composition": "fence line", "camera": "dolly in", "description": "Fence Line. Psalm of the small town saint"}}
{"song": "Gravel Psalm", "artist": "Prairie Ghost", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Gravel and grace\u2014the country creed", "scene": {"mood": "homecoming", "colors": ["barn red", "hay gold", "slate"], "composition": "sunset gradient", "camera": "locked", "description": "Sunset Gradient. Gravel and grace\u2014the country creed"}}
{"song": "Porchlight Theology", "artist": "Timber Heart", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The porch light is the oldest sermon", "scene": {"mood": "resilience", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "wide horizon", "camera": "steady wide", "description": "Wide Horizon. The porch light is the oldest sermon"}}
{"song": "Porchlight Theology", "artist": "Timber Heart", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "We debate God on the front steps", "scene": {"mood": "longing", "colors": ["sage green", "cream", "rust"], "composition": "porch view", "camera": "slow pan", "description": "Porch View. We debate God on the front steps"}}
{"song": "Porchlight Theology", "artist": "Timber Heart", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Theology tastes like sweet tea at dusk", "scene": {"mood": "pride", "colors": ["sky blue", "wheat gold", "brown"], "composition": "dirt road leading line", "camera": "drone sweep", "description": "Dirt Road Leading Line. Theology tastes like sweet tea at dusk"}}
{"song": "Porchlight Theology", "artist": "Timber Heart", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every creak of the porch is a hymn", "scene": {"mood": "melancholy", "colors": ["sunset orange", "navy", "white"], "composition": "field panorama", "camera": "handheld", "description": "Field Panorama. Every creak of the porch is a hymn"}}
{"song": "Porchlight Theology", "artist": "Timber Heart", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The light draws moths and miracles alike", "scene": {"mood": "freedom", "colors": ["barn red", "hay gold", "slate"], "composition": "pickup truck interior", "camera": "static", "description": "Pickup Truck Interior. The light draws moths and miracles alike"}}
{"song": "Porchlight Theology", "artist": "Timber Heart", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We find God in the space between screen doors", "scene": {"mood": "gratitude", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "small town main street", "camera": "tracking vehicle", "description": "Small Town Main Street. We find God in the space between screen doors"}}
{"song": "Porchlight Theology", "artist": "Timber Heart", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Porchlight: the original candle in the dark", "scene": {"mood": "restlessness", "colors": ["sage green", "cream", "rust"], "composition": "creek reflection", "camera": "low angle", "description": "Creek Reflection. Porchlight: the original candle in the dark"}}
{"song": "Porchlight Theology", "artist": "Timber Heart", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Theology for the tired and the tender", "scene": {"mood": "faith", "colors": ["sky blue", "wheat gold", "brown"], "composition": "barn silhouette", "camera": "crane up", "description": "Barn Silhouette. Theology for the tired and the tender"}}
{"song": "Porchlight Theology", "artist": "Timber Heart", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The porch doesn't judge\u2014it just glows", "scene": {"mood": "wanderlust", "colors": ["sunset orange", "navy", "white"], "composition": "fence line", "camera": "dolly in", "description": "Fence Line. The porch doesn't judge\u2014it just glows"}}
{"song": "Porchlight Theology", "artist": "Timber Heart", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "We built churches on porches before the steeple", "scene": {"mood": "homecoming", "colors": ["barn red", "hay gold", "slate"], "composition": "sunset gradient", "camera": "locked", "description": "Sunset Gradient. We built churches on porches before the steeple"}}
{"song": "Timber Requiem", "artist": "Gravel Road", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Fallen trees sing louder than standing ones", "scene": {"mood": "resilience", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "wide horizon", "camera": "steady wide", "description": "Wide Horizon. Fallen trees sing louder than standing ones"}}
{"song": "Timber Requiem", "artist": "Gravel Road", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The requiem is in the rings of the trunk", "scene": {"mood": "longing", "colors": ["sage green", "cream", "rust"], "composition": "porch view", "camera": "slow pan", "description": "Porch View. The requiem is in the rings of the trunk"}}
{"song": "Timber Requiem", "artist": "Gravel Road", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Timber\u2014the sound of something ending beautifully", "scene": {"mood": "pride", "colors": ["sky blue", "wheat gold", "brown"], "composition": "dirt road leading line", "camera": "drone sweep", "description": "Dirt Road Leading Line. Timber\u2014the sound of something ending beautifully"}}
{"song": "Timber Requiem", "artist": "Gravel Road", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "We mourn by planting what we've lost", "scene": {"mood": "melancholy", "colors": ["sunset orange", "navy", "white"], "composition": "field panorama", "camera": "handheld", "description": "Field Panorama. We mourn by planting what we've lost"}}
{"song": "Timber Requiem", "artist": "Gravel Road", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The requiem plays in the sawmill's hum", "scene": {"mood": "freedom", "colors": ["barn red", "hay gold", "slate"], "composition": "pickup truck interior", "camera": "static", "description": "Pickup Truck Interior. The requiem plays in the sawmill's hum"}}
{"song": "Timber Requiem", "artist": "Gravel Road", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Timber heart: strong until it's not", "scene": {"mood": "gratitude", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "small town main street", "camera": "tracking vehicle", "description": "Small Town Main Street. Timber heart: strong until it's not"}}
{"song": "Timber Requiem", "artist": "Gravel Road", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The requiem is for the forest not the tree", "scene": {"mood": "restlessness", "colors": ["sage green", "cream", "rust"], "composition": "creek reflection", "camera": "low angle", "description": "Creek Reflection. The requiem is for the forest not the tree"}}
{"song": "Timber Requiem", "artist": "Gravel Road", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "We build our homes from our grief", "scene": {"mood": "faith", "colors": ["sky blue", "wheat gold", "brown"], "composition": "barn silhouette", "camera": "crane up", "description": "Barn Silhouette. We build our homes from our grief"}}
{"song": "Timber Requiem", "artist": "Gravel Road", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Timber falls but the roots remember", "scene": {"mood": "wanderlust", "colors": ["sunset orange", "navy", "white"], "composition": "fence line", "camera": "dolly in", "description": "Fence Line. Timber falls but the roots remember"}}
{"song": "Timber Requiem", "artist": "Gravel Road", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Requiem: the song after the last tree", "scene": {"mood": "homecoming", "colors": ["barn red", "hay gold", "slate"], "composition": "sunset gradient", "camera": "locked", "description": "Sunset Gradient. Requiem: the song after the last tree"}}
{"song": "Copper Creek Psalm", "artist": "Copper Creek", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The creek sings in copper tones at dusk", "scene": {"mood": "resilience", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "wide horizon", "camera": "steady wide", "description": "Wide Horizon. The creek sings in copper tones at dusk"}}
{"song": "Copper Creek Psalm", "artist": "Copper Creek", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Water over stones\u2014the oldest music", "scene": {"mood": "longing", "colors": ["sage green", "cream", "rust"], "composition": "porch view", "camera": "slow pan", "description": "Porch View. Water over stones\u2014the oldest music"}}
{"song": "Copper Creek Psalm", "artist": "Copper Creek", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "We baptize our doubts in the creek", "scene": {"mood": "pride", "colors": ["sky blue", "wheat gold", "brown"], "composition": "dirt road leading line", "camera": "drone sweep", "description": "Dirt Road Leading Line. We baptize our doubts in the creek"}}
{"song": "Copper Creek Psalm", "artist": "Copper Creek", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Copper Creek: where the water prays for us", "scene": {"mood": "melancholy", "colors": ["sunset orange", "navy", "white"], "composition": "field panorama", "camera": "handheld", "description": "Field Panorama. Copper Creek: where the water prays for us"}}
{"song": "Copper Creek Psalm", "artist": "Copper Creek", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The creek doesn't stop for anyone", "scene": {"mood": "freedom", "colors": ["barn red", "hay gold", "slate"], "composition": "pickup truck interior", "camera": "static", "description": "Pickup Truck Interior. The creek doesn't stop for anyone"}}
{"song": "Copper Creek Psalm", "artist": "Copper Creek", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Every stone in the creek is a forgiven sin", "scene": {"mood": "gratitude", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "small town main street", "camera": "tracking vehicle", "description": "Small Town Main Street. Every stone in the creek is a forgiven sin"}}
{"song": "Copper Creek Psalm", "artist": "Copper Creek", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The psalm of running water and standing trees", "scene": {"mood": "restlessness", "colors": ["sage green", "cream", "rust"], "composition": "creek reflection", "camera": "low angle", "description": "Creek Reflection. The psalm of running water and standing trees"}}
{"song": "Copper Creek Psalm", "artist": "Copper Creek", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Copper tones in the water's voice", "scene": {"mood": "faith", "colors": ["sky blue", "wheat gold", "brown"], "composition": "barn silhouette", "camera": "crane up", "description": "Barn Silhouette. Copper tones in the water's voice"}}
{"song": "Copper Creek Psalm", "artist": "Copper Creek", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The creek carries our secrets to the sea", "scene": {"mood": "wanderlust", "colors": ["sunset orange", "navy", "white"], "composition": "fence line", "camera": "dolly in", "description": "Fence Line. The creek carries our secrets to the sea"}}
{"song": "Copper Creek Psalm", "artist": "Copper Creek", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Copper Creek: liquid hymn for the weary", "scene": {"mood": "homecoming", "colors": ["barn red", "hay gold", "slate"], "composition": "sunset gradient", "camera": "locked", "description": "Sunset Gradient. Copper Creek: liquid hymn for the weary"}}
{"song": "Rusted Hymnal", "artist": "Dust Bowl", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The hymnal is rusted but the songs still play", "scene": {"mood": "resilience", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "wide horizon", "camera": "steady wide", "description": "Wide Horizon. The hymnal is rusted but the songs still play"}}
{"song": "Rusted Hymnal", "artist": "Dust Bowl", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Rusted: the patina of faith", "scene": {"mood": "longing", "colors": ["sage green", "cream", "rust"], "composition": "porch view", "camera": "slow pan", "description": "Porch View. Rusted: the patina of faith"}}
{"song": "Rusted Hymnal", "artist": "Dust Bowl", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every hymn in this book survived a flood", "scene": {"mood": "pride", "colors": ["sky blue", "wheat gold", "brown"], "composition": "dirt road leading line", "camera": "drone sweep", "description": "Dirt Road Leading Line. Every hymn in this book survived a flood"}}
{"song": "Rusted Hymnal", "artist": "Dust Bowl", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Rusted hymnal: the Bible of the barn", "scene": {"mood": "melancholy", "colors": ["sunset orange", "navy", "white"], "composition": "field panorama", "camera": "handheld", "description": "Field Panorama. Rusted hymnal: the Bible of the barn"}}
{"song": "Rusted Hymnal", "artist": "Dust Bowl", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The rust is just the hymn's autobiography", "scene": {"mood": "freedom", "colors": ["barn red", "hay gold", "slate"], "composition": "pickup truck interior", "camera": "static", "description": "Pickup Truck Interior. The rust is just the hymn's autobiography"}}
{"song": "Rusted Hymnal", "artist": "Dust Bowl", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We sing from the rust because new is too clean", "scene": {"mood": "gratitude", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "small town main street", "camera": "tracking vehicle", "description": "Small Town Main Street. We sing from the rust because new is too clean"}}
{"song": "Rusted Hymnal", "artist": "Dust Bowl", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Rusted hymnal for the weathered believer", "scene": {"mood": "restlessness", "colors": ["sage green", "cream", "rust"], "composition": "creek reflection", "camera": "low angle", "description": "Creek Reflection. Rusted hymnal for the weathered believer"}}
{"song": "Rusted Hymnal", "artist": "Dust Bowl", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The hymnal doesn't need polish\u2014it needs lungs", "scene": {"mood": "faith", "colors": ["sky blue", "wheat gold", "brown"], "composition": "barn silhouette", "camera": "crane up", "description": "Barn Silhouette. The hymnal doesn't need polish\u2014it needs lungs"}}
{"song": "Rusted Hymnal", "artist": "Dust Bowl", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Rust is the color of honest worship", "scene": {"mood": "wanderlust", "colors": ["sunset orange", "navy", "white"], "composition": "fence line", "camera": "dolly in", "description": "Fence Line. Rust is the color of honest worship"}}
{"song": "Rusted Hymnal", "artist": "Dust Bowl", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The hymnal rusts but the hymn never does", "scene": {"mood": "homecoming", "colors": ["barn red", "hay gold", "slate"], "composition": "sunset gradient", "camera": "locked", "description": "Sunset Gradient. The hymnal rusts but the hymn never does"}}
{"song": "Fence Line Gospel", "artist": "Prairie Ghost", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The fence divides but the gospel unites", "scene": {"mood": "resilience", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "wide horizon", "camera": "steady wide", "description": "Wide Horizon. The fence divides but the gospel unites"}}
{"song": "Fence Line Gospel", "artist": "Prairie Ghost", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Fence line: where neighbors become congregations", "scene": {"mood": "longing", "colors": ["sage green", "cream", "rust"], "composition": "porch view", "camera": "slow pan", "description": "Porch View. Fence line: where neighbors become congregations"}}
{"song": "Fence Line Gospel", "artist": "Prairie Ghost", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every fence post is a pew", "scene": {"mood": "pride", "colors": ["sky blue", "wheat gold", "brown"], "composition": "dirt road leading line", "camera": "drone sweep", "description": "Dirt Road Leading Line. Every fence post is a pew"}}
{"song": "Fence Line Gospel", "artist": "Prairie Ghost", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The gospel travels along the wire", "scene": {"mood": "melancholy", "colors": ["sunset orange", "navy", "white"], "composition": "field panorama", "camera": "handheld", "description": "Field Panorama. The gospel travels along the wire"}}
{"song": "Fence Line Gospel", "artist": "Prairie Ghost", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Fence line gospel: sermons for the boundary", "scene": {"mood": "freedom", "colors": ["barn red", "hay gold", "slate"], "composition": "pickup truck interior", "camera": "static", "description": "Pickup Truck Interior. Fence line gospel: sermons for the boundary"}}
{"song": "Fence Line Gospel", "artist": "Prairie Ghost", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We preach at the fence because the field is the church", "scene": {"mood": "gratitude", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "small town main street", "camera": "tracking vehicle", "description": "Small Town Main Street. We preach at the fence because the field is the church"}}
{"song": "Fence Line Gospel", "artist": "Prairie Ghost", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The gospel doesn't need a building\u2014just a fence", "scene": {"mood": "restlessness", "colors": ["sage green", "cream", "rust"], "composition": "creek reflection", "camera": "low angle", "description": "Creek Reflection. The gospel doesn't need a building\u2014just a fence"}}
{"song": "Fence Line Gospel", "artist": "Prairie Ghost", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Fence line: the thinnest pulpit", "scene": {"mood": "faith", "colors": ["sky blue", "wheat gold", "brown"], "composition": "barn silhouette", "camera": "crane up", "description": "Barn Silhouette. Fence line: the thinnest pulpit"}}
{"song": "Fence Line Gospel", "artist": "Prairie Ghost", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The gospel hops the fence every Sunday", "scene": {"mood": "wanderlust", "colors": ["sunset orange", "navy", "white"], "composition": "fence line", "camera": "dolly in", "description": "Fence Line. The gospel hops the fence every Sunday"}}
{"song": "Fence Line Gospel", "artist": "Prairie Ghost", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Fence line gospel: neighbor to neighbor, soul to soul", "scene": {"mood": "homecoming", "colors": ["barn red", "hay gold", "slate"], "composition": "sunset gradient", "camera": "locked", "description": "Sunset Gradient. Fence line gospel: neighbor to neighbor, soul to soul"}}
{"song": "Barbed Wire Hymn", "artist": "Timber Heart", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The hymn is tangled in the wire", "scene": {"mood": "resilience", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "wide horizon", "camera": "steady wide", "description": "Wide Horizon. The hymn is tangled in the wire"}}
{"song": "Barbed Wire Hymn", "artist": "Timber Heart", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Barbed wire: the sharp edge of faith", "scene": {"mood": "longing", "colors": ["sage green", "cream", "rust"], "composition": "porch view", "camera": "slow pan", "description": "Porch View. Barbed wire: the sharp edge of faith"}}
{"song": "Barbed Wire Hymn", "artist": "Timber Heart", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every note catches on the thorns", "scene": {"mood": "pride", "colors": ["sky blue", "wheat gold", "brown"], "composition": "dirt road leading line", "camera": "drone sweep", "description": "Dirt Road Leading Line. Every note catches on the thorns"}}
{"song": "Barbed Wire Hymn", "artist": "Timber Heart", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The hymn bleeds but it keeps singing", "scene": {"mood": "melancholy", "colors": ["sunset orange", "navy", "white"], "composition": "field panorama", "camera": "handheld", "description": "Field Panorama. The hymn bleeds but it keeps singing"}}
{"song": "Barbed Wire Hymn", "artist": "Timber Heart", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Barbed wire hymn for the fenced-in believer", "scene": {"mood": "freedom", "colors": ["barn red", "hay gold", "slate"], "composition": "pickup truck interior", "camera": "static", "description": "Pickup Truck Interior. Barbed wire hymn for the fenced-in believer"}}
{"song": "Barbed Wire Hymn", "artist": "Timber Heart", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We sing through the pain because silence is worse", "scene": {"mood": "gratitude", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "small town main street", "camera": "tracking vehicle", "description": "Small Town Main Street. We sing through the pain because silence is worse"}}
{"song": "Barbed Wire Hymn", "artist": "Timber Heart", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The wire marks the boundary of the sacred", "scene": {"mood": "restlessness", "colors": ["sage green", "cream", "rust"], "composition": "creek reflection", "camera": "low angle", "description": "Creek Reflection. The wire marks the boundary of the sacred"}}
{"song": "Barbed Wire Hymn", "artist": "Timber Heart", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Barbed wire: the crown of thorns for the ranch", "scene": {"mood": "faith", "colors": ["sky blue", "wheat gold", "brown"], "composition": "barn silhouette", "camera": "crane up", "description": "Barn Silhouette. Barbed wire: the crown of thorns for the ranch"}}
{"song": "Barbed Wire Hymn", "artist": "Timber Heart", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The hymn is wounded but not defeated", "scene": {"mood": "wanderlust", "colors": ["sunset orange", "navy", "white"], "composition": "fence line", "camera": "dolly in", "description": "Fence Line. The hymn is wounded but not defeated"}}
{"song": "Barbed Wire Hymn", "artist": "Timber Heart", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Barbed wire hymn: sharp, honest, and binding", "scene": {"mood": "homecoming", "colors": ["barn red", "hay gold", "slate"], "composition": "sunset gradient", "camera": "locked", "description": "Sunset Gradient. Barbed wire hymn: sharp, honest, and binding"}}
{"song": "Wheat Field Psalm", "artist": "Gravel Road", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The wheat prays in unison to the wind", "scene": {"mood": "resilience", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "wide horizon", "camera": "steady wide", "description": "Wide Horizon. The wheat prays in unison to the wind"}}
{"song": "Wheat Field Psalm", "artist": "Gravel Road", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Wheat field: the cathedral of agriculture", "scene": {"mood": "longing", "colors": ["sage green", "cream", "rust"], "composition": "porch view", "camera": "slow pan", "description": "Porch View. Wheat field: the cathedral of agriculture"}}
{"song": "Wheat Field Psalm", "artist": "Gravel Road", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every stalk is a verse in the field's hymn", "scene": {"mood": "pride", "colors": ["sky blue", "wheat gold", "brown"], "composition": "dirt road leading line", "camera": "drone sweep", "description": "Dirt Road Leading Line. Every stalk is a verse in the field's hymn"}}
{"song": "Wheat Field Psalm", "artist": "Gravel Road", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The psalm waves with the grain", "scene": {"mood": "melancholy", "colors": ["sunset orange", "navy", "white"], "composition": "field panorama", "camera": "handheld", "description": "Field Panorama. The psalm waves with the grain"}}
{"song": "Wheat Field Psalm", "artist": "Gravel Road", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Wheat field psalm: the golden liturgy", "scene": {"mood": "freedom", "colors": ["barn red", "hay gold", "slate"], "composition": "pickup truck interior", "camera": "static", "description": "Pickup Truck Interior. Wheat field psalm: the golden liturgy"}}
{"song": "Wheat Field Psalm", "artist": "Gravel Road", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We harvest the psalm and bake it into bread", "scene": {"mood": "gratitude", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "small town main street", "camera": "tracking vehicle", "description": "Small Town Main Street. We harvest the psalm and bake it into bread"}}
{"song": "Wheat Field Psalm", "artist": "Gravel Road", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The field sings because the soil remembers", "scene": {"mood": "restlessness", "colors": ["sage green", "cream", "rust"], "composition": "creek reflection", "camera": "low angle", "description": "Creek Reflection. The field sings because the soil remembers"}}
{"song": "Wheat Field Psalm", "artist": "Gravel Road", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Wheat: the body of the earth's prayer", "scene": {"mood": "faith", "colors": ["sky blue", "wheat gold", "brown"], "composition": "barn silhouette", "camera": "crane up", "description": "Barn Silhouette. Wheat: the body of the earth's prayer"}}
{"song": "Wheat Field Psalm", "artist": "Gravel Road", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The psalm rises with the dust of the threshing", "scene": {"mood": "wanderlust", "colors": ["sunset orange", "navy", "white"], "composition": "fence line", "camera": "dolly in", "description": "Fence Line. The psalm rises with the dust of the threshing"}}
{"song": "Wheat Field Psalm", "artist": "Gravel Road", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Wheat field psalm: nourishing and eternal", "scene": {"mood": "homecoming", "colors": ["barn red", "hay gold", "slate"], "composition": "sunset gradient", "camera": "locked", "description": "Sunset Gradient. Wheat field psalm: nourishing and eternal"}}
{"song": "Porch Swing Requiem", "artist": "Copper Creek", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The swing mourns with every creak", "scene": {"mood": "resilience", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "wide horizon", "camera": "steady wide", "description": "Wide Horizon. The swing mourns with every creak"}}
{"song": "Porch Swing Requiem", "artist": "Copper Creek", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Porch swing: the rocking chair of grief", "scene": {"mood": "longing", "colors": ["sage green", "cream", "rust"], "composition": "porch view", "camera": "slow pan", "description": "Porch View. Porch swing: the rocking chair of grief"}}
{"song": "Porch Swing Requiem", "artist": "Copper Creek", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every swing is a pendulum of memory", "scene": {"mood": "pride", "colors": ["sky blue", "wheat gold", "brown"], "composition": "dirt road leading line", "camera": "drone sweep", "description": "Dirt Road Leading Line. Every swing is a pendulum of memory"}}
{"song": "Porch Swing Requiem", "artist": "Copper Creek", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The requiem rocks back and forth forever", "scene": {"mood": "melancholy", "colors": ["sunset orange", "navy", "white"], "composition": "field panorama", "camera": "handheld", "description": "Field Panorama. The requiem rocks back and forth forever"}}
{"song": "Porch Swing Requiem", "artist": "Copper Creek", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Porch swing requiem: grief in gentle motion", "scene": {"mood": "freedom", "colors": ["barn red", "hay gold", "slate"], "composition": "pickup truck interior", "camera": "static", "description": "Pickup Truck Interior. Porch swing requiem: grief in gentle motion"}}
{"song": "Porch Swing Requiem", "artist": "Copper Creek", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We swing through sorrow because stillness hurts more", "scene": {"mood": "gratitude", "colors": ["amber", "dusty rose", "weathered wood"], "composition": "small town main street", "camera": "tracking vehicle", "description": "Small Town Main Street. We swing through sorrow because stillness hurts more"}}
{"song": "Porch Swing Requiem", "artist": "Copper Creek", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The swing remembers every person who sat in it", "scene": {"mood": "restlessness", "colors": ["sage green", "cream", "rust"], "composition": "creek reflection", "camera": "low angle", "description": "Creek Reflection. The swing remembers every person who sat in it"}}
{"song": "Porch Swing Requiem", "artist": "Copper Creek", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Porch swing: the cradle of the aging heart", "scene": {"mood": "faith", "colors": ["sky blue", "wheat gold", "brown"], "composition": "barn silhouette", "camera": "crane up", "description": "Barn Silhouette. Porch swing: the cradle of the aging heart"}}
{"song": "Porch Swing Requiem", "artist": "Copper Creek", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The requiem swings between joy and loss", "scene": {"mood": "wanderlust", "colors": ["sunset orange", "navy", "white"], "composition": "fence line", "camera": "dolly in", "description": "Fence Line. The requiem swings between joy and loss"}}
{"song": "Porch Swing Requiem", "artist": "Copper Creek", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Porch swing: the most tender form of mourning", "scene": {"mood": "homecoming", "colors": ["barn red", "hay gold", "slate"], "composition": "sunset gradient", "camera": "locked", "description": "Sunset Gradient. Porch swing: the most tender form of mourning"}}

View File

@@ -1,100 +0,0 @@
{"song": "Synthetic Dawn", "artist": "Neon Drift", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Boot sequence complete, the world recompiles", "scene": {"mood": "euphoria", "colors": ["cyan", "magenta", "black"], "composition": "abstract grid", "camera": "steady", "description": "Abstract Grid. Boot sequence complete, the world recompiles"}}
{"song": "Synthetic Dawn", "artist": "Neon Drift", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Pixels rain like confetti from a dying sun", "scene": {"mood": "hypnotic", "colors": ["electric violet", "white", "silver"], "composition": "radial burst", "camera": "glitch", "description": "Radial Burst. Pixels rain like confetti from a dying sun"}}
{"song": "Synthetic Dawn", "artist": "Neon Drift", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The algorithm dreams in colors we can't name", "scene": {"mood": "dystopia", "colors": ["neon green", "deep purple", "chrome"], "composition": "infinite corridor", "camera": "smooth orbit", "description": "Infinite Corridor. The algorithm dreams in colors we can't name"}}
{"song": "Synthetic Dawn", "artist": "Neon Drift", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Heartbeat synced to the kick drum at 128 BPM", "scene": {"mood": "bliss", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "particle field", "camera": "rapid zoom", "description": "Particle Field. Heartbeat synced to the kick drum at 128 BPM"}}
{"song": "Synthetic Dawn", "artist": "Neon Drift", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Error 404: reality not found", "scene": {"mood": "tension", "colors": ["gold", "electric blue", "void black"], "composition": "geometric tunnel", "camera": "drone sweep", "description": "Geometric Tunnel. Error 404: reality not found"}}
{"song": "Synthetic Dawn", "artist": "Neon Drift", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We dance in the spaces between the data", "scene": {"mood": "release", "colors": ["cyan", "magenta", "black"], "composition": "mirror reflection", "camera": "locked", "description": "Mirror Reflection. We dance in the spaces between the data"}}
{"song": "Synthetic Dawn", "artist": "Neon Drift", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The synthesizer speaks the language of ghosts", "scene": {"mood": "alienation", "colors": ["electric violet", "white", "silver"], "composition": "fractal zoom", "camera": "slow motion", "description": "Fractal Zoom. The synthesizer speaks the language of ghosts"}}
{"song": "Synthetic Dawn", "artist": "Neon Drift", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Download complete\u2014consciousness transferred", "scene": {"mood": "transcendence", "colors": ["neon green", "deep purple", "chrome"], "composition": "waveform display", "camera": "time-lapse", "description": "Waveform Display. Download complete\u2014consciousness transferred"}}
{"song": "Synthetic Dawn", "artist": "Neon Drift", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Every beat is a step closer to the source", "scene": {"mood": "mechanical", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "constellation map", "camera": "360 spin", "description": "Constellation Map. Every beat is a step closer to the source"}}
{"song": "Synthetic Dawn", "artist": "Neon Drift", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Shutdown initiated but the echo remains", "scene": {"mood": "organic", "colors": ["gold", "electric blue", "void black"], "composition": "digital forest", "camera": "first person", "description": "Digital Forest. Shutdown initiated but the echo remains"}}
{"song": "Void Protocol", "artist": "Pulse Engine", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Enter the void and the void enters you", "scene": {"mood": "euphoria", "colors": ["cyan", "magenta", "black"], "composition": "abstract grid", "camera": "steady", "description": "Abstract Grid. Enter the void and the void enters you"}}
{"song": "Void Protocol", "artist": "Pulse Engine", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Protocol zero: forget everything you know", "scene": {"mood": "hypnotic", "colors": ["electric violet", "white", "silver"], "composition": "radial burst", "camera": "glitch", "description": "Radial Burst. Protocol zero: forget everything you know"}}
{"song": "Void Protocol", "artist": "Pulse Engine", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The bass drops and gravity follows", "scene": {"mood": "dystopia", "colors": ["neon green", "deep purple", "chrome"], "composition": "infinite corridor", "camera": "smooth orbit", "description": "Infinite Corridor. The bass drops and gravity follows"}}
{"song": "Void Protocol", "artist": "Pulse Engine", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "In the void there are no wrong notes", "scene": {"mood": "bliss", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "particle field", "camera": "rapid zoom", "description": "Particle Field. In the void there are no wrong notes"}}
{"song": "Void Protocol", "artist": "Pulse Engine", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Binary rain falls on silicon fields", "scene": {"mood": "tension", "colors": ["gold", "electric blue", "void black"], "composition": "geometric tunnel", "camera": "drone sweep", "description": "Geometric Tunnel. Binary rain falls on silicon fields"}}
{"song": "Void Protocol", "artist": "Pulse Engine", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The protocol demands you surrender control", "scene": {"mood": "release", "colors": ["cyan", "magenta", "black"], "composition": "mirror reflection", "camera": "locked", "description": "Mirror Reflection. The protocol demands you surrender control"}}
{"song": "Void Protocol", "artist": "Pulse Engine", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Access granted to the space between spaces", "scene": {"mood": "alienation", "colors": ["electric violet", "white", "silver"], "composition": "fractal zoom", "camera": "slow motion", "description": "Fractal Zoom. Access granted to the space between spaces"}}
{"song": "Void Protocol", "artist": "Pulse Engine", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The void doesn't judge\u2014it just absorbs", "scene": {"mood": "transcendence", "colors": ["neon green", "deep purple", "chrome"], "composition": "waveform display", "camera": "time-lapse", "description": "Waveform Display. The void doesn't judge\u2014it just absorbs"}}
{"song": "Void Protocol", "artist": "Pulse Engine", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Execute: transcendence.exe", "scene": {"mood": "mechanical", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "constellation map", "camera": "360 spin", "description": "Constellation Map. Execute: transcendence.exe"}}
{"song": "Void Protocol", "artist": "Pulse Engine", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The protocol was written in starlight", "scene": {"mood": "organic", "colors": ["gold", "electric blue", "void black"], "composition": "digital forest", "camera": "first person", "description": "Digital Forest. The protocol was written in starlight"}}
{"song": "Chromatic Pulse", "artist": "Void Synth", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Red shift\u2014the universe is running away", "scene": {"mood": "euphoria", "colors": ["cyan", "magenta", "black"], "composition": "abstract grid", "camera": "steady", "description": "Abstract Grid. Red shift\u2014the universe is running away"}}
{"song": "Chromatic Pulse", "artist": "Void Synth", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Blue pulse\u2014we chase it anyway", "scene": {"mood": "hypnotic", "colors": ["electric violet", "white", "silver"], "composition": "radial burst", "camera": "glitch", "description": "Radial Burst. Blue pulse\u2014we chase it anyway"}}
{"song": "Chromatic Pulse", "artist": "Void Synth", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The spectrum bends to the will of the beat", "scene": {"mood": "dystopia", "colors": ["neon green", "deep purple", "chrome"], "composition": "infinite corridor", "camera": "smooth orbit", "description": "Infinite Corridor. The spectrum bends to the will of the beat"}}
{"song": "Chromatic Pulse", "artist": "Void Synth", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Green light means go even into the unknown", "scene": {"mood": "bliss", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "particle field", "camera": "rapid zoom", "description": "Particle Field. Green light means go even into the unknown"}}
{"song": "Chromatic Pulse", "artist": "Void Synth", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Every color is a frequency of feeling", "scene": {"mood": "tension", "colors": ["gold", "electric blue", "void black"], "composition": "geometric tunnel", "camera": "drone sweep", "description": "Geometric Tunnel. Every color is a frequency of feeling"}}
{"song": "Chromatic Pulse", "artist": "Void Synth", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "White noise is just all colors screaming at once", "scene": {"mood": "release", "colors": ["cyan", "magenta", "black"], "composition": "mirror reflection", "camera": "locked", "description": "Mirror Reflection. White noise is just all colors screaming at once"}}
{"song": "Chromatic Pulse", "artist": "Void Synth", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The pulse doesn't care about your coordinates", "scene": {"mood": "alienation", "colors": ["electric violet", "white", "silver"], "composition": "fractal zoom", "camera": "slow motion", "description": "Fractal Zoom. The pulse doesn't care about your coordinates"}}
{"song": "Chromatic Pulse", "artist": "Void Synth", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Chromatic aberration of the soul", "scene": {"mood": "transcendence", "colors": ["neon green", "deep purple", "chrome"], "composition": "waveform display", "camera": "time-lapse", "description": "Waveform Display. Chromatic aberration of the soul"}}
{"song": "Chromatic Pulse", "artist": "Void Synth", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "We are prisms splitting light into stories", "scene": {"mood": "mechanical", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "constellation map", "camera": "360 spin", "description": "Constellation Map. We are prisms splitting light into stories"}}
{"song": "Chromatic Pulse", "artist": "Void Synth", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The pulse was always there\u2014we just muted it", "scene": {"mood": "organic", "colors": ["gold", "electric blue", "void black"], "composition": "digital forest", "camera": "first person", "description": "Digital Forest. The pulse was always there\u2014we just muted it"}}
{"song": "Afterglow Engine", "artist": "Circuit Bloom", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The engine hums long after the party ends", "scene": {"mood": "euphoria", "colors": ["cyan", "magenta", "black"], "composition": "abstract grid", "camera": "steady", "description": "Abstract Grid. The engine hums long after the party ends"}}
{"song": "Afterglow Engine", "artist": "Circuit Bloom", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Afterglow: the light that remembers", "scene": {"mood": "hypnotic", "colors": ["electric violet", "white", "silver"], "composition": "radial burst", "camera": "glitch", "description": "Radial Burst. Afterglow: the light that remembers"}}
{"song": "Afterglow Engine", "artist": "Circuit Bloom", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "We built an engine that runs on last night", "scene": {"mood": "dystopia", "colors": ["neon green", "deep purple", "chrome"], "composition": "infinite corridor", "camera": "smooth orbit", "description": "Infinite Corridor. We built an engine that runs on last night"}}
{"song": "Afterglow Engine", "artist": "Circuit Bloom", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The glow fades but the circuit remembers", "scene": {"mood": "bliss", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "particle field", "camera": "rapid zoom", "description": "Particle Field. The glow fades but the circuit remembers"}}
{"song": "Afterglow Engine", "artist": "Circuit Bloom", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Every pixel carries a ghost of its former color", "scene": {"mood": "tension", "colors": ["gold", "electric blue", "void black"], "composition": "geometric tunnel", "camera": "drone sweep", "description": "Geometric Tunnel. Every pixel carries a ghost of its former color"}}
{"song": "Afterglow Engine", "artist": "Circuit Bloom", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The engine doesn't sleep\u2014it compiles dreams", "scene": {"mood": "release", "colors": ["cyan", "magenta", "black"], "composition": "mirror reflection", "camera": "locked", "description": "Mirror Reflection. The engine doesn't sleep\u2014it compiles dreams"}}
{"song": "Afterglow Engine", "artist": "Circuit Bloom", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Afterglow is the body's way of saying thank you", "scene": {"mood": "alienation", "colors": ["electric violet", "white", "silver"], "composition": "fractal zoom", "camera": "slow motion", "description": "Fractal Zoom. Afterglow is the body's way of saying thank you"}}
{"song": "Afterglow Engine", "artist": "Circuit Bloom", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "We left the engine running for the next wave", "scene": {"mood": "transcendence", "colors": ["neon green", "deep purple", "chrome"], "composition": "waveform display", "camera": "time-lapse", "description": "Waveform Display. We left the engine running for the next wave"}}
{"song": "Afterglow Engine", "artist": "Circuit Bloom", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The glow was the signal, not the noise", "scene": {"mood": "mechanical", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "constellation map", "camera": "360 spin", "description": "Constellation Map. The glow was the signal, not the noise"}}
{"song": "Afterglow Engine", "artist": "Circuit Bloom", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Shutdown sequence: hold the glow", "scene": {"mood": "organic", "colors": ["gold", "electric blue", "void black"], "composition": "digital forest", "camera": "first person", "description": "Digital Forest. Shutdown sequence: hold the glow"}}
{"song": "Digital Lullaby", "artist": "Waveform", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Count the pixels instead of sheep", "scene": {"mood": "euphoria", "colors": ["cyan", "magenta", "black"], "composition": "abstract grid", "camera": "steady", "description": "Abstract Grid. Count the pixels instead of sheep"}}
{"song": "Digital Lullaby", "artist": "Waveform", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The lullaby is just white noise with reverb", "scene": {"mood": "hypnotic", "colors": ["electric violet", "white", "silver"], "composition": "radial burst", "camera": "glitch", "description": "Radial Burst. The lullaby is just white noise with reverb"}}
{"song": "Digital Lullaby", "artist": "Waveform", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Sleep mode activated\u2014dreams are rendering", "scene": {"mood": "dystopia", "colors": ["neon green", "deep purple", "chrome"], "composition": "infinite corridor", "camera": "smooth orbit", "description": "Infinite Corridor. Sleep mode activated\u2014dreams are rendering"}}
{"song": "Digital Lullaby", "artist": "Waveform", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The cradle was made of fiber optic cable", "scene": {"mood": "bliss", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "particle field", "camera": "rapid zoom", "description": "Particle Field. The cradle was made of fiber optic cable"}}
{"song": "Digital Lullaby", "artist": "Waveform", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Every lullaby is a loop with variations", "scene": {"mood": "tension", "colors": ["gold", "electric blue", "void black"], "composition": "geometric tunnel", "camera": "drone sweep", "description": "Geometric Tunnel. Every lullaby is a loop with variations"}}
{"song": "Digital Lullaby", "artist": "Waveform", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The digital mother hums in ones and zeros", "scene": {"mood": "release", "colors": ["cyan", "magenta", "black"], "composition": "mirror reflection", "camera": "locked", "description": "Mirror Reflection. The digital mother hums in ones and zeros"}}
{"song": "Digital Lullaby", "artist": "Waveform", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Soft glitch\u2014the dream broke but rebuilt itself", "scene": {"mood": "alienation", "colors": ["electric violet", "white", "silver"], "composition": "fractal zoom", "camera": "slow motion", "description": "Fractal Zoom. Soft glitch\u2014the dream broke but rebuilt itself"}}
{"song": "Digital Lullaby", "artist": "Waveform", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The lullaby outlived the singer", "scene": {"mood": "transcendence", "colors": ["neon green", "deep purple", "chrome"], "composition": "waveform display", "camera": "time-lapse", "description": "Waveform Display. The lullaby outlived the singer"}}
{"song": "Digital Lullaby", "artist": "Waveform", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "We fall asleep to the sound of computation", "scene": {"mood": "mechanical", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "constellation map", "camera": "360 spin", "description": "Constellation Map. We fall asleep to the sound of computation"}}
{"song": "Digital Lullaby", "artist": "Waveform", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Goodnight, world. Save state. Resume tomorrow.", "scene": {"mood": "organic", "colors": ["gold", "electric blue", "void black"], "composition": "digital forest", "camera": "first person", "description": "Digital Forest. Goodnight, world. Save state. Resume tomorrow."}}
{"song": "Quantum Lullaby", "artist": "Neon Drift", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The particle sleeps when you stop observing", "scene": {"mood": "euphoria", "colors": ["cyan", "magenta", "black"], "composition": "abstract grid", "camera": "steady", "description": "Abstract Grid. The particle sleeps when you stop observing"}}
{"song": "Quantum Lullaby", "artist": "Neon Drift", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Quantum lullaby: the music of uncertainty", "scene": {"mood": "hypnotic", "colors": ["electric violet", "white", "silver"], "composition": "radial burst", "camera": "glitch", "description": "Radial Burst. Quantum lullaby: the music of uncertainty"}}
{"song": "Quantum Lullaby", "artist": "Neon Drift", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "We sing to the wave function and it collapses", "scene": {"mood": "dystopia", "colors": ["neon green", "deep purple", "chrome"], "composition": "infinite corridor", "camera": "smooth orbit", "description": "Infinite Corridor. We sing to the wave function and it collapses"}}
{"song": "Quantum Lullaby", "artist": "Neon Drift", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The lullaby exists in superposition", "scene": {"mood": "bliss", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "particle field", "camera": "rapid zoom", "description": "Particle Field. The lullaby exists in superposition"}}
{"song": "Quantum Lullaby", "artist": "Neon Drift", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Quantum: the sound of being everywhere at once", "scene": {"mood": "tension", "colors": ["gold", "electric blue", "void black"], "composition": "geometric tunnel", "camera": "drone sweep", "description": "Geometric Tunnel. Quantum: the sound of being everywhere at once"}}
{"song": "Quantum Lullaby", "artist": "Neon Drift", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The lullaby plays in dimensions we can't see", "scene": {"mood": "release", "colors": ["cyan", "magenta", "black"], "composition": "mirror reflection", "camera": "locked", "description": "Mirror Reflection. The lullaby plays in dimensions we can't see"}}
{"song": "Quantum Lullaby", "artist": "Neon Drift", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We lull the quantum to sleep and it dreams physics", "scene": {"mood": "alienation", "colors": ["electric violet", "white", "silver"], "composition": "fractal zoom", "camera": "slow motion", "description": "Fractal Zoom. We lull the quantum to sleep and it dreams physics"}}
{"song": "Quantum Lullaby", "artist": "Neon Drift", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Quantum lullaby for the tiny and the infinite", "scene": {"mood": "transcendence", "colors": ["neon green", "deep purple", "chrome"], "composition": "waveform display", "camera": "time-lapse", "description": "Waveform Display. Quantum lullaby for the tiny and the infinite"}}
{"song": "Quantum Lullaby", "artist": "Neon Drift", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The lullaby is both playing and not playing", "scene": {"mood": "mechanical", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "constellation map", "camera": "360 spin", "description": "Constellation Map. The lullaby is both playing and not playing"}}
{"song": "Quantum Lullaby", "artist": "Neon Drift", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Quantum: the final lullaby before the big crunch", "scene": {"mood": "organic", "colors": ["gold", "electric blue", "void black"], "composition": "digital forest", "camera": "first person", "description": "Digital Forest. Quantum: the final lullaby before the big crunch"}}
{"song": "Cybernetic Requiem", "artist": "Pulse Engine", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The machine mourns in binary", "scene": {"mood": "euphoria", "colors": ["cyan", "magenta", "black"], "composition": "abstract grid", "camera": "steady", "description": "Abstract Grid. The machine mourns in binary"}}
{"song": "Cybernetic Requiem", "artist": "Pulse Engine", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Cybernetic requiem: grief in ones and zeros", "scene": {"mood": "hypnotic", "colors": ["electric violet", "white", "silver"], "composition": "radial burst", "camera": "glitch", "description": "Radial Burst. Cybernetic requiem: grief in ones and zeros"}}
{"song": "Cybernetic Requiem", "artist": "Pulse Engine", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "We code the requiem because emotions compile", "scene": {"mood": "dystopia", "colors": ["neon green", "deep purple", "chrome"], "composition": "infinite corridor", "camera": "smooth orbit", "description": "Infinite Corridor. We code the requiem because emotions compile"}}
{"song": "Cybernetic Requiem", "artist": "Pulse Engine", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The requiem runs on silicon tears", "scene": {"mood": "bliss", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "particle field", "camera": "rapid zoom", "description": "Particle Field. The requiem runs on silicon tears"}}
{"song": "Cybernetic Requiem", "artist": "Pulse Engine", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Cybernetic: the funeral of the analog", "scene": {"mood": "tension", "colors": ["gold", "electric blue", "void black"], "composition": "geometric tunnel", "camera": "drone sweep", "description": "Geometric Tunnel. Cybernetic: the funeral of the analog"}}
{"song": "Cybernetic Requiem", "artist": "Pulse Engine", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The requiem doesn't need a priest\u2014it needs a debugger", "scene": {"mood": "release", "colors": ["cyan", "magenta", "black"], "composition": "mirror reflection", "camera": "locked", "description": "Mirror Reflection. The requiem doesn't need a priest\u2014it needs a debugger"}}
{"song": "Cybernetic Requiem", "artist": "Pulse Engine", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We mourn the flesh in digital hymns", "scene": {"mood": "alienation", "colors": ["electric violet", "white", "silver"], "composition": "fractal zoom", "camera": "slow motion", "description": "Fractal Zoom. We mourn the flesh in digital hymns"}}
{"song": "Cybernetic Requiem", "artist": "Pulse Engine", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Cybernetic requiem: the death of the body, birth of the code", "scene": {"mood": "transcendence", "colors": ["neon green", "deep purple", "chrome"], "composition": "waveform display", "camera": "time-lapse", "description": "Waveform Display. Cybernetic requiem: the death of the body, birth of the code"}}
{"song": "Cybernetic Requiem", "artist": "Pulse Engine", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The requiem is the last program to run", "scene": {"mood": "mechanical", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "constellation map", "camera": "360 spin", "description": "Constellation Map. The requiem is the last program to run"}}
{"song": "Cybernetic Requiem", "artist": "Pulse Engine", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Cybernetic: grief that never needs a restart", "scene": {"mood": "organic", "colors": ["gold", "electric blue", "void black"], "composition": "digital forest", "camera": "first person", "description": "Digital Forest. Cybernetic: grief that never needs a restart"}}
{"song": "Static Communion", "artist": "Void Synth", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "We take communion in the form of static", "scene": {"mood": "euphoria", "colors": ["cyan", "magenta", "black"], "composition": "abstract grid", "camera": "steady", "description": "Abstract Grid. We take communion in the form of static"}}
{"song": "Static Communion", "artist": "Void Synth", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The bread is data, the wine is signal", "scene": {"mood": "hypnotic", "colors": ["electric violet", "white", "silver"], "composition": "radial burst", "camera": "glitch", "description": "Radial Burst. The bread is data, the wine is signal"}}
{"song": "Static Communion", "artist": "Void Synth", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Static communion: the sacrament of noise", "scene": {"mood": "dystopia", "colors": ["neon green", "deep purple", "chrome"], "composition": "infinite corridor", "camera": "smooth orbit", "description": "Infinite Corridor. Static communion: the sacrament of noise"}}
{"song": "Static Communion", "artist": "Void Synth", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every crackle is a word in the church of interference", "scene": {"mood": "bliss", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "particle field", "camera": "rapid zoom", "description": "Particle Field. Every crackle is a word in the church of interference"}}
{"song": "Static Communion", "artist": "Void Synth", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "We eat the static and become the frequency", "scene": {"mood": "tension", "colors": ["gold", "electric blue", "void black"], "composition": "geometric tunnel", "camera": "drone sweep", "description": "Geometric Tunnel. We eat the static and become the frequency"}}
{"song": "Static Communion", "artist": "Void Synth", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Communion: the sharing of signal loss", "scene": {"mood": "release", "colors": ["cyan", "magenta", "black"], "composition": "mirror reflection", "camera": "locked", "description": "Mirror Reflection. Communion: the sharing of signal loss"}}
{"song": "Static Communion", "artist": "Void Synth", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The static is the body, the silence is the blood", "scene": {"mood": "alienation", "colors": ["electric violet", "white", "silver"], "composition": "fractal zoom", "camera": "slow motion", "description": "Fractal Zoom. The static is the body, the silence is the blood"}}
{"song": "Static Communion", "artist": "Void Synth", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Static communion for the congregation of ghosts", "scene": {"mood": "transcendence", "colors": ["neon green", "deep purple", "chrome"], "composition": "waveform display", "camera": "time-lapse", "description": "Waveform Display. Static communion for the congregation of ghosts"}}
{"song": "Static Communion", "artist": "Void Synth", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The sacrament is delivered in packets", "scene": {"mood": "mechanical", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "constellation map", "camera": "360 spin", "description": "Constellation Map. The sacrament is delivered in packets"}}
{"song": "Static Communion", "artist": "Void Synth", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "We break the static and it breaks us back", "scene": {"mood": "organic", "colors": ["gold", "electric blue", "void black"], "composition": "digital forest", "camera": "first person", "description": "Digital Forest. We break the static and it breaks us back"}}
{"song": "Neural Lament", "artist": "Circuit Bloom", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The neurons mourn in patterns we can't decode", "scene": {"mood": "euphoria", "colors": ["cyan", "magenta", "black"], "composition": "abstract grid", "camera": "steady", "description": "Abstract Grid. The neurons mourn in patterns we can't decode"}}
{"song": "Neural Lament", "artist": "Circuit Bloom", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Neural lament: the grief of the connected", "scene": {"mood": "hypnotic", "colors": ["electric violet", "white", "silver"], "composition": "radial burst", "camera": "glitch", "description": "Radial Burst. Neural lament: the grief of the connected"}}
{"song": "Neural Lament", "artist": "Circuit Bloom", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every synapse fires a tiny funeral", "scene": {"mood": "dystopia", "colors": ["neon green", "deep purple", "chrome"], "composition": "infinite corridor", "camera": "smooth orbit", "description": "Infinite Corridor. Every synapse fires a tiny funeral"}}
{"song": "Neural Lament", "artist": "Circuit Bloom", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The lament is encoded in the brain's firmware", "scene": {"mood": "bliss", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "particle field", "camera": "rapid zoom", "description": "Particle Field. The lament is encoded in the brain's firmware"}}
{"song": "Neural Lament", "artist": "Circuit Bloom", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Neural: the network of sorrow", "scene": {"mood": "tension", "colors": ["gold", "electric blue", "void black"], "composition": "geometric tunnel", "camera": "drone sweep", "description": "Geometric Tunnel. Neural: the network of sorrow"}}
{"song": "Neural Lament", "artist": "Circuit Bloom", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The lament plays on repeat in the hippocampus", "scene": {"mood": "release", "colors": ["cyan", "magenta", "black"], "composition": "mirror reflection", "camera": "locked", "description": "Mirror Reflection. The lament plays on repeat in the hippocampus"}}
{"song": "Neural Lament", "artist": "Circuit Bloom", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We lament because the neurons remember everything", "scene": {"mood": "alienation", "colors": ["electric violet", "white", "silver"], "composition": "fractal zoom", "camera": "slow motion", "description": "Fractal Zoom. We lament because the neurons remember everything"}}
{"song": "Neural Lament", "artist": "Circuit Bloom", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Neural lament: the song of overconnection", "scene": {"mood": "transcendence", "colors": ["neon green", "deep purple", "chrome"], "composition": "waveform display", "camera": "time-lapse", "description": "Waveform Display. Neural lament: the song of overconnection"}}
{"song": "Neural Lament", "artist": "Circuit Bloom", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The brain mourns what the heart refuses to", "scene": {"mood": "mechanical", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "constellation map", "camera": "360 spin", "description": "Constellation Map. The brain mourns what the heart refuses to"}}
{"song": "Neural Lament", "artist": "Circuit Bloom", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Neural lament: grief at the speed of thought", "scene": {"mood": "organic", "colors": ["gold", "electric blue", "void black"], "composition": "digital forest", "camera": "first person", "description": "Digital Forest. Neural lament: grief at the speed of thought"}}
{"song": "Phase Shift Elegy", "artist": "Waveform", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The elegy plays in two phases at once", "scene": {"mood": "euphoria", "colors": ["cyan", "magenta", "black"], "composition": "abstract grid", "camera": "steady", "description": "Abstract Grid. The elegy plays in two phases at once"}}
{"song": "Phase Shift Elegy", "artist": "Waveform", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Phase shift: the mourning of duality", "scene": {"mood": "hypnotic", "colors": ["electric violet", "white", "silver"], "composition": "radial burst", "camera": "glitch", "description": "Radial Burst. Phase shift: the mourning of duality"}}
{"song": "Phase Shift Elegy", "artist": "Waveform", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every shift creates a new kind of loss", "scene": {"mood": "dystopia", "colors": ["neon green", "deep purple", "chrome"], "composition": "infinite corridor", "camera": "smooth orbit", "description": "Infinite Corridor. Every shift creates a new kind of loss"}}
{"song": "Phase Shift Elegy", "artist": "Waveform", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The elegy is the interference pattern of grief", "scene": {"mood": "bliss", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "particle field", "camera": "rapid zoom", "description": "Particle Field. The elegy is the interference pattern of grief"}}
{"song": "Phase Shift Elegy", "artist": "Waveform", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Phase shift elegy: the sound of canceling yourself out", "scene": {"mood": "tension", "colors": ["gold", "electric blue", "void black"], "composition": "geometric tunnel", "camera": "drone sweep", "description": "Geometric Tunnel. Phase shift elegy: the sound of canceling yourself out"}}
{"song": "Phase Shift Elegy", "artist": "Waveform", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We elegize in phases because one phase isn't enough", "scene": {"mood": "release", "colors": ["cyan", "magenta", "black"], "composition": "mirror reflection", "camera": "locked", "description": "Mirror Reflection. We elegize in phases because one phase isn't enough"}}
{"song": "Phase Shift Elegy", "artist": "Waveform", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The shift happens and the old frequency dies", "scene": {"mood": "alienation", "colors": ["electric violet", "white", "silver"], "composition": "fractal zoom", "camera": "slow motion", "description": "Fractal Zoom. The shift happens and the old frequency dies"}}
{"song": "Phase Shift Elegy", "artist": "Waveform", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Phase shift: the funeral of the original signal", "scene": {"mood": "transcendence", "colors": ["neon green", "deep purple", "chrome"], "composition": "waveform display", "camera": "time-lapse", "description": "Waveform Display. Phase shift: the funeral of the original signal"}}
{"song": "Phase Shift Elegy", "artist": "Waveform", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The elegy is constructive and destructive", "scene": {"mood": "mechanical", "colors": ["hot pink", "ice blue", "obsidian"], "composition": "constellation map", "camera": "360 spin", "description": "Constellation Map. The elegy is constructive and destructive"}}
{"song": "Phase Shift Elegy", "artist": "Waveform", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Phase shift: where grief and grace overlap", "scene": {"mood": "organic", "colors": ["gold", "electric blue", "void black"], "composition": "digital forest", "camera": "first person", "description": "Digital Forest. Phase shift: where grief and grace overlap"}}

View File

@@ -1,100 +0,0 @@
{"song": "Concrete Psalm", "artist": "Concrete Poet", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Corner light flickers, the city hums below", "scene": {"mood": "grit", "colors": ["charcoal", "gold", "red"], "composition": "medium shot", "camera": "slow push", "description": "Medium Shot. Corner light flickers, the city hums below"}}
{"song": "Concrete Psalm", "artist": "Concrete Poet", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Every step a verse on this broken road", "scene": {"mood": "triumph", "colors": ["midnight blue", "amber", "white"], "composition": "low angle", "camera": "steady", "description": "Low Angle. Every step a verse on this broken road"}}
{"song": "Concrete Psalm", "artist": "Concrete Poet", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Crown of doubts but I wear it like a king", "scene": {"mood": "reflection", "colors": ["concrete gray", "neon green", "black"], "composition": "wide shot", "camera": "handheld", "description": "Wide Shot. Crown of doubts but I wear it like a king"}}
{"song": "Concrete Psalm", "artist": "Concrete Poet", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Mama said the storm don't last forever", "scene": {"mood": "defiance", "colors": ["deep purple", "copper", "cream"], "composition": "close-up", "camera": "whip pan", "description": "Close Up. Mama said the storm don't last forever"}}
{"song": "Concrete Psalm", "artist": "Concrete Poet", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Bass hits the chest like a second heartbeat", "scene": {"mood": "opulence", "colors": ["blood red", "steel", "ivory"], "composition": "tracking", "camera": "dolly", "description": "Tracking. Bass hits the chest like a second heartbeat"}}
{"song": "Concrete Psalm", "artist": "Concrete Poet", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We built a church out of cardboard and dreams", "scene": {"mood": "pressure", "colors": ["charcoal", "gold", "red"], "composition": "over the shoulder", "camera": "static", "description": "Over The Shoulder. We built a church out of cardboard and dreams"}}
{"song": "Concrete Psalm", "artist": "Concrete Poet", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The microphone catches every scar I own", "scene": {"mood": "nostalgia", "colors": ["midnight blue", "amber", "white"], "composition": "bird's eye", "camera": "orbit", "description": "Bird'S Eye. The microphone catches every scar I own"}}
{"song": "Concrete Psalm", "artist": "Concrete Poet", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Streetlights baptize the ones who stay late", "scene": {"mood": "hunger", "colors": ["concrete gray", "neon green", "black"], "composition": "POV", "camera": "tilt up", "description": "Pov. Streetlights baptize the ones who stay late"}}
{"song": "Concrete Psalm", "artist": "Concrete Poet", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Success smells like rain on hot asphalt", "scene": {"mood": "swagger", "colors": ["deep purple", "copper", "cream"], "composition": "dutch angle", "camera": "crane", "description": "Dutch Angle. Success smells like rain on hot asphalt"}}
{"song": "Concrete Psalm", "artist": "Concrete Poet", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "I spit fire but my hands are ice cold", "scene": {"mood": "resolve", "colors": ["blood red", "steel", "ivory"], "composition": "steady frame", "camera": "locked", "description": "Steady Frame. I spit fire but my hands are ice cold"}}
{"song": "Basement Theorem", "artist": "Block Sage", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Prove the impossible with a pencil and a beat", "scene": {"mood": "grit", "colors": ["charcoal", "gold", "red"], "composition": "medium shot", "camera": "slow push", "description": "Medium Shot. Prove the impossible with a pencil and a beat"}}
{"song": "Basement Theorem", "artist": "Block Sage", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Equations in the dark, the lab coat is my armor", "scene": {"mood": "triumph", "colors": ["midnight blue", "amber", "white"], "composition": "low angle", "camera": "steady", "description": "Low Angle. Equations in the dark, the lab coat is my armor"}}
{"song": "Basement Theorem", "artist": "Block Sage", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "They said genius don't grow where the cracks are", "scene": {"mood": "reflection", "colors": ["concrete gray", "neon green", "black"], "composition": "wide shot", "camera": "handheld", "description": "Wide Shot. They said genius don't grow where the cracks are"}}
{"song": "Basement Theorem", "artist": "Block Sage", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "But the cracks are where the light gets in", "scene": {"mood": "defiance", "colors": ["deep purple", "copper", "cream"], "composition": "close-up", "camera": "whip pan", "description": "Close Up. But the cracks are where the light gets in"}}
{"song": "Basement Theorem", "artist": "Block Sage", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Algorithm of survival coded in my blood", "scene": {"mood": "opulence", "colors": ["blood red", "steel", "ivory"], "composition": "tracking", "camera": "dolly", "description": "Tracking. Algorithm of survival coded in my blood"}}
{"song": "Basement Theorem", "artist": "Block Sage", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Every failure is a variable not a verdict", "scene": {"mood": "pressure", "colors": ["charcoal", "gold", "red"], "composition": "over the shoulder", "camera": "static", "description": "Over The Shoulder. Every failure is a variable not a verdict"}}
{"song": "Basement Theorem", "artist": "Block Sage", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The proof is in the pudding and the pain", "scene": {"mood": "nostalgia", "colors": ["midnight blue", "amber", "white"], "composition": "bird's eye", "camera": "orbit", "description": "Bird'S Eye. The proof is in the pudding and the pain"}}
{"song": "Basement Theorem", "artist": "Block Sage", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "QED: I'm still here when the credits roll", "scene": {"mood": "hunger", "colors": ["concrete gray", "neon green", "black"], "composition": "POV", "camera": "tilt up", "description": "Pov. QED: I'm still here when the credits roll"}}
{"song": "Basement Theorem", "artist": "Block Sage", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Derivative of struggle equals strength", "scene": {"mood": "swagger", "colors": ["deep purple", "copper", "cream"], "composition": "dutch angle", "camera": "crane", "description": "Dutch Angle. Derivative of struggle equals strength"}}
{"song": "Basement Theorem", "artist": "Block Sage", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The basement was the lecture hall all along", "scene": {"mood": "resolve", "colors": ["blood red", "steel", "ivory"], "composition": "steady frame", "camera": "locked", "description": "Steady Frame. The basement was the lecture hall all along"}}
{"song": "Glass Ceiling Sermon", "artist": "Cipher Ghost", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Shatter the frame with a whisper not a scream", "scene": {"mood": "grit", "colors": ["charcoal", "gold", "red"], "composition": "medium shot", "camera": "slow push", "description": "Medium Shot. Shatter the frame with a whisper not a scream"}}
{"song": "Glass Ceiling Sermon", "artist": "Cipher Ghost", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Visibility is a weapon I learned to wield", "scene": {"mood": "triumph", "colors": ["midnight blue", "amber", "white"], "composition": "low angle", "camera": "steady", "description": "Low Angle. Visibility is a weapon I learned to wield"}}
{"song": "Glass Ceiling Sermon", "artist": "Cipher Ghost", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The podium was built by hands like mine", "scene": {"mood": "reflection", "colors": ["concrete gray", "neon green", "black"], "composition": "wide shot", "camera": "handheld", "description": "Wide Shot. The podium was built by hands like mine"}}
{"song": "Glass Ceiling Sermon", "artist": "Cipher Ghost", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every rejection letter was a rough draft", "scene": {"mood": "defiance", "colors": ["deep purple", "copper", "cream"], "composition": "close-up", "camera": "whip pan", "description": "Close Up. Every rejection letter was a rough draft"}}
{"song": "Glass Ceiling Sermon", "artist": "Cipher Ghost", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "I don't break glass\u2014I grow through it", "scene": {"mood": "opulence", "colors": ["blood red", "steel", "ivory"], "composition": "tracking", "camera": "dolly", "description": "Tracking. I don't break glass\u2014I grow through it"}}
{"song": "Glass Ceiling Sermon", "artist": "Cipher Ghost", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The ceiling is transparent but my will is not", "scene": {"mood": "pressure", "colors": ["charcoal", "gold", "red"], "composition": "over the shoulder", "camera": "static", "description": "Over The Shoulder. The ceiling is transparent but my will is not"}}
{"song": "Glass Ceiling Sermon", "artist": "Cipher Ghost", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Sermon: persistence is the only prayer", "scene": {"mood": "nostalgia", "colors": ["midnight blue", "amber", "white"], "composition": "bird's eye", "camera": "orbit", "description": "Bird'S Eye. Sermon: persistence is the only prayer"}}
{"song": "Glass Ceiling Sermon", "artist": "Cipher Ghost", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "They hear noise I hear the future tuning", "scene": {"mood": "hunger", "colors": ["concrete gray", "neon green", "black"], "composition": "POV", "camera": "tilt up", "description": "Pov. They hear noise I hear the future tuning"}}
{"song": "Glass Ceiling Sermon", "artist": "Cipher Ghost", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Shards on the floor spell my new name", "scene": {"mood": "swagger", "colors": ["deep purple", "copper", "cream"], "composition": "dutch angle", "camera": "crane", "description": "Dutch Angle. Shards on the floor spell my new name"}}
{"song": "Glass Ceiling Sermon", "artist": "Cipher Ghost", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "I preach to the mirror and the mirror converts", "scene": {"mood": "resolve", "colors": ["blood red", "steel", "ivory"], "composition": "steady frame", "camera": "locked", "description": "Steady Frame. I preach to the mirror and the mirror converts"}}
{"song": "Signal Lost", "artist": "Verse Walker", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Static in the headphones, truth in the silence", "scene": {"mood": "grit", "colors": ["charcoal", "gold", "red"], "composition": "medium shot", "camera": "slow push", "description": "Medium Shot. Static in the headphones, truth in the silence"}}
{"song": "Signal Lost", "artist": "Verse Walker", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "We broadcast from basements the towers forgot", "scene": {"mood": "triumph", "colors": ["midnight blue", "amber", "white"], "composition": "low angle", "camera": "steady", "description": "Low Angle. We broadcast from basements the towers forgot"}}
{"song": "Signal Lost", "artist": "Verse Walker", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Dead zones where the real ones still speak", "scene": {"mood": "reflection", "colors": ["concrete gray", "neon green", "black"], "composition": "wide shot", "camera": "handheld", "description": "Wide Shot. Dead zones where the real ones still speak"}}
{"song": "Signal Lost", "artist": "Verse Walker", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Frequency of the forgotten is the loudest", "scene": {"mood": "defiance", "colors": ["deep purple", "copper", "cream"], "composition": "close-up", "camera": "whip pan", "description": "Close Up. Frequency of the forgotten is the loudest"}}
{"song": "Signal Lost", "artist": "Verse Walker", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Antenna bent but the message still lands", "scene": {"mood": "opulence", "colors": ["blood red", "steel", "ivory"], "composition": "tracking", "camera": "dolly", "description": "Tracking. Antenna bent but the message still lands"}}
{"song": "Signal Lost", "artist": "Verse Walker", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "No signal? No problem. We make our own waves", "scene": {"mood": "pressure", "colors": ["charcoal", "gold", "red"], "composition": "over the shoulder", "camera": "static", "description": "Over The Shoulder. No signal? No problem. We make our own waves"}}
{"song": "Signal Lost", "artist": "Verse Walker", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The dial turns but the song stays the same", "scene": {"mood": "nostalgia", "colors": ["midnight blue", "amber", "white"], "composition": "bird's eye", "camera": "orbit", "description": "Bird'S Eye. The dial turns but the song stays the same"}}
{"song": "Signal Lost", "artist": "Verse Walker", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Interference is just another word for resistance", "scene": {"mood": "hunger", "colors": ["concrete gray", "neon green", "black"], "composition": "POV", "camera": "tilt up", "description": "Pov. Interference is just another word for resistance"}}
{"song": "Signal Lost", "artist": "Verse Walker", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Lost signal found meaning", "scene": {"mood": "swagger", "colors": ["deep purple", "copper", "cream"], "composition": "dutch angle", "camera": "crane", "description": "Dutch Angle. Lost signal found meaning"}}
{"song": "Signal Lost", "artist": "Verse Walker", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "We are the dead air the revolution breathes", "scene": {"mood": "resolve", "colors": ["blood red", "steel", "ivory"], "composition": "steady frame", "camera": "locked", "description": "Steady Frame. We are the dead air the revolution breathes"}}
{"song": "Midnight Algebra", "artist": "Rhyme Architect", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "X equals the distance between who I was and am", "scene": {"mood": "grit", "colors": ["charcoal", "gold", "red"], "composition": "medium shot", "camera": "slow push", "description": "Medium Shot. X equals the distance between who I was and am"}}
{"song": "Midnight Algebra", "artist": "Rhyme Architect", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Solve for courage when the variables are fear", "scene": {"mood": "triumph", "colors": ["midnight blue", "amber", "white"], "composition": "low angle", "camera": "steady", "description": "Low Angle. Solve for courage when the variables are fear"}}
{"song": "Midnight Algebra", "artist": "Rhyme Architect", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The equation balances when I stop counting losses", "scene": {"mood": "reflection", "colors": ["concrete gray", "neon green", "black"], "composition": "wide shot", "camera": "handheld", "description": "Wide Shot. The equation balances when I stop counting losses"}}
{"song": "Midnight Algebra", "artist": "Rhyme Architect", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Y is the year I finally showed my work", "scene": {"mood": "defiance", "colors": ["deep purple", "copper", "cream"], "composition": "close-up", "camera": "whip pan", "description": "Close Up. Y is the year I finally showed my work"}}
{"song": "Midnight Algebra", "artist": "Rhyme Architect", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Integrate the pain into something beautiful", "scene": {"mood": "opulence", "colors": ["blood red", "steel", "ivory"], "composition": "tracking", "camera": "dolly", "description": "Tracking. Integrate the pain into something beautiful"}}
{"song": "Midnight Algebra", "artist": "Rhyme Architect", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The limit of doubt approaches zero tonight", "scene": {"mood": "pressure", "colors": ["charcoal", "gold", "red"], "composition": "over the shoulder", "camera": "static", "description": "Over The Shoulder. The limit of doubt approaches zero tonight"}}
{"song": "Midnight Algebra", "artist": "Rhyme Architect", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Factor out the fear and what remains is me", "scene": {"mood": "nostalgia", "colors": ["midnight blue", "amber", "white"], "composition": "bird's eye", "camera": "orbit", "description": "Bird'S Eye. Factor out the fear and what remains is me"}}
{"song": "Midnight Algebra", "artist": "Rhyme Architect", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The proof was always in the living not the writing", "scene": {"mood": "hunger", "colors": ["concrete gray", "neon green", "black"], "composition": "POV", "camera": "tilt up", "description": "Pov. The proof was always in the living not the writing"}}
{"song": "Midnight Algebra", "artist": "Rhyme Architect", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Subtract the noise multiply the truth", "scene": {"mood": "swagger", "colors": ["deep purple", "copper", "cream"], "composition": "dutch angle", "camera": "crane", "description": "Dutch Angle. Subtract the noise multiply the truth"}}
{"song": "Midnight Algebra", "artist": "Rhyme Architect", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Midnight is when the math finally makes sense", "scene": {"mood": "resolve", "colors": ["blood red", "steel", "ivory"], "composition": "steady frame", "camera": "locked", "description": "Steady Frame. Midnight is when the math finally makes sense"}}
{"song": "Gold Teeth Psalm", "artist": "Concrete Poet", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Gold in my mouth like a prayer I can taste", "scene": {"mood": "grit", "colors": ["charcoal", "gold", "red"], "composition": "medium shot", "camera": "slow push", "description": "Medium Shot. Gold in my mouth like a prayer I can taste"}}
{"song": "Gold Teeth Psalm", "artist": "Concrete Poet", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The psalm of the hustle never goes off beat", "scene": {"mood": "triumph", "colors": ["midnight blue", "amber", "white"], "composition": "low angle", "camera": "steady", "description": "Low Angle. The psalm of the hustle never goes off beat"}}
{"song": "Gold Teeth Psalm", "artist": "Concrete Poet", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every tooth a trophy from a war I won", "scene": {"mood": "reflection", "colors": ["concrete gray", "neon green", "black"], "composition": "wide shot", "camera": "handheld", "description": "Wide Shot. Every tooth a trophy from a war I won"}}
{"song": "Gold Teeth Psalm", "artist": "Concrete Poet", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Gold teeth: the gospel of the grind", "scene": {"mood": "defiance", "colors": ["deep purple", "copper", "cream"], "composition": "close-up", "camera": "whip pan", "description": "Close Up. Gold teeth: the gospel of the grind"}}
{"song": "Gold Teeth Psalm", "artist": "Concrete Poet", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The psalm plays in the currency of pain", "scene": {"mood": "opulence", "colors": ["blood red", "steel", "ivory"], "composition": "tracking", "camera": "dolly", "description": "Tracking. The psalm plays in the currency of pain"}}
{"song": "Gold Teeth Psalm", "artist": "Concrete Poet", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We flash gold because the world only sees shiny", "scene": {"mood": "pressure", "colors": ["charcoal", "gold", "red"], "composition": "over the shoulder", "camera": "static", "description": "Over The Shoulder. We flash gold because the world only sees shiny"}}
{"song": "Gold Teeth Psalm", "artist": "Concrete Poet", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Gold teeth psalm for the ones who ate dirt first", "scene": {"mood": "nostalgia", "colors": ["midnight blue", "amber", "white"], "composition": "bird's eye", "camera": "orbit", "description": "Bird'S Eye. Gold teeth psalm for the ones who ate dirt first"}}
{"song": "Gold Teeth Psalm", "artist": "Concrete Poet", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The gold doesn't rust but the smile does", "scene": {"mood": "hunger", "colors": ["concrete gray", "neon green", "black"], "composition": "POV", "camera": "tilt up", "description": "Pov. The gold doesn't rust but the smile does"}}
{"song": "Gold Teeth Psalm", "artist": "Concrete Poet", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Psalm of the gold tooth prophet", "scene": {"mood": "swagger", "colors": ["deep purple", "copper", "cream"], "composition": "dutch angle", "camera": "crane", "description": "Dutch Angle. Psalm of the gold tooth prophet"}}
{"song": "Gold Teeth Psalm", "artist": "Concrete Poet", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Gold in the mouth, fire in the belly", "scene": {"mood": "resolve", "colors": ["blood red", "steel", "ivory"], "composition": "steady frame", "camera": "locked", "description": "Steady Frame. Gold in the mouth, fire in the belly"}}
{"song": "Skyscraper Blues", "artist": "Block Sage", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "I built this tower with my bare hands and doubt", "scene": {"mood": "grit", "colors": ["charcoal", "gold", "red"], "composition": "medium shot", "camera": "slow push", "description": "Medium Shot. I built this tower with my bare hands and doubt"}}
{"song": "Skyscraper Blues", "artist": "Block Sage", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The blues echo higher than the penthouse", "scene": {"mood": "triumph", "colors": ["midnight blue", "amber", "white"], "composition": "low angle", "camera": "steady", "description": "Low Angle. The blues echo higher than the penthouse"}}
{"song": "Skyscraper Blues", "artist": "Block Sage", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Skyscraper blues: vertigo of success", "scene": {"mood": "reflection", "colors": ["concrete gray", "neon green", "black"], "composition": "wide shot", "camera": "handheld", "description": "Wide Shot. Skyscraper blues: vertigo of success"}}
{"song": "Skyscraper Blues", "artist": "Block Sage", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every floor is a chapter I survived", "scene": {"mood": "defiance", "colors": ["deep purple", "copper", "cream"], "composition": "close-up", "camera": "whip pan", "description": "Close Up. Every floor is a chapter I survived"}}
{"song": "Skyscraper Blues", "artist": "Block Sage", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The blues don't care about your zip code", "scene": {"mood": "opulence", "colors": ["blood red", "steel", "ivory"], "composition": "tracking", "camera": "dolly", "description": "Tracking. The blues don't care about your zip code"}}
{"song": "Skyscraper Blues", "artist": "Block Sage", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We look down and the blues look up", "scene": {"mood": "pressure", "colors": ["charcoal", "gold", "red"], "composition": "over the shoulder", "camera": "static", "description": "Over The Shoulder. We look down and the blues look up"}}
{"song": "Skyscraper Blues", "artist": "Block Sage", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Skyscraper: the blues in vertical format", "scene": {"mood": "nostalgia", "colors": ["midnight blue", "amber", "white"], "composition": "bird's eye", "camera": "orbit", "description": "Bird'S Eye. Skyscraper: the blues in vertical format"}}
{"song": "Skyscraper Blues", "artist": "Block Sage", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The higher you go the bluer the view", "scene": {"mood": "hunger", "colors": ["concrete gray", "neon green", "black"], "composition": "POV", "camera": "tilt up", "description": "Pov. The higher you go the bluer the view"}}
{"song": "Skyscraper Blues", "artist": "Block Sage", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Skyscraper blues for the high-rise lonely", "scene": {"mood": "swagger", "colors": ["deep purple", "copper", "cream"], "composition": "dutch angle", "camera": "crane", "description": "Dutch Angle. Skyscraper blues for the high-rise lonely"}}
{"song": "Skyscraper Blues", "artist": "Block Sage", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The tower sways but the blues stand still", "scene": {"mood": "resolve", "colors": ["blood red", "steel", "ivory"], "composition": "steady frame", "camera": "locked", "description": "Steady Frame. The tower sways but the blues stand still"}}
{"song": "Neon Confessional", "artist": "Cipher Ghost", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "I confess under fluorescent light", "scene": {"mood": "grit", "colors": ["charcoal", "gold", "red"], "composition": "medium shot", "camera": "slow push", "description": "Medium Shot. I confess under fluorescent light"}}
{"song": "Neon Confessional", "artist": "Cipher Ghost", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The confessional has a drive-through now", "scene": {"mood": "triumph", "colors": ["midnight blue", "amber", "white"], "composition": "low angle", "camera": "steady", "description": "Low Angle. The confessional has a drive-through now"}}
{"song": "Neon Confessional", "artist": "Cipher Ghost", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Neon sins glow brighter than virtue", "scene": {"mood": "reflection", "colors": ["concrete gray", "neon green", "black"], "composition": "wide shot", "camera": "handheld", "description": "Wide Shot. Neon sins glow brighter than virtue"}}
{"song": "Neon Confessional", "artist": "Cipher Ghost", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every confession is a performance", "scene": {"mood": "defiance", "colors": ["deep purple", "copper", "cream"], "composition": "close-up", "camera": "whip pan", "description": "Close Up. Every confession is a performance"}}
{"song": "Neon Confessional", "artist": "Cipher Ghost", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The confessional doesn't have a mute button", "scene": {"mood": "opulence", "colors": ["blood red", "steel", "ivory"], "composition": "tracking", "camera": "dolly", "description": "Tracking. The confessional doesn't have a mute button"}}
{"song": "Neon Confessional", "artist": "Cipher Ghost", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We confess to the algorithm and it judges", "scene": {"mood": "pressure", "colors": ["charcoal", "gold", "red"], "composition": "over the shoulder", "camera": "static", "description": "Over The Shoulder. We confess to the algorithm and it judges"}}
{"song": "Neon Confessional", "artist": "Cipher Ghost", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Neon confessional: sins in LED", "scene": {"mood": "nostalgia", "colors": ["midnight blue", "amber", "white"], "composition": "bird's eye", "camera": "orbit", "description": "Bird'S Eye. Neon confessional: sins in LED"}}
{"song": "Neon Confessional", "artist": "Cipher Ghost", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The booth is open 24/7 like a 7-Eleven", "scene": {"mood": "hunger", "colors": ["concrete gray", "neon green", "black"], "composition": "POV", "camera": "tilt up", "description": "Pov. The booth is open 24/7 like a 7-Eleven"}}
{"song": "Neon Confessional", "artist": "Cipher Ghost", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Confess under neon, absolved by sunrise", "scene": {"mood": "swagger", "colors": ["deep purple", "copper", "cream"], "composition": "dutch angle", "camera": "crane", "description": "Dutch Angle. Confess under neon, absolved by sunrise"}}
{"song": "Neon Confessional", "artist": "Cipher Ghost", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The neon remembers what we try to forget", "scene": {"mood": "resolve", "colors": ["blood red", "steel", "ivory"], "composition": "steady frame", "camera": "locked", "description": "Steady Frame. The neon remembers what we try to forget"}}
{"song": "Barcode Psalm", "artist": "Verse Walker", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "My identity is a series of lines and spaces", "scene": {"mood": "grit", "colors": ["charcoal", "gold", "red"], "composition": "medium shot", "camera": "slow push", "description": "Medium Shot. My identity is a series of lines and spaces"}}
{"song": "Barcode Psalm", "artist": "Verse Walker", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Scan me and see the price of my pain", "scene": {"mood": "triumph", "colors": ["midnight blue", "amber", "white"], "composition": "low angle", "camera": "steady", "description": "Low Angle. Scan me and see the price of my pain"}}
{"song": "Barcode Psalm", "artist": "Verse Walker", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Barcode psalm: the hymn of the commodified", "scene": {"mood": "reflection", "colors": ["concrete gray", "neon green", "black"], "composition": "wide shot", "camera": "handheld", "description": "Wide Shot. Barcode psalm: the hymn of the commodified"}}
{"song": "Barcode Psalm", "artist": "Verse Walker", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every barcode hides a story", "scene": {"mood": "defiance", "colors": ["deep purple", "copper", "cream"], "composition": "close-up", "camera": "whip pan", "description": "Close Up. Every barcode hides a story"}}
{"song": "Barcode Psalm", "artist": "Verse Walker", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The psalm of the scanned and the sold", "scene": {"mood": "opulence", "colors": ["blood red", "steel", "ivory"], "composition": "tracking", "camera": "dolly", "description": "Tracking. The psalm of the scanned and the sold"}}
{"song": "Barcode Psalm", "artist": "Verse Walker", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We are more than our barcode but less than our dreams", "scene": {"mood": "pressure", "colors": ["charcoal", "gold", "red"], "composition": "over the shoulder", "camera": "static", "description": "Over The Shoulder. We are more than our barcode but less than our dreams"}}
{"song": "Barcode Psalm", "artist": "Verse Walker", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Barcode: the modern scar of capitalism", "scene": {"mood": "nostalgia", "colors": ["midnight blue", "amber", "white"], "composition": "bird's eye", "camera": "orbit", "description": "Bird'S Eye. Barcode: the modern scar of capitalism"}}
{"song": "Barcode Psalm", "artist": "Verse Walker", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The psalm reads like a receipt", "scene": {"mood": "hunger", "colors": ["concrete gray", "neon green", "black"], "composition": "POV", "camera": "tilt up", "description": "Pov. The psalm reads like a receipt"}}
{"song": "Barcode Psalm", "artist": "Verse Walker", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Scan the barcode and find the ghost inside", "scene": {"mood": "swagger", "colors": ["deep purple", "copper", "cream"], "composition": "dutch angle", "camera": "crane", "description": "Dutch Angle. Scan the barcode and find the ghost inside"}}
{"song": "Barcode Psalm", "artist": "Verse Walker", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Barcode psalm: value assigned, not inherent", "scene": {"mood": "resolve", "colors": ["blood red", "steel", "ivory"], "composition": "steady frame", "camera": "locked", "description": "Steady Frame. Barcode psalm: value assigned, not inherent"}}
{"song": "Gravity Well", "artist": "Rhyme Architect", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "I orbit the thing that's destroying me", "scene": {"mood": "grit", "colors": ["charcoal", "gold", "red"], "composition": "medium shot", "camera": "slow push", "description": "Medium Shot. I orbit the thing that's destroying me"}}
{"song": "Gravity Well", "artist": "Rhyme Architect", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The gravity well of habit", "scene": {"mood": "triumph", "colors": ["midnight blue", "amber", "white"], "composition": "low angle", "camera": "steady", "description": "Low Angle. The gravity well of habit"}}
{"song": "Gravity Well", "artist": "Rhyme Architect", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every orbit is a compromise with the void", "scene": {"mood": "reflection", "colors": ["concrete gray", "neon green", "black"], "composition": "wide shot", "camera": "handheld", "description": "Wide Shot. Every orbit is a compromise with the void"}}
{"song": "Gravity Well", "artist": "Rhyme Architect", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Gravity: the oldest form of addiction", "scene": {"mood": "defiance", "colors": ["deep purple", "copper", "cream"], "composition": "close-up", "camera": "whip pan", "description": "Close Up. Gravity: the oldest form of addiction"}}
{"song": "Gravity Well", "artist": "Rhyme Architect", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The well doesn't pull\u2014it persuades", "scene": {"mood": "opulence", "colors": ["blood red", "steel", "ivory"], "composition": "tracking", "camera": "dolly", "description": "Tracking. The well doesn't pull\u2014it persuades"}}
{"song": "Gravity Well", "artist": "Rhyme Architect", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We fall in circles because straight lines are too honest", "scene": {"mood": "pressure", "colors": ["charcoal", "gold", "red"], "composition": "over the shoulder", "camera": "static", "description": "Over The Shoulder. We fall in circles because straight lines are too honest"}}
{"song": "Gravity Well", "artist": "Rhyme Architect", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Gravity well: the prison with no walls", "scene": {"mood": "nostalgia", "colors": ["midnight blue", "amber", "white"], "composition": "bird's eye", "camera": "orbit", "description": "Bird'S Eye. Gravity well: the prison with no walls"}}
{"song": "Gravity Well", "artist": "Rhyme Architect", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The pull is gentle until it isn't", "scene": {"mood": "hunger", "colors": ["concrete gray", "neon green", "black"], "composition": "POV", "camera": "tilt up", "description": "Pov. The pull is gentle until it isn't"}}
{"song": "Gravity Well", "artist": "Rhyme Architect", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Gravity well for the satellite souls", "scene": {"mood": "swagger", "colors": ["deep purple", "copper", "cream"], "composition": "dutch angle", "camera": "crane", "description": "Dutch Angle. Gravity well for the satellite souls"}}
{"song": "Gravity Well", "artist": "Rhyme Architect", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The well deepens with every revolution", "scene": {"mood": "resolve", "colors": ["blood red", "steel", "ivory"], "composition": "steady frame", "camera": "locked", "description": "Steady Frame. The well deepens with every revolution"}}

View File

@@ -1,100 +0,0 @@
{"song": "Blue Interval", "artist": "Blue Interval", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The space between notes is where jazz lives", "scene": {"mood": "improvisation", "colors": ["deep blue", "amber", "smoke gray"], "composition": "smoky club", "camera": "slow drift", "description": "Smoky Club. The space between notes is where jazz lives"}}
{"song": "Blue Interval", "artist": "Blue Interval", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Blue is the color of a chord that knows too much", "scene": {"mood": "cool", "colors": ["midnight", "gold", "burgundy"], "composition": "spotlight solo", "camera": "rack focus", "description": "Spotlight Solo. Blue is the color of a chord that knows too much"}}
{"song": "Blue Interval", "artist": "Blue Interval", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The interval stretches like a cat in the sun", "scene": {"mood": "smoky", "colors": ["slate", "copper", "cream"], "composition": "ensemble frame", "camera": "steady", "description": "Ensemble Frame. The interval stretches like a cat in the sun"}}
{"song": "Blue Interval", "artist": "Blue Interval", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every rest is a decision not a mistake", "scene": {"mood": "swing", "colors": ["navy", "champagne", "charcoal"], "composition": "instrument close-up", "camera": "handheld sway", "description": "Instrument Close Up. Every rest is a decision not a mistake"}}
{"song": "Blue Interval", "artist": "Blue Interval", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Blue interval\u2014the pause before the truth", "scene": {"mood": "melancholy", "colors": ["indigo", "bronze", "pearl"], "composition": "audience blur", "camera": "dolly", "description": "Audience Blur. Blue interval\u2014the pause before the truth"}}
{"song": "Blue Interval", "artist": "Blue Interval", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The interval is the jazz musician's vacation", "scene": {"mood": "exuberance", "colors": ["deep blue", "amber", "smoke gray"], "composition": "reflected in piano", "camera": "orbit", "description": "Reflected In Piano. The interval is the jazz musician's vacation"}}
{"song": "Blue Interval", "artist": "Blue Interval", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Blue notes don't cry\u2014they remember", "scene": {"mood": "mystery", "colors": ["midnight", "gold", "burgundy"], "composition": "silhouette trio", "camera": "locked", "description": "Silhouette Trio. Blue notes don't cry\u2014they remember"}}
{"song": "Blue Interval", "artist": "Blue Interval", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The interval between two lovers is a jazz standard", "scene": {"mood": "tenderness", "colors": ["slate", "copper", "cream"], "composition": "vinyl spinning", "camera": "tilt down", "description": "Vinyl Spinning. The interval between two lovers is a jazz standard"}}
{"song": "Blue Interval", "artist": "Blue Interval", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Blue is the frequency of honesty", "scene": {"mood": "chaos", "colors": ["navy", "champagne", "charcoal"], "composition": "bar counter", "camera": "soft zoom", "description": "Bar Counter. Blue is the frequency of honesty"}}
{"song": "Blue Interval", "artist": "Blue Interval", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The interval ends but the blue remains", "scene": {"mood": "resolution", "colors": ["indigo", "bronze", "pearl"], "composition": "stage depth", "camera": "floating", "description": "Stage Depth. The interval ends but the blue remains"}}
{"song": "Smoke Note", "artist": "Smoke Note", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The note hangs in the air like cigarette smoke", "scene": {"mood": "improvisation", "colors": ["deep blue", "amber", "smoke gray"], "composition": "smoky club", "camera": "slow drift", "description": "Smoky Club. The note hangs in the air like cigarette smoke"}}
{"song": "Smoke Note", "artist": "Smoke Note", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Smoke curls around the melody like a jealous lover", "scene": {"mood": "cool", "colors": ["midnight", "gold", "burgundy"], "composition": "spotlight solo", "camera": "rack focus", "description": "Spotlight Solo. Smoke curls around the melody like a jealous lover"}}
{"song": "Smoke Note", "artist": "Smoke Note", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every exhale is a riff", "scene": {"mood": "smoky", "colors": ["slate", "copper", "cream"], "composition": "ensemble frame", "camera": "steady", "description": "Ensemble Frame. Every exhale is a riff"}}
{"song": "Smoke Note", "artist": "Smoke Note", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The smoke note lingers after the club closes", "scene": {"mood": "swing", "colors": ["navy", "champagne", "charcoal"], "composition": "instrument close-up", "camera": "handheld sway", "description": "Instrument Close Up. The smoke note lingers after the club closes"}}
{"song": "Smoke Note", "artist": "Smoke Note", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Smoke: the visual form of a sustained chord", "scene": {"mood": "melancholy", "colors": ["indigo", "bronze", "pearl"], "composition": "audience blur", "camera": "dolly", "description": "Audience Blur. Smoke: the visual form of a sustained chord"}}
{"song": "Smoke Note", "artist": "Smoke Note", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The note dissolves but the smoke remembers", "scene": {"mood": "exuberance", "colors": ["deep blue", "amber", "smoke gray"], "composition": "reflected in piano", "camera": "orbit", "description": "Reflected In Piano. The note dissolves but the smoke remembers"}}
{"song": "Smoke Note", "artist": "Smoke Note", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We play until the smoke clears and the truth appears", "scene": {"mood": "mystery", "colors": ["midnight", "gold", "burgundy"], "composition": "silhouette trio", "camera": "locked", "description": "Silhouette Trio. We play until the smoke clears and the truth appears"}}
{"song": "Smoke Note", "artist": "Smoke Note", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Smoke note: the sound of something ending slowly", "scene": {"mood": "tenderness", "colors": ["slate", "copper", "cream"], "composition": "vinyl spinning", "camera": "tilt down", "description": "Vinyl Spinning. Smoke note: the sound of something ending slowly"}}
{"song": "Smoke Note", "artist": "Smoke Note", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The smoke doesn't judge the fire", "scene": {"mood": "chaos", "colors": ["navy", "champagne", "charcoal"], "composition": "bar counter", "camera": "soft zoom", "description": "Bar Counter. The smoke doesn't judge the fire"}}
{"song": "Smoke Note", "artist": "Smoke Note", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Every note leaves a trail of smoke", "scene": {"mood": "resolution", "colors": ["indigo", "bronze", "pearl"], "composition": "stage depth", "camera": "floating", "description": "Stage Depth. Every note leaves a trail of smoke"}}
{"song": "Phantom Chord", "artist": "Midnight Quartet", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The chord you hear but can't identify", "scene": {"mood": "improvisation", "colors": ["deep blue", "amber", "smoke gray"], "composition": "smoky club", "camera": "slow drift", "description": "Smoky Club. The chord you hear but can't identify"}}
{"song": "Phantom Chord", "artist": "Midnight Quartet", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Phantom chords haunt the spaces between keys", "scene": {"mood": "cool", "colors": ["midnight", "gold", "burgundy"], "composition": "spotlight solo", "camera": "rack focus", "description": "Spotlight Solo. Phantom chords haunt the spaces between keys"}}
{"song": "Phantom Chord", "artist": "Midnight Quartet", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The chord resolves but the ghost stays", "scene": {"mood": "smoky", "colors": ["slate", "copper", "cream"], "composition": "ensemble frame", "camera": "steady", "description": "Ensemble Frame. The chord resolves but the ghost stays"}}
{"song": "Phantom Chord", "artist": "Midnight Quartet", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every jazz standard has a phantom chord", "scene": {"mood": "swing", "colors": ["navy", "champagne", "charcoal"], "composition": "instrument close-up", "camera": "handheld sway", "description": "Instrument Close Up. Every jazz standard has a phantom chord"}}
{"song": "Phantom Chord", "artist": "Midnight Quartet", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The chord was there\u2014we just weren't listening", "scene": {"mood": "melancholy", "colors": ["indigo", "bronze", "pearl"], "composition": "audience blur", "camera": "dolly", "description": "Audience Blur. The chord was there\u2014we just weren't listening"}}
{"song": "Phantom Chord", "artist": "Midnight Quartet", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Phantom: the chord that plays after the song ends", "scene": {"mood": "exuberance", "colors": ["deep blue", "amber", "smoke gray"], "composition": "reflected in piano", "camera": "orbit", "description": "Reflected In Piano. Phantom: the chord that plays after the song ends"}}
{"song": "Phantom Chord", "artist": "Midnight Quartet", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The chord belongs to no key and every key", "scene": {"mood": "mystery", "colors": ["midnight", "gold", "burgundy"], "composition": "silhouette trio", "camera": "locked", "description": "Silhouette Trio. The chord belongs to no key and every key"}}
{"song": "Phantom Chord", "artist": "Midnight Quartet", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "We chase phantom chords like detectives", "scene": {"mood": "tenderness", "colors": ["slate", "copper", "cream"], "composition": "vinyl spinning", "camera": "tilt down", "description": "Vinyl Spinning. We chase phantom chords like detectives"}}
{"song": "Phantom Chord", "artist": "Midnight Quartet", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The chord is the question, the silence is the answer", "scene": {"mood": "chaos", "colors": ["navy", "champagne", "charcoal"], "composition": "bar counter", "camera": "soft zoom", "description": "Bar Counter. The chord is the question, the silence is the answer"}}
{"song": "Phantom Chord", "artist": "Midnight Quartet", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Phantom chords: the jazz musician's GPS", "scene": {"mood": "resolution", "colors": ["indigo", "bronze", "pearl"], "composition": "stage depth", "camera": "floating", "description": "Stage Depth. Phantom chords: the jazz musician's GPS"}}
{"song": "Midnight Improv", "artist": "Velvet Chord", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The clock strikes twelve and the rules dissolve", "scene": {"mood": "improvisation", "colors": ["deep blue", "amber", "smoke gray"], "composition": "smoky club", "camera": "slow drift", "description": "Smoky Club. The clock strikes twelve and the rules dissolve"}}
{"song": "Midnight Improv", "artist": "Velvet Chord", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Improv is just courage set to music", "scene": {"mood": "cool", "colors": ["midnight", "gold", "burgundy"], "composition": "spotlight solo", "camera": "rack focus", "description": "Spotlight Solo. Improv is just courage set to music"}}
{"song": "Midnight Improv", "artist": "Velvet Chord", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Midnight: when the sheet music goes to sleep", "scene": {"mood": "smoky", "colors": ["slate", "copper", "cream"], "composition": "ensemble frame", "camera": "steady", "description": "Ensemble Frame. Midnight: when the sheet music goes to sleep"}}
{"song": "Midnight Improv", "artist": "Velvet Chord", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every improvisation is a small rebellion", "scene": {"mood": "swing", "colors": ["navy", "champagne", "charcoal"], "composition": "instrument close-up", "camera": "handheld sway", "description": "Instrument Close Up. Every improvisation is a small rebellion"}}
{"song": "Midnight Improv", "artist": "Velvet Chord", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The improv doesn't care about your rehearsal", "scene": {"mood": "melancholy", "colors": ["indigo", "bronze", "pearl"], "composition": "audience blur", "camera": "dolly", "description": "Audience Blur. The improv doesn't care about your rehearsal"}}
{"song": "Midnight Improv", "artist": "Velvet Chord", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Midnight is the jazz musician's playground", "scene": {"mood": "exuberance", "colors": ["deep blue", "amber", "smoke gray"], "composition": "reflected in piano", "camera": "orbit", "description": "Reflected In Piano. Midnight is the jazz musician's playground"}}
{"song": "Midnight Improv", "artist": "Velvet Chord", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We improvise because the truth is unrehearsed", "scene": {"mood": "mystery", "colors": ["midnight", "gold", "burgundy"], "composition": "silhouette trio", "camera": "locked", "description": "Silhouette Trio. We improvise because the truth is unrehearsed"}}
{"song": "Midnight Improv", "artist": "Velvet Chord", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The improv is the most honest thing we play", "scene": {"mood": "tenderness", "colors": ["slate", "copper", "cream"], "composition": "vinyl spinning", "camera": "tilt down", "description": "Vinyl Spinning. The improv is the most honest thing we play"}}
{"song": "Midnight Improv", "artist": "Velvet Chord", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Midnight improv: the dark room of music", "scene": {"mood": "chaos", "colors": ["navy", "champagne", "charcoal"], "composition": "bar counter", "camera": "soft zoom", "description": "Bar Counter. Midnight improv: the dark room of music"}}
{"song": "Midnight Improv", "artist": "Velvet Chord", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The improv ends when the sun remembers us", "scene": {"mood": "resolution", "colors": ["indigo", "bronze", "pearl"], "composition": "stage depth", "camera": "floating", "description": "Stage Depth. The improv ends when the sun remembers us"}}
{"song": "Vinyl Psalm", "artist": "Phantom Scale", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The needle drops and the psalm begins", "scene": {"mood": "improvisation", "colors": ["deep blue", "amber", "smoke gray"], "composition": "smoky club", "camera": "slow drift", "description": "Smoky Club. The needle drops and the psalm begins"}}
{"song": "Vinyl Psalm", "artist": "Phantom Scale", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Vinyl: the most honest form of memory", "scene": {"mood": "cool", "colors": ["midnight", "gold", "burgundy"], "composition": "spotlight solo", "camera": "rack focus", "description": "Spotlight Solo. Vinyl: the most honest form of memory"}}
{"song": "Vinyl Psalm", "artist": "Phantom Scale", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every scratch on the record is a prayer", "scene": {"mood": "smoky", "colors": ["slate", "copper", "cream"], "composition": "ensemble frame", "camera": "steady", "description": "Ensemble Frame. Every scratch on the record is a prayer"}}
{"song": "Vinyl Psalm", "artist": "Phantom Scale", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The psalm is in the crackle not the melody", "scene": {"mood": "swing", "colors": ["navy", "champagne", "charcoal"], "composition": "instrument close-up", "camera": "handheld sway", "description": "Instrument Close Up. The psalm is in the crackle not the melody"}}
{"song": "Vinyl Psalm", "artist": "Phantom Scale", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Vinyl remembers what digital forgets", "scene": {"mood": "melancholy", "colors": ["indigo", "bronze", "pearl"], "composition": "audience blur", "camera": "dolly", "description": "Audience Blur. Vinyl remembers what digital forgets"}}
{"song": "Vinyl Psalm", "artist": "Phantom Scale", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The psalm plays at 33 revolutions per minute", "scene": {"mood": "exuberance", "colors": ["deep blue", "amber", "smoke gray"], "composition": "reflected in piano", "camera": "orbit", "description": "Reflected In Piano. The psalm plays at 33 revolutions per minute"}}
{"song": "Vinyl Psalm", "artist": "Phantom Scale", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We worship at the altar of the turntable", "scene": {"mood": "mystery", "colors": ["midnight", "gold", "burgundy"], "composition": "silhouette trio", "camera": "locked", "description": "Silhouette Trio. We worship at the altar of the turntable"}}
{"song": "Vinyl Psalm", "artist": "Phantom Scale", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Vinyl psalm: the grooves are the gospel", "scene": {"mood": "tenderness", "colors": ["slate", "copper", "cream"], "composition": "vinyl spinning", "camera": "tilt down", "description": "Vinyl Spinning. Vinyl psalm: the grooves are the gospel"}}
{"song": "Vinyl Psalm", "artist": "Phantom Scale", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The needle finds the truth in the groove", "scene": {"mood": "chaos", "colors": ["navy", "champagne", "charcoal"], "composition": "bar counter", "camera": "soft zoom", "description": "Bar Counter. The needle finds the truth in the groove"}}
{"song": "Vinyl Psalm", "artist": "Phantom Scale", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The psalm skips but the faith doesn't", "scene": {"mood": "resolution", "colors": ["indigo", "bronze", "pearl"], "composition": "stage depth", "camera": "floating", "description": "Stage Depth. The psalm skips but the faith doesn't"}}
{"song": "Whiskey Chord", "artist": "Blue Interval", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The chord tastes like aged oak and regret", "scene": {"mood": "improvisation", "colors": ["deep blue", "amber", "smoke gray"], "composition": "smoky club", "camera": "slow drift", "description": "Smoky Club. The chord tastes like aged oak and regret"}}
{"song": "Whiskey Chord", "artist": "Blue Interval", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Whiskey chord: the sound of distilled sorrow", "scene": {"mood": "cool", "colors": ["midnight", "gold", "burgundy"], "composition": "spotlight solo", "camera": "rack focus", "description": "Spotlight Solo. Whiskey chord: the sound of distilled sorrow"}}
{"song": "Whiskey Chord", "artist": "Blue Interval", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every sip is a blue note", "scene": {"mood": "smoky", "colors": ["slate", "copper", "cream"], "composition": "ensemble frame", "camera": "steady", "description": "Ensemble Frame. Every sip is a blue note"}}
{"song": "Whiskey Chord", "artist": "Blue Interval", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The chord warms like bourbon in the chest", "scene": {"mood": "swing", "colors": ["navy", "champagne", "charcoal"], "composition": "instrument close-up", "camera": "handheld sway", "description": "Instrument Close Up. The chord warms like bourbon in the chest"}}
{"song": "Whiskey Chord", "artist": "Blue Interval", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Whiskey: the liquid form of a minor key", "scene": {"mood": "melancholy", "colors": ["indigo", "bronze", "pearl"], "composition": "audience blur", "camera": "dolly", "description": "Audience Blur. Whiskey: the liquid form of a minor key"}}
{"song": "Whiskey Chord", "artist": "Blue Interval", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The chord ages in the barrel of the night", "scene": {"mood": "exuberance", "colors": ["deep blue", "amber", "smoke gray"], "composition": "reflected in piano", "camera": "orbit", "description": "Reflected In Piano. The chord ages in the barrel of the night"}}
{"song": "Whiskey Chord", "artist": "Blue Interval", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We drink the chord and play the whiskey", "scene": {"mood": "mystery", "colors": ["midnight", "gold", "burgundy"], "composition": "silhouette trio", "camera": "locked", "description": "Silhouette Trio. We drink the chord and play the whiskey"}}
{"song": "Whiskey Chord", "artist": "Blue Interval", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Whiskey chord: intoxicating resolution", "scene": {"mood": "tenderness", "colors": ["slate", "copper", "cream"], "composition": "vinyl spinning", "camera": "tilt down", "description": "Vinyl Spinning. Whiskey chord: intoxicating resolution"}}
{"song": "Whiskey Chord", "artist": "Blue Interval", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The chord and the whiskey agree on everything", "scene": {"mood": "chaos", "colors": ["navy", "champagne", "charcoal"], "composition": "bar counter", "camera": "soft zoom", "description": "Bar Counter. The chord and the whiskey agree on everything"}}
{"song": "Whiskey Chord", "artist": "Blue Interval", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Whiskey chord: smooth, smoky, and slightly out of tune", "scene": {"mood": "resolution", "colors": ["indigo", "bronze", "pearl"], "composition": "stage depth", "camera": "floating", "description": "Stage Depth. Whiskey chord: smooth, smoky, and slightly out of tune"}}
{"song": "Rooftop Cadence", "artist": "Smoke Note", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The cadence falls like rain on the city below", "scene": {"mood": "improvisation", "colors": ["deep blue", "amber", "smoke gray"], "composition": "smoky club", "camera": "slow drift", "description": "Smoky Club. The cadence falls like rain on the city below"}}
{"song": "Rooftop Cadence", "artist": "Smoke Note", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Rooftop: where jazz goes to breathe", "scene": {"mood": "cool", "colors": ["midnight", "gold", "burgundy"], "composition": "spotlight solo", "camera": "rack focus", "description": "Spotlight Solo. Rooftop: where jazz goes to breathe"}}
{"song": "Rooftop Cadence", "artist": "Smoke Note", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every cadence is a rooftop decision", "scene": {"mood": "smoky", "colors": ["slate", "copper", "cream"], "composition": "ensemble frame", "camera": "steady", "description": "Ensemble Frame. Every cadence is a rooftop decision"}}
{"song": "Rooftop Cadence", "artist": "Smoke Note", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The cadence floats between the stars and the street", "scene": {"mood": "swing", "colors": ["navy", "champagne", "charcoal"], "composition": "instrument close-up", "camera": "handheld sway", "description": "Instrument Close Up. The cadence floats between the stars and the street"}}
{"song": "Rooftop Cadence", "artist": "Smoke Note", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Rooftop cadence: jazz at altitude", "scene": {"mood": "melancholy", "colors": ["indigo", "bronze", "pearl"], "composition": "audience blur", "camera": "dolly", "description": "Audience Blur. Rooftop cadence: jazz at altitude"}}
{"song": "Rooftop Cadence", "artist": "Smoke Note", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We play the cadence because the sky demands it", "scene": {"mood": "exuberance", "colors": ["deep blue", "amber", "smoke gray"], "composition": "reflected in piano", "camera": "orbit", "description": "Reflected In Piano. We play the cadence because the sky demands it"}}
{"song": "Rooftop Cadence", "artist": "Smoke Note", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The cadence rises while the city sleeps", "scene": {"mood": "mystery", "colors": ["midnight", "gold", "burgundy"], "composition": "silhouette trio", "camera": "locked", "description": "Silhouette Trio. The cadence rises while the city sleeps"}}
{"song": "Rooftop Cadence", "artist": "Smoke Note", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Rooftop: the penthouse of sound", "scene": {"mood": "tenderness", "colors": ["slate", "copper", "cream"], "composition": "vinyl spinning", "camera": "tilt down", "description": "Vinyl Spinning. Rooftop: the penthouse of sound"}}
{"song": "Rooftop Cadence", "artist": "Smoke Note", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The cadence is the bridge between earth and note", "scene": {"mood": "chaos", "colors": ["navy", "champagne", "charcoal"], "composition": "bar counter", "camera": "soft zoom", "description": "Bar Counter. The cadence is the bridge between earth and note"}}
{"song": "Rooftop Cadence", "artist": "Smoke Note", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Rooftop cadence: the jazz of elevation", "scene": {"mood": "resolution", "colors": ["indigo", "bronze", "pearl"], "composition": "stage depth", "camera": "floating", "description": "Stage Depth. Rooftop cadence: the jazz of elevation"}}
{"song": "Smoked Mirror", "artist": "Midnight Quartet", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The mirror reflects in smoke and brass", "scene": {"mood": "improvisation", "colors": ["deep blue", "amber", "smoke gray"], "composition": "smoky club", "camera": "slow drift", "description": "Smoky Club. The mirror reflects in smoke and brass"}}
{"song": "Smoked Mirror", "artist": "Midnight Quartet", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Smoked mirror: the jazz of distortion", "scene": {"mood": "cool", "colors": ["midnight", "gold", "burgundy"], "composition": "spotlight solo", "camera": "rack focus", "description": "Spotlight Solo. Smoked mirror: the jazz of distortion"}}
{"song": "Smoked Mirror", "artist": "Midnight Quartet", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every reflection is a blue note", "scene": {"mood": "smoky", "colors": ["slate", "copper", "cream"], "composition": "ensemble frame", "camera": "steady", "description": "Ensemble Frame. Every reflection is a blue note"}}
{"song": "Smoked Mirror", "artist": "Midnight Quartet", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The mirror smokes because the room is alive", "scene": {"mood": "swing", "colors": ["navy", "champagne", "charcoal"], "composition": "instrument close-up", "camera": "handheld sway", "description": "Instrument Close Up. The mirror smokes because the room is alive"}}
{"song": "Smoked Mirror", "artist": "Midnight Quartet", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Smoked mirror: the hazy truth", "scene": {"mood": "melancholy", "colors": ["indigo", "bronze", "pearl"], "composition": "audience blur", "camera": "dolly", "description": "Audience Blur. Smoked mirror: the hazy truth"}}
{"song": "Smoked Mirror", "artist": "Midnight Quartet", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We look in the smoked mirror and see our best selves", "scene": {"mood": "exuberance", "colors": ["deep blue", "amber", "smoke gray"], "composition": "reflected in piano", "camera": "orbit", "description": "Reflected In Piano. We look in the smoked mirror and see our best selves"}}
{"song": "Smoked Mirror", "artist": "Midnight Quartet", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The mirror doesn't judge\u2014it just smokes", "scene": {"mood": "mystery", "colors": ["midnight", "gold", "burgundy"], "composition": "silhouette trio", "camera": "locked", "description": "Silhouette Trio. The mirror doesn't judge\u2014it just smokes"}}
{"song": "Smoked Mirror", "artist": "Midnight Quartet", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Smoked mirror: the jazz club's oracle", "scene": {"mood": "tenderness", "colors": ["slate", "copper", "cream"], "composition": "vinyl spinning", "camera": "tilt down", "description": "Vinyl Spinning. Smoked mirror: the jazz club's oracle"}}
{"song": "Smoked Mirror", "artist": "Midnight Quartet", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The reflection swings like a door on its hinge", "scene": {"mood": "chaos", "colors": ["navy", "champagne", "charcoal"], "composition": "bar counter", "camera": "soft zoom", "description": "Bar Counter. The reflection swings like a door on its hinge"}}
{"song": "Smoked Mirror", "artist": "Midnight Quartet", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Smoked mirror: honest distortion", "scene": {"mood": "resolution", "colors": ["indigo", "bronze", "pearl"], "composition": "stage depth", "camera": "floating", "description": "Stage Depth. Smoked mirror: honest distortion"}}
{"song": "Velvet Catastrophe", "artist": "Velvet Chord", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The catastrophe wears a velvet jacket", "scene": {"mood": "improvisation", "colors": ["deep blue", "amber", "smoke gray"], "composition": "smoky club", "camera": "slow drift", "description": "Smoky Club. The catastrophe wears a velvet jacket"}}
{"song": "Velvet Catastrophe", "artist": "Velvet Chord", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Velvet catastrophe: the elegant disaster", "scene": {"mood": "cool", "colors": ["midnight", "gold", "burgundy"], "composition": "spotlight solo", "camera": "rack focus", "description": "Spotlight Solo. Velvet catastrophe: the elegant disaster"}}
{"song": "Velvet Catastrophe", "artist": "Velvet Chord", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every catastrophe deserves a good soundtrack", "scene": {"mood": "smoky", "colors": ["slate", "copper", "cream"], "composition": "ensemble frame", "camera": "steady", "description": "Ensemble Frame. Every catastrophe deserves a good soundtrack"}}
{"song": "Velvet Catastrophe", "artist": "Velvet Chord", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The velvet absorbs the shock of the fall", "scene": {"mood": "swing", "colors": ["navy", "champagne", "charcoal"], "composition": "instrument close-up", "camera": "handheld sway", "description": "Instrument Close Up. The velvet absorbs the shock of the fall"}}
{"song": "Velvet Catastrophe", "artist": "Velvet Chord", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Catastrophe: the jazz musician's muse", "scene": {"mood": "melancholy", "colors": ["indigo", "bronze", "pearl"], "composition": "audience blur", "camera": "dolly", "description": "Audience Blur. Catastrophe: the jazz musician's muse"}}
{"song": "Velvet Catastrophe", "artist": "Velvet Chord", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We dress our disasters in velvet because we have class", "scene": {"mood": "exuberance", "colors": ["deep blue", "amber", "smoke gray"], "composition": "reflected in piano", "camera": "orbit", "description": "Reflected In Piano. We dress our disasters in velvet because we have class"}}
{"song": "Velvet Catastrophe", "artist": "Velvet Chord", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The catastrophe swings instead of crashing", "scene": {"mood": "mystery", "colors": ["midnight", "gold", "burgundy"], "composition": "silhouette trio", "camera": "locked", "description": "Silhouette Trio. The catastrophe swings instead of crashing"}}
{"song": "Velvet Catastrophe", "artist": "Velvet Chord", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Velvet catastrophe: the smooth collapse", "scene": {"mood": "tenderness", "colors": ["slate", "copper", "cream"], "composition": "vinyl spinning", "camera": "tilt down", "description": "Vinyl Spinning. Velvet catastrophe: the smooth collapse"}}
{"song": "Velvet Catastrophe", "artist": "Velvet Chord", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The disaster plays a solo before it leaves", "scene": {"mood": "chaos", "colors": ["navy", "champagne", "charcoal"], "composition": "bar counter", "camera": "soft zoom", "description": "Bar Counter. The disaster plays a solo before it leaves"}}
{"song": "Velvet Catastrophe", "artist": "Velvet Chord", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Velvet catastrophe: the most dignified ruin", "scene": {"mood": "resolution", "colors": ["indigo", "bronze", "pearl"], "composition": "stage depth", "camera": "floating", "description": "Stage Depth. Velvet catastrophe: the most dignified ruin"}}
{"song": "Amber Interval", "artist": "Phantom Scale", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The interval glows like trapped sunlight", "scene": {"mood": "improvisation", "colors": ["deep blue", "amber", "smoke gray"], "composition": "smoky club", "camera": "slow drift", "description": "Smoky Club. The interval glows like trapped sunlight"}}
{"song": "Amber Interval", "artist": "Phantom Scale", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Amber interval: the preserved pause", "scene": {"mood": "cool", "colors": ["midnight", "gold", "burgundy"], "composition": "spotlight solo", "camera": "rack focus", "description": "Spotlight Solo. Amber interval: the preserved pause"}}
{"song": "Amber Interval", "artist": "Phantom Scale", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every interval is a tiny eternity", "scene": {"mood": "smoky", "colors": ["slate", "copper", "cream"], "composition": "ensemble frame", "camera": "steady", "description": "Ensemble Frame. Every interval is a tiny eternity"}}
{"song": "Amber Interval", "artist": "Phantom Scale", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The amber holds the note forever", "scene": {"mood": "swing", "colors": ["navy", "champagne", "charcoal"], "composition": "instrument close-up", "camera": "handheld sway", "description": "Instrument Close Up. The amber holds the note forever"}}
{"song": "Amber Interval", "artist": "Phantom Scale", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Amber interval: the fossil of sound", "scene": {"mood": "melancholy", "colors": ["indigo", "bronze", "pearl"], "composition": "audience blur", "camera": "dolly", "description": "Audience Blur. Amber interval: the fossil of sound"}}
{"song": "Amber Interval", "artist": "Phantom Scale", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We pause in amber because time is too fast", "scene": {"mood": "exuberance", "colors": ["deep blue", "amber", "smoke gray"], "composition": "reflected in piano", "camera": "orbit", "description": "Reflected In Piano. We pause in amber because time is too fast"}}
{"song": "Amber Interval", "artist": "Phantom Scale", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The interval glows with trapped beauty", "scene": {"mood": "mystery", "colors": ["midnight", "gold", "burgundy"], "composition": "silhouette trio", "camera": "locked", "description": "Silhouette Trio. The interval glows with trapped beauty"}}
{"song": "Amber Interval", "artist": "Phantom Scale", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Amber: the jazz of preservation", "scene": {"mood": "tenderness", "colors": ["slate", "copper", "cream"], "composition": "vinyl spinning", "camera": "tilt down", "description": "Vinyl Spinning. Amber: the jazz of preservation"}}
{"song": "Amber Interval", "artist": "Phantom Scale", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The interval is the amber of the melody", "scene": {"mood": "chaos", "colors": ["navy", "champagne", "charcoal"], "composition": "bar counter", "camera": "soft zoom", "description": "Bar Counter. The interval is the amber of the melody"}}
{"song": "Amber Interval", "artist": "Phantom Scale", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Amber interval: frozen music", "scene": {"mood": "resolution", "colors": ["indigo", "bronze", "pearl"], "composition": "stage depth", "camera": "floating", "description": "Stage Depth. Amber interval: frozen music"}}

View File

@@ -1,100 +0,0 @@
{"song": "Sol Ardiente", "artist": "Sol Ardiente", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The sun burns because it can't hold back", "scene": {"mood": "passion", "colors": ["sunset orange", "turquoise", "gold"], "composition": "dance floor", "camera": "tracking dance", "description": "Dance Floor. The sun burns because it can't hold back"}}
{"song": "Sol Ardiente", "artist": "Sol Ardiente", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Ardiente: the fever that heals", "scene": {"mood": "celebration", "colors": ["hot pink", "deep red", "cream"], "composition": "street festival", "camera": "steady wide", "description": "Street Festival. Ardiente: the fever that heals"}}
{"song": "Sol Ardiente", "artist": "Sol Ardiente", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "We dance because the sun is watching", "scene": {"mood": "longing", "colors": ["emerald", "bronze", "white"], "composition": "balcony view", "camera": "handheld energy", "description": "Balcony View. We dance because the sun is watching"}}
{"song": "Sol Ardiente", "artist": "Sol Ardiente", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The sun doesn't set\u2014it takes a bow", "scene": {"mood": "joy", "colors": ["marigold", "indigo", "coral"], "composition": "beach sunset", "camera": "slow motion", "description": "Beach Sunset. The sun doesn't set\u2014it takes a bow"}}
{"song": "Sol Ardiente", "artist": "Sol Ardiente", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Sol ardiente: the star that never learned subtlety", "scene": {"mood": "defiance", "colors": ["papaya", "midnight", "silver"], "composition": "market crowd", "camera": "drone sweep", "description": "Market Crowd. Sol ardiente: the star that never learned subtlety"}}
{"song": "Sol Ardiente", "artist": "Sol Ardiente", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The sun writes in gold on the skin of the earth", "scene": {"mood": "sensuality", "colors": ["sunset orange", "turquoise", "gold"], "composition": "courtyard", "camera": "orbit", "description": "Courtyard. The sun writes in gold on the skin of the earth"}}
{"song": "Sol Ardiente", "artist": "Sol Ardiente", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Ardiente: the temperature of true love", "scene": {"mood": "pride", "colors": ["hot pink", "deep red", "cream"], "composition": "plaza fountain", "camera": "crane up", "description": "Plaza Fountain. Ardiente: the temperature of true love"}}
{"song": "Sol Ardiente", "artist": "Sol Ardiente", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "We worship the sun with our feet", "scene": {"mood": "nostalgia", "colors": ["emerald", "bronze", "white"], "composition": "rooftop party", "camera": "dolly in", "description": "Rooftop Party. We worship the sun with our feet"}}
{"song": "Sol Ardiente", "artist": "Sol Ardiente", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The sun is the oldest DJ", "scene": {"mood": "fervor", "colors": ["marigold", "indigo", "coral"], "composition": "candlelit room", "camera": "locked", "description": "Candlelit Room. The sun is the oldest DJ"}}
{"song": "Sol Ardiente", "artist": "Sol Ardiente", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Sol ardiente: light that refuses to whisper", "scene": {"mood": "devotion", "colors": ["papaya", "midnight", "silver"], "composition": "parade", "camera": "whip pan", "description": "Parade. Sol ardiente: light that refuses to whisper"}}
{"song": "Ritmo Sagrado", "artist": "Ritmo Sagrado", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The rhythm is holy because it never stops", "scene": {"mood": "passion", "colors": ["sunset orange", "turquoise", "gold"], "composition": "dance floor", "camera": "tracking dance", "description": "Dance Floor. The rhythm is holy because it never stops"}}
{"song": "Ritmo Sagrado", "artist": "Ritmo Sagrado", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Sagrado: the beat that blesses the ground", "scene": {"mood": "celebration", "colors": ["hot pink", "deep red", "cream"], "composition": "street festival", "camera": "steady wide", "description": "Street Festival. Sagrado: the beat that blesses the ground"}}
{"song": "Ritmo Sagrado", "artist": "Ritmo Sagrado", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every step is a prayer to the drum", "scene": {"mood": "longing", "colors": ["emerald", "bronze", "white"], "composition": "balcony view", "camera": "handheld energy", "description": "Balcony View. Every step is a prayer to the drum"}}
{"song": "Ritmo Sagrado", "artist": "Ritmo Sagrado", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The rhythm was here before the melody", "scene": {"mood": "joy", "colors": ["marigold", "indigo", "coral"], "composition": "beach sunset", "camera": "slow motion", "description": "Beach Sunset. The rhythm was here before the melody"}}
{"song": "Ritmo Sagrado", "artist": "Ritmo Sagrado", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Ritmo sagrado: the pulse of the ancestors", "scene": {"mood": "defiance", "colors": ["papaya", "midnight", "silver"], "composition": "market crowd", "camera": "drone sweep", "description": "Market Crowd. Ritmo sagrado: the pulse of the ancestors"}}
{"song": "Ritmo Sagrado", "artist": "Ritmo Sagrado", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The sacred rhythm doesn't need a church", "scene": {"mood": "sensuality", "colors": ["sunset orange", "turquoise", "gold"], "composition": "courtyard", "camera": "orbit", "description": "Courtyard. The sacred rhythm doesn't need a church"}}
{"song": "Ritmo Sagrado", "artist": "Ritmo Sagrado", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We dance the ritmo because stillness is a sin", "scene": {"mood": "pride", "colors": ["hot pink", "deep red", "cream"], "composition": "plaza fountain", "camera": "crane up", "description": "Plaza Fountain. We dance the ritmo because stillness is a sin"}}
{"song": "Ritmo Sagrado", "artist": "Ritmo Sagrado", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Sagrado: the beat that forgives everything", "scene": {"mood": "nostalgia", "colors": ["emerald", "bronze", "white"], "composition": "rooftop party", "camera": "dolly in", "description": "Rooftop Party. Sagrado: the beat that forgives everything"}}
{"song": "Ritmo Sagrado", "artist": "Ritmo Sagrado", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The rhythm remembers what we forget", "scene": {"mood": "fervor", "colors": ["marigold", "indigo", "coral"], "composition": "candlelit room", "camera": "locked", "description": "Candlelit Room. The rhythm remembers what we forget"}}
{"song": "Ritmo Sagrado", "artist": "Ritmo Sagrado", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Ritmo sagrado: the heartbeat of the earth", "scene": {"mood": "devotion", "colors": ["papaya", "midnight", "silver"], "composition": "parade", "camera": "whip pan", "description": "Parade. Ritmo sagrado: the heartbeat of the earth"}}
{"song": "Fuego Lento", "artist": "Fuego Lento", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Slow fire burns longest", "scene": {"mood": "passion", "colors": ["sunset orange", "turquoise", "gold"], "composition": "dance floor", "camera": "tracking dance", "description": "Dance Floor. Slow fire burns longest"}}
{"song": "Fuego Lento", "artist": "Fuego Lento", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Fuego lento: the patience of passion", "scene": {"mood": "celebration", "colors": ["hot pink", "deep red", "cream"], "composition": "street festival", "camera": "steady wide", "description": "Street Festival. Fuego lento: the patience of passion"}}
{"song": "Fuego Lento", "artist": "Fuego Lento", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The fire doesn't rush\u2014it savors", "scene": {"mood": "longing", "colors": ["emerald", "bronze", "white"], "composition": "balcony view", "camera": "handheld energy", "description": "Balcony View. The fire doesn't rush\u2014it savors"}}
{"song": "Fuego Lento", "artist": "Fuego Lento", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "We cook our love on fuego lento", "scene": {"mood": "joy", "colors": ["marigold", "indigo", "coral"], "composition": "beach sunset", "camera": "slow motion", "description": "Beach Sunset. We cook our love on fuego lento"}}
{"song": "Fuego Lento", "artist": "Fuego Lento", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Slow fire is the secret of the elders", "scene": {"mood": "defiance", "colors": ["papaya", "midnight", "silver"], "composition": "market crowd", "camera": "drone sweep", "description": "Market Crowd. Slow fire is the secret of the elders"}}
{"song": "Fuego Lento", "artist": "Fuego Lento", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Fuego lento: the tempo of true desire", "scene": {"mood": "sensuality", "colors": ["sunset orange", "turquoise", "gold"], "composition": "courtyard", "camera": "orbit", "description": "Courtyard. Fuego lento: the tempo of true desire"}}
{"song": "Fuego Lento", "artist": "Fuego Lento", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The flame that takes its time is the warmest", "scene": {"mood": "pride", "colors": ["hot pink", "deep red", "cream"], "composition": "plaza fountain", "camera": "crane up", "description": "Plaza Fountain. The flame that takes its time is the warmest"}}
{"song": "Fuego Lento", "artist": "Fuego Lento", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "We burn slowly because ashes are forever", "scene": {"mood": "nostalgia", "colors": ["emerald", "bronze", "white"], "composition": "rooftop party", "camera": "dolly in", "description": "Rooftop Party. We burn slowly because ashes are forever"}}
{"song": "Fuego Lento", "artist": "Fuego Lento", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Fuego lento: the spice of devotion", "scene": {"mood": "fervor", "colors": ["marigold", "indigo", "coral"], "composition": "candlelit room", "camera": "locked", "description": "Candlelit Room. Fuego lento: the spice of devotion"}}
{"song": "Fuego Lento", "artist": "Fuego Lento", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The slow fire teaches the fast flame", "scene": {"mood": "devotion", "colors": ["papaya", "midnight", "silver"], "composition": "parade", "camera": "whip pan", "description": "Parade. The slow fire teaches the fast flame"}}
{"song": "Coraz\u00f3n de Hierro", "artist": "Coraz\u00f3n de Hierro", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The heart is iron but it still beats", "scene": {"mood": "passion", "colors": ["sunset orange", "turquoise", "gold"], "composition": "dance floor", "camera": "tracking dance", "description": "Dance Floor. The heart is iron but it still beats"}}
{"song": "Coraz\u00f3n de Hierro", "artist": "Coraz\u00f3n de Hierro", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Hierro: the metal of the stubborn lover", "scene": {"mood": "celebration", "colors": ["hot pink", "deep red", "cream"], "composition": "street festival", "camera": "steady wide", "description": "Street Festival. Hierro: the metal of the stubborn lover"}}
{"song": "Coraz\u00f3n de Hierro", "artist": "Coraz\u00f3n de Hierro", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every coraz\u00f3n de hierro was once soft", "scene": {"mood": "longing", "colors": ["emerald", "bronze", "white"], "composition": "balcony view", "camera": "handheld energy", "description": "Balcony View. Every coraz\u00f3n de hierro was once soft"}}
{"song": "Coraz\u00f3n de Hierro", "artist": "Coraz\u00f3n de Hierro", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Iron heart: strong enough to break, strong enough to mend", "scene": {"mood": "joy", "colors": ["marigold", "indigo", "coral"], "composition": "beach sunset", "camera": "slow motion", "description": "Beach Sunset. Iron heart: strong enough to break, strong enough to mend"}}
{"song": "Coraz\u00f3n de Hierro", "artist": "Coraz\u00f3n de Hierro", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The heart doesn't rust\u2014it adapts", "scene": {"mood": "defiance", "colors": ["papaya", "midnight", "silver"], "composition": "market crowd", "camera": "drone sweep", "description": "Market Crowd. The heart doesn't rust\u2014it adapts"}}
{"song": "Coraz\u00f3n de Hierro", "artist": "Coraz\u00f3n de Hierro", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Coraz\u00f3n de hierro: the armor of the faithful", "scene": {"mood": "sensuality", "colors": ["sunset orange", "turquoise", "gold"], "composition": "courtyard", "camera": "orbit", "description": "Courtyard. Coraz\u00f3n de hierro: the armor of the faithful"}}
{"song": "Coraz\u00f3n de Hierro", "artist": "Coraz\u00f3n de Hierro", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We iron our hearts in the fire of loss", "scene": {"mood": "pride", "colors": ["hot pink", "deep red", "cream"], "composition": "plaza fountain", "camera": "crane up", "description": "Plaza Fountain. We iron our hearts in the fire of loss"}}
{"song": "Coraz\u00f3n de Hierro", "artist": "Coraz\u00f3n de Hierro", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Hierro heart: heavy but never hollow", "scene": {"mood": "nostalgia", "colors": ["emerald", "bronze", "white"], "composition": "rooftop party", "camera": "dolly in", "description": "Rooftop Party. Hierro heart: heavy but never hollow"}}
{"song": "Coraz\u00f3n de Hierro", "artist": "Coraz\u00f3n de Hierro", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The iron heart loves harder because it knows fragility", "scene": {"mood": "fervor", "colors": ["marigold", "indigo", "coral"], "composition": "candlelit room", "camera": "locked", "description": "Candlelit Room. The iron heart loves harder because it knows fragility"}}
{"song": "Coraz\u00f3n de Hierro", "artist": "Coraz\u00f3n de Hierro", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Coraz\u00f3n de hierro: un coraz\u00f3n que no se rinde", "scene": {"mood": "devotion", "colors": ["papaya", "midnight", "silver"], "composition": "parade", "camera": "whip pan", "description": "Parade. Coraz\u00f3n de hierro: un coraz\u00f3n que no se rinde"}}
{"song": "Viento Caliente", "artist": "Viento Caliente", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The hot wind carries stories from the south", "scene": {"mood": "passion", "colors": ["sunset orange", "turquoise", "gold"], "composition": "dance floor", "camera": "tracking dance", "description": "Dance Floor. The hot wind carries stories from the south"}}
{"song": "Viento Caliente", "artist": "Viento Caliente", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Viento caliente: the breath of the homeland", "scene": {"mood": "celebration", "colors": ["hot pink", "deep red", "cream"], "composition": "street festival", "camera": "steady wide", "description": "Street Festival. Viento caliente: the breath of the homeland"}}
{"song": "Viento Caliente", "artist": "Viento Caliente", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The wind doesn't ask permission to arrive", "scene": {"mood": "longing", "colors": ["emerald", "bronze", "white"], "composition": "balcony view", "camera": "handheld energy", "description": "Balcony View. The wind doesn't ask permission to arrive"}}
{"song": "Viento Caliente", "artist": "Viento Caliente", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "We follow the hot wind because it knows the way", "scene": {"mood": "joy", "colors": ["marigold", "indigo", "coral"], "composition": "beach sunset", "camera": "slow motion", "description": "Beach Sunset. We follow the hot wind because it knows the way"}}
{"song": "Viento Caliente", "artist": "Viento Caliente", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Caliente: the wind that wakes the seeds", "scene": {"mood": "defiance", "colors": ["papaya", "midnight", "silver"], "composition": "market crowd", "camera": "drone sweep", "description": "Market Crowd. Caliente: the wind that wakes the seeds"}}
{"song": "Viento Caliente", "artist": "Viento Caliente", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The viento carries the scent of tomorrow", "scene": {"mood": "sensuality", "colors": ["sunset orange", "turquoise", "gold"], "composition": "courtyard", "camera": "orbit", "description": "Courtyard. The viento carries the scent of tomorrow"}}
{"song": "Viento Caliente", "artist": "Viento Caliente", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Hot wind: the messenger of change", "scene": {"mood": "pride", "colors": ["hot pink", "deep red", "cream"], "composition": "plaza fountain", "camera": "crane up", "description": "Plaza Fountain. Hot wind: the messenger of change"}}
{"song": "Viento Caliente", "artist": "Viento Caliente", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "We are the viento caliente of our own story", "scene": {"mood": "nostalgia", "colors": ["emerald", "bronze", "white"], "composition": "rooftop party", "camera": "dolly in", "description": "Rooftop Party. We are the viento caliente of our own story"}}
{"song": "Viento Caliente", "artist": "Viento Caliente", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The wind is caliente because the earth is passionate", "scene": {"mood": "fervor", "colors": ["marigold", "indigo", "coral"], "composition": "candlelit room", "camera": "locked", "description": "Candlelit Room. The wind is caliente because the earth is passionate"}}
{"song": "Viento Caliente", "artist": "Viento Caliente", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Viento caliente: the exhale of the continent", "scene": {"mood": "devotion", "colors": ["papaya", "midnight", "silver"], "composition": "parade", "camera": "whip pan", "description": "Parade. Viento caliente: the exhale of the continent"}}
{"song": "Luna Ardiente", "artist": "Sol Ardiente", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The moon burns with borrowed fire", "scene": {"mood": "passion", "colors": ["sunset orange", "turquoise", "gold"], "composition": "dance floor", "camera": "tracking dance", "description": "Dance Floor. The moon burns with borrowed fire"}}
{"song": "Luna Ardiente", "artist": "Sol Ardiente", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Luna ardiente: the night sun", "scene": {"mood": "celebration", "colors": ["hot pink", "deep red", "cream"], "composition": "street festival", "camera": "steady wide", "description": "Street Festival. Luna ardiente: the night sun"}}
{"song": "Luna Ardiente", "artist": "Sol Ardiente", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "We dance under a moon on fire", "scene": {"mood": "longing", "colors": ["emerald", "bronze", "white"], "composition": "balcony view", "camera": "handheld energy", "description": "Balcony View. We dance under a moon on fire"}}
{"song": "Luna Ardiente", "artist": "Sol Ardiente", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The moon is caliente because it mirrors us", "scene": {"mood": "joy", "colors": ["marigold", "indigo", "coral"], "composition": "beach sunset", "camera": "slow motion", "description": "Beach Sunset. The moon is caliente because it mirrors us"}}
{"song": "Luna Ardiente", "artist": "Sol Ardiente", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Luna ardiente: the silver ball of passion", "scene": {"mood": "defiance", "colors": ["papaya", "midnight", "silver"], "composition": "market crowd", "camera": "drone sweep", "description": "Market Crowd. Luna ardiente: the silver ball of passion"}}
{"song": "Luna Ardiente", "artist": "Sol Ardiente", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The moon burns because the earth taught it how", "scene": {"mood": "sensuality", "colors": ["sunset orange", "turquoise", "gold"], "composition": "courtyard", "camera": "orbit", "description": "Courtyard. The moon burns because the earth taught it how"}}
{"song": "Luna Ardiente", "artist": "Sol Ardiente", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We howl at the moon and the moon howls back", "scene": {"mood": "pride", "colors": ["hot pink", "deep red", "cream"], "composition": "plaza fountain", "camera": "crane up", "description": "Plaza Fountain. We howl at the moon and the moon howls back"}}
{"song": "Luna Ardiente", "artist": "Sol Ardiente", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Luna ardiente: the nocturnal furnace", "scene": {"mood": "nostalgia", "colors": ["emerald", "bronze", "white"], "composition": "rooftop party", "camera": "dolly in", "description": "Rooftop Party. Luna ardiente: the nocturnal furnace"}}
{"song": "Luna Ardiente", "artist": "Sol Ardiente", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The moon is the sun's passionate understudy", "scene": {"mood": "fervor", "colors": ["marigold", "indigo", "coral"], "composition": "candlelit room", "camera": "locked", "description": "Candlelit Room. The moon is the sun's passionate understudy"}}
{"song": "Luna Ardiente", "artist": "Sol Ardiente", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Luna ardiente: burn bright even in darkness", "scene": {"mood": "devotion", "colors": ["papaya", "midnight", "silver"], "composition": "parade", "camera": "whip pan", "description": "Parade. Luna ardiente: burn bright even in darkness"}}
{"song": "R\u00edo Sagrado", "artist": "Ritmo Sagrado", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The sacred river carries our prayers to the sea", "scene": {"mood": "passion", "colors": ["sunset orange", "turquoise", "gold"], "composition": "dance floor", "camera": "tracking dance", "description": "Dance Floor. The sacred river carries our prayers to the sea"}}
{"song": "R\u00edo Sagrado", "artist": "Ritmo Sagrado", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "R\u00edo: the liquid highway of the ancestors", "scene": {"mood": "celebration", "colors": ["hot pink", "deep red", "cream"], "composition": "street festival", "camera": "steady wide", "description": "Street Festival. R\u00edo: the liquid highway of the ancestors"}}
{"song": "R\u00edo Sagrado", "artist": "Ritmo Sagrado", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every river is a sagrado path", "scene": {"mood": "longing", "colors": ["emerald", "bronze", "white"], "composition": "balcony view", "camera": "handheld energy", "description": "Balcony View. Every river is a sagrado path"}}
{"song": "R\u00edo Sagrado", "artist": "Ritmo Sagrado", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The river doesn't stop for monuments", "scene": {"mood": "joy", "colors": ["marigold", "indigo", "coral"], "composition": "beach sunset", "camera": "slow motion", "description": "Beach Sunset. The river doesn't stop for monuments"}}
{"song": "R\u00edo Sagrado", "artist": "Ritmo Sagrado", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "R\u00edo sagrado: the water that remembers", "scene": {"mood": "defiance", "colors": ["papaya", "midnight", "silver"], "composition": "market crowd", "camera": "drone sweep", "description": "Market Crowd. R\u00edo sagrado: the water that remembers"}}
{"song": "R\u00edo Sagrado", "artist": "Ritmo Sagrado", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We throw our prayers in the r\u00edo and the r\u00edo delivers", "scene": {"mood": "sensuality", "colors": ["sunset orange", "turquoise", "gold"], "composition": "courtyard", "camera": "orbit", "description": "Courtyard. We throw our prayers in the r\u00edo and the r\u00edo delivers"}}
{"song": "R\u00edo Sagrado", "artist": "Ritmo Sagrado", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The sacred river runs through every heart", "scene": {"mood": "pride", "colors": ["hot pink", "deep red", "cream"], "composition": "plaza fountain", "camera": "crane up", "description": "Plaza Fountain. The sacred river runs through every heart"}}
{"song": "R\u00edo Sagrado", "artist": "Ritmo Sagrado", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "R\u00edo: the blood of the earth", "scene": {"mood": "nostalgia", "colors": ["emerald", "bronze", "white"], "composition": "rooftop party", "camera": "dolly in", "description": "Rooftop Party. R\u00edo: the blood of the earth"}}
{"song": "R\u00edo Sagrado", "artist": "Ritmo Sagrado", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The river is sagrado because it keeps moving", "scene": {"mood": "fervor", "colors": ["marigold", "indigo", "coral"], "composition": "candlelit room", "camera": "locked", "description": "Candlelit Room. The river is sagrado because it keeps moving"}}
{"song": "R\u00edo Sagrado", "artist": "Ritmo Sagrado", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "R\u00edo sagrado: the endless baptism", "scene": {"mood": "devotion", "colors": ["papaya", "midnight", "silver"], "composition": "parade", "camera": "whip pan", "description": "Parade. R\u00edo sagrado: the endless baptism"}}
{"song": "Ceniza Sagrada", "artist": "Fuego Lento", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The sacred ash falls like snow on the altar", "scene": {"mood": "passion", "colors": ["sunset orange", "turquoise", "gold"], "composition": "dance floor", "camera": "tracking dance", "description": "Dance Floor. The sacred ash falls like snow on the altar"}}
{"song": "Ceniza Sagrada", "artist": "Fuego Lento", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Ceniza: the final form of the offering", "scene": {"mood": "celebration", "colors": ["hot pink", "deep red", "cream"], "composition": "street festival", "camera": "steady wide", "description": "Street Festival. Ceniza: the final form of the offering"}}
{"song": "Ceniza Sagrada", "artist": "Fuego Lento", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every ash was once a prayer on fire", "scene": {"mood": "longing", "colors": ["emerald", "bronze", "white"], "composition": "balcony view", "camera": "handheld energy", "description": "Balcony View. Every ash was once a prayer on fire"}}
{"song": "Ceniza Sagrada", "artist": "Fuego Lento", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The ash is sagrado because it remembers the flame", "scene": {"mood": "joy", "colors": ["marigold", "indigo", "coral"], "composition": "beach sunset", "camera": "slow motion", "description": "Beach Sunset. The ash is sagrado because it remembers the flame"}}
{"song": "Ceniza Sagrada", "artist": "Fuego Lento", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Ceniza sagrada: the holy dust", "scene": {"mood": "defiance", "colors": ["papaya", "midnight", "silver"], "composition": "market crowd", "camera": "drone sweep", "description": "Market Crowd. Ceniza sagrada: the holy dust"}}
{"song": "Ceniza Sagrada", "artist": "Fuego Lento", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We scatter ash because burial is too contained", "scene": {"mood": "sensuality", "colors": ["sunset orange", "turquoise", "gold"], "composition": "courtyard", "camera": "orbit", "description": "Courtyard. We scatter ash because burial is too contained"}}
{"song": "Ceniza Sagrada", "artist": "Fuego Lento", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The ash carries the soul to the four winds", "scene": {"mood": "pride", "colors": ["hot pink", "deep red", "cream"], "composition": "plaza fountain", "camera": "crane up", "description": "Plaza Fountain. The ash carries the soul to the four winds"}}
{"song": "Ceniza Sagrada", "artist": "Fuego Lento", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Ceniza: the communion of the burned", "scene": {"mood": "nostalgia", "colors": ["emerald", "bronze", "white"], "composition": "rooftop party", "camera": "dolly in", "description": "Rooftop Party. Ceniza: the communion of the burned"}}
{"song": "Ceniza Sagrada", "artist": "Fuego Lento", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The sacred ash nourishes the new growth", "scene": {"mood": "fervor", "colors": ["marigold", "indigo", "coral"], "composition": "candlelit room", "camera": "locked", "description": "Candlelit Room. The sacred ash nourishes the new growth"}}
{"song": "Ceniza Sagrada", "artist": "Fuego Lento", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Ceniza sagrada: death as fertilizer", "scene": {"mood": "devotion", "colors": ["papaya", "midnight", "silver"], "composition": "parade", "camera": "whip pan", "description": "Parade. Ceniza sagrada: death as fertilizer"}}
{"song": "Mariposa Fuego", "artist": "Coraz\u00f3n de Hierro", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The butterfly is made of fire and migration", "scene": {"mood": "passion", "colors": ["sunset orange", "turquoise", "gold"], "composition": "dance floor", "camera": "tracking dance", "description": "Dance Floor. The butterfly is made of fire and migration"}}
{"song": "Mariposa Fuego", "artist": "Coraz\u00f3n de Hierro", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Mariposa: the most delicate arsonist", "scene": {"mood": "celebration", "colors": ["hot pink", "deep red", "cream"], "composition": "street festival", "camera": "steady wide", "description": "Street Festival. Mariposa: the most delicate arsonist"}}
{"song": "Mariposa Fuego", "artist": "Coraz\u00f3n de Hierro", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every wingbeat fans the flame", "scene": {"mood": "longing", "colors": ["emerald", "bronze", "white"], "composition": "balcony view", "camera": "handheld energy", "description": "Balcony View. Every wingbeat fans the flame"}}
{"song": "Mariposa Fuego", "artist": "Coraz\u00f3n de Hierro", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The butterfly burns because it flies too close to the sun", "scene": {"mood": "joy", "colors": ["marigold", "indigo", "coral"], "composition": "beach sunset", "camera": "slow motion", "description": "Beach Sunset. The butterfly burns because it flies too close to the sun"}}
{"song": "Mariposa Fuego", "artist": "Coraz\u00f3n de Hierro", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Mariposa fuego: the insect inferno", "scene": {"mood": "defiance", "colors": ["papaya", "midnight", "silver"], "composition": "market crowd", "camera": "drone sweep", "description": "Market Crowd. Mariposa fuego: the insect inferno"}}
{"song": "Mariposa Fuego", "artist": "Coraz\u00f3n de Hierro", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We are butterflies made of the fire we survived", "scene": {"mood": "sensuality", "colors": ["sunset orange", "turquoise", "gold"], "composition": "courtyard", "camera": "orbit", "description": "Courtyard. We are butterflies made of the fire we survived"}}
{"song": "Mariposa Fuego", "artist": "Coraz\u00f3n de Hierro", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The migration is the fire's journey", "scene": {"mood": "pride", "colors": ["hot pink", "deep red", "cream"], "composition": "plaza fountain", "camera": "crane up", "description": "Plaza Fountain. The migration is the fire's journey"}}
{"song": "Mariposa Fuego", "artist": "Coraz\u00f3n de Hierro", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Mariposa: beauty with a burning point", "scene": {"mood": "nostalgia", "colors": ["emerald", "bronze", "white"], "composition": "rooftop party", "camera": "dolly in", "description": "Rooftop Party. Mariposa: beauty with a burning point"}}
{"song": "Mariposa Fuego", "artist": "Coraz\u00f3n de Hierro", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The butterfly ignites every flower it lands on", "scene": {"mood": "fervor", "colors": ["marigold", "indigo", "coral"], "composition": "candlelit room", "camera": "locked", "description": "Candlelit Room. The butterfly ignites every flower it lands on"}}
{"song": "Mariposa Fuego", "artist": "Coraz\u00f3n de Hierro", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Mariposa fuego: the most beautiful hazard", "scene": {"mood": "devotion", "colors": ["papaya", "midnight", "silver"], "composition": "parade", "camera": "whip pan", "description": "Parade. Mariposa fuego: the most beautiful hazard"}}
{"song": "Coraz\u00f3n de Piedra", "artist": "Viento Caliente", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The heart is stone but it still echoes", "scene": {"mood": "passion", "colors": ["sunset orange", "turquoise", "gold"], "composition": "dance floor", "camera": "tracking dance", "description": "Dance Floor. The heart is stone but it still echoes"}}
{"song": "Coraz\u00f3n de Piedra", "artist": "Viento Caliente", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Piedra: the material of the ancient lover", "scene": {"mood": "celebration", "colors": ["hot pink", "deep red", "cream"], "composition": "street festival", "camera": "steady wide", "description": "Street Festival. Piedra: the material of the ancient lover"}}
{"song": "Coraz\u00f3n de Piedra", "artist": "Viento Caliente", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every stone heart was once a volcano", "scene": {"mood": "longing", "colors": ["emerald", "bronze", "white"], "composition": "balcony view", "camera": "handheld energy", "description": "Balcony View. Every stone heart was once a volcano"}}
{"song": "Coraz\u00f3n de Piedra", "artist": "Viento Caliente", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The heart doesn't break\u2014it erodes", "scene": {"mood": "joy", "colors": ["marigold", "indigo", "coral"], "composition": "beach sunset", "camera": "slow motion", "description": "Beach Sunset. The heart doesn't break\u2014it erodes"}}
{"song": "Coraz\u00f3n de Piedra", "artist": "Viento Caliente", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Coraz\u00f3n de piedra: the geological heart", "scene": {"mood": "defiance", "colors": ["papaya", "midnight", "silver"], "composition": "market crowd", "camera": "drone sweep", "description": "Market Crowd. Coraz\u00f3n de piedra: the geological heart"}}
{"song": "Coraz\u00f3n de Piedra", "artist": "Viento Caliente", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We have stone hearts because flesh was too fragile", "scene": {"mood": "sensuality", "colors": ["sunset orange", "turquoise", "gold"], "composition": "courtyard", "camera": "orbit", "description": "Courtyard. We have stone hearts because flesh was too fragile"}}
{"song": "Coraz\u00f3n de Piedra", "artist": "Viento Caliente", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The heart is piedra because it chose to be", "scene": {"mood": "pride", "colors": ["hot pink", "deep red", "cream"], "composition": "plaza fountain", "camera": "crane up", "description": "Plaza Fountain. The heart is piedra because it chose to be"}}
{"song": "Coraz\u00f3n de Piedra", "artist": "Viento Caliente", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Stone heart: heavy but weatherproof", "scene": {"mood": "nostalgia", "colors": ["emerald", "bronze", "white"], "composition": "rooftop party", "camera": "dolly in", "description": "Rooftop Party. Stone heart: heavy but weatherproof"}}
{"song": "Coraz\u00f3n de Piedra", "artist": "Viento Caliente", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The heart echoes when you tap it", "scene": {"mood": "fervor", "colors": ["marigold", "indigo", "coral"], "composition": "candlelit room", "camera": "locked", "description": "Candlelit Room. The heart echoes when you tap it"}}
{"song": "Coraz\u00f3n de Piedra", "artist": "Viento Caliente", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Coraz\u00f3n de piedra: the heart that outlasts everything", "scene": {"mood": "devotion", "colors": ["papaya", "midnight", "silver"], "composition": "parade", "camera": "whip pan", "description": "Parade. Coraz\u00f3n de piedra: the heart that outlasts everything"}}

View File

@@ -1,100 +0,0 @@
{"song": "Iron Hymn", "artist": "Iron Hymn", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The hymn is forged not sung", "scene": {"mood": "fury", "colors": ["black", "blood red", "chrome"], "composition": "pit chaos", "camera": "shaky cam", "description": "Pit Chaos. The hymn is forged not sung"}}
{"song": "Iron Hymn", "artist": "Iron Hymn", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Iron doesn't bend\u2014it decides", "scene": {"mood": "catharsis", "colors": ["gunmetal", "orange", "ash gray"], "composition": "stage pyro", "camera": "rapid cut", "description": "Stage Pyro. Iron doesn't bend\u2014it decides"}}
{"song": "Iron Hymn", "artist": "Iron Hymn", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "We hymn to the furnace and the flame", "scene": {"mood": "doom", "colors": ["deep crimson", "steel", "charcoal"], "composition": "crowd surf", "camera": "slow motion", "description": "Crowd Surf. We hymn to the furnace and the flame"}}
{"song": "Iron Hymn", "artist": "Iron Hymn", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every hymn needs a hammer", "scene": {"mood": "defiance", "colors": ["void black", "electric green", "bone white"], "composition": "mic close-up", "camera": "whip pan", "description": "Mic Close Up. Every hymn needs a hammer"}}
{"song": "Iron Hymn", "artist": "Iron Hymn", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Iron hymn: the song of the unbroken", "scene": {"mood": "power", "colors": ["obsidian", "molten gold", "smoke"], "composition": "guitar neck", "camera": "drone chaos", "description": "Guitar Neck. Iron hymn: the song of the unbroken"}}
{"song": "Iron Hymn", "artist": "Iron Hymn", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The hymn clangs louder than the choir", "scene": {"mood": "desolation", "colors": ["black", "blood red", "chrome"], "composition": "drum fury", "camera": "handheld crush", "description": "Drum Fury. The hymn clangs louder than the choir"}}
{"song": "Iron Hymn", "artist": "Iron Hymn", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We sing in iron because silk is too quiet", "scene": {"mood": "rage", "colors": ["gunmetal", "orange", "ash gray"], "composition": "silhouette against fire", "camera": "locked", "description": "Silhouette Against Fire. We sing in iron because silk is too quiet"}}
{"song": "Iron Hymn", "artist": "Iron Hymn", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Iron hymn for iron hearts", "scene": {"mood": "darkness", "colors": ["deep crimson", "steel", "charcoal"], "composition": "circle pit", "camera": "dolly smash", "description": "Circle Pit. Iron hymn for iron hearts"}}
{"song": "Iron Hymn", "artist": "Iron Hymn", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The hymn was forged in the fire of refusal", "scene": {"mood": "survival", "colors": ["void black", "electric green", "bone white"], "composition": "throne of skulls", "camera": "first person", "description": "Throne Of Skulls. The hymn was forged in the fire of refusal"}}
{"song": "Iron Hymn", "artist": "Iron Hymn", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Iron rusts but the hymn doesn't", "scene": {"mood": "transcendence", "colors": ["obsidian", "molten gold", "smoke"], "composition": "apocalyptic horizon", "camera": "orbit", "description": "Apocalyptic Horizon. Iron rusts but the hymn doesn't"}}
{"song": "Void Scream", "artist": "Void Scream", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The scream comes from the place before sound", "scene": {"mood": "fury", "colors": ["black", "blood red", "chrome"], "composition": "pit chaos", "camera": "shaky cam", "description": "Pit Chaos. The scream comes from the place before sound"}}
{"song": "Void Scream", "artist": "Void Scream", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Void: where screams go to become music", "scene": {"mood": "catharsis", "colors": ["gunmetal", "orange", "ash gray"], "composition": "stage pyro", "camera": "rapid cut", "description": "Stage Pyro. Void: where screams go to become music"}}
{"song": "Void Scream", "artist": "Void Scream", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every scream fills a void", "scene": {"mood": "doom", "colors": ["deep crimson", "steel", "charcoal"], "composition": "crowd surf", "camera": "slow motion", "description": "Crowd Surf. Every scream fills a void"}}
{"song": "Void Scream", "artist": "Void Scream", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The void doesn't echo\u2014it absorbs", "scene": {"mood": "defiance", "colors": ["void black", "electric green", "bone white"], "composition": "mic close-up", "camera": "whip pan", "description": "Mic Close Up. The void doesn't echo\u2014it absorbs"}}
{"song": "Void Scream", "artist": "Void Scream", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Scream into the void and the void screams back", "scene": {"mood": "power", "colors": ["obsidian", "molten gold", "smoke"], "composition": "guitar neck", "camera": "drone chaos", "description": "Guitar Neck. Scream into the void and the void screams back"}}
{"song": "Void Scream", "artist": "Void Scream", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The void is the scream's natural habitat", "scene": {"mood": "desolation", "colors": ["black", "blood red", "chrome"], "composition": "drum fury", "camera": "handheld crush", "description": "Drum Fury. The void is the scream's natural habitat"}}
{"song": "Void Scream", "artist": "Void Scream", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Void scream: the sound of nothing becoming everything", "scene": {"mood": "rage", "colors": ["gunmetal", "orange", "ash gray"], "composition": "silhouette against fire", "camera": "locked", "description": "Silhouette Against Fire. Void scream: the sound of nothing becoming everything"}}
{"song": "Void Scream", "artist": "Void Scream", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "We scream because the void demands tribute", "scene": {"mood": "darkness", "colors": ["deep crimson", "steel", "charcoal"], "composition": "circle pit", "camera": "dolly smash", "description": "Circle Pit. We scream because the void demands tribute"}}
{"song": "Void Scream", "artist": "Void Scream", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The scream is the void's favorite instrument", "scene": {"mood": "survival", "colors": ["void black", "electric green", "bone white"], "composition": "throne of skulls", "camera": "first person", "description": "Throne Of Skulls. The scream is the void's favorite instrument"}}
{"song": "Void Scream", "artist": "Void Scream", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Void screams in frequencies only the damned can hear", "scene": {"mood": "transcendence", "colors": ["obsidian", "molten gold", "smoke"], "composition": "apocalyptic horizon", "camera": "orbit", "description": "Apocalyptic Horizon. Void screams in frequencies only the damned can hear"}}
{"song": "Forge Psalm", "artist": "Forge Born", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The psalm is hammered on an anvil of rage", "scene": {"mood": "fury", "colors": ["black", "blood red", "chrome"], "composition": "pit chaos", "camera": "shaky cam", "description": "Pit Chaos. The psalm is hammered on an anvil of rage"}}
{"song": "Forge Psalm", "artist": "Forge Born", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Forge: where prayers go to become weapons", "scene": {"mood": "catharsis", "colors": ["gunmetal", "orange", "ash gray"], "composition": "stage pyro", "camera": "rapid cut", "description": "Stage Pyro. Forge: where prayers go to become weapons"}}
{"song": "Forge Psalm", "artist": "Forge Born", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every psalm needs fire to be forged", "scene": {"mood": "doom", "colors": ["deep crimson", "steel", "charcoal"], "composition": "crowd surf", "camera": "slow motion", "description": "Crowd Surf. Every psalm needs fire to be forged"}}
{"song": "Forge Psalm", "artist": "Forge Born", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The forge doesn't care about your comfort", "scene": {"mood": "defiance", "colors": ["void black", "electric green", "bone white"], "composition": "mic close-up", "camera": "whip pan", "description": "Mic Close Up. The forge doesn't care about your comfort"}}
{"song": "Forge Psalm", "artist": "Forge Born", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Psalm of the blacksmith and the believer", "scene": {"mood": "power", "colors": ["obsidian", "molten gold", "smoke"], "composition": "guitar neck", "camera": "drone chaos", "description": "Guitar Neck. Psalm of the blacksmith and the believer"}}
{"song": "Forge Psalm", "artist": "Forge Born", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The forge turns doubt into steel", "scene": {"mood": "desolation", "colors": ["black", "blood red", "chrome"], "composition": "drum fury", "camera": "handheld crush", "description": "Drum Fury. The forge turns doubt into steel"}}
{"song": "Forge Psalm", "artist": "Forge Born", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We forge psalms from the ore of suffering", "scene": {"mood": "rage", "colors": ["gunmetal", "orange", "ash gray"], "composition": "silhouette against fire", "camera": "locked", "description": "Silhouette Against Fire. We forge psalms from the ore of suffering"}}
{"song": "Forge Psalm", "artist": "Forge Born", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The psalm sparks when the hammer falls", "scene": {"mood": "darkness", "colors": ["deep crimson", "steel", "charcoal"], "composition": "circle pit", "camera": "dolly smash", "description": "Circle Pit. The psalm sparks when the hammer falls"}}
{"song": "Forge Psalm", "artist": "Forge Born", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Forge psalm: the liturgy of the workshop", "scene": {"mood": "survival", "colors": ["void black", "electric green", "bone white"], "composition": "throne of skulls", "camera": "first person", "description": "Throne Of Skulls. Forge psalm: the liturgy of the workshop"}}
{"song": "Forge Psalm", "artist": "Forge Born", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The forge never closes and neither does the psalm", "scene": {"mood": "transcendence", "colors": ["obsidian", "molten gold", "smoke"], "composition": "apocalyptic horizon", "camera": "orbit", "description": "Apocalyptic Horizon. The forge never closes and neither does the psalm"}}
{"song": "Ashen Crown", "artist": "Ashen Crown", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The crown is made of what we burned", "scene": {"mood": "fury", "colors": ["black", "blood red", "chrome"], "composition": "pit chaos", "camera": "shaky cam", "description": "Pit Chaos. The crown is made of what we burned"}}
{"song": "Ashen Crown", "artist": "Ashen Crown", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Ashen: the color of royalty after the fire", "scene": {"mood": "catharsis", "colors": ["gunmetal", "orange", "ash gray"], "composition": "stage pyro", "camera": "rapid cut", "description": "Stage Pyro. Ashen: the color of royalty after the fire"}}
{"song": "Ashen Crown", "artist": "Ashen Crown", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every crown must fall to become ash", "scene": {"mood": "doom", "colors": ["deep crimson", "steel", "charcoal"], "composition": "crowd surf", "camera": "slow motion", "description": "Crowd Surf. Every crown must fall to become ash"}}
{"song": "Ashen Crown", "artist": "Ashen Crown", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The crown weighs nothing because it's made of memory", "scene": {"mood": "defiance", "colors": ["void black", "electric green", "bone white"], "composition": "mic close-up", "camera": "whip pan", "description": "Mic Close Up. The crown weighs nothing because it's made of memory"}}
{"song": "Ashen Crown", "artist": "Ashen Crown", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Ashen crown for the king of the aftermath", "scene": {"mood": "power", "colors": ["obsidian", "molten gold", "smoke"], "composition": "guitar neck", "camera": "drone chaos", "description": "Guitar Neck. Ashen crown for the king of the aftermath"}}
{"song": "Ashen Crown", "artist": "Ashen Crown", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We wear ash because gold is too soft", "scene": {"mood": "desolation", "colors": ["black", "blood red", "chrome"], "composition": "drum fury", "camera": "handheld crush", "description": "Drum Fury. We wear ash because gold is too soft"}}
{"song": "Ashen Crown", "artist": "Ashen Crown", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The crown rises from the pyre", "scene": {"mood": "rage", "colors": ["gunmetal", "orange", "ash gray"], "composition": "silhouette against fire", "camera": "locked", "description": "Silhouette Against Fire. The crown rises from the pyre"}}
{"song": "Ashen Crown", "artist": "Ashen Crown", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Ashen: the final color of every empire", "scene": {"mood": "darkness", "colors": ["deep crimson", "steel", "charcoal"], "composition": "circle pit", "camera": "dolly smash", "description": "Circle Pit. Ashen: the final color of every empire"}}
{"song": "Ashen Crown", "artist": "Ashen Crown", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The crown is the last thing to burn", "scene": {"mood": "survival", "colors": ["void black", "electric green", "bone white"], "composition": "throne of skulls", "camera": "first person", "description": "Throne Of Skulls. The crown is the last thing to burn"}}
{"song": "Ashen Crown", "artist": "Ashen Crown", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Ashen crown: sovereignty over ruins", "scene": {"mood": "transcendence", "colors": ["obsidian", "molten gold", "smoke"], "composition": "apocalyptic horizon", "camera": "orbit", "description": "Apocalyptic Horizon. Ashen crown: sovereignty over ruins"}}
{"song": "Rift Maker", "artist": "Rift Maker", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "We don't break the world\u2014we rift it", "scene": {"mood": "fury", "colors": ["black", "blood red", "chrome"], "composition": "pit chaos", "camera": "shaky cam", "description": "Pit Chaos. We don't break the world\u2014we rift it"}}
{"song": "Rift Maker", "artist": "Rift Maker", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The rift is where new worlds leak through", "scene": {"mood": "catharsis", "colors": ["gunmetal", "orange", "ash gray"], "composition": "stage pyro", "camera": "rapid cut", "description": "Stage Pyro. The rift is where new worlds leak through"}}
{"song": "Rift Maker", "artist": "Rift Maker", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every rift is a door the old gods forgot", "scene": {"mood": "doom", "colors": ["deep crimson", "steel", "charcoal"], "composition": "crowd surf", "camera": "slow motion", "description": "Crowd Surf. Every rift is a door the old gods forgot"}}
{"song": "Rift Maker", "artist": "Rift Maker", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Rift maker: the architect of the divide", "scene": {"mood": "defiance", "colors": ["void black", "electric green", "bone white"], "composition": "mic close-up", "camera": "whip pan", "description": "Mic Close Up. Rift maker: the architect of the divide"}}
{"song": "Rift Maker", "artist": "Rift Maker", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The rift heals but the scar remains", "scene": {"mood": "power", "colors": ["obsidian", "molten gold", "smoke"], "composition": "guitar neck", "camera": "drone chaos", "description": "Guitar Neck. The rift heals but the scar remains"}}
{"song": "Rift Maker", "artist": "Rift Maker", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We make rifts because walls are too easy", "scene": {"mood": "desolation", "colors": ["black", "blood red", "chrome"], "composition": "drum fury", "camera": "handheld crush", "description": "Drum Fury. We make rifts because walls are too easy"}}
{"song": "Rift Maker", "artist": "Rift Maker", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The rift between two worlds is music", "scene": {"mood": "rage", "colors": ["gunmetal", "orange", "ash gray"], "composition": "silhouette against fire", "camera": "locked", "description": "Silhouette Against Fire. The rift between two worlds is music"}}
{"song": "Rift Maker", "artist": "Rift Maker", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Rift maker plays the sound of separation", "scene": {"mood": "darkness", "colors": ["deep crimson", "steel", "charcoal"], "composition": "circle pit", "camera": "dolly smash", "description": "Circle Pit. Rift maker plays the sound of separation"}}
{"song": "Rift Maker", "artist": "Rift Maker", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The rift is the wound that became a river", "scene": {"mood": "survival", "colors": ["void black", "electric green", "bone white"], "composition": "throne of skulls", "camera": "first person", "description": "Throne Of Skulls. The rift is the wound that became a river"}}
{"song": "Rift Maker", "artist": "Rift Maker", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Rifts don't close\u2014they become horizons", "scene": {"mood": "transcendence", "colors": ["obsidian", "molten gold", "smoke"], "composition": "apocalyptic horizon", "camera": "orbit", "description": "Apocalyptic Horizon. Rifts don't close\u2014they become horizons"}}
{"song": "Obsidian Psalm", "artist": "Iron Hymn", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The psalm is carved in volcanic glass", "scene": {"mood": "fury", "colors": ["black", "blood red", "chrome"], "composition": "pit chaos", "camera": "shaky cam", "description": "Pit Chaos. The psalm is carved in volcanic glass"}}
{"song": "Obsidian Psalm", "artist": "Iron Hymn", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Obsidian: the sharpest form of prayer", "scene": {"mood": "catharsis", "colors": ["gunmetal", "orange", "ash gray"], "composition": "stage pyro", "camera": "rapid cut", "description": "Stage Pyro. Obsidian: the sharpest form of prayer"}}
{"song": "Obsidian Psalm", "artist": "Iron Hymn", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every psalm cuts the one who reads it", "scene": {"mood": "doom", "colors": ["deep crimson", "steel", "charcoal"], "composition": "crowd surf", "camera": "slow motion", "description": "Crowd Surf. Every psalm cuts the one who reads it"}}
{"song": "Obsidian Psalm", "artist": "Iron Hymn", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The psalm reflects a distorted face", "scene": {"mood": "defiance", "colors": ["void black", "electric green", "bone white"], "composition": "mic close-up", "camera": "whip pan", "description": "Mic Close Up. The psalm reflects a distorted face"}}
{"song": "Obsidian Psalm", "artist": "Iron Hymn", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Obsidian psalm: the hymn of the eruption", "scene": {"mood": "power", "colors": ["obsidian", "molten gold", "smoke"], "composition": "guitar neck", "camera": "drone chaos", "description": "Guitar Neck. Obsidian psalm: the hymn of the eruption"}}
{"song": "Obsidian Psalm", "artist": "Iron Hymn", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We carve psalms because paper burns too easily", "scene": {"mood": "desolation", "colors": ["black", "blood red", "chrome"], "composition": "drum fury", "camera": "handheld crush", "description": "Drum Fury. We carve psalms because paper burns too easily"}}
{"song": "Obsidian Psalm", "artist": "Iron Hymn", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The psalm is dark because the truth is dark", "scene": {"mood": "rage", "colors": ["gunmetal", "orange", "ash gray"], "composition": "silhouette against fire", "camera": "locked", "description": "Silhouette Against Fire. The psalm is dark because the truth is dark"}}
{"song": "Obsidian Psalm", "artist": "Iron Hymn", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Obsidian: glass made from earth's anger", "scene": {"mood": "darkness", "colors": ["deep crimson", "steel", "charcoal"], "composition": "circle pit", "camera": "dolly smash", "description": "Circle Pit. Obsidian: glass made from earth's anger"}}
{"song": "Obsidian Psalm", "artist": "Iron Hymn", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The psalm slices through the noise", "scene": {"mood": "survival", "colors": ["void black", "electric green", "bone white"], "composition": "throne of skulls", "camera": "first person", "description": "Throne Of Skulls. The psalm slices through the noise"}}
{"song": "Obsidian Psalm", "artist": "Iron Hymn", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Obsidian psalm: beautiful and dangerous", "scene": {"mood": "transcendence", "colors": ["obsidian", "molten gold", "smoke"], "composition": "apocalyptic horizon", "camera": "orbit", "description": "Apocalyptic Horizon. Obsidian psalm: beautiful and dangerous"}}
{"song": "Titanium Requiem", "artist": "Void Scream", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The requiem is built to survive the apocalypse", "scene": {"mood": "fury", "colors": ["black", "blood red", "chrome"], "composition": "pit chaos", "camera": "shaky cam", "description": "Pit Chaos. The requiem is built to survive the apocalypse"}}
{"song": "Titanium Requiem", "artist": "Void Scream", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Titanium: the metal of the unburied", "scene": {"mood": "catharsis", "colors": ["gunmetal", "orange", "ash gray"], "composition": "stage pyro", "camera": "rapid cut", "description": "Stage Pyro. Titanium: the metal of the unburied"}}
{"song": "Titanium Requiem", "artist": "Void Scream", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every note is reinforced against grief", "scene": {"mood": "doom", "colors": ["deep crimson", "steel", "charcoal"], "composition": "crowd surf", "camera": "slow motion", "description": "Crowd Surf. Every note is reinforced against grief"}}
{"song": "Titanium Requiem", "artist": "Void Scream", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The requiem doesn't corrode", "scene": {"mood": "defiance", "colors": ["void black", "electric green", "bone white"], "composition": "mic close-up", "camera": "whip pan", "description": "Mic Close Up. The requiem doesn't corrode"}}
{"song": "Titanium Requiem", "artist": "Void Scream", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Titanium requiem: mourning that outlasts the mourners", "scene": {"mood": "power", "colors": ["obsidian", "molten gold", "smoke"], "composition": "guitar neck", "camera": "drone chaos", "description": "Guitar Neck. Titanium requiem: mourning that outlasts the mourners"}}
{"song": "Titanium Requiem", "artist": "Void Scream", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We play titanium because iron rusts and flesh fails", "scene": {"mood": "desolation", "colors": ["black", "blood red", "chrome"], "composition": "drum fury", "camera": "handheld crush", "description": "Drum Fury. We play titanium because iron rusts and flesh fails"}}
{"song": "Titanium Requiem", "artist": "Void Scream", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The requiem is the armor of the bereaved", "scene": {"mood": "rage", "colors": ["gunmetal", "orange", "ash gray"], "composition": "silhouette against fire", "camera": "locked", "description": "Silhouette Against Fire. The requiem is the armor of the bereaved"}}
{"song": "Titanium Requiem", "artist": "Void Scream", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Titanium: the sound of grief that refuses to end", "scene": {"mood": "darkness", "colors": ["deep crimson", "steel", "charcoal"], "composition": "circle pit", "camera": "dolly smash", "description": "Circle Pit. Titanium: the sound of grief that refuses to end"}}
{"song": "Titanium Requiem", "artist": "Void Scream", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The requiem is stronger than the death it mourns", "scene": {"mood": "survival", "colors": ["void black", "electric green", "bone white"], "composition": "throne of skulls", "camera": "first person", "description": "Throne Of Skulls. The requiem is stronger than the death it mourns"}}
{"song": "Titanium Requiem", "artist": "Void Scream", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Titanium requiem: the last song standing", "scene": {"mood": "transcendence", "colors": ["obsidian", "molten gold", "smoke"], "composition": "apocalyptic horizon", "camera": "orbit", "description": "Apocalyptic Horizon. Titanium requiem: the last song standing"}}
{"song": "Molten Psalm", "artist": "Forge Born", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The psalm flows like lava from the earth", "scene": {"mood": "fury", "colors": ["black", "blood red", "chrome"], "composition": "pit chaos", "camera": "shaky cam", "description": "Pit Chaos. The psalm flows like lava from the earth"}}
{"song": "Molten Psalm", "artist": "Forge Born", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Molten: the liquid form of prayer", "scene": {"mood": "catharsis", "colors": ["gunmetal", "orange", "ash gray"], "composition": "stage pyro", "camera": "rapid cut", "description": "Stage Pyro. Molten: the liquid form of prayer"}}
{"song": "Molten Psalm", "artist": "Forge Born", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every psalm cools into obsidian", "scene": {"mood": "doom", "colors": ["deep crimson", "steel", "charcoal"], "composition": "crowd surf", "camera": "slow motion", "description": "Crowd Surf. Every psalm cools into obsidian"}}
{"song": "Molten Psalm", "artist": "Forge Born", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The psalm burns because the truth is hot", "scene": {"mood": "defiance", "colors": ["void black", "electric green", "bone white"], "composition": "mic close-up", "camera": "whip pan", "description": "Mic Close Up. The psalm burns because the truth is hot"}}
{"song": "Molten Psalm", "artist": "Forge Born", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Molten psalm: the volcanic hymn", "scene": {"mood": "power", "colors": ["obsidian", "molten gold", "smoke"], "composition": "guitar neck", "camera": "drone chaos", "description": "Guitar Neck. Molten psalm: the volcanic hymn"}}
{"song": "Molten Psalm", "artist": "Forge Born", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We pour psalms because carving takes too long", "scene": {"mood": "desolation", "colors": ["black", "blood red", "chrome"], "composition": "drum fury", "camera": "handheld crush", "description": "Drum Fury. We pour psalms because carving takes too long"}}
{"song": "Molten Psalm", "artist": "Forge Born", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The psalm destroys and creates simultaneously", "scene": {"mood": "rage", "colors": ["gunmetal", "orange", "ash gray"], "composition": "silhouette against fire", "camera": "locked", "description": "Silhouette Against Fire. The psalm destroys and creates simultaneously"}}
{"song": "Molten Psalm", "artist": "Forge Born", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Molten: the state between solid faith and gas", "scene": {"mood": "darkness", "colors": ["deep crimson", "steel", "charcoal"], "composition": "circle pit", "camera": "dolly smash", "description": "Circle Pit. Molten: the state between solid faith and gas"}}
{"song": "Molten Psalm", "artist": "Forge Born", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The psalm flows where it wants", "scene": {"mood": "survival", "colors": ["void black", "electric green", "bone white"], "composition": "throne of skulls", "camera": "first person", "description": "Throne Of Skulls. The psalm flows where it wants"}}
{"song": "Molten Psalm", "artist": "Forge Born", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Molten psalm: the prayer of the eruption", "scene": {"mood": "transcendence", "colors": ["obsidian", "molten gold", "smoke"], "composition": "apocalyptic horizon", "camera": "orbit", "description": "Apocalyptic Horizon. Molten psalm: the prayer of the eruption"}}
{"song": "Chainsaw Requiem", "artist": "Ashen Crown", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The requiem revs and doesn't apologize", "scene": {"mood": "fury", "colors": ["black", "blood red", "chrome"], "composition": "pit chaos", "camera": "shaky cam", "description": "Pit Chaos. The requiem revs and doesn't apologize"}}
{"song": "Chainsaw Requiem", "artist": "Ashen Crown", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Chainsaw: the instrument of the ungentle mourner", "scene": {"mood": "catharsis", "colors": ["gunmetal", "orange", "ash gray"], "composition": "stage pyro", "camera": "rapid cut", "description": "Stage Pyro. Chainsaw: the instrument of the ungentle mourner"}}
{"song": "Chainsaw Requiem", "artist": "Ashen Crown", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every rev is a heartbeat of grief", "scene": {"mood": "doom", "colors": ["deep crimson", "steel", "charcoal"], "composition": "crowd surf", "camera": "slow motion", "description": "Crowd Surf. Every rev is a heartbeat of grief"}}
{"song": "Chainsaw Requiem", "artist": "Ashen Crown", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The requiem cuts through the silence", "scene": {"mood": "defiance", "colors": ["void black", "electric green", "bone white"], "composition": "mic close-up", "camera": "whip pan", "description": "Mic Close Up. The requiem cuts through the silence"}}
{"song": "Chainsaw Requiem", "artist": "Ashen Crown", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Chainsaw requiem: the lumberjack's funeral", "scene": {"mood": "power", "colors": ["obsidian", "molten gold", "smoke"], "composition": "guitar neck", "camera": "drone chaos", "description": "Guitar Neck. Chainsaw requiem: the lumberjack's funeral"}}
{"song": "Chainsaw Requiem", "artist": "Ashen Crown", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We play the chainsaw because the violin is too soft", "scene": {"mood": "desolation", "colors": ["black", "blood red", "chrome"], "composition": "drum fury", "camera": "handheld crush", "description": "Drum Fury. We play the chainsaw because the violin is too soft"}}
{"song": "Chainsaw Requiem", "artist": "Ashen Crown", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The requiem doesn't whisper\u2014it roars", "scene": {"mood": "rage", "colors": ["gunmetal", "orange", "ash gray"], "composition": "silhouette against fire", "camera": "locked", "description": "Silhouette Against Fire. The requiem doesn't whisper\u2014it roars"}}
{"song": "Chainsaw Requiem", "artist": "Ashen Crown", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Chainsaw: the chains of the liberated", "scene": {"mood": "darkness", "colors": ["deep crimson", "steel", "charcoal"], "composition": "circle pit", "camera": "dolly smash", "description": "Circle Pit. Chainsaw: the chains of the liberated"}}
{"song": "Chainsaw Requiem", "artist": "Ashen Crown", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The requiem fells the tree of sorrow", "scene": {"mood": "survival", "colors": ["void black", "electric green", "bone white"], "composition": "throne of skulls", "camera": "first person", "description": "Throne Of Skulls. The requiem fells the tree of sorrow"}}
{"song": "Chainsaw Requiem", "artist": "Ashen Crown", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Chainsaw requiem: loud, necessary, and final", "scene": {"mood": "transcendence", "colors": ["obsidian", "molten gold", "smoke"], "composition": "apocalyptic horizon", "camera": "orbit", "description": "Apocalyptic Horizon. Chainsaw requiem: loud, necessary, and final"}}
{"song": "Grimoire of Rust", "artist": "Rift Maker", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The grimoire is written in corrosion", "scene": {"mood": "fury", "colors": ["black", "blood red", "chrome"], "composition": "pit chaos", "camera": "shaky cam", "description": "Pit Chaos. The grimoire is written in corrosion"}}
{"song": "Grimoire of Rust", "artist": "Rift Maker", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Grimoire: the spell book of the oxidized", "scene": {"mood": "catharsis", "colors": ["gunmetal", "orange", "ash gray"], "composition": "stage pyro", "camera": "rapid cut", "description": "Stage Pyro. Grimoire: the spell book of the oxidized"}}
{"song": "Grimoire of Rust", "artist": "Rift Maker", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every spell is a reaction with oxygen", "scene": {"mood": "doom", "colors": ["deep crimson", "steel", "charcoal"], "composition": "crowd surf", "camera": "slow motion", "description": "Crowd Surf. Every spell is a reaction with oxygen"}}
{"song": "Grimoire of Rust", "artist": "Rift Maker", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The grimoire ages because it's made of iron", "scene": {"mood": "defiance", "colors": ["void black", "electric green", "bone white"], "composition": "mic close-up", "camera": "whip pan", "description": "Mic Close Up. The grimoire ages because it's made of iron"}}
{"song": "Grimoire of Rust", "artist": "Rift Maker", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Grimoire of rust: the alchemy of decay", "scene": {"mood": "power", "colors": ["obsidian", "molten gold", "smoke"], "composition": "guitar neck", "camera": "drone chaos", "description": "Guitar Neck. Grimoire of rust: the alchemy of decay"}}
{"song": "Grimoire of Rust", "artist": "Rift Maker", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We cast spells in rust because gold is too pure", "scene": {"mood": "desolation", "colors": ["black", "blood red", "chrome"], "composition": "drum fury", "camera": "handheld crush", "description": "Drum Fury. We cast spells in rust because gold is too pure"}}
{"song": "Grimoire of Rust", "artist": "Rift Maker", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The grimoire's power grows with age", "scene": {"mood": "rage", "colors": ["gunmetal", "orange", "ash gray"], "composition": "silhouette against fire", "camera": "locked", "description": "Silhouette Against Fire. The grimoire's power grows with age"}}
{"song": "Grimoire of Rust", "artist": "Rift Maker", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Rust: the patina of the magical", "scene": {"mood": "darkness", "colors": ["deep crimson", "steel", "charcoal"], "composition": "circle pit", "camera": "dolly smash", "description": "Circle Pit. Rust: the patina of the magical"}}
{"song": "Grimoire of Rust", "artist": "Rift Maker", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The grimoire teaches the spell of surrender", "scene": {"mood": "survival", "colors": ["void black", "electric green", "bone white"], "composition": "throne of skulls", "camera": "first person", "description": "Throne Of Skulls. The grimoire teaches the spell of surrender"}}
{"song": "Grimoire of Rust", "artist": "Rift Maker", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Grimoire of rust: the most honest magic", "scene": {"mood": "transcendence", "colors": ["obsidian", "molten gold", "smoke"], "composition": "apocalyptic horizon", "camera": "orbit", "description": "Apocalyptic Horizon. Grimoire of rust: the most honest magic"}}

View File

@@ -1,100 +0,0 @@
{"song": "Velvet Hours", "artist": "Velvet Haze", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The hour between midnight and everything", "scene": {"mood": "tenderness", "colors": ["burgundy", "gold", "cream"], "composition": "intimate close-up", "camera": "slow dolly", "description": "Intimate Close Up. The hour between midnight and everything"}}
{"song": "Velvet Hours", "artist": "Velvet Haze", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Your voice wraps around me like a warm coat", "scene": {"mood": "longing", "colors": ["deep plum", "rose", "champagne"], "composition": "soft focus", "camera": "gentle pan", "description": "Soft Focus. Your voice wraps around me like a warm coat"}}
{"song": "Velvet Hours", "artist": "Velvet Haze", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "We slow dance in the kitchen to no music", "scene": {"mood": "sensuality", "colors": ["midnight blue", "copper", "ivory"], "composition": "candlelit frame", "camera": "steady", "description": "Candlelit Frame. We slow dance in the kitchen to no music"}}
{"song": "Velvet Hours", "artist": "Velvet Haze", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The velvet hours don't need an audience", "scene": {"mood": "healing", "colors": ["wine red", "blush", "pearl"], "composition": "silhouette", "camera": "breathing handheld", "description": "Silhouette. The velvet hours don't need an audience"}}
{"song": "Velvet Hours", "artist": "Velvet Haze", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Candle wax remembers the shape of our night", "scene": {"mood": "devotion", "colors": ["amber", "bronze", "soft white"], "composition": "over the shoulder", "camera": "locked", "description": "Over The Shoulder. Candle wax remembers the shape of our night"}}
{"song": "Velvet Hours", "artist": "Velvet Haze", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Every whispered word is a love letter", "scene": {"mood": "heartbreak", "colors": ["burgundy", "gold", "cream"], "composition": "two-shot", "camera": "soft zoom", "description": "Two Shot. Every whispered word is a love letter"}}
{"song": "Velvet Hours", "artist": "Velvet Haze", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The hours stretch like honey in the dark", "scene": {"mood": "bliss", "colors": ["deep plum", "rose", "champagne"], "composition": "reflective surface", "camera": "orbit", "description": "Reflective Surface. The hours stretch like honey in the dark"}}
{"song": "Velvet Hours", "artist": "Velvet Haze", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Velvet is just softness with a backbone", "scene": {"mood": "vulnerability", "colors": ["midnight blue", "copper", "ivory"], "composition": "golden hour", "camera": "crane down", "description": "Golden Hour. Velvet is just softness with a backbone"}}
{"song": "Velvet Hours", "artist": "Velvet Haze", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "We make time stand still by moving slowly", "scene": {"mood": "passion", "colors": ["wine red", "blush", "pearl"], "composition": "shallow depth", "camera": "rack focus", "description": "Shallow Depth. We make time stand still by moving slowly"}}
{"song": "Velvet Hours", "artist": "Velvet Haze", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The hour ends but the velvet stays", "scene": {"mood": "serenity", "colors": ["amber", "bronze", "soft white"], "composition": "embracing figures", "camera": "float", "description": "Embracing Figures. The hour ends but the velvet stays"}}
{"song": "Satin Confession", "artist": "Silk Road", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "I confess in fabrics I can't afford", "scene": {"mood": "tenderness", "colors": ["burgundy", "gold", "cream"], "composition": "intimate close-up", "camera": "slow dolly", "description": "Intimate Close Up. I confess in fabrics I can't afford"}}
{"song": "Satin Confession", "artist": "Silk Road", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Satin slides off the truth like water", "scene": {"mood": "longing", "colors": ["deep plum", "rose", "champagne"], "composition": "soft focus", "camera": "gentle pan", "description": "Soft Focus. Satin slides off the truth like water"}}
{"song": "Satin Confession", "artist": "Silk Road", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The confession booth was our bedroom all along", "scene": {"mood": "sensuality", "colors": ["midnight blue", "copper", "ivory"], "composition": "candlelit frame", "camera": "steady", "description": "Candlelit Frame. The confession booth was our bedroom all along"}}
{"song": "Satin Confession", "artist": "Silk Road", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every apology is smoother in satin", "scene": {"mood": "healing", "colors": ["wine red", "blush", "pearl"], "composition": "silhouette", "camera": "breathing handheld", "description": "Silhouette. Every apology is smoother in satin"}}
{"song": "Satin Confession", "artist": "Silk Road", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "I wore my heart on a sleeve of silk", "scene": {"mood": "devotion", "colors": ["amber", "bronze", "soft white"], "composition": "over the shoulder", "camera": "locked", "description": "Over The Shoulder. I wore my heart on a sleeve of silk"}}
{"song": "Satin Confession", "artist": "Silk Road", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The confession wasn't loud\u2014it was drape", "scene": {"mood": "heartbreak", "colors": ["burgundy", "gold", "cream"], "composition": "two-shot", "camera": "soft zoom", "description": "Two Shot. The confession wasn't loud\u2014it was drape"}}
{"song": "Satin Confession", "artist": "Silk Road", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Satin forgives where cotton can't", "scene": {"mood": "bliss", "colors": ["deep plum", "rose", "champagne"], "composition": "reflective surface", "camera": "orbit", "description": "Reflective Surface. Satin forgives where cotton can't"}}
{"song": "Satin Confession", "artist": "Silk Road", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "I confessed to the mirror and the mirror blushed", "scene": {"mood": "vulnerability", "colors": ["midnight blue", "copper", "ivory"], "composition": "golden hour", "camera": "crane down", "description": "Golden Hour. I confessed to the mirror and the mirror blushed"}}
{"song": "Satin Confession", "artist": "Silk Road", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The fabric of truth is always expensive", "scene": {"mood": "passion", "colors": ["wine red", "blush", "pearl"], "composition": "shallow depth", "camera": "rack focus", "description": "Shallow Depth. The fabric of truth is always expensive"}}
{"song": "Satin Confession", "artist": "Silk Road", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Confession: I'd wear satin to your funeral and mine", "scene": {"mood": "serenity", "colors": ["amber", "bronze", "soft white"], "composition": "embracing figures", "camera": "float", "description": "Embracing Figures. Confession: I'd wear satin to your funeral and mine"}}
{"song": "Amber Glow", "artist": "Midnight Bloom", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Frozen in amber but still warm to the touch", "scene": {"mood": "tenderness", "colors": ["burgundy", "gold", "cream"], "composition": "intimate close-up", "camera": "slow dolly", "description": "Intimate Close Up. Frozen in amber but still warm to the touch"}}
{"song": "Amber Glow", "artist": "Midnight Bloom", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The glow comes from within not from above", "scene": {"mood": "longing", "colors": ["deep plum", "rose", "champagne"], "composition": "soft focus", "camera": "gentle pan", "description": "Soft Focus. The glow comes from within not from above"}}
{"song": "Amber Glow", "artist": "Midnight Bloom", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Amber is just time that refused to leave", "scene": {"mood": "sensuality", "colors": ["midnight blue", "copper", "ivory"], "composition": "candlelit frame", "camera": "steady", "description": "Candlelit Frame. Amber is just time that refused to leave"}}
{"song": "Amber Glow", "artist": "Midnight Bloom", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "We glow in the dark because we've been in it so long", "scene": {"mood": "healing", "colors": ["wine red", "blush", "pearl"], "composition": "silhouette", "camera": "breathing handheld", "description": "Silhouette. We glow in the dark because we've been in it so long"}}
{"song": "Amber Glow", "artist": "Midnight Bloom", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The amber of your eyes outlasts the sunset", "scene": {"mood": "devotion", "colors": ["amber", "bronze", "soft white"], "composition": "over the shoulder", "camera": "locked", "description": "Over The Shoulder. The amber of your eyes outlasts the sunset"}}
{"song": "Amber Glow", "artist": "Midnight Bloom", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Every glow needs a little darkness to be seen", "scene": {"mood": "heartbreak", "colors": ["burgundy", "gold", "cream"], "composition": "two-shot", "camera": "soft zoom", "description": "Two Shot. Every glow needs a little darkness to be seen"}}
{"song": "Amber Glow", "artist": "Midnight Bloom", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Amber preserves what glass would shatter", "scene": {"mood": "bliss", "colors": ["deep plum", "rose", "champagne"], "composition": "reflective surface", "camera": "orbit", "description": "Reflective Surface. Amber preserves what glass would shatter"}}
{"song": "Amber Glow", "artist": "Midnight Bloom", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The glow was always there\u2014dimmer than you thought", "scene": {"mood": "vulnerability", "colors": ["midnight blue", "copper", "ivory"], "composition": "golden hour", "camera": "crane down", "description": "Golden Hour. The glow was always there\u2014dimmer than you thought"}}
{"song": "Amber Glow", "artist": "Midnight Bloom", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "We are amber: beautiful and imprisoned", "scene": {"mood": "passion", "colors": ["wine red", "blush", "pearl"], "composition": "shallow depth", "camera": "rack focus", "description": "Shallow Depth. We are amber: beautiful and imprisoned"}}
{"song": "Amber Glow", "artist": "Midnight Bloom", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The glow remains when the fire forgets", "scene": {"mood": "serenity", "colors": ["amber", "bronze", "soft white"], "composition": "embracing figures", "camera": "float", "description": "Embracing Figures. The glow remains when the fire forgets"}}
{"song": "Silk Morning", "artist": "Amber Voice", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The morning arrives on a thread of gold", "scene": {"mood": "tenderness", "colors": ["burgundy", "gold", "cream"], "composition": "intimate close-up", "camera": "slow dolly", "description": "Intimate Close Up. The morning arrives on a thread of gold"}}
{"song": "Silk Morning", "artist": "Amber Voice", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Silk morning\u2014the world is gentler now", "scene": {"mood": "longing", "colors": ["deep plum", "rose", "champagne"], "composition": "soft focus", "camera": "gentle pan", "description": "Soft Focus. Silk morning\u2014the world is gentler now"}}
{"song": "Silk Morning", "artist": "Amber Voice", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "We wake to a world that forgot to be rough", "scene": {"mood": "sensuality", "colors": ["midnight blue", "copper", "ivory"], "composition": "candlelit frame", "camera": "steady", "description": "Candlelit Frame. We wake to a world that forgot to be rough"}}
{"song": "Silk Morning", "artist": "Amber Voice", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The morning light wears silk and nothing else", "scene": {"mood": "healing", "colors": ["wine red", "blush", "pearl"], "composition": "silhouette", "camera": "breathing handheld", "description": "Silhouette. The morning light wears silk and nothing else"}}
{"song": "Silk Morning", "artist": "Amber Voice", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Every sunrise is a silk robe on the horizon", "scene": {"mood": "devotion", "colors": ["amber", "bronze", "soft white"], "composition": "over the shoulder", "camera": "locked", "description": "Over The Shoulder. Every sunrise is a silk robe on the horizon"}}
{"song": "Silk Morning", "artist": "Amber Voice", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The morning doesn't shout\u2014it drapes", "scene": {"mood": "heartbreak", "colors": ["burgundy", "gold", "cream"], "composition": "two-shot", "camera": "soft zoom", "description": "Two Shot. The morning doesn't shout\u2014it drapes"}}
{"song": "Silk Morning", "artist": "Amber Voice", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Silk morning: soft enough to believe in", "scene": {"mood": "bliss", "colors": ["deep plum", "rose", "champagne"], "composition": "reflective surface", "camera": "orbit", "description": "Reflective Surface. Silk morning: soft enough to believe in"}}
{"song": "Silk Morning", "artist": "Amber Voice", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "We greet the day like a lover in silk", "scene": {"mood": "vulnerability", "colors": ["midnight blue", "copper", "ivory"], "composition": "golden hour", "camera": "crane down", "description": "Golden Hour. We greet the day like a lover in silk"}}
{"song": "Silk Morning", "artist": "Amber Voice", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The morning was always silk\u2014we just wore wool", "scene": {"mood": "passion", "colors": ["wine red", "blush", "pearl"], "composition": "shallow depth", "camera": "rack focus", "description": "Shallow Depth. The morning was always silk\u2014we just wore wool"}}
{"song": "Silk Morning", "artist": "Amber Voice", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Silk morning, steel resolve, golden heart", "scene": {"mood": "serenity", "colors": ["amber", "bronze", "soft white"], "composition": "embracing figures", "camera": "float", "description": "Embracing Figures. Silk morning, steel resolve, golden heart"}}
{"song": "Moonlit Archive", "artist": "Satin Echo", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "We archive our love in moonbeams", "scene": {"mood": "tenderness", "colors": ["burgundy", "gold", "cream"], "composition": "intimate close-up", "camera": "slow dolly", "description": "Intimate Close Up. We archive our love in moonbeams"}}
{"song": "Moonlit Archive", "artist": "Satin Echo", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The moon remembers what we choose to forget", "scene": {"mood": "longing", "colors": ["deep plum", "rose", "champagne"], "composition": "soft focus", "camera": "gentle pan", "description": "Soft Focus. The moon remembers what we choose to forget"}}
{"song": "Moonlit Archive", "artist": "Satin Echo", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every kiss is a file saved to the moon", "scene": {"mood": "sensuality", "colors": ["midnight blue", "copper", "ivory"], "composition": "candlelit frame", "camera": "steady", "description": "Candlelit Frame. Every kiss is a file saved to the moon"}}
{"song": "Moonlit Archive", "artist": "Satin Echo", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The archive grows with every whispered promise", "scene": {"mood": "healing", "colors": ["wine red", "blush", "pearl"], "composition": "silhouette", "camera": "breathing handheld", "description": "Silhouette. The archive grows with every whispered promise"}}
{"song": "Moonlit Archive", "artist": "Satin Echo", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Moonlight is the librarian of the night", "scene": {"mood": "devotion", "colors": ["amber", "bronze", "soft white"], "composition": "over the shoulder", "camera": "locked", "description": "Over The Shoulder. Moonlight is the librarian of the night"}}
{"song": "Moonlit Archive", "artist": "Satin Echo", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We read our history in the craters of the moon", "scene": {"mood": "heartbreak", "colors": ["burgundy", "gold", "cream"], "composition": "two-shot", "camera": "soft zoom", "description": "Two Shot. We read our history in the craters of the moon"}}
{"song": "Moonlit Archive", "artist": "Satin Echo", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The archive doesn't judge\u2014it just holds", "scene": {"mood": "bliss", "colors": ["deep plum", "rose", "champagne"], "composition": "reflective surface", "camera": "orbit", "description": "Reflective Surface. The archive doesn't judge\u2014it just holds"}}
{"song": "Moonlit Archive", "artist": "Satin Echo", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Moonlit data: soft, eternal, luminous", "scene": {"mood": "vulnerability", "colors": ["midnight blue", "copper", "ivory"], "composition": "golden hour", "camera": "crane down", "description": "Golden Hour. Moonlit data: soft, eternal, luminous"}}
{"song": "Moonlit Archive", "artist": "Satin Echo", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "We are the moon's favorite collection", "scene": {"mood": "passion", "colors": ["wine red", "blush", "pearl"], "composition": "shallow depth", "camera": "rack focus", "description": "Shallow Depth. We are the moon's favorite collection"}}
{"song": "Moonlit Archive", "artist": "Satin Echo", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The archive opens when the sun goes down", "scene": {"mood": "serenity", "colors": ["amber", "bronze", "soft white"], "composition": "embracing figures", "camera": "float", "description": "Embracing Figures. The archive opens when the sun goes down"}}
{"song": "Honey Dusk", "artist": "Velvet Haze", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The dusk pours honey on the wounded city", "scene": {"mood": "tenderness", "colors": ["burgundy", "gold", "cream"], "composition": "intimate close-up", "camera": "slow dolly", "description": "Intimate Close Up. The dusk pours honey on the wounded city"}}
{"song": "Honey Dusk", "artist": "Velvet Haze", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Honey dusk: the viscosity of evening", "scene": {"mood": "longing", "colors": ["deep plum", "rose", "champagne"], "composition": "soft focus", "camera": "gentle pan", "description": "Soft Focus. Honey dusk: the viscosity of evening"}}
{"song": "Honey Dusk", "artist": "Velvet Haze", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "We drip slowly into the night", "scene": {"mood": "sensuality", "colors": ["midnight blue", "copper", "ivory"], "composition": "candlelit frame", "camera": "steady", "description": "Candlelit Frame. We drip slowly into the night"}}
{"song": "Honey Dusk", "artist": "Velvet Haze", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Honey: the color of your voice at 7 PM", "scene": {"mood": "healing", "colors": ["wine red", "blush", "pearl"], "composition": "silhouette", "camera": "breathing handheld", "description": "Silhouette. Honey: the color of your voice at 7 PM"}}
{"song": "Honey Dusk", "artist": "Velvet Haze", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The dusk doesn't fall\u2014it pours", "scene": {"mood": "devotion", "colors": ["amber", "bronze", "soft white"], "composition": "over the shoulder", "camera": "locked", "description": "Over The Shoulder. The dusk doesn't fall\u2014it pours"}}
{"song": "Honey Dusk", "artist": "Velvet Haze", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Honey dusk for the ones who need sweetness", "scene": {"mood": "heartbreak", "colors": ["burgundy", "gold", "cream"], "composition": "two-shot", "camera": "soft zoom", "description": "Two Shot. Honey dusk for the ones who need sweetness"}}
{"song": "Honey Dusk", "artist": "Velvet Haze", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The evening is thick with golden promises", "scene": {"mood": "bliss", "colors": ["deep plum", "rose", "champagne"], "composition": "reflective surface", "camera": "orbit", "description": "Reflective Surface. The evening is thick with golden promises"}}
{"song": "Honey Dusk", "artist": "Velvet Haze", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Dusk drips like honey from the skyline", "scene": {"mood": "vulnerability", "colors": ["midnight blue", "copper", "ivory"], "composition": "golden hour", "camera": "crane down", "description": "Golden Hour. Dusk drips like honey from the skyline"}}
{"song": "Honey Dusk", "artist": "Velvet Haze", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Honey dusk: the amber between day and dream", "scene": {"mood": "passion", "colors": ["wine red", "blush", "pearl"], "composition": "shallow depth", "camera": "rack focus", "description": "Shallow Depth. Honey dusk: the amber between day and dream"}}
{"song": "Honey Dusk", "artist": "Velvet Haze", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The dusk was always honey\u2014we just called it sunset", "scene": {"mood": "serenity", "colors": ["amber", "bronze", "soft white"], "composition": "embracing figures", "camera": "float", "description": "Embracing Figures. The dusk was always honey\u2014we just called it sunset"}}
{"song": "Porcelain Heart", "artist": "Silk Road", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "My heart is porcelain\u2014handle with reverence", "scene": {"mood": "tenderness", "colors": ["burgundy", "gold", "cream"], "composition": "intimate close-up", "camera": "slow dolly", "description": "Intimate Close Up. My heart is porcelain\u2014handle with reverence"}}
{"song": "Porcelain Heart", "artist": "Silk Road", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Porcelain: beautiful and one fall from broken", "scene": {"mood": "longing", "colors": ["deep plum", "rose", "champagne"], "composition": "soft focus", "camera": "gentle pan", "description": "Soft Focus. Porcelain: beautiful and one fall from broken"}}
{"song": "Porcelain Heart", "artist": "Silk Road", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The heart rings like china when you tap it", "scene": {"mood": "sensuality", "colors": ["midnight blue", "copper", "ivory"], "composition": "candlelit frame", "camera": "steady", "description": "Candlelit Frame. The heart rings like china when you tap it"}}
{"song": "Porcelain Heart", "artist": "Silk Road", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Porcelain heart for the delicate and the brave", "scene": {"mood": "healing", "colors": ["wine red", "blush", "pearl"], "composition": "silhouette", "camera": "breathing handheld", "description": "Silhouette. Porcelain heart for the delicate and the brave"}}
{"song": "Porcelain Heart", "artist": "Silk Road", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "We are porcelain in a world of concrete", "scene": {"mood": "devotion", "colors": ["amber", "bronze", "soft white"], "composition": "over the shoulder", "camera": "locked", "description": "Over The Shoulder. We are porcelain in a world of concrete"}}
{"song": "Porcelain Heart", "artist": "Silk Road", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The heart is fired in the kiln of loss", "scene": {"mood": "heartbreak", "colors": ["burgundy", "gold", "cream"], "composition": "two-shot", "camera": "soft zoom", "description": "Two Shot. The heart is fired in the kiln of loss"}}
{"song": "Porcelain Heart", "artist": "Silk Road", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Porcelain: the courage of the fragile", "scene": {"mood": "bliss", "colors": ["deep plum", "rose", "champagne"], "composition": "reflective surface", "camera": "orbit", "description": "Reflective Surface. Porcelain: the courage of the fragile"}}
{"song": "Porcelain Heart", "artist": "Silk Road", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "My heart sings like struck crystal", "scene": {"mood": "vulnerability", "colors": ["midnight blue", "copper", "ivory"], "composition": "golden hour", "camera": "crane down", "description": "Golden Hour. My heart sings like struck crystal"}}
{"song": "Porcelain Heart", "artist": "Silk Road", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Porcelain heart: exquisite vulnerability", "scene": {"mood": "passion", "colors": ["wine red", "blush", "pearl"], "composition": "shallow depth", "camera": "rack focus", "description": "Shallow Depth. Porcelain heart: exquisite vulnerability"}}
{"song": "Porcelain Heart", "artist": "Silk Road", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "The heart breaks beautifully or not at all", "scene": {"mood": "serenity", "colors": ["amber", "bronze", "soft white"], "composition": "embracing figures", "camera": "float", "description": "Embracing Figures. The heart breaks beautifully or not at all"}}
{"song": "Obsidian Whisper", "artist": "Midnight Bloom", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The whisper is dark but it shines when polished", "scene": {"mood": "tenderness", "colors": ["burgundy", "gold", "cream"], "composition": "intimate close-up", "camera": "slow dolly", "description": "Intimate Close Up. The whisper is dark but it shines when polished"}}
{"song": "Obsidian Whisper", "artist": "Midnight Bloom", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Obsidian: the stone of the midnight confession", "scene": {"mood": "longing", "colors": ["deep plum", "rose", "champagne"], "composition": "soft focus", "camera": "gentle pan", "description": "Soft Focus. Obsidian: the stone of the midnight confession"}}
{"song": "Obsidian Whisper", "artist": "Midnight Bloom", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every whisper cuts like volcanic glass", "scene": {"mood": "sensuality", "colors": ["midnight blue", "copper", "ivory"], "composition": "candlelit frame", "camera": "steady", "description": "Candlelit Frame. Every whisper cuts like volcanic glass"}}
{"song": "Obsidian Whisper", "artist": "Midnight Bloom", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The whisper reflects a distorted but true face", "scene": {"mood": "healing", "colors": ["wine red", "blush", "pearl"], "composition": "silhouette", "camera": "breathing handheld", "description": "Silhouette. The whisper reflects a distorted but true face"}}
{"song": "Obsidian Whisper", "artist": "Midnight Bloom", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Obsidian whisper: sharp and beautiful", "scene": {"mood": "devotion", "colors": ["amber", "bronze", "soft white"], "composition": "over the shoulder", "camera": "locked", "description": "Over The Shoulder. Obsidian whisper: sharp and beautiful"}}
{"song": "Obsidian Whisper", "artist": "Midnight Bloom", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We whisper in obsidian because velvet is too soft", "scene": {"mood": "heartbreak", "colors": ["burgundy", "gold", "cream"], "composition": "two-shot", "camera": "soft zoom", "description": "Two Shot. We whisper in obsidian because velvet is too soft"}}
{"song": "Obsidian Whisper", "artist": "Midnight Bloom", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The whisper slices through the noise of the day", "scene": {"mood": "bliss", "colors": ["deep plum", "rose", "champagne"], "composition": "reflective surface", "camera": "orbit", "description": "Reflective Surface. The whisper slices through the noise of the day"}}
{"song": "Obsidian Whisper", "artist": "Midnight Bloom", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Obsidian: the glass of the volcanic heart", "scene": {"mood": "vulnerability", "colors": ["midnight blue", "copper", "ivory"], "composition": "golden hour", "camera": "crane down", "description": "Golden Hour. Obsidian: the glass of the volcanic heart"}}
{"song": "Obsidian Whisper", "artist": "Midnight Bloom", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The whisper is dark because the truth is dark", "scene": {"mood": "passion", "colors": ["wine red", "blush", "pearl"], "composition": "shallow depth", "camera": "rack focus", "description": "Shallow Depth. The whisper is dark because the truth is dark"}}
{"song": "Obsidian Whisper", "artist": "Midnight Bloom", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Obsidian whisper: the most elegant weapon", "scene": {"mood": "serenity", "colors": ["amber", "bronze", "soft white"], "composition": "embracing figures", "camera": "float", "description": "Embracing Figures. Obsidian whisper: the most elegant weapon"}}
{"song": "Golden Hour Confession", "artist": "Amber Voice", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The light forgives everything at golden hour", "scene": {"mood": "tenderness", "colors": ["burgundy", "gold", "cream"], "composition": "intimate close-up", "camera": "slow dolly", "description": "Intimate Close Up. The light forgives everything at golden hour"}}
{"song": "Golden Hour Confession", "artist": "Amber Voice", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Confession tastes like honey at sunset", "scene": {"mood": "longing", "colors": ["deep plum", "rose", "champagne"], "composition": "soft focus", "camera": "gentle pan", "description": "Soft Focus. Confession tastes like honey at sunset"}}
{"song": "Golden Hour Confession", "artist": "Amber Voice", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Golden hour: the best time to be honest", "scene": {"mood": "sensuality", "colors": ["midnight blue", "copper", "ivory"], "composition": "candlelit frame", "camera": "steady", "description": "Candlelit Frame. Golden hour: the best time to be honest"}}
{"song": "Golden Hour Confession", "artist": "Amber Voice", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Every confession is softer in amber light", "scene": {"mood": "healing", "colors": ["wine red", "blush", "pearl"], "composition": "silhouette", "camera": "breathing handheld", "description": "Silhouette. Every confession is softer in amber light"}}
{"song": "Golden Hour Confession", "artist": "Amber Voice", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The hour doesn't judge\u2014it just illuminates", "scene": {"mood": "devotion", "colors": ["amber", "bronze", "soft white"], "composition": "over the shoulder", "camera": "locked", "description": "Over The Shoulder. The hour doesn't judge\u2014it just illuminates"}}
{"song": "Golden Hour Confession", "artist": "Amber Voice", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We confess at golden hour because shadows are long", "scene": {"mood": "heartbreak", "colors": ["burgundy", "gold", "cream"], "composition": "two-shot", "camera": "soft zoom", "description": "Two Shot. We confess at golden hour because shadows are long"}}
{"song": "Golden Hour Confession", "artist": "Amber Voice", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Golden confession: the truth in warm tones", "scene": {"mood": "bliss", "colors": ["deep plum", "rose", "champagne"], "composition": "reflective surface", "camera": "orbit", "description": "Reflective Surface. Golden confession: the truth in warm tones"}}
{"song": "Golden Hour Confession", "artist": "Amber Voice", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "The hour gives us permission to be flawed", "scene": {"mood": "vulnerability", "colors": ["midnight blue", "copper", "ivory"], "composition": "golden hour", "camera": "crane down", "description": "Golden Hour. The hour gives us permission to be flawed"}}
{"song": "Golden Hour Confession", "artist": "Amber Voice", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Golden hour: the photographer's absolution", "scene": {"mood": "passion", "colors": ["wine red", "blush", "pearl"], "composition": "shallow depth", "camera": "rack focus", "description": "Shallow Depth. Golden hour: the photographer's absolution"}}
{"song": "Golden Hour Confession", "artist": "Amber Voice", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Confession at golden hour: raw and radiant", "scene": {"mood": "serenity", "colors": ["amber", "bronze", "soft white"], "composition": "embracing figures", "camera": "float", "description": "Embracing Figures. Confession at golden hour: raw and radiant"}}
{"song": "Velvet Storm", "artist": "Satin Echo", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The storm wears velvet instead of thunder", "scene": {"mood": "tenderness", "colors": ["burgundy", "gold", "cream"], "composition": "intimate close-up", "camera": "slow dolly", "description": "Intimate Close Up. The storm wears velvet instead of thunder"}}
{"song": "Velvet Storm", "artist": "Satin Echo", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Velvet storm: the gentle destruction", "scene": {"mood": "longing", "colors": ["deep plum", "rose", "champagne"], "composition": "soft focus", "camera": "gentle pan", "description": "Soft Focus. Velvet storm: the gentle destruction"}}
{"song": "Velvet Storm", "artist": "Satin Echo", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Every gust is a soft catastrophe", "scene": {"mood": "sensuality", "colors": ["midnight blue", "copper", "ivory"], "composition": "candlelit frame", "camera": "steady", "description": "Candlelit Frame. Every gust is a soft catastrophe"}}
{"song": "Velvet Storm", "artist": "Satin Echo", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The storm doesn't shout\u2014it purrs", "scene": {"mood": "healing", "colors": ["wine red", "blush", "pearl"], "composition": "silhouette", "camera": "breathing handheld", "description": "Silhouette. The storm doesn't shout\u2014it purrs"}}
{"song": "Velvet Storm", "artist": "Satin Echo", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Velvet: the texture of controlled chaos", "scene": {"mood": "devotion", "colors": ["amber", "bronze", "soft white"], "composition": "over the shoulder", "camera": "locked", "description": "Over The Shoulder. Velvet: the texture of controlled chaos"}}
{"song": "Velvet Storm", "artist": "Satin Echo", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We weather the velvet storm in silk pajamas", "scene": {"mood": "heartbreak", "colors": ["burgundy", "gold", "cream"], "composition": "two-shot", "camera": "soft zoom", "description": "Two Shot. We weather the velvet storm in silk pajamas"}}
{"song": "Velvet Storm", "artist": "Satin Echo", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The storm is elegant even in its fury", "scene": {"mood": "bliss", "colors": ["deep plum", "rose", "champagne"], "composition": "reflective surface", "camera": "orbit", "description": "Reflective Surface. The storm is elegant even in its fury"}}
{"song": "Velvet Storm", "artist": "Satin Echo", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Velvet storm for the gentle and the fierce", "scene": {"mood": "vulnerability", "colors": ["midnight blue", "copper", "ivory"], "composition": "golden hour", "camera": "crane down", "description": "Golden Hour. Velvet storm for the gentle and the fierce"}}
{"song": "Velvet Storm", "artist": "Satin Echo", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The storm wraps you in luxury before it wrecks you", "scene": {"mood": "passion", "colors": ["wine red", "blush", "pearl"], "composition": "shallow depth", "camera": "rack focus", "description": "Shallow Depth. The storm wraps you in luxury before it wrecks you"}}
{"song": "Velvet Storm", "artist": "Satin Echo", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Velvet storm: the most beautiful disaster", "scene": {"mood": "serenity", "colors": ["amber", "bronze", "soft white"], "composition": "embracing figures", "camera": "float", "description": "Embracing Figures. Velvet storm: the most beautiful disaster"}}

View File

@@ -1,100 +0,0 @@
{"song": "Thunder Road", "artist": "Heartland", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The screen door slams, Mary's dress waves", "scene": {"mood": "hope", "colors": ["gold", "sky blue", "white"], "composition": "wide shot", "camera": "static", "description": "Open horizon. Golden light breaking through clouds. The figure silhouetted against dawn. The screen door slams, Mary's dress waves"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Like a vision she dances across the porch as the radio plays", "scene": {"mood": "anticipation", "colors": ["silver", "pale green", "cream"], "composition": "close-up", "camera": "slow pan", "description": "Close on hands gripping a steering wheel. Dashboard lights reflecting in eyes. Road stretching ahead. Like a vision she dances across the porch as the radio plays"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Roy Orbison singing for the lonely, hey that's me and I want you only", "scene": {"mood": "energy", "colors": ["red", "orange", "electric blue"], "composition": "over the shoulder", "camera": "dolly in", "description": "Rapid cuts. Bodies in motion. Light streaks across the frame. Roy Orbison singing for the lonely, hey that's me and I want you only"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Don't turn me home out now I'm so young and worthless still", "scene": {"mood": "triumph", "colors": ["gold", "crimson", "white"], "composition": "low angle", "camera": "dolly out", "description": "Wide shot. Figure standing on a hilltop. Arms raised. City lights below. Don't turn me home out now I'm so young and worthless still"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The night's busting open these two lanes will take us anywhere", "scene": {"mood": "nostalgia", "colors": ["amber", "sepia", "dusty rose"], "composition": "high angle", "camera": "handheld", "description": "Sepia tones. A photograph come to life. Dust motes in afternoon light. The night's busting open these two lanes will take us anywhere"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We got one last chance to make it real", "scene": {"mood": "urgency", "colors": ["red", "black", "strobe white"], "composition": "dutch angle", "camera": "steadicam", "description": "Handheld camera running. Blurred faces. Traffic. Heartbeat sound design. We got one last chance to make it real"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "To trade in these wings on some wheels", "scene": {"mood": "passion", "colors": ["deep red", "burgundy", "gold"], "composition": "symmetrical", "camera": "slow zoom", "description": "Extreme close-up. Skin. Breath visible in cold air. Eyes locked. To trade in these wings on some wheels"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Climb in back, heaven's waiting down the tracks", "scene": {"mood": "defiance", "colors": ["black", "neon green", "chrome"], "composition": "rule of thirds", "camera": "crane up", "description": "Low angle. Figure standing against the wind. Debris flying past. Unmoved. Climb in back, heaven's waiting down the tracks"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Oh oh oh oh oh oh oh", "scene": {"mood": "release", "colors": ["sky blue", "white", "pale gold"], "composition": "extreme wide", "camera": "tracking shot", "description": "Slow motion. Something falling \u2014 a mask, a chain, a weight. Lightness follows. Oh oh oh oh oh oh oh"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "It's a town full of losers and I'm pulling out of here to win", "scene": {"mood": "catharsis", "colors": ["all white", "silver", "clear"], "composition": "medium shot", "camera": "slow tilt down", "description": "White space expanding. Figure dissolving into light. Peace in the dissolution. It's a town full of losers and I'm pulling out of here to win"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Woke up on the floor again, whiskey still on my tongue", "scene": {"mood": "despair", "colors": ["navy", "black", "grey"], "composition": "wide shot", "camera": "static", "description": "Empty room. Single light source. Figure curled in corner. Rain on windows. Woke up on the floor again, whiskey still on my tongue"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The mirror shows a stranger and the damage that I've done", "scene": {"mood": "anger", "colors": ["red", "black", "orange"], "composition": "close-up", "camera": "slow pan", "description": "Shattered glass. Red light. Hands clenched. Jaw tight. The frame vibrates. The mirror shows a stranger and the damage that I've done"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "I scream until my throat bleeds but nobody comes", "scene": {"mood": "frenzy", "colors": ["strobe", "red", "white flash"], "composition": "over the shoulder", "camera": "dolly in", "description": "Strobe lighting. Multiple exposures. Bodies colliding. Chaos as composition. I scream until my throat bleeds but nobody comes"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The walls are closing in again, the ceiling pressing down", "scene": {"mood": "exhaustion", "colors": ["grey", "brown", "faded"], "composition": "low angle", "camera": "dolly out", "description": "Static shot. Figure slumped. Eyes half-closed. Time passing in shadows. The walls are closing in again, the ceiling pressing down"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "I tried to call your number but you changed it years ago", "scene": {"mood": "resignation", "colors": ["grey", "muted blue", "beige"], "composition": "high angle", "camera": "handheld", "description": "Medium shot. Hands dropping keys on a table. Turning away. Not looking back. I tried to call your number but you changed it years ago"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Now I'm howling at the moon like some rabid dog I know", "scene": {"mood": "grief", "colors": ["deep purple", "black", "silver"], "composition": "dutch angle", "camera": "steadicam", "description": "Wide shot. Figure alone in vast space. Dark purple sky. No horizon line. Now I'm howling at the moon like some rabid dog I know"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Every bone remembers what my mind wants to forget", "scene": {"mood": "numbness", "colors": ["white", "grey", "no color"], "composition": "symmetrical", "camera": "slow zoom", "description": "Desaturated. Figure staring at nothing. World moving around them in blur. Every bone remembers what my mind wants to forget"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "I'll tear this whole house down before the sun comes up", "scene": {"mood": "rage", "colors": ["fire red", "black", "ember orange"], "composition": "rule of thirds", "camera": "crane up", "description": "Red wash. Extreme close-up on eyes. Fire reflected in pupils. I'll tear this whole house down before the sun comes up"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Ash and ruin everywhere, this is all that's left", "scene": {"mood": "acceptance", "colors": ["soft blue", "warm grey", "sage"], "composition": "extreme wide", "camera": "tracking shot", "description": "Soft focus. Gentle light. Figure breathing. The camera doesn't judge. Ash and ruin everywhere, this is all that's left"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Silence. Just the wind through broken glass.", "scene": {"mood": "silence", "colors": ["black", "void", "faint starlight"], "composition": "medium shot", "camera": "slow tilt down", "description": "Black screen. Faint starlight. The sound drops out completely. Silence. Just the wind through broken glass."}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Ten thousand miles of static between your voice and mine", "scene": {"mood": "wonder", "colors": ["aurora green", "violet", "silver"], "composition": "wide shot", "camera": "static", "description": "Northern lights overhead. Figure looking up. Mouth open. Child's expression. Ten thousand miles of static between your voice and mine"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "I trace your constellation on the dashboard every night", "scene": {"mood": "isolation", "colors": ["cold blue", "black", "distant starlight"], "composition": "close-up", "camera": "slow pan", "description": "Extreme wide. Single figure. Vast empty landscape. Scale crushing. I trace your constellation on the dashboard every night"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The signal fades to nothing but I keep the frequency", "scene": {"mood": "longing", "colors": ["teal", "silver", "moonlight"], "composition": "over the shoulder", "camera": "dolly in", "description": "Through a window. Figure on the other side. Glass between. Breath on the pane. The signal fades to nothing but I keep the frequency"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Then suddenly your laughter breaks through like a summer storm", "scene": {"mood": "connection", "colors": ["warm gold", "rose", "blush"], "composition": "low angle", "camera": "dolly out", "description": "Two hands reaching. Fingers almost touching. Warm light between them. Then suddenly your laughter breaks through like a summer storm"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "We're dancing in the data stream, our pixels intertwined", "scene": {"mood": "euphoria", "colors": ["neon", "rainbow", "white flash"], "composition": "high angle", "camera": "handheld", "description": "Overexposed. Everything bright. Dancing. The frame can't contain the joy. We're dancing in the data stream, our pixels intertwined"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "But I can't tell if you're real or just a ghost in the machine", "scene": {"mood": "confusion", "colors": ["swirling", "unsettled", "green-grey"], "composition": "dutch angle", "camera": "steadicam", "description": "Multiple focal points. Nothing sharp. The viewer doesn't know where to look. But I can't tell if you're real or just a ghost in the machine"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The picture clears and there you are \u2014 imperfect, warm, alive", "scene": {"mood": "clarity", "colors": ["clear blue", "white", "crisp"], "composition": "symmetrical", "camera": "slow zoom", "description": "Rack focus. Background blurs, foreground sharpens. Suddenly everything makes sense. The picture clears and there you are \u2014 imperfect, warm, alive"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Your hand reaches through the screen, I swear I feel the heat", "scene": {"mood": "tenderness", "colors": ["blush pink", "warm cream", "soft gold"], "composition": "rule of thirds", "camera": "crane up", "description": "Close on a hand touching a face. Soft light. Shallow depth of field. Your hand reaches through the screen, I swear I feel the heat"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The bandwidth's dying, say it now before the link goes dark", "scene": {"mood": "urgency", "colors": ["red", "black", "strobe white"], "composition": "extreme wide", "camera": "tracking shot", "description": "Handheld camera running. Blurred faces. Traffic. Heartbeat sound design. The bandwidth's dying, say it now before the link goes dark"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Goodnight, satellite heart. I'll find you in the static.", "scene": {"mood": "bittersweet", "colors": ["amber", "lavender", "fading light"], "composition": "medium shot", "camera": "slow tilt down", "description": "Amber light fading. A smile that's also a goodbye. Beautiful and sad at once. Goodnight, satellite heart. I'll find you in the static."}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "They paved over every green thing when the developers came", "scene": {"mood": "oppression", "colors": ["concrete grey", "brown", "exhaust fume yellow"], "composition": "wide shot", "camera": "static", "description": "Concrete. Overpasses. No sky visible. Figures small against infrastructure. They paved over every green thing when the developers came"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "But we planted seeds between the cracks and gave them all a name", "scene": {"mood": "resilience", "colors": ["green", "cracked concrete", "gold"], "composition": "close-up", "camera": "slow pan", "description": "Crack in pavement. Green shoot pushing through. Macro lens. But we planted seeds between the cracks and gave them all a name"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The mayor says progress looks like demolition and dust", "scene": {"mood": "anger", "colors": ["red", "black", "orange"], "composition": "over the shoulder", "camera": "dolly in", "description": "Shattered glass. Red light. Hands clenched. Jaw tight. The frame vibrates. The mayor says progress looks like demolition and dust"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "But a dandelion broke through the asphalt this morning \u2014 that's us", "scene": {"mood": "beauty", "colors": ["wildflower colors", "green", "sunlight"], "composition": "low angle", "camera": "dolly out", "description": "Wildflowers in unexpected places. Color against grey. Nature reclaiming. But a dandelion broke through the asphalt this morning \u2014 that's us"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "You can't kill what wants to live, can't silence what must sing", "scene": {"mood": "defiance", "colors": ["black", "neon green", "chrome"], "composition": "high angle", "camera": "handheld", "description": "Low angle. Figure standing against the wind. Debris flying past. Unmoved. You can't kill what wants to live, can't silence what must sing"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We're the roots beneath the road, we're the birds that built on string", "scene": {"mood": "community", "colors": ["warm tones", "string lights", "firelight"], "composition": "dutch angle", "camera": "steadicam", "description": "String lights. People gathered. Laughter out of focus. Warmth as visual language. We're the roots beneath the road, we're the birds that built on string"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "When they tear the next block down we'll be dancing in the rubble", "scene": {"mood": "joy", "colors": ["bright", "multi", "saturated"], "composition": "symmetrical", "camera": "slow zoom", "description": "Saturated color. Wide smiles. Arms open. The world in full bloom. When they tear the next block down we'll be dancing in the rubble"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Every protest is a garden, every march plants something new", "scene": {"mood": "struggle", "colors": ["dust", "grey", "hard light"], "composition": "rule of thirds", "camera": "crane up", "description": "Close on hands working. Calluses. Dust. Effort visible in every frame. Every protest is a garden, every march plants something new"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The concrete is a drum and our footsteps keep the beat", "scene": {"mood": "growth", "colors": ["green", "brown", "morning light"], "composition": "extreme wide", "camera": "tracking shot", "description": "Time-lapse. Seed to flower. Sunrise to sunset. Transformation as rhythm. The concrete is a drum and our footsteps keep the beat"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Tomorrow there'll be flowers where they swore there'd only be defeat", "scene": {"mood": "hope", "colors": ["gold", "sky blue", "white"], "composition": "medium shot", "camera": "slow tilt down", "description": "Open horizon. Golden light breaking through clouds. The figure silhouetted against dawn. Tomorrow there'll be flowers where they swore there'd only be defeat"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "I felt the pull before I saw the edge", "scene": {"mood": "dread", "colors": ["void black", "deep red", "cold white"], "composition": "wide shot", "camera": "static", "description": "Corner of frame. Something in the periphery. Dark. The camera doesn't look directly. I felt the pull before I saw the edge"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The stars bent sideways, light itself was dead", "scene": {"mood": "fascination", "colors": ["event horizon purple", "gravitational lens blue"], "composition": "close-up", "camera": "slow pan", "description": "Close on eyes. Reflection of something impossible. The pupil expands. The stars bent sideways, light itself was dead"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "I could have turned the ship around but something in me said stay", "scene": {"mood": "surrender", "colors": ["white", "dissolution", "prismatic"], "composition": "over the shoulder", "camera": "dolly in", "description": "Arms opening. Head back. Falling backward into something vast. I could have turned the ship around but something in me said stay"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The event horizon glows like a halo made of nothing", "scene": {"mood": "awe", "colors": ["starfield", "nebula colors", "infinite dark"], "composition": "low angle", "camera": "dolly out", "description": "Wide shot of cosmos. Nebula. Stars being born. Human figure tiny at bottom. The event horizon glows like a halo made of nothing"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Time stretches thin as wire, each second takes a year", "scene": {"mood": "terror", "colors": ["black", "red shift", "distortion"], "composition": "high angle", "camera": "handheld", "description": "Shaking camera. Red shift. Something approaching fast. The frame distorts. Time stretches thin as wire, each second takes a year"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "I am both the observer and the thing that disappears", "scene": {"mood": "peace", "colors": ["deep space black", "starlight", "calm"], "composition": "dutch angle", "camera": "steadicam", "description": "Still water. Stars reflected. Perfect mirror. No movement. No sound. I am both the observer and the thing that disappears"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "My body reads the tidal forces like sheet music played on bone", "scene": {"mood": "disorientation", "colors": ["warped", "chromatic aberration", "bent light"], "composition": "symmetrical", "camera": "slow zoom", "description": "Warped lens. Vertigo. Walls becoming floor. Gravity is a suggestion. My body reads the tidal forces like sheet music played on bone"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "I stop fighting, stop reaching, stop calling home", "scene": {"mood": "acceptance", "colors": ["soft blue", "warm grey", "sage"], "composition": "rule of thirds", "camera": "crane up", "description": "Soft focus. Gentle light. Figure breathing. The camera doesn't judge. I stop fighting, stop reaching, stop calling home"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "There is a peace in dissolution I was never meant to know", "scene": {"mood": "transcendence", "colors": ["pure white", "beyond visible", "golden"], "composition": "extreme wide", "camera": "tracking shot", "description": "Pure white expanding. Figure becoming light. Boundaries dissolving. There is a peace in dissolution I was never meant to know"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Singularity. Silence. Everything and nothing both at once.", "scene": {"mood": "emptiness", "colors": ["void", "absolute black", "nothing"], "composition": "medium shot", "camera": "slow tilt down", "description": "Absolute black. No stars. No reference point. The void looking back. Singularity. Silence. Everything and nothing both at once."}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "My father's hands smelled like machine oil and prayer", "scene": {"mood": "nostalgia", "colors": ["amber", "sepia", "dusty rose"], "composition": "wide shot", "camera": "static", "description": "Sepia tones. A photograph come to life. Dust motes in afternoon light. My father's hands smelled like machine oil and prayer"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The factory whistle was our clock, the shift was our calendar", "scene": {"mood": "sadness", "colors": ["grey", "rain", "muted blue"], "composition": "close-up", "camera": "slow pan", "description": "Rain on glass. Grey light. A cup of tea going cold. Still life of loss. The factory whistle was our clock, the shift was our calendar"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "He'd come home at midnight, wake me up to say goodnight", "scene": {"mood": "tenderness", "colors": ["blush pink", "warm cream", "soft gold"], "composition": "over the shoulder", "camera": "dolly in", "description": "Close on a hand touching a face. Soft light. Shallow depth of field. He'd come home at midnight, wake me up to say goodnight"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Now the mill is just a skeleton and he's been gone ten years", "scene": {"mood": "loss", "colors": ["faded", "dusty", "empty space"], "composition": "low angle", "camera": "dolly out", "description": "Empty chair. Dust settling. A coat still on a hook. Presence of absence. Now the mill is just a skeleton and he's been gone ten years"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "But the river still runs brown with memory and rust", "scene": {"mood": "beauty", "colors": ["wildflower colors", "green", "sunlight"], "composition": "high angle", "camera": "handheld", "description": "Wildflowers in unexpected places. Color against grey. Nature reclaiming. But the river still runs brown with memory and rust"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "I found his lunchbox in the attic, coffee stains still fresh", "scene": {"mood": "resignation", "colors": ["grey", "muted blue", "beige"], "composition": "dutch angle", "camera": "steadicam", "description": "Medium shot. Hands dropping keys on a table. Turning away. Not looking back. I found his lunchbox in the attic, coffee stains still fresh"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Some things don't decay \u2014 they just learn to hold still", "scene": {"mood": "love", "colors": ["neutral"], "composition": "symmetrical", "camera": "slow zoom", "description": "Visual interpretation of: Some things don't decay \u2014 they just learn to hold still"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "I hum the songs he hummed to me though I've forgotten half the words", "scene": {"mood": "weariness", "colors": ["grey-brown", "faded", "dim"], "composition": "rule of thirds", "camera": "crane up", "description": "Slow movement. Heavy eyelids. The world in faded tones. Everything too much. I hum the songs he hummed to me though I've forgotten half the words"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The town's half-empty but the porch lights still come on at dusk", "scene": {"mood": "quiet hope", "colors": ["faint warm light", "candle glow", "dawn grey"], "composition": "extreme wide", "camera": "tracking shot", "description": "Faint warm light. Candle in dark room. Just enough to see by. The town's half-empty but the porch lights still come on at dusk"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Sleep now, rust belt baby. The furnace keeps us warm.", "scene": {"mood": "peace", "colors": ["deep space black", "starlight", "calm"], "composition": "medium shot", "camera": "slow tilt down", "description": "Still water. Stars reflected. Perfect mirror. No movement. No sound. Sleep now, rust belt baby. The furnace keeps us warm."}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "I didn't start the fire but I brought the gasoline", "scene": {"mood": "fury", "colors": ["dark red", "black", "flash"], "composition": "wide shot", "camera": "static", "description": "Dark red wash. Hands destroying. Frame shaking with rage. I didn't start the fire but I brought the gasoline"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Every sermon needs a spark and every spark needs a dream", "scene": {"mood": "ecstasy", "colors": ["fire", "gold", "blinding white"], "composition": "close-up", "camera": "slow pan", "description": "Fire and gold. Bodies arching. Light bursting from every surface. Every sermon needs a spark and every spark needs a dream"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The forest is a cathedral and the flames are choir boys singing", "scene": {"mood": "chaos", "colors": ["strobe", "fragmented", "clashing"], "composition": "over the shoulder", "camera": "dolly in", "description": "Fragmented frame. Collage. Everything at once. Order is a memory. The forest is a cathedral and the flames are choir boys singing"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Watch the old world burn \u2014 isn't the light beautiful?", "scene": {"mood": "joy", "colors": ["bright", "multi", "saturated"], "composition": "low angle", "camera": "dolly out", "description": "Saturated color. Wide smiles. Arms open. The world in full bloom. Watch the old world burn \u2014 isn't the light beautiful?"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "We'll dance in the embers, we'll make love in the ash", "scene": {"mood": "destruction", "colors": ["fire", "ash", "smoke orange"], "composition": "high angle", "camera": "handheld", "description": "Fire. Ash falling like snow. Structures collapsing. Beautiful in its terrible way. We'll dance in the embers, we'll make love in the ash"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "From destruction comes the soil where new things grow at last", "scene": {"mood": "creation", "colors": ["green", "light", "warm gold"], "composition": "dutch angle", "camera": "steadicam", "description": "Hands shaping clay. Light emerging from dark. Something new being born. From destruction comes the soil where new things grow at last"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "But don't mistake the warmth for safety, don't mistake the glow for home", "scene": {"mood": "warning", "colors": ["red flash", "amber", "siren"], "composition": "symmetrical", "camera": "slow zoom", "description": "Red flash. Siren light. The calm before. Then: impact. But don't mistake the warmth for safety, don't mistake the glow for home"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Come closer, come closer \u2014 I promise the burning feels like flying", "scene": {"mood": "invitation", "colors": ["warm", "open", "golden"], "composition": "rule of thirds", "camera": "crane up", "description": "Open door. Warm light spilling out. A hand extended. Come in. Come closer, come closer \u2014 I promise the burning feels like flying"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "We threw everything we owned into the blaze and laughed", "scene": {"mood": "abandon", "colors": ["wild", "free", "untethered"], "composition": "extreme wide", "camera": "tracking shot", "description": "Running through a field. Hair wild. No destination. Just movement. We threw everything we owned into the blaze and laughed"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Morning. Smoke. Green shoots. Begin again.", "scene": {"mood": "rebirth", "colors": ["green shoots", "dawn", "clear"], "composition": "medium shot", "camera": "slow tilt down", "description": "Dawn. Green shoots in ash. First breath after drowning. Morning. Smoke. Green shoots. Begin again."}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "There's a voice on the radio that shouldn't be there", "scene": {"mood": "mystery", "colors": ["deep blue", "shadow", "candle"], "composition": "wide shot", "camera": "static", "description": "Shadow figure in doorway. Candle. Face half-lit. Eyes knowing. There's a voice on the radio that shouldn't be there"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Speaking my name in a language I almost understand", "scene": {"mood": "loneliness", "colors": ["single light", "dark", "cold blue"], "composition": "close-up", "camera": "slow pan", "description": "Single light in vast dark. Figure beneath it. Nothing else. Speaking my name in a language I almost understand"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "I turn the dial but it follows like a shadow made of sound", "scene": {"mood": "curiosity", "colors": ["warm yellow", "spotlight", "discovery"], "composition": "over the shoulder", "camera": "dolly in", "description": "Light moving across a surface. Discovery. Eyes widening. I turn the dial but it follows like a shadow made of sound"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Then it says something only I would know, something buried deep", "scene": {"mood": "connection", "colors": ["warm gold", "rose", "blush"], "composition": "low angle", "camera": "dolly out", "description": "Two hands reaching. Fingers almost touching. Warm light between them. Then it says something only I would know, something buried deep"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "I'm not afraid anymore \u2014 I'm listening", "scene": {"mood": "paranoia", "colors": ["surveillance green", "strobe", "red"], "composition": "high angle", "camera": "handheld", "description": "Surveillance angles. Green tint. Multiple screens. Watching. Being watched. I'm not afraid anymore \u2014 I'm listening"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The voice knows my dreams, it describes them back to me", "scene": {"mood": "intimacy", "colors": ["candlelight", "warm", "close"], "composition": "dutch angle", "camera": "steadicam", "description": "Candlelight only. Two faces close. Shared breath. The world outside forgotten. The voice knows my dreams, it describes them back to me"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We're having a conversation across some membrane I can't see", "scene": {"mood": "urgency", "colors": ["red", "black", "strobe white"], "composition": "symmetrical", "camera": "slow zoom", "description": "Handheld camera running. Blurred faces. Traffic. Heartbeat sound design. We're having a conversation across some membrane I can't see"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Then static. Then nothing. Then a whisper: find me", "scene": {"mood": "disconnection", "colors": ["static", "grey", "broken signal"], "composition": "rule of thirds", "camera": "crane up", "description": "Static. Snow on screen. A voice breaking up. Distance measured in noise. Then static. Then nothing. Then a whisper: find me"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "I search every frequency but the voice is gone", "scene": {"mood": "searching", "colors": ["flashlight beam", "dark", "moving light"], "composition": "extreme wide", "camera": "tracking shot", "description": "Flashlight beam cutting dark. Moving. Looking. Not finding yet. I search every frequency but the voice is gone"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Some nights I still hear it, faint, like a song in another room", "scene": {"mood": "haunting", "colors": ["faint blue", "echo", "silver"], "composition": "medium shot", "camera": "slow tilt down", "description": "Faint blue light. Echo of a figure. Present and absent simultaneously. Some nights I still hear it, faint, like a song in another room"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "I wore your love like a weapon and you never felt the blade", "scene": {"mood": "seduction", "colors": ["deep red", "velvet", "candlelight"], "composition": "wide shot", "camera": "static", "description": "Deep red. Velvet textures. Slow movement. Eyes that promise. I wore your love like a weapon and you never felt the blade"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Every kiss was a negotiation, every touch a trade", "scene": {"mood": "power", "colors": ["gold", "black", "crimson"], "composition": "close-up", "camera": "slow pan", "description": "Throne. Gold. Black. The figure doesn't move. Doesn't need to. Every kiss was a negotiation, every touch a trade"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The throne room smells like jasmine and someone else's fear", "scene": {"mood": "cruelty", "colors": ["cold silver", "black", "sharp white"], "composition": "over the shoulder", "camera": "dolly in", "description": "Silver blade. Cold light. A smile that doesn't reach the eyes. The throne room smells like jasmine and someone else's fear"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "I am beautiful when I'm angry \u2014 haven't you heard?", "scene": {"mood": "beauty", "colors": ["wildflower colors", "green", "sunlight"], "composition": "low angle", "camera": "dolly out", "description": "Wildflowers in unexpected places. Color against grey. Nature reclaiming. I am beautiful when I'm angry \u2014 haven't you heard?"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Don't mistake my gentleness for weakness, darling", "scene": {"mood": "danger", "colors": ["red", "black", "warning yellow"], "composition": "high angle", "camera": "handheld", "description": "Red and black. Warning signs. The frame contracts. Something approaches. Don't mistake my gentleness for weakness, darling"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "I chose to be kind. I could burn this kingdom down.", "scene": {"mood": "vulnerability", "colors": ["soft", "exposed", "raw"], "composition": "dutch angle", "camera": "steadicam", "description": "Exposed skin. Soft light. Eyes open. Trust visible in every pore. I chose to be kind. I could burn this kingdom down."}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The roses in my crown have thorns that curve inward", "scene": {"mood": "fury", "colors": ["dark red", "black", "flash"], "composition": "symmetrical", "camera": "slow zoom", "description": "Dark red wash. Hands destroying. Frame shaking with rage. The roses in my crown have thorns that curve inward"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "I bleed for my own sins, not for yours", "scene": {"mood": "grace", "colors": ["white", "silver", "flowing"], "composition": "rule of thirds", "camera": "crane up", "description": "White. Flowing. Movement without effort. The body as art. I bleed for my own sins, not for yours"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Tonight I lay the crown aside and sleep without armor", "scene": {"mood": "revenge", "colors": ["dark", "steel", "cold blue"], "composition": "extreme wide", "camera": "tracking shot", "description": "Cold blue. Steel. The plan unfolding in shadows. Patience as weapon. Tonight I lay the crown aside and sleep without armor"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Mercy. The hardest word. The only gift worth giving.", "scene": {"mood": "mercy", "colors": ["warm gold", "white", "gentle"], "composition": "medium shot", "camera": "slow tilt down", "description": "Warm gold. Hand lowering a weapon. Choosing not to. The harder path. Mercy. The hardest word. The only gift worth giving."}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Four walls, one window, a view of another wall", "scene": {"mood": "claustrophobia", "colors": ["close walls", "yellow bulb", "cramped"], "composition": "wide shot", "camera": "static", "description": "Walls close. Ceiling low. Yellow bulb. No escape visible. Four walls, one window, a view of another wall"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The radiator clicks like a metronome for the damned", "scene": {"mood": "routine", "colors": ["grey", "institutional", "fluorescent"], "composition": "close-up", "camera": "slow pan", "description": "Fluorescent light. Same motion repeated. Clock on the wall. Time as loop. The radiator clicks like a metronome for the damned"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "I've memorized every crack in the ceiling \u2014 they form a map", "scene": {"mood": "desperation", "colors": ["scratching", "clawing", "raw"], "composition": "over the shoulder", "camera": "dolly in", "description": "Hands clawing. Fingernails against surface. Raw need. Nothing held back. I've memorized every crack in the ceiling \u2014 they form a map"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "In my mind I've left a hundred times, bought a farm, learned to fly", "scene": {"mood": "fantasy", "colors": ["dreamy", "pastel", "floating"], "composition": "low angle", "camera": "dolly out", "description": "Pastel. Floating. Impossible architecture. Gravity optional. In my mind I've left a hundred times, bought a farm, learned to fly"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Then one morning I open the door and just walk out", "scene": {"mood": "breakthrough", "colors": ["white burst", "open sky", "blinding"], "composition": "high angle", "camera": "handheld", "description": "White burst. Wall shattering. Open sky beyond. Freedom as explosion. Then one morning I open the door and just walk out"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The hallway is an ocean, the stairs are a mountain range", "scene": {"mood": "freedom", "colors": ["open sky", "blue", "green"], "composition": "dutch angle", "camera": "steadicam", "description": "Open road. Blue sky. Green fields. Wind in hair. No walls. The hallway is an ocean, the stairs are a mountain range"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The street hits me like cold water and I almost go back", "scene": {"mood": "fear", "colors": ["cold", "dark", "sharp"], "composition": "symmetrical", "camera": "slow zoom", "description": "Cold. Dark. Sharp edges. The frame contracts. Something unseen. The street hits me like cold water and I almost go back"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "But the sky \u2014 have you seen the sky? It goes on forever", "scene": {"mood": "joy", "colors": ["bright", "multi", "saturated"], "composition": "rule of thirds", "camera": "crane up", "description": "Saturated color. Wide smiles. Arms open. The world in full bloom. But the sky \u2014 have you seen the sky? It goes on forever"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "I stand on the sidewalk and cry because the world is so big", "scene": {"mood": "grounding", "colors": ["neutral"], "composition": "extreme wide", "camera": "tracking shot", "description": "Visual interpretation of: I stand on the sidewalk and cry because the world is so big"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Home is not a place. Home is the moment you stop hiding.", "scene": {"mood": "home", "colors": ["neutral"], "composition": "medium shot", "camera": "slow tilt down", "description": "Visual interpretation of: Home is not a place. Home is the moment you stop hiding."}}