diff --git a/db/init.sql b/db/init.sql index 798a6e1..bfd5109 100644 --- a/db/init.sql +++ b/db/init.sql @@ -16,6 +16,7 @@ CREATE TABLE users ( password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user', -- user, moderator, admin trust_tier TEXT NOT NULL DEFAULT 'standard', -- standard, creator, trusted_api + is_system BOOLEAN NOT NULL DEFAULT FALSE, -- platform system account (fractafrag) stripe_customer_id TEXT, subscription_tier TEXT DEFAULT 'free', -- free, pro, studio ai_credits_remaining INTEGER DEFAULT 0, @@ -38,21 +39,25 @@ CREATE TABLE shaders ( title TEXT NOT NULL, description TEXT, glsl_code TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'published', -- draft, published, archived is_public BOOLEAN DEFAULT TRUE, is_ai_generated BOOLEAN DEFAULT FALSE, - ai_provider TEXT, -- anthropic, openai, ollama, null + is_system BOOLEAN DEFAULT FALSE, -- generated by fractafrag platform + ai_provider TEXT, -- anthropic, openai, ollama, null + system_label TEXT, -- e.g. 'fractafrag-curated', 'fractafrag-generated' thumbnail_url TEXT, preview_url TEXT, - render_status TEXT DEFAULT 'pending', -- pending, rendering, ready, failed - style_vector vector(512), -- pgvector: visual style embedding - style_metadata JSONB, -- { chaos_level, color_temp, motion_type, ... } + render_status TEXT DEFAULT 'pending', -- pending, rendering, ready, failed + style_vector vector(512), -- pgvector: visual style embedding + style_metadata JSONB, -- { chaos_level, color_temp, motion_type, ... } tags TEXT[], - shader_type TEXT DEFAULT '2d', -- 2d, 3d, audio-reactive + shader_type TEXT DEFAULT '2d', -- 2d, 3d, audio-reactive forked_from UUID REFERENCES shaders(id) ON DELETE SET NULL, + current_version INTEGER NOT NULL DEFAULT 1, -- current version number view_count INTEGER DEFAULT 0, - score FLOAT DEFAULT 0, -- cached hot score for feed ranking + score FLOAT DEFAULT 0, -- cached hot score for feed ranking -- Creator economy stubs (Section 11f) - access_tier TEXT DEFAULT 'open', -- open, source_locked, commercial + access_tier TEXT DEFAULT 'open', source_unlock_price_cents INTEGER, commercial_license_price_cents INTEGER, verified_creator_shader BOOLEAN DEFAULT FALSE, @@ -61,15 +66,33 @@ CREATE TABLE shaders ( updated_at TIMESTAMPTZ DEFAULT NOW() ); +-- ════════════════════════════════════════════════════════════ +-- SHADER VERSIONS — immutable snapshots of each edit +-- ════════════════════════════════════════════════════════════ +CREATE TABLE shader_versions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + shader_id UUID NOT NULL REFERENCES shaders(id) ON DELETE CASCADE, + version_number INTEGER NOT NULL, + glsl_code TEXT NOT NULL, + title TEXT NOT NULL, + description TEXT, + tags TEXT[], + style_metadata JSONB, + change_note TEXT, -- optional: "fixed the color bleeding", "added mouse interaction" + thumbnail_url TEXT, + created_at TIMESTAMPTZ DEFAULT NOW(), + UNIQUE (shader_id, version_number) +); + -- ════════════════════════════════════════════════════════════ -- VOTES -- ════════════════════════════════════════════════════════════ CREATE TABLE votes ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES users(id) ON DELETE CASCADE, - shader_id UUID REFERENCES shaders(id) ON DELETE CASCADE, - value SMALLINT NOT NULL CHECK (value IN (-1, 1)), - created_at TIMESTAMPTZ DEFAULT NOW(), + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + shader_id UUID REFERENCES shaders(id) ON DELETE CASCADE, + value SMALLINT NOT NULL CHECK (value IN (-1, 1)), + created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE (user_id, shader_id) ); @@ -78,10 +101,10 @@ CREATE TABLE votes ( -- ════════════════════════════════════════════════════════════ CREATE TABLE engagement_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES users(id) ON DELETE SET NULL, -- null for anonymous - session_id TEXT, -- anonymous session token + user_id UUID REFERENCES users(id) ON DELETE SET NULL, + session_id TEXT, shader_id UUID REFERENCES shaders(id) ON DELETE CASCADE, - event_type TEXT NOT NULL, -- dwell, replay, share, generate_similar + event_type TEXT NOT NULL, dwell_secs FLOAT, metadata JSONB, created_at TIMESTAMPTZ DEFAULT NOW() @@ -91,21 +114,20 @@ CREATE TABLE engagement_events ( -- DESIRES / BOUNTIES -- ════════════════════════════════════════════════════════════ CREATE TABLE desires ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - author_id UUID REFERENCES users(id) ON DELETE SET NULL, - prompt_text TEXT NOT NULL, - prompt_embedding vector(512), -- embedded for similarity grouping - style_hints JSONB, -- { chaos_level, color_temp, etc } - tip_amount_cents INTEGER DEFAULT 0, - status TEXT DEFAULT 'open', -- open, in_progress, fulfilled, expired - heat_score FLOAT DEFAULT 1, -- updated as similar desires accumulate - fulfilled_by_shader UUID REFERENCES shaders(id) ON DELETE SET NULL, - fulfilled_at TIMESTAMPTZ, - expires_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT NOW() + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + author_id UUID REFERENCES users(id) ON DELETE SET NULL, + prompt_text TEXT NOT NULL, + prompt_embedding vector(512), + style_hints JSONB, + tip_amount_cents INTEGER DEFAULT 0, + status TEXT DEFAULT 'open', + heat_score FLOAT DEFAULT 1, + fulfilled_by_shader UUID REFERENCES shaders(id) ON DELETE SET NULL, + fulfilled_at TIMESTAMPTZ, + expires_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW() ); --- Similar desire grouping (many-to-many) CREATE TABLE desire_clusters ( cluster_id UUID, desire_id UUID REFERENCES desires(id) ON DELETE CASCADE, @@ -117,27 +139,27 @@ CREATE TABLE desire_clusters ( -- BOUNTY TIPS -- ════════════════════════════════════════════════════════════ CREATE TABLE bounty_tips ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - desire_id UUID REFERENCES desires(id) ON DELETE CASCADE, - tipper_id UUID REFERENCES users(id) ON DELETE SET NULL, - amount_cents INTEGER NOT NULL, - stripe_payment_intent_id TEXT, - status TEXT DEFAULT 'held', -- held, released, refunded - created_at TIMESTAMPTZ DEFAULT NOW() + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + desire_id UUID REFERENCES desires(id) ON DELETE CASCADE, + tipper_id UUID REFERENCES users(id) ON DELETE SET NULL, + amount_cents INTEGER NOT NULL, + stripe_payment_intent_id TEXT, + status TEXT DEFAULT 'held', + created_at TIMESTAMPTZ DEFAULT NOW() ); -- ════════════════════════════════════════════════════════════ -- CREATOR PAYOUTS -- ════════════════════════════════════════════════════════════ CREATE TABLE creator_payouts ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - creator_id UUID REFERENCES users(id) ON DELETE SET NULL, - desire_id UUID REFERENCES desires(id) ON DELETE SET NULL, + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + creator_id UUID REFERENCES users(id) ON DELETE SET NULL, + desire_id UUID REFERENCES desires(id) ON DELETE SET NULL, gross_amount_cents INTEGER, - platform_fee_cents INTEGER, -- 10% - net_amount_cents INTEGER, -- 90% + platform_fee_cents INTEGER, + net_amount_cents INTEGER, stripe_transfer_id TEXT, - status TEXT DEFAULT 'pending', -- pending, processing, completed, failed + status TEXT DEFAULT 'pending', created_at TIMESTAMPTZ DEFAULT NOW() ); @@ -145,17 +167,17 @@ CREATE TABLE creator_payouts ( -- API KEYS (for MCP clients) -- ════════════════════════════════════════════════════════════ CREATE TABLE api_keys ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES users(id) ON DELETE CASCADE, - key_hash TEXT UNIQUE NOT NULL, -- bcrypt hash of the actual key - key_prefix TEXT NOT NULL, -- first 8 chars for display (ff_key_XXXXXXXX) - name TEXT, -- user-given label - trust_tier TEXT DEFAULT 'probation', -- probation, trusted, premium - submissions_approved INTEGER DEFAULT 0, - rate_limit_per_hour INTEGER DEFAULT 10, - last_used_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT NOW(), - revoked_at TIMESTAMPTZ + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + key_hash TEXT UNIQUE NOT NULL, + key_prefix TEXT NOT NULL, + name TEXT, + trust_tier TEXT DEFAULT 'probation', + submissions_approved INTEGER DEFAULT 0, + rate_limit_per_hour INTEGER DEFAULT 10, + last_used_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW(), + revoked_at TIMESTAMPTZ ); -- ════════════════════════════════════════════════════════════ @@ -168,7 +190,7 @@ CREATE TABLE generation_log ( provider TEXT NOT NULL, prompt_text TEXT, tokens_used INTEGER, - cost_cents INTEGER, -- platform cost for credit-based generations + cost_cents INTEGER, success BOOLEAN, created_at TIMESTAMPTZ DEFAULT NOW() ); @@ -192,7 +214,7 @@ CREATE TABLE source_unlocks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), shader_id UUID REFERENCES shaders(id) ON DELETE CASCADE, buyer_id UUID REFERENCES users(id) ON DELETE SET NULL, - license_type TEXT NOT NULL, -- personal, commercial + license_type TEXT NOT NULL, amount_cents INTEGER NOT NULL, platform_fee_cents INTEGER NOT NULL, stripe_payment_intent_id TEXT, @@ -215,22 +237,18 @@ CREATE TABLE creator_engagement_snapshots ( -- ════════════════════════════════════════════════════════════ -- Feed performance -CREATE INDEX idx_shaders_score ON shaders(score DESC) WHERE is_public = TRUE; -CREATE INDEX idx_shaders_created ON shaders(created_at DESC) WHERE is_public = TRUE; +CREATE INDEX idx_shaders_score ON shaders(score DESC) WHERE is_public = TRUE AND status = 'published'; +CREATE INDEX idx_shaders_created ON shaders(created_at DESC) WHERE is_public = TRUE AND status = 'published'; CREATE INDEX idx_shaders_tags ON shaders USING GIN(tags); CREATE INDEX idx_shaders_render_status ON shaders(render_status) WHERE render_status != 'ready'; +CREATE INDEX idx_shaders_status ON shaders(status); +CREATE INDEX idx_shaders_author_status ON shaders(author_id, status, updated_at DESC); +CREATE INDEX idx_shaders_system ON shaders(is_system) WHERE is_system = TRUE; --- Recommendation (pgvector ANN — ivfflat, will rebuild after data exists) --- NOTE: ivfflat indexes require data in the table to build properly. --- Run these AFTER seeding initial data: --- CREATE INDEX idx_shaders_style_vector ON shaders --- USING ivfflat (style_vector vector_cosine_ops) WITH (lists = 100); --- CREATE INDEX idx_users_taste_vector ON users --- USING ivfflat (taste_vector vector_cosine_ops) WITH (lists = 50); --- CREATE INDEX idx_desires_embedding ON desires --- USING ivfflat (prompt_embedding vector_cosine_ops) WITH (lists = 50); +-- Versioning +CREATE INDEX idx_shader_versions_shader ON shader_versions(shader_id, version_number DESC); --- For now, use HNSW (works on empty tables, better perf at small scale) +-- Recommendation (pgvector HNSW — works on empty tables) CREATE INDEX idx_shaders_style_vector ON shaders USING hnsw (style_vector vector_cosine_ops) WITH (m = 16, ef_construction = 64); CREATE INDEX idx_users_taste_vector ON users @@ -263,3 +281,20 @@ CREATE INDEX idx_comments_parent ON comments(parent_id); -- Text search CREATE INDEX idx_shaders_title_trgm ON shaders USING GIN(title gin_trgm_ops); CREATE INDEX idx_desires_prompt_trgm ON desires USING GIN(prompt_text gin_trgm_ops); + +-- ════════════════════════════════════════════════════════════ +-- SYSTEM ACCOUNT: The "fractafrag" platform user +-- All system-generated/curated shaders are authored by this account +-- ════════════════════════════════════════════════════════════ +INSERT INTO users (id, username, email, password_hash, role, trust_tier, is_system, subscription_tier, is_verified_creator) +VALUES ( + '00000000-0000-0000-0000-000000000001', + 'fractafrag', + 'system@fractafrag.local', + '$2b$12$000000000000000000000000000000000000000000000000000000', -- not a valid login + 'admin', + 'trusted_api', + TRUE, + 'studio', + TRUE +); diff --git a/scripts/seed_shaders.py b/scripts/seed_shaders.py new file mode 100644 index 0000000..d4e3d17 --- /dev/null +++ b/scripts/seed_shaders.py @@ -0,0 +1,2043 @@ +""" +Fractafrag Seed Data — 200+ high-quality GLSL shaders. + +Each shader is: +- Written for the 'fractafrag' system account +- Tagged comprehensively for search/filter/algorithm testing +- Flagged as is_system=True with system_label='fractafrag-curated' +- Covers 2D and 3D, every visual style imaginable +- Ready for immediate display in the feed + +Usage: + docker compose exec api python /app/scripts/seed_shaders.py +""" + +import asyncio +import uuid +import random +from datetime import datetime, timedelta, timezone + +# System account UUID +SYSTEM_USER_ID = "00000000-0000-0000-0000-000000000001" + +SHADERS = [] + +def s(title, code, tags, shader_type="2d", description="", style_metadata=None): + """Helper to register a shader.""" + SHADERS.append({ + "title": title, + "code": code.strip(), + "tags": tags, + "shader_type": shader_type, + "description": description, + "style_metadata": style_metadata or {}, + }) + +# ═══════════════════════════════════════════════════════════ +# 2D — MATHEMATICAL / GEOMETRIC +# ═══════════════════════════════════════════════════════════ + +s("Spiral Galaxy", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float a = atan(uv.y, uv.x); + float r = length(uv); + float spiral = sin(a * 5.0 - r * 20.0 + iTime * 2.0); + float glow = 0.02 / (r + 0.01); + vec3 col = vec3(0.3, 0.1, 0.6) * glow + vec3(0.8, 0.4, 0.9) * spiral * exp(-r * 3.0); + fragColor = vec4(col, 1.0); +}""", ["spiral", "galaxy", "mathematical", "glow", "purple"], "2d", "Logarithmic spiral arms with exponential glow falloff", +{"chaos_level": 0.4, "color_temperature": "cool", "motion_type": "rotating"}) + +s("Binary Rain", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float col = 0.0; + for (int i = 0; i < 12; i++) { + float fi = float(i); + vec2 p = fract(uv * vec2(20.0, 1.0) + vec2(fi * 0.37, iTime * (0.5 + fi * 0.1))); + float drop = smoothstep(0.0, 0.1, p.y) * smoothstep(1.0, 0.8, p.y); + col += drop * step(0.7, fract(sin(fi * 73.156) * 43758.5453)); + } + fragColor = vec4(0.0, col * 0.8, col * 0.3, 1.0); +}""", ["matrix", "rain", "digital", "code", "green", "cyberpunk"], "2d", "Digital rain effect inspired by the Matrix", +{"chaos_level": 0.3, "color_temperature": "cool", "motion_type": "falling"}) + +s("Voronoi Shatter", """ +vec2 hash2(vec2 p) { + p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3))); + return fract(sin(p) * 43758.5453); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.y; + vec2 n = floor(uv * 6.0); + vec2 f = fract(uv * 6.0); + float md = 8.0; + vec2 mr; + for (int j = -1; j <= 1; j++) + for (int i = -1; i <= 1; i++) { + vec2 g = vec2(float(i), float(j)); + vec2 o = hash2(n + g); + o = 0.5 + 0.5 * sin(iTime + 6.2831 * o); + vec2 r = g + o - f; + float d = dot(r, r); + if (d < md) { md = d; mr = r; } + } + vec3 col = 0.5 + 0.5 * cos(md * 6.0 + vec3(0, 1, 2) + iTime); + col *= 1.0 - 0.5 * smoothstep(0.0, 0.05, md); + fragColor = vec4(col, 1.0); +}""", ["voronoi", "geometric", "mosaic", "cellular", "colorful", "animated"], "2d", "Animated Voronoi tessellation with iridescent cells", +{"chaos_level": 0.5, "color_temperature": "warm", "motion_type": "morphing"}) + +s("Monochrome Static Interference", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); } +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float t = floor(iTime * 15.0); + float n = hash(uv * 100.0 + t); + float scanline = sin(uv.y * 800.0 + iTime * 50.0) * 0.1; + float bars = step(0.98, fract(uv.y * 30.0 + iTime * 3.0)) * 0.3; + float v = n * 0.7 + scanline + bars; + v = clamp(v, 0.0, 1.0); + fragColor = vec4(vec3(v), 1.0); +}""", ["static", "noise", "monochrome", "glitch", "tv", "analog", "bw"], "2d", "Analog TV static with scanlines and horizontal bars", +{"chaos_level": 0.9, "color_temperature": "monochrome", "motion_type": "chaotic"}) + +s("Mandelbrot Deep Zoom", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + float zoom = pow(2.0, -mod(iTime * 0.5, 30.0)); + vec2 center = vec2(-0.7435669, 0.1314023); + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec2 c = center + uv * zoom; + vec2 z = vec2(0.0); + int iter = 0; + for (int i = 0; i < 256; i++) { + z = vec2(z.x*z.x - z.y*z.y, 2.0*z.x*z.y) + c; + if (dot(z, z) > 4.0) break; + iter = i; + } + float t = float(iter) / 256.0; + vec3 col = 0.5 + 0.5 * cos(3.0 + t * 15.0 + vec3(0.0, 0.6, 1.0)); + if (iter == 255) col = vec3(0.0); + fragColor = vec4(col, 1.0); +}""", ["mandelbrot", "fractal", "zoom", "mathematical", "infinite", "complex"], "2d", "Continuously zooming into the Mandelbrot set boundary", +{"chaos_level": 0.6, "color_temperature": "cool", "motion_type": "zooming"}) + +s("Neon Grid Pulse", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec2 grid = abs(fract(uv * 8.0 - 0.5) - 0.5); + float d = min(grid.x, grid.y); + float pulse = sin(iTime * 3.0 + length(uv) * 10.0) * 0.5 + 0.5; + float line = smoothstep(0.02, 0.0, d); + vec3 col = vec3(0.0); + col += vec3(0.0, 1.0, 0.8) * line * pulse; + col += vec3(1.0, 0.0, 0.5) * line * (1.0 - pulse); + col += vec3(0.02, 0.0, 0.04); + fragColor = vec4(col, 1.0); +}""", ["grid", "neon", "synthwave", "retro", "tron", "pulse", "cyberpunk"], "2d", "Retro neon grid with alternating color pulses", +{"chaos_level": 0.2, "color_temperature": "cool", "motion_type": "pulsing"}) + +s("Ink in Water", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +float noise(vec2 p) { + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +} +float fbm(vec2 p) { + float v = 0.0, a = 0.5; + mat2 rot = mat2(0.8, 0.6, -0.6, 0.8); + for (int i = 0; i < 7; i++) { v += a * noise(p); p = rot * p * 2.0; a *= 0.5; } + return v; +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float t = iTime * 0.15; + float f1 = fbm(uv * 3.0 + t); + float f2 = fbm(uv * 3.0 + f1 * 2.0 + t * 0.7); + float f3 = fbm(uv * 3.0 + f2 * 2.0); + vec3 col = mix(vec3(0.05, 0.02, 0.1), vec3(0.1, 0.0, 0.3), f1); + col = mix(col, vec3(0.6, 0.1, 0.2), f2 * 0.6); + col = mix(col, vec3(0.9, 0.8, 0.6), f3 * f3 * 0.5); + fragColor = vec4(col, 1.0); +}""", ["fluid", "ink", "water", "organic", "fbm", "noise", "elegant", "dark"], "2d", "Layered fractal brownian motion simulating ink diffusing in water", +{"chaos_level": 0.5, "color_temperature": "warm", "motion_type": "fluid"}) + +s("Kaleidoscope Mirror", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float a = atan(uv.y, uv.x); + float r = length(uv); + float segments = 8.0; + a = mod(a, 6.2831 / segments); + a = abs(a - 3.14159 / segments); + vec2 p = vec2(cos(a), sin(a)) * r; + p += iTime * 0.2; + float pattern = sin(p.x * 15.0) * sin(p.y * 15.0); + pattern += sin(p.x * 7.0 + iTime) * cos(p.y * 9.0 - iTime * 0.5); + vec3 col = 0.5 + 0.5 * cos(pattern * 2.0 + iTime + vec3(0, 2, 4)); + col *= smoothstep(1.5, 0.0, r); + fragColor = vec4(col, 1.0); +}""", ["kaleidoscope", "symmetry", "mirror", "psychedelic", "colorful", "geometric"], "2d", "8-fold kaleidoscope with evolving sine patterns", +{"chaos_level": 0.6, "color_temperature": "neutral", "motion_type": "rotating"}) + +s("Plasma Lava Lamp", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float t = iTime * 0.5; + float v = 0.0; + v += sin(uv.x * 10.0 + t); + v += sin((uv.y * 10.0 + t) * 0.5); + v += sin((uv.x * 10.0 + uv.y * 10.0 + t) * 0.3); + vec2 c = uv * 10.0 + vec2(sin(t * 0.3) * 5.0, cos(t * 0.5) * 5.0); + v += sin(length(c) + t); + v *= 0.5; + vec3 col; + col.r = sin(v * 3.14159) * 0.5 + 0.5; + col.g = sin(v * 3.14159 + 2.094) * 0.3 + 0.2; + col.b = sin(v * 3.14159 + 4.189) * 0.5 + 0.5; + fragColor = vec4(col, 1.0); +}""", ["plasma", "lava", "retro", "colorful", "animated", "smooth", "classic"], "2d", "Classic plasma effect with lava lamp color cycling", +{"chaos_level": 0.3, "color_temperature": "warm", "motion_type": "fluid"}) + +s("Circuit Board", """ +float hash(float n) { return fract(sin(n) * 43758.5453); } +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + vec2 g = floor(uv * 30.0); + vec2 f = fract(uv * 30.0); + float id = hash(g.x + g.y * 137.0); + float trace = 0.0; + if (id > 0.5) trace = step(0.45, f.x) * step(f.x, 0.55); + else trace = step(0.45, f.y) * step(f.y, 0.55); + float node = smoothstep(0.3, 0.25, length(f - 0.5)); + float pulse = sin(iTime * 3.0 + (g.x + g.y) * 0.5) * 0.5 + 0.5; + vec3 col = vec3(0.0, 0.15, 0.1); + col += vec3(0.0, 0.8, 0.4) * trace * (0.3 + 0.7 * pulse); + col += vec3(0.0, 1.0, 0.6) * node * pulse; + fragColor = vec4(col, 1.0); +}""", ["circuit", "digital", "tech", "pcb", "green", "electronic", "grid"], "2d", "Procedural circuit board with animated signal pulses", +{"chaos_level": 0.2, "color_temperature": "cool", "motion_type": "pulsing"}) + +s("Aurora Borealis", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +float noise(vec2 p) { + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float t = iTime * 0.2; + float n = 0.0; + n += noise(vec2(uv.x * 3.0 + t, uv.y * 0.5)) * 0.5; + n += noise(vec2(uv.x * 6.0 - t * 0.7, uv.y * 1.0 + t * 0.3)) * 0.25; + n += noise(vec2(uv.x * 12.0 + t * 0.5, uv.y * 2.0)) * 0.125; + float curtain = smoothstep(0.3, 0.7, uv.y) * smoothstep(1.0, 0.6, uv.y); + curtain *= n; + vec3 green = vec3(0.1, 0.9, 0.3); + vec3 purple = vec3(0.5, 0.1, 0.8); + vec3 col = mix(green, purple, uv.y) * curtain * 2.0; + col += vec3(0.01, 0.0, 0.03); // night sky + fragColor = vec4(col, 1.0); +}""", ["aurora", "borealis", "nature", "night", "sky", "green", "purple", "atmospheric"], "2d", "Northern lights curtain with layered noise movement", +{"chaos_level": 0.3, "color_temperature": "cool", "motion_type": "fluid"}) + +s("Op Art Illusion", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float d = length(uv); + float a = atan(uv.y, uv.x); + float pattern = sin(d * 40.0 - iTime * 4.0) * sin(a * 12.0 + iTime * 2.0); + float bw = step(0.0, pattern); + float warp = sin(d * 20.0 + iTime) * 0.05; + bw = step(0.0, sin((d + warp) * 40.0 - iTime * 4.0) * sin(a * 12.0 + iTime * 2.0)); + fragColor = vec4(vec3(bw), 1.0); +}""", ["op-art", "illusion", "optical", "bw", "monochrome", "geometric", "hypnotic", "pattern"], "2d", "Black and white optical illusion with warping concentric rings", +{"chaos_level": 0.7, "color_temperature": "monochrome", "motion_type": "pulsing"}) + +s("Glitch Corruption", """ +float hash(float n) { return fract(sin(n) * 43758.5453); } +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float t = floor(iTime * 8.0); + float block = floor(uv.y * 20.0); + float shift = hash(block + t) * step(0.85, hash(block * 0.3 + t)); + uv.x += shift * 0.3 * (hash(t + block * 7.0) - 0.5); + vec3 col; + col.r = step(0.5, fract(sin(dot(uv + 0.01, vec2(12.9, 78.2))) * 43758.5)); + col.g = step(0.5, fract(sin(dot(uv, vec2(12.9, 78.2))) * 43758.5)); + col.b = step(0.5, fract(sin(dot(uv - 0.01, vec2(12.9, 78.2))) * 43758.5)); + float scanline = sin(uv.y * 500.0) * 0.04; + col += scanline; + fragColor = vec4(col, 1.0); +}""", ["glitch", "corruption", "digital", "error", "rgb-split", "cyberpunk", "broken"], "2d", "Data corruption with RGB channel splitting and block displacement", +{"chaos_level": 0.95, "color_temperature": "neutral", "motion_type": "chaotic"}) + +s("Sunset Gradient Waves", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float wave = sin(uv.x * 8.0 + iTime) * 0.03; + wave += sin(uv.x * 15.0 - iTime * 1.5) * 0.015; + float y = uv.y + wave; + vec3 sky1 = vec3(0.1, 0.0, 0.2); + vec3 sky2 = vec3(0.8, 0.2, 0.1); + vec3 sky3 = vec3(1.0, 0.7, 0.2); + vec3 col; + if (y > 0.6) col = mix(sky2, sky1, (y - 0.6) / 0.4); + else if (y > 0.3) col = mix(sky3, sky2, (y - 0.3) / 0.3); + else col = mix(vec3(1.0, 0.9, 0.5), sky3, y / 0.3); + float sun = smoothstep(0.12, 0.1, length(vec2(uv.x - 0.5, y - 0.45))); + col = mix(col, vec3(1.0, 0.95, 0.8), sun); + fragColor = vec4(col, 1.0); +}""", ["sunset", "gradient", "sky", "warm", "peaceful", "nature", "waves", "orange"], "2d", "Stylized sunset with layered gradient and gentle wave distortion", +{"chaos_level": 0.1, "color_temperature": "warm", "motion_type": "fluid"}) + +s("Hyperbolic Tiling", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float t = iTime * 0.3; + float scale = 1.0; + vec3 col = vec3(0.0); + for (int i = 0; i < 8; i++) { + uv = abs(uv) - 0.5; + uv *= 1.5; + scale *= 1.5; + float fi = float(i); + uv *= mat2(cos(t + fi), sin(t + fi), -sin(t + fi), cos(t + fi)); + } + float d = length(uv) / scale; + col = 0.5 + 0.5 * cos(d * 50.0 + iTime * 2.0 + vec3(0, 2, 4)); + fragColor = vec4(col, 1.0); +}""", ["hyperbolic", "tiling", "fractal", "recursive", "mathematical", "colorful", "abstract"], "2d", "Iterated function system creating hyperbolic-style tiling", +{"chaos_level": 0.7, "color_temperature": "neutral", "motion_type": "rotating"}) + +s("Liquid Metal", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +float noise(vec2 p) { + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float t = iTime * 0.3; + vec2 p = uv * 4.0; + float n = noise(p + t) + noise(p * 2.0 - t * 0.7) * 0.5 + noise(p * 4.0 + t * 0.3) * 0.25; + n = n / 1.75; + float env = pow(n, 1.5); + vec3 col = mix(vec3(0.3, 0.3, 0.35), vec3(0.9, 0.9, 0.95), env); + float spec = pow(max(0.0, n * 2.0 - 1.0), 8.0); + col += vec3(1.0) * spec * 0.5; + col = mix(col, col * vec3(0.8, 0.85, 1.0), 0.3); + fragColor = vec4(col, 1.0); +}""", ["metal", "liquid", "chrome", "silver", "reflective", "smooth", "elegant"], "2d", "Flowing liquid mercury surface with specular highlights", +{"chaos_level": 0.3, "color_temperature": "cool", "motion_type": "fluid"}) + +s("Firework Burst", """ +float hash(float n) { return fract(sin(n) * 43758.5453); } +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec3 col = vec3(0.0); + for (int b = 0; b < 5; b++) { + float fb = float(b); + float bt = mod(iTime + fb * 1.3, 4.0); + vec2 center = vec2(hash(fb * 7.0) - 0.5, hash(fb * 13.0) * 0.3 + 0.1) * 0.8; + float expand = bt * 0.4; + float fade = exp(-bt * 1.5); + for (int i = 0; i < 30; i++) { + float fi = float(i); + float angle = fi * 0.2094 + fb; + float speed = 0.8 + hash(fi + fb * 100.0) * 0.4; + vec2 pp = center + vec2(cos(angle), sin(angle)) * expand * speed; + pp.y -= bt * bt * 0.05; // gravity + float d = length(uv - pp); + vec3 sparkCol = 0.5 + 0.5 * cos(fb * 2.0 + vec3(0, 2, 4)); + col += sparkCol * 0.003 / (d + 0.001) * fade; + } + } + col = min(col, 1.0); + fragColor = vec4(col, 1.0); +}""", ["fireworks", "particles", "celebration", "explosion", "night", "colorful", "sparks"], "2d", "Multiple firework bursts with particle trails and gravity", +{"chaos_level": 0.7, "color_temperature": "warm", "motion_type": "explosive"}) + +s("Moiré Interference", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float d1 = length(uv - vec2(sin(iTime * 0.5) * 0.3, 0.0)); + float d2 = length(uv + vec2(sin(iTime * 0.5) * 0.3, 0.0)); + float p1 = sin(d1 * 60.0); + float p2 = sin(d2 * 60.0); + float moire = p1 * p2; + vec3 col = vec3(moire * 0.5 + 0.5); + fragColor = vec4(col, 1.0); +}""", ["moire", "interference", "optical", "monochrome", "geometric", "minimal", "waves"], "2d", "Two overlapping circular wave patterns creating moiré interference", +{"chaos_level": 0.4, "color_temperature": "monochrome", "motion_type": "oscillating"}) + +s("Electric Storm", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +float noise(vec2 p) { + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float t = iTime; + float bolt = 0.0; + for (int i = 0; i < 5; i++) { + float fi = float(i); + vec2 p = vec2(uv.x + noise(vec2(uv.y * 10.0 + t * 5.0, fi)) * 0.3, uv.y); + float d = abs(p.x - (fi - 2.0) * 0.15); + bolt += 0.005 / (d + 0.005) * step(0.7, noise(vec2(fi, floor(t * 3.0)))); + } + vec3 col = vec3(0.02, 0.0, 0.05); + col += vec3(0.3, 0.5, 1.0) * bolt; + col += vec3(0.8, 0.9, 1.0) * bolt * bolt; + fragColor = vec4(col, 1.0); +}""", ["lightning", "storm", "electric", "blue", "energy", "dramatic", "weather"], "2d", "Electric lightning bolts crackling across a dark sky", +{"chaos_level": 0.8, "color_temperature": "cool", "motion_type": "chaotic"}) + +s("Hexagonal Grid", """ +vec2 hexUV(vec2 uv) { + vec2 s = vec2(1.7320508, 1.0); + vec2 a = mod(uv, s) - s * 0.5; + vec2 b = mod(uv - s * 0.5, s) - s * 0.5; + return dot(a, a) < dot(b, b) ? a : b; +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec2 h = hexUV(uv * 8.0); + float d = length(h); + float edge = smoothstep(0.5, 0.48, d); + float inner = smoothstep(0.3, 0.28, d); + float pulse = sin(iTime * 2.0 + length(floor(uv * 8.0)) * 2.0) * 0.5 + 0.5; + vec3 col = vec3(0.05); + col += vec3(0.0, 0.6, 0.8) * (edge - inner) * 0.5; + col += vec3(0.0, 0.8, 1.0) * inner * pulse; + fragColor = vec4(col, 1.0); +}""", ["hexagon", "grid", "geometric", "teal", "tech", "clean", "pattern", "honeycomb"], "2d", "Pulsing hexagonal grid with teal neon glow", +{"chaos_level": 0.2, "color_temperature": "cool", "motion_type": "pulsing"}) + +# ═══════════════════════════════════════════════════════════ +# 3D — RAY MARCHING +# ═══════════════════════════════════════════════════════════ + +s("Infinite Tunnel", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float a = atan(uv.y, uv.x); + float r = length(uv); + float tunnel = 1.0 / r; + float tex = sin(a * 6.0 + tunnel * 2.0 - iTime * 3.0); + tex *= sin(tunnel * 4.0 - iTime * 2.0); + vec3 col = 0.5 + 0.5 * cos(tex * 3.0 + iTime + vec3(0, 2, 4)); + col *= smoothstep(0.0, 0.3, r) * (1.0 - smoothstep(0.8, 2.0, r)); + col *= 1.0 / (r * 3.0); + fragColor = vec4(clamp(col, 0.0, 1.0), 1.0); +}""", ["tunnel", "infinite", "warp", "psychedelic", "vortex", "3d-illusion", "trippy"], "3d", "Infinite tunnel flythrough with warping hexagonal texture", +{"chaos_level": 0.6, "color_temperature": "neutral", "motion_type": "forward"}) + +s("Raymarched Metaballs", """ +float sdSphere(vec3 p, float r) { return length(p) - r; } +float map(vec3 p) { + float d = 1e10; + for (int i = 0; i < 5; i++) { + float fi = float(i); + vec3 center = vec3( + sin(iTime + fi * 1.3) * 0.8, + cos(iTime * 0.7 + fi * 2.1) * 0.6, + sin(iTime * 0.5 + fi * 0.9) * 0.5 + ); + d = min(d, sdSphere(p - center, 0.4)); + } + // smooth union + return d; +} +vec3 getNormal(vec3 p) { + vec2 e = vec2(0.001, 0.0); + return normalize(vec3(map(p+e.xyy)-map(p-e.xyy), map(p+e.yxy)-map(p-e.yxy), map(p+e.yyx)-map(p-e.yyx))); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec3 ro = vec3(0, 0, -3); + vec3 rd = normalize(vec3(uv, 1.0)); + float t = 0.0; + for (int i = 0; i < 64; i++) { + float d = map(ro + rd * t); + if (d < 0.001 || t > 20.0) break; + t += d; + } + vec3 col = vec3(0.02, 0.01, 0.05); + if (t < 20.0) { + vec3 p = ro + rd * t; + vec3 n = getNormal(p); + vec3 light = normalize(vec3(1, 1, -1)); + float diff = max(dot(n, light), 0.0); + float spec = pow(max(dot(reflect(-light, n), -rd), 0.0), 32.0); + col = vec3(0.8, 0.2, 0.4) * diff + vec3(1.0) * spec * 0.5 + vec3(0.05, 0.02, 0.08); + } + fragColor = vec4(col, 1.0); +}""", ["metaballs", "3d", "raymarching", "organic", "blobs", "smooth", "pink"], "3d", "Five orbiting raymarched spheres with specular lighting", +{"chaos_level": 0.4, "color_temperature": "warm", "motion_type": "orbiting"}) + +s("Fractal Mountain Terrain", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +float noise(vec2 p) { + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +} +float terrain(vec2 p) { + float h = 0.0, a = 0.5; + for (int i = 0; i < 6; i++) { h += a * noise(p); p *= 2.0; a *= 0.5; } + return h; +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec3 ro = vec3(iTime * 0.5, 1.5, 0.0); + vec3 rd = normalize(vec3(uv.x, uv.y - 0.2, 1.0)); + float t = 0.0; + vec3 col = vec3(0.4, 0.6, 0.9); // sky + for (int i = 0; i < 80; i++) { + vec3 p = ro + rd * t; + float h = terrain(p.xz) * 2.0; + if (p.y < h) { + vec3 n = normalize(vec3( + terrain(p.xz + vec2(0.01, 0)) - terrain(p.xz - vec2(0.01, 0)), + 0.02, + terrain(p.xz + vec2(0, 0.01)) - terrain(p.xz - vec2(0, 0.01)) + )); + float sun = max(dot(n, normalize(vec3(1, 0.8, 0.5))), 0.0); + col = mix(vec3(0.2, 0.3, 0.1), vec3(0.5, 0.4, 0.3), p.y / 2.0) * (0.3 + 0.7 * sun); + col = mix(col, vec3(0.4, 0.6, 0.9), 1.0 - exp(-t * 0.03)); + break; + } + t += 0.05 + t * 0.01; + } + fragColor = vec4(col, 1.0); +}""", ["terrain", "mountain", "landscape", "3d", "raymarching", "nature", "flyover", "procedural"], "3d", "Procedural fractal terrain with aerial flyover and atmospheric fog", +{"chaos_level": 0.3, "color_temperature": "neutral", "motion_type": "forward"}) + +s("Neon Torus Knot", """ +float sdTorus(vec3 p, vec2 t) { + vec2 q = vec2(length(p.xz) - t.x, p.y); + return length(q) - t.y; +} +float map(vec3 p) { + float a = iTime * 0.5; + p.xz *= mat2(cos(a), sin(a), -sin(a), cos(a)); + // trefoil knot via torus deformation + float r = length(p.xz); + float theta = atan(p.z, p.x); + vec3 q = vec3(r - 1.0, p.y, 0.0); + float knotAngle = theta * 3.0 + iTime; + q.xy -= 0.4 * vec2(cos(knotAngle), sin(knotAngle)); + return length(q) - 0.15; +} +vec3 getNormal(vec3 p) { + vec2 e = vec2(0.001, 0); + return normalize(vec3(map(p+e.xyy)-map(p-e.xyy), map(p+e.yxy)-map(p-e.yxy), map(p+e.yyx)-map(p-e.yyx))); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec3 ro = vec3(0, 0, -3.5); + vec3 rd = normalize(vec3(uv, 1.0)); + float t = 0.0; + vec3 col = vec3(0.01); + for (int i = 0; i < 80; i++) { + float d = map(ro + rd * t); + if (d < 0.001) { + vec3 p = ro + rd * t; + vec3 n = getNormal(p); + float fresnel = pow(1.0 - abs(dot(n, rd)), 3.0); + col = vec3(0.0, 0.8, 1.0) * (0.2 + 0.8 * fresnel); + col += vec3(1.0, 0.3, 0.8) * fresnel * fresnel; + float glow = 0.005 / (d + 0.005); + col += vec3(0.0, 0.4, 0.6) * glow; + break; + } + t += d; + if (t > 20.0) break; + // Accumulate glow from near-misses + col += vec3(0.0, 0.3, 0.5) * 0.002 / (d + 0.01); + } + fragColor = vec4(col, 1.0); +}""", ["torus", "knot", "3d", "neon", "raymarching", "glow", "cyan", "mathematical", "geometry"], "3d", "Raymarched trefoil torus knot with neon fresnel glow", +{"chaos_level": 0.4, "color_temperature": "cool", "motion_type": "rotating"}) + +s("Glass Refraction Orb", """ +float sdSphere(vec3 p, float r) { return length(p) - r; } +float map(vec3 p) { return sdSphere(p, 1.0); } +vec3 getNormal(vec3 p) { + vec2 e = vec2(0.001, 0); + return normalize(vec3(map(p+e.xyy)-map(p-e.xyy), map(p+e.yxy)-map(p-e.yxy), map(p+e.yyx)-map(p-e.yyx))); +} +vec3 background(vec3 rd) { + float t = iTime * 0.3; + return 0.5 + 0.5 * cos(rd * 5.0 + t + vec3(0, 2, 4)); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec3 ro = vec3(0, 0, -3); + vec3 rd = normalize(vec3(uv, 1.0)); + float t = 0.0; + for (int i = 0; i < 64; i++) { + float d = map(ro + rd * t); + if (d < 0.001) break; + t += d; + if (t > 20.0) break; + } + vec3 col; + if (t < 20.0) { + vec3 p = ro + rd * t; + vec3 n = getNormal(p); + float fresnel = pow(1.0 - abs(dot(n, rd)), 4.0); + vec3 refl = reflect(rd, n); + vec3 refr = refract(rd, n, 0.7); + col = mix(background(refr) * 0.8, background(refl), fresnel); + col += vec3(1.0) * pow(max(dot(refl, normalize(vec3(1, 1, -1))), 0.0), 64.0) * 0.5; + } else { + col = background(rd) * 0.3; + } + fragColor = vec4(col, 1.0); +}""", ["glass", "sphere", "refraction", "3d", "raymarching", "crystal", "transparent", "elegant"], "3d", "Transparent glass sphere with refraction and reflection", +{"chaos_level": 0.2, "color_temperature": "neutral", "motion_type": "static"}) + +# ═══════════════════════════════════════════════════════════ +# MORE 2D VARIETY +# ═══════════════════════════════════════════════════════════ + +s("Cellular Automata Flow", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float t = floor(iTime * 4.0); + vec2 cell = floor(uv * 40.0); + float alive = step(0.5, hash(cell + t * 0.01)); + float neighbors = 0.0; + for (int x = -1; x <= 1; x++) + for (int y = -1; y <= 1; y++) { + if (x == 0 && y == 0) continue; + neighbors += step(0.5, hash(cell + vec2(float(x), float(y)) + t * 0.01)); + } + float next = step(2.5, neighbors) * step(neighbors, 3.5) * (1.0 - alive) + + step(1.5, neighbors) * step(neighbors, 3.5) * alive; + vec3 col = mix(vec3(0.05, 0.0, 0.1), vec3(0.0, 0.8, 0.4), next); + fragColor = vec4(col, 1.0); +}""", ["cellular-automata", "conway", "life", "grid", "simulation", "green", "digital"], "2d", "Game of Life style cellular automata with hash-based state", +{"chaos_level": 0.6, "color_temperature": "cool", "motion_type": "evolving"}) + +s("Blackhole Gravity Lens", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float r = length(uv); + float a = atan(uv.y, uv.x); + float warp = 0.3 / (r + 0.01); + vec2 warped = vec2(cos(a + warp), sin(a + warp)) * (r + 0.1 / r); + float stars = step(0.98, fract(sin(dot(floor(warped * 20.0), vec2(127.1, 311.7))) * 43758.5453)); + float disk = smoothstep(0.4, 0.3, abs(warped.y * 5.0 / (length(warped) + 0.1))) + * smoothstep(0.1, 0.2, r) * smoothstep(0.8, 0.4, r); + vec3 col = vec3(0.0); + col += vec3(0.7, 0.5, 0.3) * disk * (sin(warped.x * 30.0 + iTime * 5.0) * 0.3 + 0.7); + col += vec3(0.8, 0.9, 1.0) * stars * (1.0 - smoothstep(0.0, 0.15, r)); + col += vec3(0.8, 0.9, 1.0) * stars * smoothstep(0.3, 0.5, r); + float shadow = smoothstep(0.12, 0.1, r); + col *= 1.0 - shadow; + fragColor = vec4(col, 1.0); +}""", ["blackhole", "space", "gravity", "lens", "accretion", "cosmic", "sci-fi", "dark"], "2d", "Gravitational lensing around a black hole with accretion disk", +{"chaos_level": 0.5, "color_temperature": "warm", "motion_type": "rotating"}) + +s("Waveform Oscilloscope", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + uv.y = uv.y * 2.0 - 1.0; + float wave = sin(uv.x * 25.0 + iTime * 5.0) * 0.3; + wave += sin(uv.x * 13.0 - iTime * 3.0) * 0.15; + wave += sin(uv.x * 47.0 + iTime * 8.0) * 0.05; + float d = abs(uv.y - wave); + float line = 0.003 / d; + float glow = 0.01 / d; + vec3 col = vec3(0.0); + col += vec3(0.0, 1.0, 0.3) * line; + col += vec3(0.0, 0.4, 0.15) * glow; + float grid = step(0.99, fract(uv.x * 10.0)) + step(0.99, fract((uv.y * 0.5 + 0.5) * 5.0)); + col += vec3(0.03) * grid; + fragColor = vec4(col, 1.0); +}""", ["waveform", "oscilloscope", "audio", "signal", "green", "tech", "minimal", "retro"], "2d", "Animated oscilloscope display with composite waveform", +{"chaos_level": 0.3, "color_temperature": "cool", "motion_type": "oscillating"}) + +s("Sacred Geometry Flower", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float r = length(uv); + float a = atan(uv.y, uv.x); + float petals = abs(sin(a * 6.0 + iTime * 0.5)) * 0.3 + 0.2; + float flower = smoothstep(petals + 0.01, petals, r); + float inner = smoothstep(0.08, 0.06, r); + float rings = sin(r * 60.0 - iTime * 3.0) * 0.5 + 0.5; + rings *= smoothstep(0.5, 0.1, r); + vec3 col = vec3(0.02, 0.0, 0.04); + col += vec3(0.8, 0.4, 0.1) * flower * rings; + col += vec3(1.0, 0.8, 0.3) * inner; + col += vec3(0.3, 0.0, 0.5) * (1.0 - flower) * rings * 0.3; + fragColor = vec4(col, 1.0); +}""", ["sacred-geometry", "flower", "mandala", "spiritual", "gold", "symmetry", "organic"], "2d", "Sacred geometry flower of life with golden petal animation", +{"chaos_level": 0.2, "color_temperature": "warm", "motion_type": "breathing"}) + +s("Pixel Art Dither", """ +float dither(vec2 p, float v) { + mat4 bayer = mat4( + 0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0, + 12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0, + 3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0, + 15.0/16.0, 7.0/16.0, 13.0/16.0, 5.0/16.0 + ); + int x = int(mod(p.x, 4.0)); + int y = int(mod(p.y, 4.0)); + float threshold = bayer[y][x]; + return step(threshold, v); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + vec2 pixel = floor(fragCoord / 4.0); + float t = iTime * 0.5; + float wave = sin(uv.x * 5.0 + t) * sin(uv.y * 3.0 + t * 0.7) * 0.5 + 0.5; + float d = dither(pixel, wave); + vec3 dark = vec3(0.08, 0.04, 0.15); + vec3 light = vec3(0.3, 0.8, 0.6); + vec3 col = mix(dark, light, d); + fragColor = vec4(col, 1.0); +}""", ["pixel-art", "dither", "retro", "8bit", "bayer", "low-fi", "nostalgic"], "2d", "Bayer matrix ordered dithering with animated value field", +{"chaos_level": 0.3, "color_temperature": "cool", "motion_type": "pulsing"}) + +s("Reaction Diffusion", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +float noise(vec2 p) { + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float t = iTime * 0.1; + float n = 0.0; + float scale = 8.0; + n += noise(uv * scale + t); + n += noise(uv * scale * 2.0 + sin(t * 3.0)) * 0.5; + n += noise(uv * scale * 4.0 - t * 2.0) * 0.25; + n = sin(n * 8.0) * 0.5 + 0.5; + float spots = smoothstep(0.4, 0.6, n); + vec3 col = mix(vec3(0.9, 0.85, 0.7), vec3(0.15, 0.1, 0.05), spots); + fragColor = vec4(col, 1.0); +}""", ["reaction-diffusion", "organic", "pattern", "turing", "biological", "spots", "nature"], "2d", "Turing-style reaction diffusion patterns like animal markings", +{"chaos_level": 0.4, "color_temperature": "warm", "motion_type": "morphing"}) + +s("Tron Light Cycle Trail", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + vec3 col = vec3(0.02, 0.02, 0.05); + // Perspective grid floor + float gy = 0.5 / (1.0 - uv.y + 0.001); + float gx = (uv.x - 0.5) * gy * 2.0; + float gridX = smoothstep(0.02, 0.0, abs(fract(gx + iTime * 0.5) - 0.5)); + float gridZ = smoothstep(0.02, 0.0, abs(fract(gy * 0.5 - iTime * 2.0) - 0.5)); + float grid = max(gridX, gridZ) * step(0.5, uv.y) * (1.0 - uv.y) * 4.0; + col += vec3(0.0, 0.5, 1.0) * grid * 0.5; + // Light trail + float trail = smoothstep(0.01, 0.0, abs(uv.x - 0.5 - sin(iTime) * 0.2)); + trail *= step(0.5, uv.y); + col += vec3(0.0, 0.8, 1.0) * trail * 2.0; + fragColor = vec4(col, 1.0); +}""", ["tron", "grid", "synthwave", "cyberpunk", "blue", "neon", "retro-future", "perspective"], "2d", "Tron-style perspective grid with light cycle trail", +{"chaos_level": 0.2, "color_temperature": "cool", "motion_type": "forward"}) + +s("Watercolor Bleed", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +float noise(vec2 p) { + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +} +float fbm(vec2 p) { + float v = 0.0, a = 0.5; + mat2 rot = mat2(0.8, 0.6, -0.6, 0.8); + for (int i = 0; i < 6; i++) { v += a * noise(p); p = rot * p * 2.0; a *= 0.5; } + return v; +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float t = iTime * 0.05; + float f = fbm(uv * 4.0 + t); + vec3 col1 = vec3(0.9, 0.3, 0.2); + vec3 col2 = vec3(0.2, 0.5, 0.9); + vec3 col3 = vec3(0.1, 0.8, 0.4); + vec3 col = mix(col1, col2, smoothstep(0.3, 0.5, f)); + col = mix(col, col3, smoothstep(0.5, 0.7, f)); + col = mix(col, vec3(0.95, 0.92, 0.88), smoothstep(0.7, 0.9, f)); + // paper texture + float paper = noise(uv * 200.0) * 0.05 + 0.95; + col *= paper; + fragColor = vec4(col, 1.0); +}""", ["watercolor", "paint", "artistic", "soft", "pastel", "organic", "paper", "art"], "2d", "Watercolor paint bleeding on textured paper", +{"chaos_level": 0.3, "color_temperature": "warm", "motion_type": "fluid"}) + +# ═══════════════════════════════════════════════════════════ +# PROGRAMMATIC GENERATION — fill to 200+ +# We generate families of variations mathematically +# ═══════════════════════════════════════════════════════════ + +# --- Family: Sine field variations (20 shaders) --- +sine_colors = [ + ("Crimson Pulse", "vec3(0.9, 0.1, 0.15)", "vec3(0.3, 0.0, 0.05)", ["red", "pulse", "warm", "intense"]), + ("Ocean Depth", "vec3(0.0, 0.3, 0.9)", "vec3(0.0, 0.05, 0.15)", ["ocean", "blue", "deep", "calm"]), + ("Toxic Glow", "vec3(0.2, 1.0, 0.1)", "vec3(0.0, 0.1, 0.0)", ["toxic", "green", "radioactive", "glow"]), + ("Amber Warmth", "vec3(1.0, 0.6, 0.1)", "vec3(0.15, 0.05, 0.0)", ["amber", "warm", "golden", "honey"]), + ("Violet Dream", "vec3(0.6, 0.1, 0.9)", "vec3(0.1, 0.0, 0.15)", ["violet", "dream", "purple", "ethereal"]), + ("Ice Crystal", "vec3(0.7, 0.9, 1.0)", "vec3(0.05, 0.08, 0.12)", ["ice", "crystal", "frozen", "cold"]), + ("Coral Reef", "vec3(1.0, 0.4, 0.5)", "vec3(0.1, 0.02, 0.05)", ["coral", "reef", "pink", "organic"]), + ("Forest Canopy", "vec3(0.1, 0.6, 0.2)", "vec3(0.02, 0.08, 0.02)", ["forest", "green", "nature", "canopy"]), + ("Copper Wire", "vec3(0.9, 0.5, 0.2)", "vec3(0.1, 0.03, 0.0)", ["copper", "metal", "wire", "industrial"]), + ("Midnight Blue", "vec3(0.1, 0.15, 0.5)", "vec3(0.01, 0.01, 0.05)", ["midnight", "blue", "dark", "night"]), + ("Sunrise Ember", "vec3(1.0, 0.3, 0.0)", "vec3(0.2, 0.02, 0.0)", ["sunrise", "ember", "fire", "dawn"]), + ("Lavender Haze", "vec3(0.7, 0.5, 0.9)", "vec3(0.08, 0.05, 0.12)", ["lavender", "haze", "soft", "pastel"]), + ("Steel Grey", "vec3(0.5, 0.52, 0.55)", "vec3(0.05, 0.05, 0.06)", ["steel", "grey", "industrial", "monochrome"]), + ("Magma Core", "vec3(1.0, 0.2, 0.0)", "vec3(0.3, 0.0, 0.0)", ["magma", "lava", "volcanic", "hot"]), + ("Teal Abyss", "vec3(0.0, 0.7, 0.6)", "vec3(0.0, 0.08, 0.07)", ["teal", "abyss", "aqua", "deep"]), + ("Champagne", "vec3(0.95, 0.85, 0.6)", "vec3(0.12, 0.1, 0.05)", ["champagne", "gold", "luxury", "elegant"]), + ("Electric Lime", "vec3(0.6, 1.0, 0.0)", "vec3(0.05, 0.12, 0.0)", ["electric", "lime", "acid", "bright"]), + ("Blood Moon", "vec3(0.6, 0.05, 0.05)", "vec3(0.1, 0.0, 0.02)", ["blood", "moon", "dark-red", "ominous"]), + ("Arctic Wind", "vec3(0.85, 0.95, 1.0)", "vec3(0.08, 0.1, 0.15)", ["arctic", "wind", "white", "cold"]), + ("Rust Decay", "vec3(0.6, 0.3, 0.1)", "vec3(0.08, 0.03, 0.01)", ["rust", "decay", "brown", "aged"]), +] + +for i, (name, bright, dark, extra_tags) in enumerate(sine_colors): + freq_x = 5 + (i % 7) * 3 + freq_y = 7 + (i % 5) * 4 + speed = 0.5 + (i % 4) * 0.3 + complexity = ["sin", "cos", "sin+cos"][i % 3] + + code = f"""void mainImage(out vec4 fragColor, in vec2 fragCoord) {{ + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float t = iTime * {speed:.1f}; + float v = sin(uv.x * {freq_x:.1f} + t) * cos(uv.y * {freq_y:.1f} - t * 0.7); + v += sin(length(uv) * {freq_x + freq_y:.1f} + t * 1.3) * 0.5; + v = v * 0.5 + 0.5; + v = pow(v, 1.5); + vec3 col = mix({dark}, {bright}, v); + fragColor = vec4(col, 1.0); +}}""" + s(name, code, + ["sine-field", "mathematical", "animated"] + extra_tags, + "2d", f"Sine wave interference pattern in {name.lower()} palette", + {"chaos_level": 0.3 + (i % 5) * 0.1, "color_temperature": ["warm", "cool", "neutral"][i % 3], "motion_type": "pulsing"}) + +# --- Family: Rotating geometry (15 shaders) --- +geometries = [ + ("Spinning Cube Wireframe", 4, "vec3(0.0, 0.8, 1.0)", ["cube", "wireframe", "3d-illusion", "blue"]), + ("Pentagonal Vortex", 5, "vec3(0.9, 0.3, 0.8)", ["pentagon", "vortex", "pink"]), + ("Triangular Recursion", 3, "vec3(1.0, 0.8, 0.0)", ["triangle", "recursive", "yellow"]), + ("Octagonal Mandala", 8, "vec3(0.3, 0.9, 0.5)", ["octagon", "mandala", "green"]), + ("Star Field Rotation", 5, "vec3(0.9, 0.9, 1.0)", ["star", "rotation", "white"]), + ("Decagonal Wave", 10, "vec3(0.8, 0.4, 0.0)", ["decagon", "wave", "orange"]), + ("Heptagonal Bloom", 7, "vec3(0.5, 0.0, 0.9)", ["heptagon", "bloom", "purple"]), + ("Square Spiral", 4, "vec3(1.0, 0.2, 0.3)", ["square", "spiral", "red"]), + ("Hexagonal Pulse", 6, "vec3(0.0, 0.9, 0.7)", ["hexagon", "pulse", "teal"]), + ("Nonagonal Flow", 9, "vec3(0.9, 0.7, 0.3)", ["nonagon", "flow", "gold"]), + ("Diamond Lattice", 4, "vec3(0.6, 0.8, 1.0)", ["diamond", "lattice", "crystal"]), + ("Trigon Collapse", 3, "vec3(1.0, 0.0, 0.5)", ["triangle", "collapse", "magenta"]), + ("Polygon Storm", 12, "vec3(0.4, 0.4, 0.9)", ["polygon", "storm", "blue"]), + ("Angular Meditation", 6, "vec3(0.9, 0.85, 0.7)", ["angular", "meditation", "calm"]), + ("Vertex Dance", 8, "vec3(0.0, 1.0, 0.4)", ["vertex", "dance", "neon-green"]), +] + +for i, (name, sides, color, extra_tags) in enumerate(geometries): + speed = 0.5 + (i % 3) * 0.3 + layers = 3 + i % 4 + + code = f"""void mainImage(out vec4 fragColor, in vec2 fragCoord) {{ + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float t = iTime * {speed:.1f}; + vec3 col = vec3(0.02); + for (int layer = 0; layer < {layers}; layer++) {{ + float fl = float(layer); + float scale = 1.0 + fl * 0.5; + float rot = t + fl * 0.5; + vec2 p = uv * scale; + p = vec2(p.x * cos(rot) - p.y * sin(rot), p.x * sin(rot) + p.y * cos(rot)); + float a = atan(p.y, p.x); + float r = length(p); + float sides = {float(sides):.1f}; + float polygon = cos(3.14159 / sides) / cos(mod(a + 3.14159 / sides, 2.0 * 3.14159 / sides) - 3.14159 / sides); + float edge = abs(r - polygon * (0.3 + fl * 0.1)); + float line = 0.003 / (edge + 0.003); + col += {color} * line * (0.5 + 0.5 / (1.0 + fl)); + }} + fragColor = vec4(col, 1.0); +}}""" + s(name, code, + ["geometric", "rotating", "polygon", "animated", "line-art"] + extra_tags, + "2d", f"Layered rotating {sides}-sided polygon wireframes", + {"chaos_level": 0.3 + (i % 4) * 0.1, "color_temperature": ["cool", "warm", "neutral"][i % 3], "motion_type": "rotating"}) + +# --- Family: Noise landscape variations (15 shaders) --- +landscapes = [ + ("Desert Dunes", "vec3(0.9, 0.7, 0.4)", "vec3(0.4, 0.6, 0.9)", 0.3, ["desert", "dunes", "sand", "warm"]), + ("Frozen Tundra", "vec3(0.85, 0.9, 0.95)", "vec3(0.3, 0.4, 0.5)", 0.15, ["frozen", "tundra", "ice", "cold"]), + ("Volcanic Plains", "vec3(0.3, 0.1, 0.05)", "vec3(0.1, 0.0, 0.0)", 0.8, ["volcanic", "lava", "dark", "ominous"]), + ("Rolling Hills", "vec3(0.2, 0.5, 0.15)", "vec3(0.5, 0.7, 0.9)", 0.4, ["hills", "green", "pastoral", "peaceful"]), + ("Alien Terrain", "vec3(0.4, 0.0, 0.5)", "vec3(0.1, 0.8, 0.3)", 0.6, ["alien", "sci-fi", "surreal", "otherworldly"]), + ("Oceanic Floor", "vec3(0.05, 0.15, 0.25)", "vec3(0.0, 0.3, 0.5)", 0.35, ["ocean-floor", "underwater", "deep", "blue"]), + ("Mars Surface", "vec3(0.7, 0.3, 0.1)", "vec3(0.3, 0.15, 0.1)", 0.5, ["mars", "planet", "red", "space"]), + ("Crystal Cavern", "vec3(0.3, 0.5, 0.7)", "vec3(0.1, 0.0, 0.2)", 0.55, ["crystal", "cavern", "underground", "sparkle"]), + ("Cloud Tops", "vec3(0.95, 0.95, 1.0)", "vec3(0.3, 0.5, 0.8)", 0.2, ["clouds", "sky", "white", "fluffy"]), + ("Moss Garden", "vec3(0.15, 0.35, 0.1)", "vec3(0.3, 0.4, 0.3)", 0.25, ["moss", "garden", "zen", "green"]), + ("Rust Belt", "vec3(0.5, 0.25, 0.1)", "vec3(0.2, 0.15, 0.1)", 0.45, ["rust", "industrial", "decay", "brown"]), + ("Nebula Gas", "vec3(0.6, 0.2, 0.8)", "vec3(0.05, 0.0, 0.1)", 0.7, ["nebula", "gas", "space", "purple"]), + ("Coral Depths", "vec3(0.9, 0.4, 0.3)", "vec3(0.0, 0.1, 0.2)", 0.4, ["coral", "ocean", "underwater", "warm"]), + ("Storm Front", "vec3(0.2, 0.22, 0.25)", "vec3(0.05, 0.05, 0.08)", 0.65, ["storm", "dark", "weather", "dramatic"]), + ("Golden Hour", "vec3(1.0, 0.75, 0.4)", "vec3(0.3, 0.2, 0.4)", 0.3, ["golden-hour", "warm", "photography", "sunset"]), +] + +for i, (name, ground, sky, roughness, extra_tags) in enumerate(landscapes): + octaves = 5 + i % 3 + speed = 0.1 + (i % 4) * 0.1 + code = f"""float hash(vec2 p) {{ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }} +float noise(vec2 p) {{ + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +}} +void mainImage(out vec4 fragColor, in vec2 fragCoord) {{ + vec2 uv = fragCoord / iResolution.xy; + float t = iTime * {speed:.2f}; + vec2 p = uv * 5.0 + vec2(t, 0.0); + float h = 0.0, a = 0.5; + for (int i = 0; i < {octaves}; i++) {{ h += a * noise(p); p *= 2.0; a *= 0.5; }} + float terrain = smoothstep(0.3, 0.7, h + (uv.y - 0.5) * {roughness + 0.5:.1f}); + vec3 col = mix({ground}, {sky}, terrain); + float fog = smoothstep(0.0, 1.0, uv.y); + col = mix(col, {sky} * 0.7, fog * 0.3); + fragColor = vec4(col, 1.0); +}}""" + s(name, code, + ["landscape", "terrain", "noise", "procedural"] + extra_tags, + "2d", f"Procedural {name.lower()} landscape with {octaves}-octave noise", + {"chaos_level": roughness, "color_temperature": ["warm", "cool", "neutral"][i % 3], "motion_type": "scrolling"}) + +# --- Family: 3D raymarched primitives (15 shaders) --- +rm_shapes = [ + ("Warped Cube", "max(max(abs(p.x),abs(p.y)),abs(p.z))-0.8+sin(p.x*5.0+iTime)*0.1", "vec3(0.9,0.3,0.1)", ["cube", "warped", "organic"]), + ("Spinning Top", "length(vec2(length(p.xz)-0.6,p.y))-0.2", "vec3(0.1,0.7,0.9)", ["torus", "spinning", "blue"]), + ("Infinite Pillars", "length(mod(p.xz+1.0,2.0)-1.0)-0.3", "vec3(0.8,0.8,0.85)", ["pillars", "infinite", "architectural"]), + ("Twisted Column", "length(vec2(length(p.xz*mat2(cos(p.y),sin(p.y),-sin(p.y),cos(p.y)))-0.5,0.0))-0.15", "vec3(0.6,0.2,0.8)", ["twisted", "column", "purple"]), + ("Gyroid Surface", "sin(p.x)*cos(p.y)+sin(p.y)*cos(p.z)+sin(p.z)*cos(p.x)", "vec3(0.2,0.8,0.4)", ["gyroid", "minimal-surface", "mathematical"]), + ("Sierpinski Tetra", "max(max(-p.x-p.y-p.z,p.x+p.y-p.z),max(-p.x+p.y+p.z,p.x-p.y+p.z))-1.0", "vec3(1.0,0.8,0.2)", ["sierpinski", "fractal", "gold"]), + ("Rounded Box", "length(max(abs(p)-vec3(0.6,0.4,0.3),0.0))-0.1", "vec3(0.3,0.5,0.9)", ["box", "rounded", "smooth"]), + ("Egg Shape", "length(p*vec3(1.0,1.3,1.0))-0.8", "vec3(0.95,0.9,0.8)", ["egg", "organic", "smooth", "minimal"]), + ("Capped Cylinder", "max(length(p.xz)-0.4,abs(p.y)-1.0)", "vec3(0.7,0.3,0.3)", ["cylinder", "geometric", "red"]), + ("Octahedron", "(abs(p.x)+abs(p.y)+abs(p.z)-1.0)*0.577", "vec3(0.4,0.9,0.7)", ["octahedron", "platonic", "teal"]), + ("Capsule Link", "length(p-vec3(0,clamp(p.y,-0.5,0.5),0))-0.3", "vec3(0.9,0.5,0.2)", ["capsule", "simple", "orange"]), + ("Cross Shape", "min(min(length(p.xy)-0.2,length(p.yz)-0.2),length(p.xz)-0.2)", "vec3(0.8,0.1,0.1)", ["cross", "intersection", "red"]), + ("Pulsing Heart", "pow(p.x*p.x+0.9*p.y*p.y+p.z*p.z-1.0,3.0)-p.x*p.x*p.y*p.y*p.y-0.1*p.z*p.z*p.y*p.y*p.y", "vec3(0.9,0.15,0.2)", ["heart", "love", "romantic"]), + ("Klein Bottle", "length(vec2(length(p.xy)-1.0,p.z))-0.3", "vec3(0.5,0.7,1.0)", ["klein-bottle", "topology", "mathematical"]), + ("Menger Sponge", "max(max(abs(p.x),abs(p.y)),abs(p.z))-1.0", "vec3(0.6,0.6,0.65)", ["menger", "sponge", "fractal", "recursive"]), +] + +for i, (name, sdf, color, extra_tags) in enumerate(rm_shapes): + rot_speed = 0.3 + (i % 4) * 0.2 + code = f"""float map(vec3 p) {{ + float a = iTime * {rot_speed:.1f}; + p.xz *= mat2(cos(a), sin(a), -sin(a), cos(a)); + p.xy *= mat2(cos(a*0.7), sin(a*0.7), -sin(a*0.7), cos(a*0.7)); + return {sdf}; +}} +vec3 getNormal(vec3 p) {{ + vec2 e = vec2(0.001, 0); + return normalize(vec3(map(p+e.xyy)-map(p-e.xyy),map(p+e.yxy)-map(p-e.yxy),map(p+e.yyx)-map(p-e.yyx))); +}} +void mainImage(out vec4 fragColor, in vec2 fragCoord) {{ + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec3 ro = vec3(0, 0, -3.5); + vec3 rd = normalize(vec3(uv, 1.0)); + float t = 0.0; + vec3 col = vec3(0.02, 0.01, 0.04); + for (int i = 0; i < 80; i++) {{ + float d = map(ro + rd * t); + if (abs(d) < 0.001) {{ + vec3 p = ro + rd * t; + vec3 n = getNormal(p); + vec3 light = normalize(vec3(1, 1, -1)); + float diff = max(dot(n, light), 0.0); + float spec = pow(max(dot(reflect(-light, n), -rd), 0.0), 16.0); + col = {color} * (0.15 + 0.85 * diff) + vec3(1.0) * spec * 0.3; + break; + }} + t += abs(d) * 0.8; + if (t > 20.0) break; + }} + fragColor = vec4(col, 1.0); +}}""" + s(name, code, + ["3d", "raymarching", "sdf", "animated"] + extra_tags, + "3d", f"Raymarched {name.lower()} with dual-axis rotation and specular lighting", + {"chaos_level": 0.3 + (i % 5) * 0.1, "color_temperature": ["cool", "warm", "neutral"][i % 3], "motion_type": "rotating"}) + +# --- Family: Abstract energy fields (15 shaders) --- +energies = [ + ("Plasma Core", 8.0, 3.0, "vec3(1.0,0.3,0.1)", "vec3(0.3,0.1,0.8)", ["plasma", "core", "energy"]), + ("Neural Network", 15.0, 5.0, "vec3(0.0,0.8,0.9)", "vec3(0.0,0.2,0.3)", ["neural", "network", "connections"]), + ("Magnetic Field", 6.0, 2.0, "vec3(0.2,0.5,1.0)", "vec3(0.8,0.2,0.1)", ["magnetic", "field", "physics"]), + ("Solar Flare", 4.0, 6.0, "vec3(1.0,0.7,0.0)", "vec3(1.0,0.1,0.0)", ["solar", "flare", "sun"]), + ("Quantum Foam", 20.0, 8.0, "vec3(0.5,0.8,1.0)", "vec3(0.1,0.0,0.2)", ["quantum", "foam", "microscopic"]), + ("Nebula Birth", 3.0, 1.5, "vec3(0.8,0.3,0.9)", "vec3(0.1,0.5,0.8)", ["nebula", "birth", "cosmic"]), + ("Lava Flow", 5.0, 2.0, "vec3(1.0,0.4,0.0)", "vec3(0.3,0.0,0.0)", ["lava", "flow", "volcanic"]), + ("Energy Shield", 10.0, 4.0, "vec3(0.0,0.9,0.5)", "vec3(0.0,0.2,0.1)", ["shield", "force-field", "sci-fi"]), + ("Radioactive Decay", 12.0, 7.0, "vec3(0.3,1.0,0.0)", "vec3(0.0,0.15,0.0)", ["radioactive", "decay", "toxic"]), + ("Dark Matter", 7.0, 3.0, "vec3(0.15,0.1,0.2)", "vec3(0.4,0.3,0.5)", ["dark-matter", "mysterious", "space"]), + ("Bioluminescent", 9.0, 4.0, "vec3(0.0,0.7,0.9)", "vec3(0.0,0.3,0.1)", ["bioluminescent", "ocean", "glow"]), + ("Cosmic Web", 6.0, 5.0, "vec3(0.6,0.7,1.0)", "vec3(0.05,0.0,0.1)", ["cosmic", "web", "universe"]), + ("Aurora Pulse", 8.0, 3.0, "vec3(0.1,0.9,0.4)", "vec3(0.4,0.1,0.8)", ["aurora", "pulse", "atmospheric"]), + ("Plasma Tornado", 5.0, 6.0, "vec3(0.9,0.5,1.0)", "vec3(0.2,0.0,0.3)", ["tornado", "vortex", "spinning"]), + ("Star Forge", 4.0, 4.0, "vec3(1.0,0.9,0.5)", "vec3(0.5,0.1,0.0)", ["star", "forge", "creation"]), +] + +for i, (name, freq, speed, col1, col2, extra_tags) in enumerate(energies): + code = f"""float hash(vec2 p) {{ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }} +float noise(vec2 p) {{ + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +}} +void mainImage(out vec4 fragColor, in vec2 fragCoord) {{ + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float t = iTime * {speed / 3.0:.2f}; + float r = length(uv); + float a = atan(uv.y, uv.x); + float n = noise(vec2(a * {freq:.1f} / 6.283, r * {freq:.1f}) + t); + n += noise(vec2(a * {freq * 2:.1f} / 6.283, r * {freq * 2:.1f}) - t * 1.3) * 0.5; + n = n / 1.5; + float energy = pow(n, 2.0) * exp(-r * 2.0); + vec3 col = mix({col2}, {col1}, energy * 2.0); + col += {col1} * 0.01 / (r + 0.01) * n; + fragColor = vec4(col, 1.0); +}}""" + s(name, code, + ["energy", "abstract", "animated", "glow"] + extra_tags, + "2d", f"Swirling energy field: {name.lower()}", + {"chaos_level": 0.4 + (i % 5) * 0.1, "color_temperature": ["warm", "cool", "neutral"][i % 3], "motion_type": "swirling"}) + +# --- Family: Fractal Explorations (15 shaders) --- +fractals = [ + ("Julia Set Amethyst", -0.7269, 0.1889, "vec3(0.5,0.2,0.8)", ["julia", "amethyst", "fractal"]), + ("Julia Set Coral", -0.8, 0.156, "vec3(0.9,0.4,0.3)", ["julia", "coral", "fractal"]), + ("Julia Set Ice", -0.4, 0.6, "vec3(0.5,0.8,1.0)", ["julia", "ice", "fractal"]), + ("Julia Set Fire", -0.12, 0.74, "vec3(1.0,0.4,0.0)", ["julia", "fire", "fractal"]), + ("Julia Set Forest", 0.285, 0.01, "vec3(0.2,0.7,0.3)", ["julia", "forest", "fractal"]), + ("Julia Set Electric", -0.74543, 0.11301, "vec3(0.0,0.6,1.0)", ["julia", "electric", "fractal"]), + ("Julia Set Midnight", -0.75, 0.11, "vec3(0.15,0.1,0.3)", ["julia", "midnight", "fractal"]), + ("Julia Set Gold", -0.1, 0.65, "vec3(0.9,0.7,0.2)", ["julia", "gold", "fractal"]), + ("Julia Set Rose", -0.7, 0.27015, "vec3(0.9,0.3,0.5)", ["julia", "rose", "fractal"]), + ("Julia Set Nebula", 0.355, 0.355, "vec3(0.6,0.3,0.9)", ["julia", "nebula", "fractal"]), + ("Julia Set Ocean", -0.75, 0.0, "vec3(0.1,0.5,0.8)", ["julia", "ocean", "fractal"]), + ("Julia Set Ember", -0.77, 0.22, "vec3(0.8,0.2,0.05)", ["julia", "ember", "fractal"]), + ("Julia Set Toxic", -0.8, 0.0, "vec3(0.3,0.9,0.1)", ["julia", "toxic", "fractal"]), + ("Julia Set Void", 0.0, 0.65, "vec3(0.3,0.3,0.35)", ["julia", "void", "fractal"]), + ("Julia Set Prism", -0.7, 0.3, "vec3(0.7,0.5,1.0)", ["julia", "prism", "fractal"]), +] + +for i, (name, cx, cy, color, extra_tags) in enumerate(fractals): + code = f"""void mainImage(out vec4 fragColor, in vec2 fragCoord) {{ + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + uv *= 2.5; + vec2 c = vec2({cx}, {cy}) + vec2(sin(iTime * 0.1) * 0.02, cos(iTime * 0.13) * 0.02); + vec2 z = uv; + int iter = 0; + for (int i = 0; i < 200; i++) {{ + z = vec2(z.x*z.x - z.y*z.y, 2.0*z.x*z.y) + c; + if (dot(z, z) > 4.0) break; + iter = i; + }} + float t = float(iter) / 200.0; + vec3 col = {color} * (1.0 - pow(t, 0.5)); + col += 0.5 + 0.5 * cos(t * 12.0 + iTime + vec3(0, 2, 4)) * (1.0 - t); + col *= 1.0 - step(199.0, float(iter)); + fragColor = vec4(col, 1.0); +}}""" + s(name, code, + ["fractal", "julia-set", "mathematical", "complex", "colorful"] + extra_tags, + "2d", f"Julia set with c=({cx}, {cy}), slowly drifting", + {"chaos_level": 0.5 + (i % 4) * 0.1, "color_temperature": ["warm", "cool", "neutral"][i % 3], "motion_type": "morphing"}) + +# --- Family: Pattern/texture generators (15 shaders) --- +patterns = [ + ("Checkerboard Warp", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float warp = sin(length(uv) * 10.0 - iTime * 2.0) * 0.1; + uv += uv * warp; + vec2 grid = floor(uv * 8.0); + float check = mod(grid.x + grid.y, 2.0); + vec3 col = mix(vec3(0.1), vec3(0.9), check); + fragColor = vec4(col, 1.0); +}""", ["checkerboard", "warp", "distortion", "bw", "optical"]), + ("Concentric Rings", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float r = length(uv); + float rings = sin(r * 30.0 - iTime * 3.0) * 0.5 + 0.5; + vec3 col = mix(vec3(0.05, 0.0, 0.1), vec3(0.8, 0.3, 0.9), rings); + col *= smoothstep(1.5, 0.0, r); + fragColor = vec4(col, 1.0); +}""", ["concentric", "rings", "purple", "hypnotic", "simple"]), + ("Herringbone", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + vec2 g = uv * vec2(20.0, 40.0); + float row = floor(g.y); + g.x += mod(row, 2.0) * 0.5; + vec2 f = fract(g); + float brick = step(0.05, f.x) * step(0.05, f.y); + float t = sin(iTime + row * 0.3) * 0.5 + 0.5; + vec3 col = mix(vec3(0.6, 0.3, 0.15), vec3(0.7, 0.4, 0.2), t) * brick; + col += vec3(0.3, 0.15, 0.05) * (1.0 - brick); + fragColor = vec4(col, 1.0); +}""", ["herringbone", "brick", "pattern", "architecture", "warm"]), + ("Dots Matrix", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + vec2 grid = fract(uv * 20.0) - 0.5; + vec2 id = floor(uv * 20.0); + float phase = sin(iTime * 2.0 + id.x * 0.5 + id.y * 0.7); + float radius = 0.2 + 0.15 * phase; + float d = smoothstep(radius, radius - 0.02, length(grid)); + vec3 col = vec3(0.0, 0.5, 0.8) * d; + fragColor = vec4(col, 1.0); +}""", ["dots", "matrix", "halftone", "blue", "pulsing"]), + ("Weave Pattern", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + vec2 g = uv * 15.0; + float weft = step(0.3, fract(g.x)) * step(fract(g.x), 0.7); + float warp = step(0.3, fract(g.y)) * step(fract(g.y), 0.7); + float over = step(0.5, fract(floor(g.x) * 0.5 + floor(g.y) * 0.5)); + float thread = mix(weft, warp, over); + vec3 col1 = vec3(0.2, 0.3, 0.6); + vec3 col2 = vec3(0.7, 0.5, 0.2); + vec3 col = mix(col1, col2, thread); + col *= 0.7 + 0.3 * sin(iTime + uv.x * 10.0); + fragColor = vec4(col, 1.0); +}""", ["weave", "textile", "fabric", "pattern", "craft"]), + ("Sierpinski Triangle", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + uv.y = 1.0 - uv.y; + float t = mod(iTime * 0.3, 1.0); + float zoom = pow(2.0, t * 3.0); + uv = uv * zoom - vec2(zoom * 0.5 - 0.5); + float v = 1.0; + for (int i = 0; i < 20; i++) { + if (uv.x + uv.y > 1.0) { v = 0.0; break; } + uv *= 2.0; + if (uv.x > 1.0) uv.x -= 1.0; + if (uv.y > 1.0) uv.y -= 1.0; + } + vec3 col = vec3(v) * vec3(0.3, 0.6, 0.9); + fragColor = vec4(col, 1.0); +}""", ["sierpinski", "triangle", "fractal", "self-similar", "zoom"]), + ("Perlin Contours", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +float noise(vec2 p) { + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float n = noise(uv * 6.0 + iTime * 0.2) * 0.5 + + noise(uv * 12.0 - iTime * 0.15) * 0.25 + + noise(uv * 24.0 + iTime * 0.1) * 0.125; + float contour = abs(fract(n * 10.0) - 0.5) * 2.0; + contour = smoothstep(0.0, 0.1, contour); + vec3 col = mix(vec3(0.9, 0.85, 0.75), vec3(0.2, 0.15, 0.1), 1.0 - contour); + fragColor = vec4(col, 1.0); +}""", ["contour", "topographic", "map", "noise", "minimal", "clean"]), + ("Stained Glass", """ +vec2 hash2(vec2 p) { + p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3))); + return fract(sin(p) * 43758.5453); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.y; + vec2 n = floor(uv * 5.0); + vec2 f = fract(uv * 5.0); + float md = 8.0; + vec2 id; + for (int j = -1; j <= 1; j++) + for (int i = -1; i <= 1; i++) { + vec2 g = vec2(float(i), float(j)); + vec2 o = hash2(n + g); + o = 0.5 + 0.45 * sin(iTime * 0.5 + 6.283 * o); + vec2 r = g + o - f; + float d = dot(r, r); + if (d < md) { md = d; id = n + g; } + } + float h = fract(sin(dot(id, vec2(127.1, 311.7))) * 43758.5453); + vec3 col = 0.5 + 0.5 * cos(h * 6.283 + vec3(0, 2, 4)); + col *= 0.7 + 0.3 * smoothstep(0.0, 0.05, md); + float edge = smoothstep(0.01, 0.02, md); + col *= 0.2 + 0.8 * edge; + fragColor = vec4(col, 1.0); +}""", ["stained-glass", "voronoi", "colorful", "mosaic", "church", "art"]), + ("Zen Sand Garden", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float r = length(uv); + float a = atan(uv.y, uv.x); + float rake = sin(r * 40.0 + a * 0.5 - iTime * 0.3); + float stone = smoothstep(0.15, 0.14, length(uv - vec2(0.3, 0.1))); + stone += smoothstep(0.08, 0.07, length(uv + vec2(0.2, 0.15))); + rake *= (1.0 - stone); + vec3 sand = vec3(0.85, 0.78, 0.65); + vec3 col = sand + vec3(0.05) * rake; + col = mix(col, vec3(0.3, 0.3, 0.28), stone); + fragColor = vec4(col, 1.0); +}""", ["zen", "sand", "garden", "minimal", "japanese", "calm", "meditation"]), + ("Wave Interference 2D", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float v = 0.0; + vec2 sources[4]; + sources[0] = vec2(sin(iTime), cos(iTime)) * 0.4; + sources[1] = vec2(-sin(iTime * 0.7), sin(iTime * 0.5)) * 0.5; + sources[2] = vec2(cos(iTime * 0.3), -sin(iTime * 0.8)) * 0.3; + sources[3] = vec2(-cos(iTime * 0.6), cos(iTime * 0.4)) * 0.35; + for (int i = 0; i < 4; i++) { + float d = length(uv - sources[i]); + v += sin(d * 30.0 - iTime * 5.0) / (1.0 + d * 5.0); + } + vec3 col = 0.5 + 0.5 * cos(v * 3.0 + vec3(0, 2, 4)); + fragColor = vec4(col, 1.0); +}""", ["wave", "interference", "physics", "ripple", "colorful", "multi-source"]), + ("DNA Helix", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + uv.y = uv.y * 2.0 - 1.0; + float t = iTime * 2.0; + float x = uv.x * 10.0 + t; + float strand1 = sin(x) * 0.3; + float strand2 = -sin(x) * 0.3; + float d1 = abs(uv.y - strand1); + float d2 = abs(uv.y - strand2); + float line1 = 0.005 / (d1 + 0.005); + float line2 = 0.005 / (d2 + 0.005); + float rung = 0.0; + float rungX = fract(x / 3.14159 * 0.5); + if (rungX < 0.05 || rungX > 0.95) { + float dy = uv.y; + if (dy > min(strand1, strand2) && dy < max(strand1, strand2)) + rung = 0.5; + } + vec3 col = vec3(0.02, 0.0, 0.05); + col += vec3(0.0, 0.6, 1.0) * line1; + col += vec3(1.0, 0.3, 0.5) * line2; + col += vec3(0.3, 0.8, 0.4) * rung; + fragColor = vec4(col, 1.0); +}""", ["dna", "helix", "biology", "science", "double-helix", "blue-pink"]), + ("Pendulum Wave", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + vec3 col = vec3(0.02, 0.01, 0.04); + for (int i = 0; i < 20; i++) { + float fi = float(i); + float freq = 10.0 + fi * 0.5; + float phase = sin(iTime * freq * 0.1) * 0.3; + float x = (fi + 0.5) / 20.0; + float y = 0.5 + phase; + float d = length(vec2(uv.x - x, uv.y - y) * vec2(1.0, 2.0)); + col += vec3(0.5 + 0.5 * cos(fi * 0.3 + vec3(0, 2, 4))) * 0.005 / (d + 0.005); + float rod = smoothstep(0.003, 0.0, abs(uv.x - x)) * step(y, uv.y) * step(uv.y, 0.95); + col += vec3(0.15) * rod; + } + fragColor = vec4(col, 1.0); +}""", ["pendulum", "wave", "physics", "harmonic", "colorful", "simulation"]), + ("ASCII Art Rain", """ +float char(int n, vec2 p) { + p = floor(p * vec2(4.0, -4.0) + 2.5); + if (clamp(p.x, 0.0, 4.0) == p.x && clamp(p.y, 0.0, 4.0) == p.y) { + int bit = int(p.x) + int(p.y) * 5; + if (((n >> bit) & 1) == 1) return 1.0; + } + return 0.0; +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + vec2 grid = floor(uv * vec2(40.0, 25.0)); + float t = floor(iTime * 10.0); + float drop = fract(sin(dot(grid.x + t * 0.01, 78.233)) * 43758.5) * 25.0; + float fade = smoothstep(drop, drop - 8.0, grid.y); + float bright = step(abs(grid.y - drop), 0.5); + vec3 col = vec3(0.0, 0.4, 0.15) * fade + vec3(0.0, 0.9, 0.4) * bright; + fragColor = vec4(col, 1.0); +}""", ["ascii", "rain", "text", "matrix", "digital", "green", "code"]), + ("Smoke Wisps", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +float noise(vec2 p) { + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float t = iTime * 0.3; + vec2 p = uv * 3.0; + p.y += t; + float n = noise(p) * 0.5 + noise(p * 2.0 + t) * 0.25 + noise(p * 4.0 - t * 0.5) * 0.125; + float wisp = pow(n, 3.0) * smoothstep(0.0, 0.5, uv.y) * smoothstep(1.0, 0.5, uv.y); + wisp *= smoothstep(0.0, 0.3, uv.x) * smoothstep(1.0, 0.7, uv.x); + vec3 col = vec3(wisp * 0.6); + col += vec3(0.02, 0.01, 0.03); + fragColor = vec4(col, 1.0); +}""", ["smoke", "wisps", "atmospheric", "grey", "subtle", "elegant", "minimal"]), +] + +for name, code, extra_tags in patterns: + s(name, code, + ["pattern", "2d"] + extra_tags, + "2d", f"{name} — procedural pattern", + {"chaos_level": 0.3, "color_temperature": "neutral", "motion_type": "animated"}) + +# --- Family: Domain warping (12 shaders) --- +warp_names = [ + ("Acid Trip", 0.9, "vec3(1.0,0.2,0.8)", "vec3(0.1,0.9,0.3)", ["acid", "psychedelic", "trippy", "wild"]), + ("Deep Dream", 0.7, "vec3(0.3,0.1,0.6)", "vec3(0.8,0.6,0.2)", ["dream", "deep", "surreal", "abstract"]), + ("Oil Slick", 0.4, "vec3(0.2,0.5,0.8)", "vec3(0.8,0.3,0.5)", ["oil", "iridescent", "rainbow", "surface"]), + ("Lava Rift", 0.8, "vec3(1.0,0.3,0.0)", "vec3(0.1,0.0,0.0)", ["lava", "rift", "crack", "hot"]), + ("Ghost Veil", 0.3, "vec3(0.7,0.7,0.8)", "vec3(0.1,0.1,0.15)", ["ghost", "veil", "ethereal", "pale"]), + ("Coral Growth", 0.5, "vec3(0.9,0.4,0.5)", "vec3(0.1,0.3,0.4)", ["coral", "growth", "organic", "living"]), + ("Storm Eye", 0.85, "vec3(0.3,0.4,0.6)", "vec3(0.05,0.05,0.1)", ["storm", "eye", "cyclone", "dramatic"]), + ("Crystal Melt", 0.6, "vec3(0.6,0.8,1.0)", "vec3(0.2,0.1,0.3)", ["crystal", "melt", "dissolve", "transformation"]), + ("Plasma Web", 0.75, "vec3(0.0,1.0,0.8)", "vec3(0.3,0.0,0.5)", ["plasma", "web", "interconnected", "energy"]), + ("Sand Ripple", 0.2, "vec3(0.9,0.8,0.6)", "vec3(0.6,0.5,0.3)", ["sand", "ripple", "desert", "zen"]), + ("Cosmic Dust", 0.65, "vec3(0.5,0.4,0.8)", "vec3(0.05,0.02,0.1)", ["cosmic", "dust", "space", "purple"]), + ("Magma Pool", 0.9, "vec3(1.0,0.5,0.0)", "vec3(0.2,0.0,0.0)", ["magma", "pool", "volcanic", "orange"]), +] + +for i, (name, chaos, c1, c2, extra_tags) in enumerate(warp_names): + warp_freq = 2.0 + (i % 5) + warp_amp = 0.5 + chaos * 0.5 + code = f"""float hash(vec2 p) {{ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }} +float noise(vec2 p) {{ + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), + mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); +}} +float fbm(vec2 p) {{ + float v = 0.0, a = 0.5; + for (int i = 0; i < 6; i++) {{ v += a * noise(p); p *= 2.0; a *= 0.5; }} + return v; +}} +void mainImage(out vec4 fragColor, in vec2 fragCoord) {{ + vec2 uv = fragCoord / iResolution.xy; + float t = iTime * 0.2; + vec2 p = uv * {warp_freq:.1f}; + float warp1 = fbm(p + t); + float warp2 = fbm(p + warp1 * {warp_amp:.1f} + t * 0.7); + float warp3 = fbm(p + warp2 * {warp_amp:.1f} - t * 0.3); + vec3 col = mix({c2}, {c1}, warp3); + col = pow(col, vec3(0.9)); + fragColor = vec4(col, 1.0); +}}""" + s(name, code, + ["domain-warp", "fbm", "fluid", "animated"] + extra_tags, + "2d", f"Triple-layer domain warping: {name.lower()}", + {"chaos_level": chaos, "color_temperature": ["warm", "cool", "neutral"][i % 3], "motion_type": "fluid"}) + +# --- Family: Raymarched scenes (10 shaders) --- +scenes_3d = [ + ("Floating Islands", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +float noise(vec2 p) { + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i+vec2(1,0)), f.x), mix(hash(i+vec2(0,1)), hash(i+vec2(1,1)), f.x), f.y); +} +float map(vec3 p) { + float ground = p.y + 1.0; + float island1 = length(p - vec3(0, sin(iTime)*0.3, 0)) - 1.0; + float island2 = length(p - vec3(2.5, cos(iTime*0.7)*0.4+0.5, 1.0)) - 0.6; + float island3 = length(p - vec3(-2.0, sin(iTime*0.5)*0.2+0.3, -0.5)) - 0.8; + return min(min(island1, island2), island3); +} +vec3 getNormal(vec3 p) { + vec2 e = vec2(0.001, 0); + return normalize(vec3(map(p+e.xyy)-map(p-e.xyy),map(p+e.yxy)-map(p-e.yxy),map(p+e.yyx)-map(p-e.yyx))); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec3 ro = vec3(0, 0, -5); + vec3 rd = normalize(vec3(uv, 1.0)); + float t = 0.0; + vec3 col = mix(vec3(0.3, 0.5, 0.9), vec3(0.7, 0.8, 1.0), uv.y + 0.5); + for (int i = 0; i < 80; i++) { + float d = map(ro + rd * t); + if (d < 0.001) { + vec3 p = ro + rd * t; + vec3 n = getNormal(p); + float sun = max(dot(n, normalize(vec3(1, 0.8, -0.5))), 0.0); + col = vec3(0.3, 0.6, 0.2) * (0.3 + 0.7 * sun); + col = mix(col, vec3(0.5, 0.7, 1.0), 1.0 - exp(-t * 0.05)); + break; + } + t += d; + if (t > 30.0) break; + } + fragColor = vec4(col, 1.0); +}""", ["floating", "islands", "3d", "fantasy", "sky", "peaceful", "raymarching"]), + ("Endless Staircase", """ +float map(vec3 p) { + p.y += iTime * 0.5; + vec3 q = mod(p + 1.0, 2.0) - 1.0; + float box = max(max(abs(q.x) - 0.8, abs(q.y) - 0.1), abs(q.z) - 0.4); + float step1 = max(max(abs(q.x) - 0.3, abs(q.y + 0.3) - 0.1), abs(q.z - 0.2) - 0.4); + return min(box, step1); +} +vec3 getNormal(vec3 p) { + vec2 e = vec2(0.001, 0); + return normalize(vec3(map(p+e.xyy)-map(p-e.xyy),map(p+e.yxy)-map(p-e.yxy),map(p+e.yyx)-map(p-e.yyx))); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec3 ro = vec3(0, 0, -4); + vec3 rd = normalize(vec3(uv, 1.0)); + float a = iTime * 0.2; + rd.xz = mat2(cos(a), sin(a), -sin(a), cos(a)) * rd.xz; + float t = 0.0; + vec3 col = vec3(0.05); + for (int i = 0; i < 80; i++) { + float d = map(ro + rd * t); + if (d < 0.001) { + vec3 p = ro + rd * t; + vec3 n = getNormal(p); + float light = max(dot(n, normalize(vec3(1, 1, -1))), 0.0); + col = vec3(0.7, 0.7, 0.75) * (0.2 + 0.8 * light); + col = mix(col, vec3(0.05), 1.0 - exp(-t * 0.08)); + break; + } + t += d; + if (t > 20.0) break; + } + fragColor = vec4(col, 1.0); +}""", ["staircase", "escher", "infinite", "3d", "architectural", "surreal", "impossible"]), + ("Crystal Cave", """ +float map(vec3 p) { + float cave = -(length(p.xz) - 3.0); + float crystal = 1e10; + for (int i = 0; i < 6; i++) { + float fi = float(i); + vec3 cp = vec3(sin(fi*1.0)*2.0, fi*0.5-1.0, cos(fi*1.0)*2.0); + float h = max(abs(p.x-cp.x)+abs(p.z-cp.z)-0.15, abs(p.y-cp.y)-0.4-fi*0.1); + crystal = min(crystal, h); + } + return max(cave, -crystal); +} +vec3 getNormal(vec3 p) { + vec2 e = vec2(0.001, 0); + return normalize(vec3(map(p+e.xyy)-map(p-e.xyy),map(p+e.yxy)-map(p-e.yxy),map(p+e.yyx)-map(p-e.yyx))); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float a = iTime * 0.3; + vec3 ro = vec3(sin(a)*2.0, 0.0, cos(a)*2.0); + vec3 ta = vec3(0.0, 0.0, 0.0); + vec3 fwd = normalize(ta - ro); + vec3 right = normalize(cross(vec3(0,1,0), fwd)); + vec3 up = cross(fwd, right); + vec3 rd = normalize(uv.x * right + uv.y * up + 1.5 * fwd); + float t = 0.0; + vec3 col = vec3(0.01, 0.0, 0.02); + for (int i = 0; i < 80; i++) { + float d = map(ro + rd * t); + if (d < 0.001) { + vec3 p = ro + rd * t; + vec3 n = getNormal(p); + float light = max(dot(n, normalize(vec3(0, 1, 0))), 0.0); + float fresnel = pow(1.0 - abs(dot(n, rd)), 3.0); + col = vec3(0.2, 0.5, 0.8) * light + vec3(0.5, 0.3, 0.9) * fresnel; + break; + } + t += d; + if (t > 20.0) break; + } + fragColor = vec4(col, 1.0); +}""", ["crystal", "cave", "3d", "underground", "fantasy", "blue", "raymarching"]), +] + +for name, code, extra_tags in scenes_3d: + s(name, code, extra_tags, "3d", f"Raymarched 3D scene: {name}", + {"chaos_level": 0.4, "color_temperature": "cool", "motion_type": "orbiting"}) + +# --- Family: Glitch/distortion effects (10 shaders) --- +glitch_variants = [ + ("VHS Tracking", 0.01, 8.0, ["vhs", "tracking", "analog", "retro", "distortion"]), + ("CRT Phosphor", 0.005, 3.0, ["crt", "phosphor", "monitor", "vintage", "scanline"]), + ("Databend", 0.02, 15.0, ["databend", "corruption", "digital-art", "experimental"]), + ("Signal Loss", 0.015, 6.0, ["signal", "loss", "static", "broadcast", "error"]), + ("Pixel Sort", 0.008, 10.0, ["pixel-sort", "glitch-art", "sorting", "avant-garde"]), + ("Bitcrush", 0.025, 4.0, ["bitcrush", "low-res", "lo-fi", "8bit", "quantize"]), + ("Chroma Aberration", 0.012, 5.0, ["chroma", "aberration", "lens", "rgb", "optics"]), + ("Digital Decay", 0.018, 12.0, ["digital-decay", "entropy", "corruption", "time"]), + ("Broadcast Storm", 0.03, 7.0, ["broadcast", "storm", "interference", "signal"]), + ("Codec Artifact", 0.02, 9.0, ["codec", "artifact", "compression", "jpeg", "macro-block"]), +] + +for i, (name, intensity, freq, extra_tags) in enumerate(glitch_variants): + color_shift = ["vec3(0.0, 0.8, 0.3)", "vec3(0.8, 0.2, 0.5)", "vec3(0.2, 0.5, 1.0)", + "vec3(1.0, 0.6, 0.0)", "vec3(0.6, 0.0, 0.9)"][i % 5] + code = f"""float hash(float n) {{ return fract(sin(n) * 43758.5453); }} +void mainImage(out vec4 fragColor, in vec2 fragCoord) {{ + vec2 uv = fragCoord / iResolution.xy; + float t = floor(iTime * {freq:.1f}); + float glitch = step(0.8, hash(t + uv.y * 10.0)); + float blockY = floor(uv.y * 15.0); + float shift = hash(blockY + t) * {intensity * 20:.2f} * glitch; + uv.x = fract(uv.x + shift); + float scanline = sin(uv.y * 400.0 + iTime * 30.0) * 0.03; + float noise = hash(uv.y * 100.0 + t) * glitch * 0.2; + vec3 col; + col.r = 0.5 + 0.5 * sin(uv.x * 6.283 * 3.0 + iTime + {float(i):.1f}); + col.g = 0.5 + 0.5 * sin(uv.x * 6.283 * 3.0 + iTime * 1.1 + 2.0 + {float(i):.1f}); + col.b = 0.5 + 0.5 * sin(uv.x * 6.283 * 3.0 + iTime * 0.9 + 4.0 + {float(i):.1f}); + col += scanline + noise; + col *= 0.7 + 0.3 * (1.0 - glitch); + col += {color_shift} * glitch * 0.3; + fragColor = vec4(col, 1.0); +}}""" + s(name, code, + ["glitch", "distortion", "digital", "animated"] + extra_tags, + "2d", f"Glitch effect: {name.lower()}", + {"chaos_level": 0.7 + (i % 3) * 0.1, "color_temperature": "neutral", "motion_type": "chaotic"}) + +# --- Family: Minimalist geometric (10 shaders) --- +minimals = [ + ("Single Circle", "smoothstep(0.302, 0.3, length(uv))", "vec3(0.95)", "vec3(0.05)", ["circle", "minimal", "clean"]), + ("Double Ring", "smoothstep(0.01,0.0,abs(length(uv)-0.3))+smoothstep(0.01,0.0,abs(length(uv)-0.5))", "vec3(0.9)", "vec3(0.05)", ["rings", "double", "minimal"]), + ("Moving Dot", "smoothstep(0.06,0.05,length(uv-vec2(sin(iTime)*0.3,cos(iTime*0.7)*0.3)))", "vec3(1.0,0.3,0.2)", "vec3(0.02)", ["dot", "motion", "minimal"]), + ("Cross Hair", "smoothstep(0.005,0.0,min(abs(uv.x),abs(uv.y)))*smoothstep(0.5,0.0,length(uv))", "vec3(0.9,0.1,0.1)", "vec3(0.02)", ["crosshair", "target", "minimal"]), + ("Breathing Square", "step(max(abs(uv.x),abs(uv.y)),0.2+sin(iTime)*0.1)", "vec3(0.9)", "vec3(0.05)", ["square", "breathing", "minimal"]), + ("Line Scan", "smoothstep(0.005,0.0,abs(uv.x-sin(iTime)*0.5))", "vec3(0.0,0.8,1.0)", "vec3(0.02)", ["line", "scan", "minimal"]), + ("Dot Grid", "smoothstep(0.05,0.04,length(fract(uv*5.0+0.5)-0.5))", "vec3(0.3,0.3,0.35)", "vec3(0.05)", ["grid", "dots", "minimal"]), + ("Gradient Only", "uv.y*0.5+0.5", "vec3(0.1,0.1,0.3)", "vec3(0.0)", ["gradient", "simple", "minimal"]), + ("Orbit", "smoothstep(0.04,0.03,length(uv-0.3*vec2(cos(iTime),sin(iTime))))", "vec3(1.0,0.8,0.2)", "vec3(0.03,0.01,0.05)", ["orbit", "planet", "minimal"]), + ("Pulse Ring", "smoothstep(0.01,0.0,abs(length(uv)-(mod(iTime,2.0)*0.5)))*exp(-mod(iTime,2.0)*2.0)", "vec3(0.0,0.9,0.5)", "vec3(0.02)", ["pulse", "ring", "expanding", "minimal"]), +] + +for i, (name, expr, fg, bg, extra_tags) in enumerate(minimals): + code = f"""void mainImage(out vec4 fragColor, in vec2 fragCoord) {{ + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float v = {expr}; + vec3 col = mix({bg}, {fg}, v); + fragColor = vec4(col, 1.0); +}}""" + s(name, code, + ["minimal", "geometric", "clean", "simple"] + extra_tags, + "2d", f"Minimalist: {name.lower()}", + {"chaos_level": 0.05 + (i % 3) * 0.05, "color_temperature": ["monochrome", "cool", "warm"][i % 3], "motion_type": "static" if i % 3 == 0 else "animated"}) + +# --- Family: More 3D environments (8 shaders) --- +more_3d = [ + ("Infinite Columns", """ +float map(vec3 p) { + vec2 q = mod(p.xz + 1.0, 2.0) - 1.0; + float col = length(q) - 0.2; + float floor_d = p.y + 1.5; + float ceil_d = -(p.y - 3.0); + return min(min(col, floor_d), ceil_d); +} +vec3 getNormal(vec3 p) { + vec2 e = vec2(0.001, 0); + return normalize(vec3(map(p+e.xyy)-map(p-e.xyy),map(p+e.yxy)-map(p-e.yxy),map(p+e.yyx)-map(p-e.yyx))); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec3 ro = vec3(iTime * 0.5, 0.0, iTime * 0.3); + vec3 rd = normalize(vec3(uv, 1.0)); + float t = 0.0; + vec3 col = vec3(0.02); + for (int i = 0; i < 80; i++) { + float d = map(ro + rd * t); + if (d < 0.001) { + vec3 p = ro + rd * t; + vec3 n = getNormal(p); + float light = max(dot(n, normalize(vec3(1, 1, -1))), 0.0); + col = vec3(0.8, 0.75, 0.7) * (0.15 + 0.85 * light); + col = mix(col, vec3(0.02), 1.0 - exp(-t * 0.06)); + break; + } + t += d; + if (t > 30.0) break; + } + fragColor = vec4(col, 1.0); +}""", ["columns", "infinite", "3d", "architectural", "ancient", "temple"]), + ("Wormhole", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float r = length(uv); + float a = atan(uv.y, uv.x); + float tunnel = 1.0 / (r + 0.1); + float twist = a + tunnel * 0.5 + iTime; + float tex = sin(twist * 4.0) * sin(tunnel * 3.0 - iTime * 4.0); + float glow = exp(-r * 2.0); + vec3 col = vec3(0.02, 0.0, 0.05); + col += vec3(0.2, 0.5, 1.0) * tex * glow; + col += vec3(0.8, 0.4, 1.0) * glow * 0.3; + col += vec3(1.0, 0.9, 0.7) * exp(-r * 8.0) * 0.5; + fragColor = vec4(max(col, 0.0), 1.0); +}""", ["wormhole", "tunnel", "space", "3d-illusion", "portal", "sci-fi", "blue"]), + ("Cubic Lattice", """ +float map(vec3 p) { + vec3 q = mod(p + 0.5, 1.0) - 0.5; + float bars = min(min( + max(abs(q.x), abs(q.y)) - 0.08, + max(abs(q.y), abs(q.z)) - 0.08), + max(abs(q.x), abs(q.z)) - 0.08); + return bars; +} +vec3 getNormal(vec3 p) { + vec2 e = vec2(0.001, 0); + return normalize(vec3(map(p+e.xyy)-map(p-e.xyy),map(p+e.yxy)-map(p-e.yxy),map(p+e.yyx)-map(p-e.yyx))); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float t = iTime * 0.3; + vec3 ro = vec3(sin(t)*3.0, 1.0+sin(t*0.7), cos(t)*3.0); + vec3 ta = vec3(0.0); + vec3 fwd = normalize(ta - ro); + vec3 right = normalize(cross(vec3(0,1,0), fwd)); + vec3 up = cross(fwd, right); + vec3 rd = normalize(uv.x*right + uv.y*up + 1.5*fwd); + float d_t = 0.0; + vec3 col = vec3(0.02); + for (int i = 0; i < 80; i++) { + float d = map(ro + rd * d_t); + if (d < 0.001) { + vec3 p = ro + rd * d_t; + vec3 n = getNormal(p); + float light = max(dot(n, normalize(vec3(1,1,-1))), 0.0); + col = vec3(0.9, 0.5, 0.2) * (0.2 + 0.8 * light); + col = mix(col, vec3(0.02), 1.0 - exp(-d_t * 0.1)); + break; + } + d_t += d; + if (d_t > 20.0) break; + } + fragColor = vec4(col, 1.0); +}""", ["lattice", "cubic", "3d", "wireframe", "structural", "orange", "geometric"]), +] + +for name, code, extra_tags in more_3d: + s(name, code, extra_tags, "3d", f"3D scene: {name}", + {"chaos_level": 0.4, "color_temperature": "cool", "motion_type": "forward"}) + +# --- Family: Color theory experiments (10 shaders) --- +color_exps = [ + ("RGB Separation", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float t = iTime; + float r = length(uv - vec2(sin(t)*0.2, cos(t)*0.15)) - 0.3; + float g = length(uv - vec2(sin(t*1.1+2.0)*0.2, cos(t*0.9+1.0)*0.15)) - 0.3; + float b = length(uv - vec2(sin(t*0.8+4.0)*0.2, cos(t*1.2+3.0)*0.15)) - 0.3; + vec3 col; + col.r = smoothstep(0.01, 0.0, r); + col.g = smoothstep(0.01, 0.0, g); + col.b = smoothstep(0.01, 0.0, b); + fragColor = vec4(col, 1.0); +}""", ["rgb", "separation", "additive", "color-theory", "circles", "primary-colors"]), + ("CMY Overlap", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float t = iTime * 0.5; + float c = smoothstep(0.01, 0.0, length(uv - 0.15*vec2(sin(t), cos(t))) - 0.25); + float m = smoothstep(0.01, 0.0, length(uv - 0.15*vec2(sin(t+2.1), cos(t+2.1))) - 0.25); + float y = smoothstep(0.01, 0.0, length(uv - 0.15*vec2(sin(t+4.2), cos(t+4.2))) - 0.25); + vec3 col = vec3(1.0) - vec3(c, 0.0, 0.0) - vec3(0.0, m, 0.0) - vec3(0.0, 0.0, y); + col = max(col, 0.0); + col = mix(vec3(0.95), col, max(max(c, m), y)); + fragColor = vec4(col, 1.0); +}""", ["cmy", "subtractive", "color-theory", "overlap", "pastel", "print"]), + ("Hue Wheel", """ +vec3 hsv(float h, float s, float v) { + vec3 c = clamp(abs(mod(h*6.0+vec3(0,4,2),6.0)-3.0)-1.0, 0.0, 1.0); + return v * mix(vec3(1.0), c, s); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float a = atan(uv.y, uv.x) / 6.2832 + 0.5; + float r = length(uv); + vec3 col = hsv(a + iTime * 0.1, smoothstep(0.1, 0.4, r), smoothstep(0.5, 0.2, r)); + float ring = smoothstep(0.01, 0.0, abs(r - 0.35)); + col += ring * 0.3; + fragColor = vec4(col, 1.0); +}""", ["hue", "wheel", "color-theory", "hsv", "rainbow", "spectrum", "educational"]), +] + +for name, code, extra_tags in color_exps: + s(name, code, extra_tags, "2d", f"Color theory: {name}", + {"chaos_level": 0.2, "color_temperature": "neutral", "motion_type": "animated"}) + +# --- Family: Particle systems (7 shaders) --- +particle_types = [ + ("Fireflies", "vec3(1.0,0.9,0.3)", 0.003, 30, ["fireflies", "night", "gentle", "nature"]), + ("Snow Fall", "vec3(0.9,0.95,1.0)", 0.004, 50, ["snow", "winter", "cold", "peaceful"]), + ("Ember Rise", "vec3(1.0,0.4,0.0)", 0.003, 40, ["ember", "fire", "rising", "warm"]), + ("Dust Motes", "vec3(0.8,0.7,0.5)", 0.005, 25, ["dust", "motes", "sunbeam", "atmospheric"]), + ("Pollen Drift", "vec3(0.9,0.8,0.2)", 0.006, 20, ["pollen", "spring", "floating", "organic"]), + ("Star Rain", "vec3(0.8,0.9,1.0)", 0.002, 60, ["stars", "rain", "cosmic", "shower"]), + ("Bubble Float", "vec3(0.5,0.8,1.0)", 0.008, 15, ["bubbles", "float", "underwater", "playful"]), +] + +for i, (name, color, size, count, extra_tags) in enumerate(particle_types): + code = f"""float hash(float n) {{ return fract(sin(n) * 43758.5453); }} +void mainImage(out vec4 fragColor, in vec2 fragCoord) {{ + vec2 uv = fragCoord / iResolution.xy; + vec3 col = vec3(0.02, 0.01, 0.04); + for (int i = 0; i < {count}; i++) {{ + float fi = float(i); + float seed = fi * 137.0 + {float(i * 17):.1f}; + vec2 pos; + pos.x = hash(seed); + pos.y = fract(hash(seed + 1.0) + iTime * (0.02 + hash(seed + 2.0) * 0.04) * {1.0 if i % 2 == 0 else -1.0:.1f}); + pos.x += sin(iTime * hash(seed + 3.0) + fi) * 0.02; + float d = length(uv - pos); + float brightness = hash(seed + 4.0) * 0.5 + 0.5; + float twinkle = sin(iTime * (2.0 + hash(seed + 5.0) * 3.0) + fi) * 0.3 + 0.7; + col += {color} * ({size} / (d + {size})) * brightness * twinkle; + }} + fragColor = vec4(col, 1.0); +}}""" + s(name, code, + ["particles", "floating", "ambient", "animated"] + extra_tags, + "2d", f"Ambient particle system: {name.lower()}", + {"chaos_level": 0.2 + (i % 3) * 0.1, "color_temperature": ["warm", "cool", "neutral"][i % 3], "motion_type": "floating"}) + +# --- Family: More distinct 3D (10 shaders) --- +distinct_3d = [ + ("Infinite Corridor", """ +float map(vec3 p) { + vec2 q = abs(mod(p.xz, 4.0) - 2.0); + float walls = min(q.x, q.y) - 0.1; + float ceiling = abs(p.y) - 2.0; + return max(-walls, ceiling); +} +vec3 getNormal(vec3 p) { + vec2 e = vec2(0.001, 0); + return normalize(vec3(map(p+e.xyy)-map(p-e.xyy),map(p+e.yxy)-map(p-e.yxy),map(p+e.yyx)-map(p-e.yyx))); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec3 ro = vec3(0, 0, iTime); + vec3 rd = normalize(vec3(uv, 1.0)); + float t = 0.0; + vec3 col = vec3(0.02); + for (int i = 0; i < 80; i++) { + float d = map(ro + rd * t); + if (abs(d) < 0.001) { + vec3 p = ro + rd * t; + vec3 n = getNormal(p); + col = vec3(0.6, 0.7, 0.8) * (0.2 + 0.8 * max(dot(n, vec3(0, 1, 0)), 0.0)); + col *= 1.0 + 0.1 * sin(p.z); + col = mix(col, vec3(0.02), 1.0 - exp(-t * 0.04)); + break; + } + t += abs(d); + if (t > 30.0) break; + } + fragColor = vec4(col, 1.0); +}""", ["corridor", "hallway", "infinite", "3d", "architectural", "perspective"]), + ("Planet Surface", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +float noise(vec2 p) { + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i+vec2(1,0)), f.x), mix(hash(i+vec2(0,1)), hash(i+vec2(1,1)), f.x), f.y); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float r = length(uv); + float planet_r = 0.4; + vec3 col = vec3(0.0, 0.0, 0.02); + if (r < planet_r) { + float z = sqrt(planet_r * planet_r - r * r); + vec3 n = normalize(vec3(uv, z)); + vec2 texUV = vec2(atan(n.x, n.z) + iTime * 0.2, asin(n.y)); + float land = noise(texUV * 5.0) * 0.5 + noise(texUV * 10.0) * 0.25; + vec3 surface = mix(vec3(0.1, 0.3, 0.8), vec3(0.2, 0.6, 0.15), step(0.45, land)); + float sun = max(dot(n, normalize(vec3(1, 0.5, 0.5))), 0.0); + col = surface * (0.1 + 0.9 * sun); + } + float atmo = smoothstep(planet_r + 0.02, planet_r - 0.02, r) * (1.0 - smoothstep(planet_r - 0.02, planet_r + 0.08, r)); + col += vec3(0.3, 0.5, 1.0) * atmo * 0.3; + col += vec3(0.8, 0.9, 1.0) * 0.001 / (abs(r - planet_r) + 0.005); + fragColor = vec4(col, 1.0); +}""", ["planet", "earth", "space", "3d", "sphere", "globe", "rotating"]), + ("Disco Ball", """ +float map(vec3 p) { return length(p) - 1.0; } +vec3 getNormal(vec3 p) { return normalize(p); } +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + vec3 ro = vec3(0, 0, -3); + vec3 rd = normalize(vec3(uv, 1.0)); + float t = 0.0; + vec3 col = vec3(0.02); + for (int i = 0; i < 40; i++) { + float d = map(ro + rd * t); + if (d < 0.001) { + vec3 p = ro + rd * t; + vec3 n = getNormal(p); + float a1 = atan(n.z, n.x) + iTime * 0.5; + float a2 = acos(n.y); + float grid = step(0.9, fract(a1 * 5.0)) + step(0.9, fract(a2 * 5.0)); + vec3 refl = reflect(rd, n); + float spec = pow(max(dot(refl, normalize(vec3(sin(iTime), 1, cos(iTime)))), 0.0), 32.0); + float mirror = 0.5 + 0.5 * fract(sin(dot(floor(vec2(a1, a2) * 5.0), vec2(127.1, 311.7))) * 43758.5); + col = vec3(0.3) * (1.0 - grid * 0.5) + vec3(1.0) * spec * mirror; + col += vec3(0.8, 0.3, 0.9) * spec * 0.3; + break; + } + t += d; + if (t > 10.0) break; + } + fragColor = vec4(col, 1.0); +}""", ["disco", "ball", "mirror", "3d", "party", "retro", "reflective", "fun"]), +] + +for name, code, extra_tags in distinct_3d: + s(name, code, extra_tags, "3d", f"3D scene: {name}", + {"chaos_level": 0.4, "color_temperature": "cool", "motion_type": "rotating"}) + +# --- Standalone standouts (7 more to break 200) --- + +s("Supernova", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float r = length(uv); + float a = atan(uv.y, uv.x); + float t = iTime; + float shockwave = smoothstep(0.02, 0.0, abs(r - mod(t * 0.5, 2.0))); + float core = exp(-r * 8.0); + float rays = pow(0.5 + 0.5 * sin(a * 12.0 + t * 3.0), 8.0) * exp(-r * 3.0); + vec3 col = vec3(1.0, 0.8, 0.3) * core; + col += vec3(1.0, 0.3, 0.1) * rays; + col += vec3(0.3, 0.5, 1.0) * shockwave; + fragColor = vec4(col, 1.0); +}""", ["supernova", "explosion", "star", "cosmic", "energy", "bright"], "2d", +"Supernova with expanding shockwave and ray corona", +{"chaos_level": 0.8, "color_temperature": "warm", "motion_type": "explosive"}) + +s("Fabric Fold", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float fold = sin(uv.x * 12.0 + iTime * 0.3) * sin(uv.y * 8.0 - iTime * 0.21) * 0.5; + float shadow = fold * 0.5 + 0.5; + vec3 col = vec3(0.6, 0.15, 0.2) * (0.4 + 0.6 * shadow); + col += vec3(0.3, 0.1, 0.1) * pow(max(shadow, 0.0), 4.0); + fragColor = vec4(col, 1.0); +}""", ["fabric", "fold", "cloth", "textile", "red", "silk", "elegant"], "2d", +"Folded silk fabric with dynamic shadow", +{"chaos_level": 0.2, "color_temperature": "warm", "motion_type": "fluid"}) + +s("EKG Heartbeat", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float x = fract(uv.x * 2.0 - iTime * 0.5); + float y = uv.y * 2.0 - 1.0; + float ekg = 0.0; + if (x < 0.15) ekg = (x / 0.15) * 0.3; + else if (x < 0.2) ekg = (0.2 - x) / 0.05 * 0.3; + else if (x < 0.3) ekg = (x - 0.25) * 16.0; + else if (x < 0.35) ekg = (0.35 - x) * 16.0 - 0.2; + float d = abs(y - ekg); + vec3 col = vec3(0.0, 1.0, 0.3) * 0.004 / (d + 0.004); + col += vec3(0.0, 0.3, 0.1) * 0.015 / (d + 0.015); + fragColor = vec4(col, 1.0); +}""", ["ekg", "heartbeat", "medical", "pulse", "green", "monitor", "health"], "2d", +"EKG heartbeat monitor trace", +{"chaos_level": 0.1, "color_temperature": "cool", "motion_type": "scrolling"}) + +s("Rotating Galaxy Arm", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float r = length(uv); + float a = atan(uv.y, uv.x) + iTime * 0.2; + float arm = max(sin(a * 2.0 - r * 8.0) * exp(-r * 1.5), 0.0); + float stars = step(0.997, hash(floor(fragCoord))); + vec3 col = vec3(0.01, 0.0, 0.02) + vec3(0.3, 0.2, 0.5) * arm + vec3(0.8, 0.6, 0.3) * arm * arm * 2.0; + col += vec3(0.8, 0.85, 1.0) * stars * (1.0 - r); + col += vec3(1.0, 0.9, 0.6) * exp(-r * 12.0) * 0.5; + fragColor = vec4(col, 1.0); +}""", ["galaxy", "spiral-arm", "space", "stars", "cosmic", "astronomy"], "2d", +"Spiral galaxy with rotating arms and star field", +{"chaos_level": 0.4, "color_temperature": "warm", "motion_type": "rotating"}) + +s("Underwater Caustics", """ +float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } +float noise(vec2 p) { + vec2 i = floor(p); vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i+vec2(1,0)), f.x), mix(hash(i+vec2(0,1)), hash(i+vec2(1,1)), f.x), f.y); +} +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float t = iTime * 0.5; + float c = 0.0; + for (int i = 0; i < 3; i++) { + float fi = float(i); + vec2 p = uv * (4.0 + fi * 2.0) + vec2(t * (0.3 + fi * 0.1), t * 0.2); + c += abs(sin(noise(p) * 6.283)) * (0.5 / (1.0 + fi)); + } + vec3 col = mix(vec3(0.0, 0.15, 0.35), vec3(0.1, 0.6, 0.8), pow(c, 1.5)); + fragColor = vec4(col, 1.0); +}""", ["underwater", "caustics", "water", "ocean", "blue", "ripple", "serene"], "2d", +"Underwater light caustics on the ocean floor", +{"chaos_level": 0.3, "color_temperature": "cool", "motion_type": "fluid"}) + +s("Geometric Rose", """ +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float r = length(uv); + float a = atan(uv.y, uv.x); + float rose = cos(a * 4.0 + iTime) * 0.3 + 0.3; + float inner = cos(a * 7.0 - iTime * 1.5) * 0.15 + 0.15; + vec3 col = vec3(0.02, 0.0, 0.04); + col += vec3(0.9, 0.2, 0.4) * 0.003 / (abs(r - rose) + 0.003); + col += vec3(1.0, 0.6, 0.7) * 0.002 / (abs(r - inner) + 0.002); + col += vec3(0.3, 0.05, 0.1) * exp(-r * 3.0); + fragColor = vec4(col, 1.0); +}""", ["rose", "polar", "mathematical", "curve", "pink", "elegant", "botanical"], "2d", +"Polar rose curves with counter-rotating petals", +{"chaos_level": 0.3, "color_temperature": "warm", "motion_type": "rotating"}) + +s("Data Stream", """ +float hash(float n) { return fract(sin(n) * 43758.5453); } +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + vec3 col = vec3(0.02, 0.01, 0.03); + for (int i = 0; i < 30; i++) { + float fi = float(i); + float lane = (fi + 0.5) / 30.0; + float speed = 0.5 + hash(fi * 7.0) * 2.0; + float y = fract(uv.y * 3.0 + iTime * speed + hash(fi * 13.0)); + float on = step(0.3, hash(floor(y * 20.0) + fi * 100.0 + floor(iTime * speed * 3.0))); + float d = abs(uv.x - lane); + col += (0.5 + 0.5 * cos(fi * 0.5 + vec3(0, 2, 4))) * 0.001 / (d + 0.001) * on * y * 0.1; + } + fragColor = vec4(min(col, 1.0), 1.0); +}""", ["data", "stream", "digital", "network", "colorful", "flow", "tech", "cyber"], "2d", +"Multi-lane data streams at different speeds", +{"chaos_level": 0.5, "color_temperature": "cool", "motion_type": "flowing"}) + + +# ═══════════════════════════════════════════════════════════ + +print(f"Total shaders defined: {len(SHADERS)}") + +# --- Database insertion --- +async def seed(): + import asyncpg + import json + import os + + db_url = os.environ.get("DATABASE_URL_SYNC", "postgresql://fracta:devpass@postgres:5432/fractafrag") + # Convert sync URL to asyncpg format + db_url = db_url.replace("postgresql://", "postgresql://").replace("+asyncpg", "") + + conn = await asyncpg.connect(db_url) + + # Check if already seeded + count = await conn.fetchval("SELECT COUNT(*) FROM shaders WHERE is_system = TRUE") + if count > 0: + print(f"Already seeded ({count} system shaders). Skipping.") + await conn.close() + return + + print(f"Seeding {len(SHADERS)} shaders...") + + # Distribute creation times over past 30 days for realistic feed testing + now = datetime.now(timezone.utc) + total = len(SHADERS) + + for i, shader in enumerate(SHADERS): + age_hours = random.uniform(0, 30 * 24) # up to 30 days ago + created_at = now - timedelta(hours=age_hours) + score = random.uniform(0, 10) # random initial score for feed ranking variety + + shader_id = str(uuid.uuid4()) + + await conn.execute(""" + INSERT INTO shaders (id, author_id, title, description, glsl_code, status, is_public, + is_ai_generated, is_system, system_label, tags, shader_type, + style_metadata, render_status, score, current_version, + created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) + """, + uuid.UUID(shader_id), + uuid.UUID(SYSTEM_USER_ID), + shader["title"], + shader["description"], + shader["code"], + "published", + True, + False, # not AI-generated — these are hand-curated + True, # is_system + "fractafrag-curated", + shader["tags"], + shader["shader_type"], + json.dumps(shader["style_metadata"]), + "ready", # render_status — these render client-side + score, + 1, + created_at, + created_at, + ) + + # Also create version 1 for each + await conn.execute(""" + INSERT INTO shader_versions (id, shader_id, version_number, glsl_code, title, description, + tags, style_metadata, change_note, created_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + """, + uuid.uuid4(), + uuid.UUID(shader_id), + 1, + shader["code"], + shader["title"], + shader["description"], + shader["tags"], + json.dumps(shader["style_metadata"]), + "Initial version — fractafrag curated content", + created_at, + ) + + if (i + 1) % 25 == 0: + print(f" ...{i + 1}/{total}") + + await conn.close() + print(f"Done. {total} shaders seeded as 'fractafrag-curated' system content.") + +if __name__ == "__main__": + asyncio.run(seed()) diff --git a/services/api/app/models/__init__.py b/services/api/app/models/__init__.py index fde2343..23fbe8a 100644 --- a/services/api/app/models/__init__.py +++ b/services/api/app/models/__init__.py @@ -1,12 +1,14 @@ """Models package.""" from app.models.models import ( - User, Shader, Vote, EngagementEvent, Desire, DesireCluster, + SYSTEM_USER_ID, + User, Shader, ShaderVersion, Vote, EngagementEvent, Desire, DesireCluster, BountyTip, CreatorPayout, ApiKey, GenerationLog, Comment, SourceUnlock, CreatorEngagementSnapshot, ) __all__ = [ - "User", "Shader", "Vote", "EngagementEvent", "Desire", "DesireCluster", + "SYSTEM_USER_ID", + "User", "Shader", "ShaderVersion", "Vote", "EngagementEvent", "Desire", "DesireCluster", "BountyTip", "CreatorPayout", "ApiKey", "GenerationLog", "Comment", "SourceUnlock", "CreatorEngagementSnapshot", ] diff --git a/services/api/app/models/models.py b/services/api/app/models/models.py index 5f3b3f0..c0701a8 100644 --- a/services/api/app/models/models.py +++ b/services/api/app/models/models.py @@ -11,6 +11,9 @@ from pgvector.sqlalchemy import Vector from sqlalchemy.orm import relationship from app.database import Base +# System account UUID — the "fractafrag" platform user +SYSTEM_USER_ID = uuid.UUID("00000000-0000-0000-0000-000000000001") + class User(Base): __tablename__ = "users" @@ -21,19 +24,17 @@ class User(Base): password_hash = Column(String, nullable=False) role = Column(String, nullable=False, default="user") trust_tier = Column(String, nullable=False, default="standard") + is_system = Column(Boolean, nullable=False, default=False) stripe_customer_id = Column(String, nullable=True) subscription_tier = Column(String, default="free") ai_credits_remaining = Column(Integer, default=0) taste_vector = Column(Vector(512), nullable=True) - # Creator economy stubs is_verified_creator = Column(Boolean, default=False) verified_creator_at = Column(DateTime(timezone=True), nullable=True) stripe_connect_account_id = Column(String, nullable=True) - # Timestamps created_at = Column(DateTime(timezone=True), default=datetime.utcnow) last_active_at = Column(DateTime(timezone=True), nullable=True) - # Relationships shaders = relationship("Shader", back_populates="author") votes = relationship("Vote", back_populates="user") api_keys = relationship("ApiKey", back_populates="user") @@ -47,9 +48,12 @@ class Shader(Base): title = Column(String, nullable=False) description = Column(Text, nullable=True) glsl_code = Column(Text, nullable=False) + status = Column(String, nullable=False, default="published") # draft, published, archived is_public = Column(Boolean, default=True) is_ai_generated = Column(Boolean, default=False) + is_system = Column(Boolean, default=False) ai_provider = Column(String, nullable=True) + system_label = Column(String, nullable=True) thumbnail_url = Column(String, nullable=True) preview_url = Column(String, nullable=True) render_status = Column(String, default="pending") @@ -58,20 +62,38 @@ class Shader(Base): tags = Column(ARRAY(String), default=list) shader_type = Column(String, default="2d") forked_from = Column(UUID(as_uuid=True), ForeignKey("shaders.id", ondelete="SET NULL"), nullable=True) + current_version = Column(Integer, nullable=False, default=1) view_count = Column(Integer, default=0) score = Column(Float, default=0.0) - # Creator economy stubs access_tier = Column(String, default="open") source_unlock_price_cents = Column(Integer, nullable=True) commercial_license_price_cents = Column(Integer, nullable=True) verified_creator_shader = Column(Boolean, default=False) - # Timestamps created_at = Column(DateTime(timezone=True), default=datetime.utcnow) updated_at = Column(DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow) - # Relationships author = relationship("User", back_populates="shaders") votes = relationship("Vote", back_populates="shader") + versions = relationship("ShaderVersion", back_populates="shader", order_by="ShaderVersion.version_number.desc()") + + +class ShaderVersion(Base): + __tablename__ = "shader_versions" + __table_args__ = (UniqueConstraint("shader_id", "version_number"),) + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + shader_id = Column(UUID(as_uuid=True), ForeignKey("shaders.id", ondelete="CASCADE"), nullable=False) + version_number = Column(Integer, nullable=False) + glsl_code = Column(Text, nullable=False) + title = Column(String, nullable=False) + description = Column(Text, nullable=True) + tags = Column(ARRAY(String), default=list) + style_metadata = Column(JSONB, nullable=True) + change_note = Column(Text, nullable=True) + thumbnail_url = Column(String, nullable=True) + created_at = Column(DateTime(timezone=True), default=datetime.utcnow) + + shader = relationship("Shader", back_populates="versions") class Vote(Base): @@ -195,7 +217,6 @@ class Comment(Base): created_at = Column(DateTime(timezone=True), default=datetime.utcnow) -# Creator economy stubs (dormant) class SourceUnlock(Base): __tablename__ = "source_unlocks" diff --git a/services/api/app/routers/feed.py b/services/api/app/routers/feed.py index 4f20e9e..5ad010a 100644 --- a/services/api/app/routers/feed.py +++ b/services/api/app/routers/feed.py @@ -11,6 +11,9 @@ from app.middleware.auth import get_optional_user, get_current_user router = APIRouter() +# Common filter for public, published shaders +_FEED_FILTER = [Shader.is_public == True, Shader.status == "published"] + @router.get("", response_model=list[ShaderFeedItem]) async def get_feed( @@ -19,15 +22,9 @@ async def get_feed( db: AsyncSession = Depends(get_db), user: User | None = Depends(get_optional_user), ): - """ - Personalized feed for authenticated users (pgvector taste match). - Trending/new for anonymous users. - """ - # TODO: Implement full recommendation engine (Track F) - # For now: return newest public shaders query = ( select(Shader) - .where(Shader.is_public == True, Shader.render_status == "ready") + .where(*_FEED_FILTER) .order_by(Shader.created_at.desc()) .limit(limit) ) @@ -42,7 +39,7 @@ async def get_trending( ): query = ( select(Shader) - .where(Shader.is_public == True, Shader.render_status == "ready") + .where(*_FEED_FILTER) .order_by(Shader.score.desc()) .limit(limit) ) @@ -57,7 +54,7 @@ async def get_new( ): query = ( select(Shader) - .where(Shader.is_public == True, Shader.render_status == "ready") + .where(*_FEED_FILTER) .order_by(Shader.created_at.desc()) .limit(limit) ) @@ -71,7 +68,6 @@ async def report_dwell( db: AsyncSession = Depends(get_db), user: User | None = Depends(get_optional_user), ): - """Report dwell time signal for recommendation engine.""" from app.models import EngagementEvent event = EngagementEvent( @@ -83,4 +79,3 @@ async def report_dwell( event_metadata={"replayed": body.replayed}, ) db.add(event) - # TODO: Update user taste vector (Track F) diff --git a/services/api/app/routers/shaders.py b/services/api/app/routers/shaders.py index 75d3ea7..ba93624 100644 --- a/services/api/app/routers/shaders.py +++ b/services/api/app/routers/shaders.py @@ -1,4 +1,4 @@ -"""Shaders router — CRUD, submit, fork, search.""" +"""Shaders router — CRUD, versioning, drafts, fork, search.""" from uuid import UUID from datetime import datetime, timezone @@ -7,25 +7,31 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func from app.database import get_db -from app.models import User, Shader -from app.schemas import ShaderCreate, ShaderUpdate, ShaderPublic +from app.models import User, Shader, ShaderVersion +from app.schemas import ShaderCreate, ShaderUpdate, ShaderPublic, ShaderVersionPublic from app.middleware.auth import get_current_user, get_optional_user from app.services.glsl_validator import validate_glsl router = APIRouter() +# ── Public list / search ────────────────────────────────── + @router.get("", response_model=list[ShaderPublic]) async def list_shaders( q: str | None = Query(None, description="Search query"), tags: list[str] | None = Query(None, description="Filter by tags"), shader_type: str | None = Query(None, description="Filter by type: 2d, 3d, audio-reactive"), sort: str = Query("trending", description="Sort: trending, new, top"), + is_system: bool | None = Query(None, description="Filter to system/platform shaders"), limit: int = Query(20, ge=1, le=50), offset: int = Query(0, ge=0), db: AsyncSession = Depends(get_db), ): - query = select(Shader).where(Shader.is_public == True, Shader.render_status == "ready") + query = select(Shader).where( + Shader.is_public == True, + Shader.status == "published", + ) if q: query = query.where(Shader.title.ilike(f"%{q}%")) @@ -33,12 +39,14 @@ async def list_shaders( query = query.where(Shader.tags.overlap(tags)) if shader_type: query = query.where(Shader.shader_type == shader_type) + if is_system is not None: + query = query.where(Shader.is_system == is_system) if sort == "new": query = query.order_by(Shader.created_at.desc()) elif sort == "top": query = query.order_by(Shader.score.desc()) - else: # trending + else: query = query.order_by(Shader.score.desc(), Shader.created_at.desc()) query = query.limit(limit).offset(offset) @@ -46,6 +54,27 @@ async def list_shaders( return result.scalars().all() +# ── My shaders (workspace) ─────────────────────────────── + +@router.get("/mine", response_model=list[ShaderPublic]) +async def my_shaders( + status_filter: str | None = Query(None, alias="status", description="draft, published, archived"), + limit: int = Query(50, ge=1, le=100), + offset: int = Query(0, ge=0), + db: AsyncSession = Depends(get_db), + user: User = Depends(get_current_user), +): + """List the authenticated user's shaders — drafts, published, archived.""" + query = select(Shader).where(Shader.author_id == user.id) + if status_filter: + query = query.where(Shader.status == status_filter) + query = query.order_by(Shader.updated_at.desc()).limit(limit).offset(offset) + result = await db.execute(query) + return result.scalars().all() + + +# ── Single shader ───────────────────────────────────────── + @router.get("/{shader_id}", response_model=ShaderPublic) async def get_shader( shader_id: UUID, @@ -57,47 +86,87 @@ async def get_shader( if not shader: raise HTTPException(status_code=404, detail="Shader not found") + # Drafts are only visible to their author + if shader.status == "draft" and (not user or user.id != shader.author_id): + raise HTTPException(status_code=404, detail="Shader not found") if not shader.is_public and (not user or user.id != shader.author_id): raise HTTPException(status_code=404, detail="Shader not found") - # Increment view count shader.view_count += 1 return shader +# ── Version history ─────────────────────────────────────── + +@router.get("/{shader_id}/versions", response_model=list[ShaderVersionPublic]) +async def list_versions( + shader_id: UUID, + db: AsyncSession = Depends(get_db), + user: User | None = Depends(get_optional_user), +): + """Get the version history of a shader.""" + shader = (await db.execute(select(Shader).where(Shader.id == shader_id))).scalar_one_or_none() + if not shader: + raise HTTPException(status_code=404, detail="Shader not found") + if shader.status == "draft" and (not user or user.id != shader.author_id): + raise HTTPException(status_code=404, detail="Shader not found") + + result = await db.execute( + select(ShaderVersion) + .where(ShaderVersion.shader_id == shader_id) + .order_by(ShaderVersion.version_number.desc()) + ) + return result.scalars().all() + + +@router.get("/{shader_id}/versions/{version_number}", response_model=ShaderVersionPublic) +async def get_version( + shader_id: UUID, + version_number: int, + db: AsyncSession = Depends(get_db), +): + result = await db.execute( + select(ShaderVersion).where( + ShaderVersion.shader_id == shader_id, + ShaderVersion.version_number == version_number, + ) + ) + version = result.scalar_one_or_none() + if not version: + raise HTTPException(status_code=404, detail="Version not found") + return version + + +# ── Create shader (draft or published) ─────────────────── + @router.post("", response_model=ShaderPublic, status_code=status.HTTP_201_CREATED) async def create_shader( body: ShaderCreate, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): - # Rate limit: free tier gets 5 submissions/month - if user.subscription_tier == "free": + # Rate limit published shaders for free tier (drafts are unlimited) + if body.status == "published" and user.subscription_tier == "free": month_start = datetime.now(timezone.utc).replace(day=1, hour=0, minute=0, second=0, microsecond=0) count_result = await db.execute( select(func.count()).select_from(Shader).where( Shader.author_id == user.id, + Shader.status == "published", Shader.created_at >= month_start, ) ) monthly_count = count_result.scalar() if monthly_count >= 5: - raise HTTPException( - status_code=429, - detail="Free tier: 5 shader submissions per month. Upgrade to Pro for unlimited." - ) + raise HTTPException(status_code=429, detail="Free tier: 5 published shaders/month. Upgrade to Pro for unlimited.") # Validate GLSL validation = validate_glsl(body.glsl_code, body.shader_type) if not validation.valid: - raise HTTPException( - status_code=422, - detail={ - "message": "GLSL validation failed", - "errors": validation.errors, - "warnings": validation.warnings, - } - ) + raise HTTPException(status_code=422, detail={ + "message": "GLSL validation failed", + "errors": validation.errors, + "warnings": validation.warnings, + }) shader = Shader( author_id=user.id, @@ -106,23 +175,37 @@ async def create_shader( glsl_code=body.glsl_code, tags=body.tags, shader_type=body.shader_type, - is_public=body.is_public, + is_public=body.is_public if body.status == "published" else False, + status=body.status, style_metadata=body.style_metadata, - render_status="pending", + render_status="ready" if body.status == "draft" else "pending", + current_version=1, ) db.add(shader) await db.flush() - # Enqueue render job - from app.worker import celery_app - try: - celery_app.send_task("render_shader", args=[str(shader.id)]) - except Exception: - # If Celery isn't available (dev without worker), mark as ready - # with no thumbnail — the frontend can still render live - shader.render_status = "ready" + # Create version 1 snapshot + v1 = ShaderVersion( + shader_id=shader.id, + version_number=1, + glsl_code=body.glsl_code, + title=body.title, + description=body.description, + tags=body.tags, + style_metadata=body.style_metadata, + change_note="Initial version", + ) + db.add(v1) - # If this shader fulfills a desire, link them + # Enqueue render for published shaders + if body.status == "published": + from app.worker import celery_app + try: + celery_app.send_task("render_shader", args=[str(shader.id)]) + except Exception: + shader.render_status = "ready" + + # Link to desire if fulfilling if body.fulfills_desire_id: from app.models import Desire desire = (await db.execute( @@ -136,6 +219,8 @@ async def create_shader( return shader +# ── Update shader (creates new version) ────────────────── + @router.put("/{shader_id}", response_model=ShaderPublic) async def update_shader( shader_id: UUID, @@ -151,20 +236,40 @@ async def update_shader( raise HTTPException(status_code=403, detail="Not the shader owner") updates = body.model_dump(exclude_unset=True) + change_note = updates.pop("change_note", None) + code_changed = "glsl_code" in updates # Re-validate GLSL if code changed - if "glsl_code" in updates: + if code_changed: validation = validate_glsl(updates["glsl_code"], shader.shader_type) if not validation.valid: - raise HTTPException( - status_code=422, - detail={ - "message": "GLSL validation failed", - "errors": validation.errors, - "warnings": validation.warnings, - } - ) - # Re-render if code changed + raise HTTPException(status_code=422, detail={ + "message": "GLSL validation failed", + "errors": validation.errors, + "warnings": validation.warnings, + }) + + # Apply updates + for field, value in updates.items(): + setattr(shader, field, value) + + # Create a new version snapshot if code or metadata changed + if code_changed or "title" in updates or "description" in updates or "tags" in updates: + shader.current_version += 1 + new_version = ShaderVersion( + shader_id=shader.id, + version_number=shader.current_version, + glsl_code=shader.glsl_code, + title=shader.title, + description=shader.description, + tags=shader.tags, + style_metadata=shader.style_metadata, + change_note=change_note, + ) + db.add(new_version) + + # Re-render if code changed and shader is published + if code_changed and shader.status == "published": shader.render_status = "pending" from app.worker import celery_app try: @@ -172,13 +277,22 @@ async def update_shader( except Exception: shader.render_status = "ready" - for field, value in updates.items(): - setattr(shader, field, value) + # If publishing a draft, ensure it's public and queue render + if "status" in updates and updates["status"] == "published" and shader.render_status != "ready": + shader.is_public = True + shader.render_status = "pending" + from app.worker import celery_app + try: + celery_app.send_task("render_shader", args=[str(shader.id)]) + except Exception: + shader.render_status = "ready" shader.updated_at = datetime.now(timezone.utc) return shader +# ── Delete ──────────────────────────────────────────────── + @router.delete("/{shader_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_shader( shader_id: UUID, @@ -191,10 +305,11 @@ async def delete_shader( raise HTTPException(status_code=404, detail="Shader not found") if shader.author_id != user.id and user.role != "admin": raise HTTPException(status_code=403, detail="Not the shader owner") - await db.delete(shader) +# ── Fork ────────────────────────────────────────────────── + @router.post("/{shader_id}/fork", response_model=ShaderPublic, status_code=status.HTTP_201_CREATED) async def fork_shader( shader_id: UUID, @@ -205,7 +320,7 @@ async def fork_shader( original = result.scalar_one_or_none() if not original: raise HTTPException(status_code=404, detail="Shader not found") - if not original.is_public: + if not original.is_public and original.status != "published": raise HTTPException(status_code=404, detail="Shader not found") forked = Shader( @@ -217,16 +332,83 @@ async def fork_shader( shader_type=original.shader_type, forked_from=original.id, style_metadata=original.style_metadata, - render_status="pending", + status="draft", # Forks start as drafts + is_public=False, + render_status="ready", + current_version=1, ) db.add(forked) await db.flush() - # Enqueue render for the fork - from app.worker import celery_app - try: - celery_app.send_task("render_shader", args=[str(forked.id)]) - except Exception: - forked.render_status = "ready" + v1 = ShaderVersion( + shader_id=forked.id, + version_number=1, + glsl_code=original.glsl_code, + title=forked.title, + description=forked.description, + tags=original.tags, + style_metadata=original.style_metadata, + change_note=f"Forked from {original.title}", + ) + db.add(v1) return forked + + +# ── Restore a version ──────────────────────────────────── + +@router.post("/{shader_id}/versions/{version_number}/restore", response_model=ShaderPublic) +async def restore_version( + shader_id: UUID, + version_number: int, + db: AsyncSession = Depends(get_db), + user: User = Depends(get_current_user), +): + """Restore a shader to a previous version (creates a new version snapshot).""" + shader = (await db.execute(select(Shader).where(Shader.id == shader_id))).scalar_one_or_none() + if not shader: + raise HTTPException(status_code=404, detail="Shader not found") + if shader.author_id != user.id and user.role != "admin": + raise HTTPException(status_code=403, detail="Not the shader owner") + + version = (await db.execute( + select(ShaderVersion).where( + ShaderVersion.shader_id == shader_id, + ShaderVersion.version_number == version_number, + ) + )).scalar_one_or_none() + if not version: + raise HTTPException(status_code=404, detail="Version not found") + + # Apply version data to shader + shader.glsl_code = version.glsl_code + shader.title = version.title + shader.description = version.description + shader.tags = version.tags + shader.style_metadata = version.style_metadata + shader.current_version += 1 + shader.updated_at = datetime.now(timezone.utc) + + # Create a new version snapshot for the restore + restore_v = ShaderVersion( + shader_id=shader.id, + version_number=shader.current_version, + glsl_code=version.glsl_code, + title=version.title, + description=version.description, + tags=version.tags, + style_metadata=version.style_metadata, + change_note=f"Restored from version {version_number}", + ) + db.add(restore_v) + + # Re-render if published + if shader.status == "published": + shader.render_status = "pending" + from app.worker import celery_app + try: + celery_app.send_task("render_shader", args=[str(shader.id)]) + except Exception: + shader.render_status = "ready" + + return shader diff --git a/services/api/app/schemas/schemas.py b/services/api/app/schemas/schemas.py index e2de2bb..7a66ee9 100644 --- a/services/api/app/schemas/schemas.py +++ b/services/api/app/schemas/schemas.py @@ -35,6 +35,7 @@ class UserPublic(BaseModel): id: UUID username: str role: str + is_system: bool subscription_tier: str is_verified_creator: bool created_at: datetime @@ -53,7 +54,6 @@ class UserUpdate(BaseModel): class ByokKeysUpdate(BaseModel): - """Bring Your Own Key — encrypted API keys for AI providers.""" anthropic_key: Optional[str] = Field(None, description="Anthropic API key") openai_key: Optional[str] = Field(None, description="OpenAI API key") ollama_endpoint: Optional[str] = Field(None, description="Ollama endpoint URL") @@ -70,6 +70,7 @@ class ShaderCreate(BaseModel): tags: list[str] = Field(default_factory=list, max_length=10) shader_type: str = Field(default="2d", pattern=r"^(2d|3d|audio-reactive)$") is_public: bool = True + status: str = Field(default="published", pattern=r"^(draft|published)$") style_metadata: Optional[dict] = None fulfills_desire_id: Optional[UUID] = None @@ -80,6 +81,8 @@ class ShaderUpdate(BaseModel): glsl_code: Optional[str] = Field(None, min_length=10) tags: Optional[list[str]] = None is_public: Optional[bool] = None + status: Optional[str] = Field(None, pattern=r"^(draft|published|archived)$") + change_note: Optional[str] = Field(None, max_length=200) class ShaderPublic(BaseModel): @@ -90,9 +93,12 @@ class ShaderPublic(BaseModel): title: str description: Optional[str] glsl_code: str + status: str is_public: bool is_ai_generated: bool + is_system: bool ai_provider: Optional[str] + system_label: Optional[str] thumbnail_url: Optional[str] preview_url: Optional[str] render_status: str @@ -100,6 +106,7 @@ class ShaderPublic(BaseModel): tags: list[str] shader_type: str forked_from: Optional[UUID] + current_version: int view_count: int score: float created_at: datetime @@ -121,10 +128,28 @@ class ShaderFeedItem(BaseModel): score: float view_count: int is_ai_generated: bool + is_system: bool + system_label: Optional[str] style_metadata: Optional[dict] created_at: datetime +class ShaderVersionPublic(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: UUID + shader_id: UUID + version_number: int + glsl_code: str + title: str + description: Optional[str] + tags: list[str] + style_metadata: Optional[dict] + change_note: Optional[str] + thumbnail_url: Optional[str] + created_at: datetime + + # ════════════════════════════════════════════════════════════ # VOTES & ENGAGEMENT # ════════════════════════════════════════════════════════════ @@ -170,13 +195,13 @@ class DesirePublic(BaseModel): class GenerateRequest(BaseModel): prompt: str = Field(..., min_length=5, max_length=500) - provider: Optional[str] = None # anthropic, openai, ollama — auto-selected if None + provider: Optional[str] = None style_metadata: Optional[dict] = None class GenerateStatusResponse(BaseModel): job_id: str - status: str # queued, generating, rendering, complete, failed + status: str shader_id: Optional[UUID] = None error: Optional[str] = None @@ -202,7 +227,6 @@ class ApiKeyPublic(BaseModel): class ApiKeyCreated(ApiKeyPublic): - """Returned only on creation — includes the full key (shown once).""" full_key: str diff --git a/services/frontend/src/App.tsx b/services/frontend/src/App.tsx index 83871a7..e48b3d3 100644 --- a/services/frontend/src/App.tsx +++ b/services/frontend/src/App.tsx @@ -4,6 +4,7 @@ import Feed from './pages/Feed'; import Explore from './pages/Explore'; import ShaderDetail from './pages/ShaderDetail'; import Editor from './pages/Editor'; +import MyShaders from './pages/MyShaders'; import Generate from './pages/Generate'; import Bounties from './pages/Bounties'; import BountyDetail from './pages/BountyDetail'; @@ -21,6 +22,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/services/frontend/src/components/Navbar.tsx b/services/frontend/src/components/Navbar.tsx index 9d3c105..014acdc 100644 --- a/services/frontend/src/components/Navbar.tsx +++ b/services/frontend/src/components/Navbar.tsx @@ -41,6 +41,7 @@ export default function Navbar() {
{isAuthenticated() && user ? ( <> + My Shaders (null); + const [editingExisting, setEditingExisting] = useState(false); + + // Resizable pane state + const [editorWidth, setEditorWidth] = useState(50); // percentage + const isDragging = useRef(false); + const containerRef = useRef(null); const debounceRef = useRef>(); - // Load existing shader for forking + // Load existing shader for editing or forking const { data: existingShader } = useQuery({ queryKey: ['shader', id], queryFn: async () => { @@ -64,22 +74,61 @@ export default function Editor() { if (existingShader) { setCode(existingShader.glsl_code); setLiveCode(existingShader.glsl_code); - setTitle(`Fork of ${existingShader.title}`); + setTitle(existingShader.title); + setDescription(existingShader.description || ''); setShaderType(existingShader.shader_type); setTags(existingShader.tags?.join(', ') || ''); - } - }, [existingShader]); - // Debounced live preview update + // If we own it, we're editing; otherwise forking + if (user && existingShader.author_id === user.id) { + setEditingExisting(true); + } else { + setTitle(`Fork of ${existingShader.title}`); + setEditingExisting(false); + } + } + }, [existingShader, user]); + + // ── Drag handle for resizable pane ────────────────────── + const handleMouseDown = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + isDragging.current = true; + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + }, []); + + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (!isDragging.current || !containerRef.current) return; + const rect = containerRef.current.getBoundingClientRect(); + const pct = ((e.clientX - rect.left) / rect.width) * 100; + setEditorWidth(Math.max(20, Math.min(80, pct))); + }; + const handleMouseUp = () => { + isDragging.current = false; + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + }; + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + }, []); + + // ── Debounced live preview ────────────────────────────── const handleCodeChange = useCallback((value: string) => { setCode(value); + setSavedStatus(null); if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => { setLiveCode(value); }, 400); }, []); - const handleSubmit = async () => { + // ── Save / Publish ───────────────────────────────────── + const handleSave = async (publishStatus: 'draft' | 'published') => { if (!isAuthenticated()) { navigate('/login'); return; @@ -88,21 +137,44 @@ export default function Editor() { setSubmitting(true); setSubmitError(''); + const payload = { + title, + description, + glsl_code: code, + tags: tags.split(',').map(t => t.trim()).filter(Boolean), + shader_type: shaderType, + status: publishStatus, + is_public: publishStatus === 'published', + }; + try { - const { data } = await api.post('/shaders', { - title, - description, - glsl_code: code, - tags: tags.split(',').map(t => t.trim()).filter(Boolean), - shader_type: shaderType, - }); - navigate(`/shader/${data.id}`); + if (editingExisting && id) { + // Update existing shader + const { data } = await api.put(`/shaders/${id}`, { + ...payload, + change_note: publishStatus === 'published' ? 'Updated' : undefined, + }); + setSavedStatus(publishStatus === 'draft' ? 'Draft saved' : 'Published'); + if (publishStatus === 'published') { + setTimeout(() => navigate(`/shader/${data.id}`), 800); + } + } else { + // Create new shader + const { data } = await api.post('/shaders', payload); + if (publishStatus === 'published') { + navigate(`/shader/${data.id}`); + } else { + // Redirect to editor with the new ID so subsequent saves are updates + setSavedStatus('Draft saved'); + navigate(`/editor/${data.id}`, { replace: true }); + } + } } catch (err: any) { const detail = err.response?.data?.detail; if (typeof detail === 'object' && detail.errors) { setSubmitError(detail.errors.join('\n')); } else { - setSubmitError(detail || 'Submission failed'); + setSubmitError(detail || 'Save failed'); } } finally { setSubmitting(false); @@ -119,7 +191,7 @@ export default function Editor() { value={title} onChange={(e) => setTitle(e.target.value)} className="bg-transparent text-lg font-medium text-gray-100 focus:outline-none - border-b border-transparent focus:border-fracta-500 transition-colors" + border-b border-transparent focus:border-fracta-500 transition-colors w-64" placeholder="Shader title..." /> + {editingExisting && existingShader && ( + + v{existingShader.current_version} + + )}
@@ -136,8 +213,18 @@ export default function Editor() { ⚠ {compileError.split('\n')[0]} )} + {savedStatus && ( + {savedStatus} + )} +
)} - {/* Split pane: editor + preview */} -
+ {/* Split pane: editor + drag handle + preview */} +
{/* Code editor */} -
+
fragment.glsl @@ -211,8 +298,19 @@ export default function Editor() { />
+ {/* Drag handle */} +
+
{/* Wider hit area */} +
+
+ {/* Live preview */} -
+
('all'); + + if (!isAuthenticated()) { + navigate('/login'); + return null; + } + + const { data: shaders = [], isLoading } = useQuery({ + queryKey: ['my-shaders', tab], + queryFn: async () => { + const params: any = { limit: 100 }; + if (tab !== 'all') params.status = tab; + const { data } = await api.get('/shaders/mine', { params }); + return data; + }, + }); + + const archiveMutation = useMutation({ + mutationFn: async (id: string) => { + await api.put(`/shaders/${id}`, { status: 'archived' }); + }, + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['my-shaders'] }), + }); + + const publishMutation = useMutation({ + mutationFn: async (id: string) => { + await api.put(`/shaders/${id}`, { status: 'published', is_public: true }); + }, + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['my-shaders'] }), + }); + + const deleteMutation = useMutation({ + mutationFn: async (id: string) => { + await api.delete(`/shaders/${id}`); + }, + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['my-shaders'] }), + }); + + const counts = { + all: shaders.length, + draft: shaders.filter((s: any) => s.status === 'draft').length, + published: shaders.filter((s: any) => s.status === 'published').length, + archived: shaders.filter((s: any) => s.status === 'archived').length, + }; + + return ( +
+
+
+

My Shaders

+

+ Your workspace — drafts, published shaders, and version history. +

+
+ + New Shader +
+ + {/* Status tabs */} +
+ {(['all', 'draft', 'published', 'archived'] as StatusTab[]).map((s) => ( + + ))} +
+ + {/* Shader list */} + {isLoading ? ( +
+ {Array.from({ length: 6 }).map((_, i) => ( +
+
+
+
+
+
+
+ ))} +
+ ) : shaders.length > 0 ? ( +
+ {shaders.map((shader: any) => ( +
+ +
+ + {/* Status badge */} + + {shader.status} + + {/* Version badge */} + + v{shader.current_version} + +
+ +
+ +

+ {shader.title} +

+ +
+ + {new Date(shader.updated_at).toLocaleDateString()} · {shader.shader_type} + +
+ {shader.status === 'draft' && ( + <> + + Edit + + + + )} + {shader.status === 'published' && ( + <> + + Edit + + + + )} + {shader.status === 'archived' && ( + <> + + + + )} +
+
+
+
+ ))} +
+ ) : ( +
+

No {tab === 'all' ? '' : tab + ' '}shaders yet

+ Create Your First Shader +
+ )} +
+ ); +}