# Create GlitchComponent Create a new GlitchComponent project that conforms to the standardized plugin architecture for Glitch University. ## Arguments - `$ARGUMENTS` - Component name (e.g., "matrix-rain", "particle-system") ## Instructions When creating a new GlitchComponent: 1. **If no directory specified**, create in a sibling directory: `../glitch-components/$ARGUMENTS/` 2. **Create the full project structure**: ``` $ARGUMENTS/ ├── src/ │ ├── index.tsx # Main export with metadata │ ├── Component.tsx # Component implementation │ ├── styles.module.css # Scoped CSS styles │ ├── types.ts # TypeScript interfaces │ └── dev.tsx # Leva dev harness ├── vite.config.ts ├── package.json ├── tsconfig.json └── index.html ``` 3. After creation, explain how to: - Run `npm install && npm run dev` for local development - Build with `npm run build` - Deploy output to host app --- ## Architecture Principles 1. **Build as Library**: Use Vite Library Mode to output a self-contained bundle 2. **CSS Isolation**: Use CSS Modules to prevent style bleeding 3. **Inversion of Control**: Accept props for all configurable parameters 4. **Local Dev Support**: Use Leva controls during development, props in production --- ## Required TypeScript Interfaces ```typescript // src/types.ts export interface GlitchComponentConfig { id: string; name: string; version: string; params: Record; } export interface GlitchComponentProps { config: GlitchComponentConfig; onComplete: (result: GlitchComponentResult) => void; onProgress?: (percent: number) => void; theme?: GlitchTheme; className?: string; } export interface GlitchComponentResult { success: boolean; score?: number; data?: unknown; error?: string; } export interface GlitchTheme { primary: string; // #6366f1 accent: string; // #22d3ee bg: string; // #0a0a0f bgSecondary: string; // #12121a text: string; // #e8e8ec textMuted: string; // #9999a8 border: string; // #2a2a3a } export interface GlitchComponentMetadata { name: string; displayName: string; version: string; paramSchema: ParamSchema; defaultParams: Record; } export interface ParamSchema { [key: string]: { type: 'number' | 'string' | 'boolean' | 'color' | 'select' | 'range'; label?: string; description?: string; default: unknown; options?: { value: string | number; label: string }[]; min?: number; max?: number; step?: number; }; } ``` --- ## Main Export Structure ```typescript // src/index.tsx import Component from './Component'; import type { GlitchComponentMetadata } from './types'; export default Component; export const metadata: GlitchComponentMetadata = { name: 'my-component', displayName: 'My Component', version: '1.0.0', paramSchema: { speed: { type: 'range', label: 'Speed', default: 1.0, min: 0.1, max: 5.0, step: 0.1 }, primaryColor: { type: 'color', label: 'Color', default: '#6366f1' } }, defaultParams: { speed: 1.0, primaryColor: '#6366f1' } }; export type { GlitchComponentProps, GlitchComponentConfig, GlitchComponentResult } from './types'; ``` --- ## Component Template ```typescript // src/Component.tsx import { useCallback, useEffect, useState } from 'react'; import styles from './styles.module.css'; import type { GlitchComponentProps } from './types'; interface ComponentParams { speed: number; primaryColor: string; } export default function MyComponent({ config, onComplete, onProgress, theme, className }: GlitchComponentProps) { const params = config.params as ComponentParams; const { speed = 1.0, primaryColor } = params; const handleComplete = useCallback((success: boolean, score?: number) => { onComplete({ success, score, data: { completedAt: new Date().toISOString() } }); }, [onComplete]); const cssVars = { '--gc-primary': primaryColor || theme?.primary || '#6366f1', '--gc-accent': theme?.accent || '#22d3ee', '--gc-bg': theme?.bg || '#0a0a0f', '--gc-text': theme?.text || '#e8e8ec', } as React.CSSProperties; return (

My GlitchComponent

); } ``` --- ## CSS Module Template ```css /* src/styles.module.css */ .container { background: var(--gc-bg, #0a0a0f); color: var(--gc-text, #e8e8ec); padding: 2rem; border-radius: 12px; box-sizing: border-box; font-family: system-ui, -apple-system, sans-serif; line-height: 1.5; } .container *, .container *::before, .container *::after { box-sizing: inherit; } .title { color: var(--gc-primary, #6366f1); margin: 0 0 1rem; font-size: 1.5rem; font-weight: 600; } .button { background: var(--gc-primary, #6366f1); color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 8px; font-size: 1rem; cursor: pointer; transition: opacity 0.2s ease; } .button:hover { opacity: 0.9; } .button:disabled { opacity: 0.5; cursor: not-allowed; } ``` --- ## Vite Configuration ```typescript // vite.config.ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'; import { resolve } from 'path'; export default defineConfig(({ mode }) => ({ plugins: [react(), cssInjectedByJsPlugin()], build: { lib: { entry: resolve(__dirname, 'src/index.tsx'), name: 'MyGlitchComponent', fileName: 'my-glitch-component', formats: ['es'] }, rollupOptions: { external: ['react', 'react-dom', 'react/jsx-runtime'], output: { globals: { react: 'React', 'react-dom': 'ReactDOM', 'react/jsx-runtime': 'jsxRuntime' }, assetFileNames: 'assets/[name][extname]' } }, sourcemap: true, minify: mode === 'production' }, server: { port: 3001, open: true } })); ``` --- ## Package.json ```json { "name": "my-glitch-component", "version": "1.0.0", "type": "module", "main": "./dist/my-glitch-component.js", "module": "./dist/my-glitch-component.js", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" }, "devDependencies": { "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.2.0", "leva": "^0.9.35", "typescript": "^5.3.0", "vite": "^5.0.0", "vite-plugin-css-injected-by-js": "^3.3.0" } } ``` --- ## Dev Harness with Leva ```typescript // src/dev.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import { useControls, Leva } from 'leva'; import Component from './Component'; import { metadata } from './index'; function DevHarness() { const levaSchema = Object.entries(metadata.paramSchema).reduce((acc, [key, schema]) => { if (schema.type === 'range' || schema.type === 'number') { acc[key] = { value: schema.default, min: schema.min, max: schema.max, step: schema.step }; } else if (schema.type === 'select') { acc[key] = { value: schema.default, options: schema.options?.reduce((o, opt) => ({ ...o, [opt.label]: opt.value }), {}) }; } else { acc[key] = schema.default; } return acc; }, {} as Record); const params = useControls(levaSchema); return (
{ console.log('Complete:', r); alert(`Done! Score: ${r.score}`); }} />
); } ReactDOM.createRoot(document.getElementById('root')!).render(); ``` --- ## Three.js / R3F Components For 3D components, add to externals in vite.config.ts: ```typescript external: ['react', 'react-dom', 'react/jsx-runtime', 'three', '@react-three/fiber', '@react-three/drei'] ``` --- ## Deployment 0. `nvm use 20` # to select node version 20 1. `npm run build` 2. Copy `dist/*.js` to host app's `src/components/glitch/` 3. Add database entry: ```sql INSERT INTO glitch_components (name, display_name, version, file_path, param_schema, default_params) VALUES ('my-component', 'My Component', '1.0.0', 'my-component.js', '{"speed":{"type":"range","default":1.0}}', '{"speed":1.0}'); ``` 4. Link to tech via `glitch_component_id`