# Create GlitchComponent Create a new GlitchComponent project that conforms to the standardized plugin architecture for Glitch University. A glitchComponent is a way to create mini-games and functionality packages that can be easily included within Glitch University Goal: Given this skill file, any capable developer should be able to create a GlitchComponent. The js file can be copied from dist into the public folder. Then the component is registered, and from there - integrated within Glitch University. Goal is to test Component mini-games outside the scope of the full platform. A glitchComponent is a way to create mini-games and functionality packages that can be easily included within Glitch University. The tradeoffs are of course CSS style leakage (good and bad), sound system compatibility etc. ## 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 `cd backend & 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; } ``` --- ## CSS variables The following css variables are defined in the master project. Using these in the packaged css should pick up the variables value in the deployed context. For the testing context, please use the the following variables and definitions in a css file ````css --color-bg: #0a0a0f; --color-bg-secondary: #12121a; --color-text: #e8e8ec; --color-text-muted: #9999a8; --color-primary: #6366f1; --color-primary-hover: #818cf8; --color-accent: #22d3ee; --color-accent-secondary: #a855f7; --color-border: #2a2a3a; --font-main: 'Iceland', -apple-system, BlinkMacSystemFont, sans-serif; --font-display: 'Russo One', -apple-system, BlinkMacSystemFont, sans-serif; --font-mono: 'JetBrains Mono', 'Iceland', monospace; --small-font: 1.875rem; --spacing-xs: 0.5rem; --spacing-sm: 1rem; --spacing-md: 2rem; --spacing-lg: 4rem; --spacing-xl: 6rem; --max-width: 1200px; --border-radius: 12px; --border-radius-sm: 8px; --transition: 0.2s ease; ``` ## Sounds If you want to use sounds in the component, note that the app uses Howler, and sound files are defined in the constant like this in the parent project, ``` const SOUND_DEFINITIONS = { // ========================================== // UI - General interactions // ========================================== 'ui.button_click': { file: 'pop-402323.mp3', folder: 'ui', volume: 0.4 }, 'ui.button_hover': { file: 'pop-402323.mp3', folder: 'ui', volume: 0.15 }, } ``` These sounds are called using code like this ``` playSound('ui.button_click'); ``` This for info so that the sound system created for the componet can work with the containing framework. `--- ## 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`