Files
glitch_bloodsugar/CLAUDE.md
T

406 lines
11 KiB
Markdown
Raw Normal View History

2026-03-24 11:30:14 +01:00
# 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<string, unknown>;
}
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<string, unknown>;
}
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 (
<div className={`${styles.container} ${className || ''}`} style={cssVars}>
<h2 className={styles.title}>My GlitchComponent</h2>
<button className={styles.button} onClick={() => handleComplete(true, 100)}>
Complete
</button>
</div>
);
}
```
---
## 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<string, unknown>);
const params = useControls(levaSchema);
return (
<div style={{ minHeight: '100vh', background: '#0a0a0f', padding: '2rem' }}>
<Leva collapsed={false} />
<Component
config={{ id: 'dev', name: metadata.name, version: metadata.version, params }}
theme={{ primary: '#6366f1', accent: '#22d3ee', bg: '#0a0a0f', bgSecondary: '#12121a', text: '#e8e8ec', textMuted: '#9999a8', border: '#2a2a3a' }}
onComplete={(r) => { console.log('Complete:', r); alert(`Done! Score: ${r.score}`); }}
/>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')!).render(<React.StrictMode><DevHarness /></React.StrictMode>);
```
---
## 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`