Adding updates to fartmachine

This commit is contained in:
2026-03-17 22:06:41 +01:00
commit 9e15e32b94
56 changed files with 7306 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
# How to Teach Antigravity New Skills
To teach me a new skill that persists across different sessions and folders, you can add it to your personal skills library.
## 1. Location
Skills are stored in your home directory under `.gemini/antigravity/skills`.
I have just created this directory for you if it didn't exist:
`/Users/jenstandstad/.gemini/antigravity/skills`
## 2. Structure
Each skill should be its own folder within the `skills` directory. The folder name should be the skill's identifier (e.g., `react-best-practices` or `python-data-analysis`).
Inside the skill folder, you **MUST** include a `SKILL.md` file.
Example structure:
```
/Users/jenstandstad/.gemini/antigravity/skills/
└── my-custom-skill/
├── SKILL.md (Required)
├── scripts/ (Optional: helper scripts)
└── templates/ (Optional: code templates)
```
## 3. The `SKILL.md` Format
The `SKILL.md` file is the core of the skill. It must start with YAML frontmatter defining its name and description, followed by the markdown instructions.
**Template:**
```markdown
---
name: [Skill Name]
description: [Short description of what this skill does and when I should use it]
---
# [Skill Name] Instructions
[Detailed instructions on how to perform the skill.]
## Guidelines
- Rule 1
- Rule 2
## Examples
...
```
## 4. How I Use Skills
When you ask me to perform a task, I will check your skills library. If a skill's description matches your request, I will read the `SKILL.md` file and follow its instructions.
## Example: Creating a "Code Review" Skill
1. Create the folder:
`mkdir -p ~/.gemini/antigravity/skills/code-review`
2. Create `~/.gemini/antigravity/skills/code-review/SKILL.md` with:
```markdown
---
name: Strict Code Review
description: Guidelines for performing a strict security and style validaton code review.
---
# Strict Code Review
When reviewing code, focus on:
1. **Security**: Check for injection vulnerabilities.
2. **Performance**: O(n^2) loops are banned.
3. **Style**: Use 4 spaces for indentation in Python.
```
Now, whenever you ask me to "review this code", I will apply these specific rules.
+2
View File
@@ -0,0 +1,2 @@
import type { GlitchComponentProps } from './types';
export default function AssumptionToggle({ config, onComplete, onProgress, theme, className, host }: GlitchComponentProps): import("react/jsx-runtime").JSX.Element;
+560
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
export {};
+8
View File
@@ -0,0 +1,8 @@
import type { GlitchHostBridge } from './types';
export type DevHostBridgeOptions = {
isMuted?: () => boolean;
onSound?: (id: string, payload?: Record<string, unknown>) => void;
onEmit?: (type: string, payload?: unknown) => void;
};
export declare function createDevHostBridge(options?: DevHostBridgeOptions): GlitchHostBridge;
export declare function attachWindowSoundBridge(host: GlitchHostBridge): () => void;
+17
View File
@@ -0,0 +1,17 @@
import type { GlitchHostBridge } from './types';
export declare const HOST_SOUND_EVENT = "glitch:play-sound";
export declare const HOST_STOP_SOUND_EVENT = "glitch:stop-sound";
export declare const HOST_EMIT_PREFIX = "glitch:host:";
export declare const SOUND_IDS: {
readonly click: "ui.button_click";
readonly hover: "ui.button_hover";
readonly computeStart: "machine.compute_start";
readonly computeStep: "machine.compute_step";
readonly computeDone: "machine.compute_done";
readonly verdictStable: "machine.verdict_stable";
readonly verdictAlert: "machine.verdict_alert";
};
export type SoundId = (typeof SOUND_IDS)[keyof typeof SOUND_IDS];
export declare function safePlaySound(host: GlitchHostBridge | undefined, id: string, payload?: Record<string, unknown>): void;
export declare function safeStopSound(host: GlitchHostBridge | undefined, id: string, payload?: Record<string, unknown>): void;
export declare function safeEmit(host: GlitchHostBridge | undefined, type: string, payload?: unknown): void;
+6
View File
@@ -0,0 +1,6 @@
import './styles.css';
import Component from './Component';
import type { GlitchComponentMetadata } from './types';
export default Component;
export declare const metadata: GlitchComponentMetadata;
export type { GlitchComponentProps, GlitchComponentConfig, GlitchComponentResult } from './types';
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+56
View File
@@ -0,0 +1,56 @@
export interface GlitchComponentConfig {
id: string;
name: string;
version: string;
params: Record<string, unknown>;
}
export interface GlitchHostBridge {
playSound?: (id: string, payload?: Record<string, unknown>) => void;
stopSound?: (id: string, payload?: Record<string, unknown>) => void;
emit?: (type: string, payload?: unknown) => void;
}
export interface GlitchComponentProps {
config: GlitchComponentConfig;
onComplete: (result: GlitchComponentResult) => void;
onProgress?: (percent: number) => void;
theme?: GlitchTheme;
className?: string;
host?: GlitchHostBridge;
}
export interface GlitchComponentResult {
success: boolean;
score?: number;
data?: unknown;
error?: string;
}
export interface GlitchTheme {
primary: string;
accent: string;
bg: string;
bgSecondary: string;
text: string;
textMuted: string;
border: string;
}
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;
};
}
+16
View File
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Foundational Assumption Recombinator Tool</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: #496e5b; }
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/dev.tsx"></script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 826 KiB

+1995
View File
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
{
"name": "@nommo/assumption-toggle",
"version": "1.0.0",
"type": "module",
"engines": {
"node": ">=21 <22"
},
"main": "./dist/assumption-toggle.js",
"module": "./dist/assumption-toggle.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/assumption-toggle.js",
"types": "./dist/index.d.ts"
}
},
"files": [
"dist"
],
"scripts": {
"dev": "vite",
"build": "vite build && tsc",
"preview": "vite preview",
"typecheck": "tsc --noEmit"
},
"peerDependencies": {
"react": "^18.2.0 || ^19.0.0",
"react-dom": "^18.2.0 || ^19.0.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@vitejs/plugin-react": "^4.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.3.0",
"vite": "^4.5.0",
"vite-plugin-css-injected-by-js": "^3.3.0"
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+304
View File
@@ -0,0 +1,304 @@
document.addEventListener('DOMContentLoaded', () => {
const switchA = document.getElementById('switchA');
const switchB = document.getElementById('switchB');
const switchC = document.getElementById('switchC');
const resultValue = document.getElementById('resultValue');
const simulateBtn = document.getElementById('simulateBtn');
const backBtn = document.getElementById('backBtn');
const slide1 = document.getElementById('slide1');
const slide2 = document.getElementById('slide2');
const splashSlide = document.getElementById('splashSlide');
const initBtn = document.getElementById('initBtn');
const switches = [switchA, switchB, switchC];
function calculateOutcome() {
const a = switchA.checked; // false: Discrete, true: Continuous
const b = switchB.checked; // false: Finite, true: Infinite Precision
const c = switchC.checked; // false: Point, true: Chunk
// 0 = false, 1 = true
// State presented as binary string ABC
// A: Discrete(0) / Continuous(1)
// B: Finite(0) / Infinite(1)
// C: Point(0) / Chunk(1)
// Logic Mapping (Default placeholder logic based on common physical intuition prompts)
// You can edit this mapping
// 000: Discrete, Finite, Point -> Stable (Digital Physics)
// 001: Discrete, Finite, Chunk -> Stable (Voxel Universe)
// 010: Discrete, Infinite, Point -> Paradoxical (Zeno's paradoxes prone?)
// 011: Discrete, Infinite, Chunk -> Paradoxical
// 100: Continuous, Finite, Point -> Stable (Classical Mechanics approx)
// 101: Continuous, Finite, Chunk -> Paradoxical (Boundary issues?)
// 110: Continuous, Infinite, Point -> Infinite (Field Theory singularities)
// 111: Continuous, Infinite, Chunk -> Paradoxical (Measure problem?)
// Let's implement a simplified look-up for now based on "Paradoxical, Infinite, Stable"
let state = (a ? 4 : 0) + (b ? 2 : 0) + (c ? 1 : 0);
let outcome = "Stable";
let description = "";
let visualClass = "default";
switch (state) {
case 0: // 000: Discrete, Finite, Point
outcome = "Paradoxical";
intermediate = "A point contains > 1 bit of information?";
description = "space not really discrete ";
visualClass = "paradox-point";
break;
case 1: // 001: Discrete, Finite, Chunk
outcome = "Digital physics";
intermediate = "Instruction programs are finite length";
description = "Contradicts isotropy!";
visualClass = "stable-chunk";
break;
case 2: // 010: Discrete, Infinite, Point
outcome = "Memory paradox";
intermediate = "Instruction programs are infintely long";
description = "infinite memory in each cell";
visualClass = "paradox-point";
break;
case 3: // 011: Discrete, Infinite, Chunk
outcome = "Memory paradox";
intermediate = "Instruction programs are infintely long";
description = "Infinitely big particles!";
visualClass = "paradox-chunk";
break;
case 4: // 100: Continuous, Finite, Point
outcome = "Non-standard";
intermediate = "not isotropic";
description = "contradicts isotropy!";
visualClass = "non-standard-particle-point";
break;
case 5: // 101: Continuous, Finite, Chunk
outcome = "Non-standard";
intermediate = "not isotropic";
description = "but finite precision contradicts standard assumptions!";
visualClass = "non-standard-particle-chunk";
break;
case 6: // 110: Continuous, Infinite, Point
outcome = "Standard physics";
intermediate = "isotropic";
description = "Particles are not emergent";
visualClass = "standard-particle-point";
break;
case 7: // 111: Continuous, Infinite, Chunk
outcome = "Uncertain";
intermediate = "isotropic";
description = "But what is the center of a chunk?";
visualClass = "standard-particle-chunk";
break;
}
updateUI(outcome, description, visualClass, intermediate);
}
function updateUI(outcome, description, visualClass, intermediate) {
const visualization = document.getElementById('visualization');
const artCanvas = visualization.querySelector('.art-canvas');
const descriptionText = document.getElementById('descriptionText');
const intermediateText = document.getElementById('intermediateResult');
const assumptionsText = document.getElementById('assumptionsText'); // New field
// Construct Assumptions String
const switchA = document.getElementById('switchA');
const switchB = document.getElementById('switchB');
const switchC = document.getElementById('switchC');
const textA = switchA.checked ? "Continuous universe" : "Discrete universe";
const textB = switchB.checked ? "Infinite precision" : "Finite precision";
const textC = switchC.checked ? "Chunk particles" : "Point particles";
const assumptionsString = `${textA} + ${textB} + ${textC}`;
// Animate out
resultValue.style.opacity = 0;
descriptionText.style.opacity = 0;
artCanvas.style.opacity = 0;
if (assumptionsText) assumptionsText.style.opacity = 0;
intermediateText.style.opacity = 0;
setTimeout(() => {
resultValue.textContent = outcome;
descriptionText.textContent = description;
intermediateText.textContent = intermediate || "";
if (assumptionsText) assumptionsText.textContent = assumptionsString;
// Remove old classes
resultValue.classList.remove('stable', 'paradoxical', 'gnommos-paradox', 'infinite', 'unexplained', 'standard', 'non-standard');
artCanvas.className = 'art-canvas'; // Reset base class
artCanvas.innerHTML = ''; // Clear previous custom HTML
// Handle Global CRT Background for Paradox
const crtScreen = document.querySelector('.crt-screen');
if (visualClass.includes('paradox')) {
crtScreen.classList.add('paradox-errors');
} else {
crtScreen.classList.remove('paradox-errors');
}
// Add new classes
const normalizedOutcome = outcome.toLowerCase().replace(' ', '-');
if (resultValue.classList.contains(normalizedOutcome)) {
// already added
} else {
resultValue.classList.add(normalizedOutcome);
}
artCanvas.classList.add(visualClass);
// Inject Custom HTML for complex visuals
if (visualClass === 'paradox-point') {
artCanvas.innerHTML = `
<div class="cube-wrapper">
<div class="cube">
<div class="face front"></div>
<div class="face back"></div>
<div class="face right"></div>
<div class="face left"></div>
<div class="face top"></div>
<div class="face bottom"></div>
<div class="particle-point"></div>
</div>
<div class="info-arrow">
<div class="arrow-line"></div>
<div class="arrow-head"></div>
<span class="infinite-symbol">∞</span>
</div>
</div>
`;
} else if (visualClass === 'stable-chunk') {
// Chakana Symbol: A cross shape. 5 cubes structure (Center + 4 arms)
// We'll use a container and multiple cubes.
artCanvas.innerHTML = `
<div class="chakana-wrapper">
<!-- Center -->
<div class="gold-cube center">
<div class="face front"></div><div class="face back"></div>
<div class="face right"></div><div class="face left"></div>
<div class="face top"></div><div class="face bottom"></div>
</div>
<!-- Top -->
<div class="gold-cube top-arm">
<div class="face front"></div><div class="face back"></div>
<div class="face right"></div><div class="face left"></div>
<div class="face top"></div><div class="face bottom"></div>
</div>
<!-- Bottom -->
<div class="gold-cube bottom-arm">
<div class="face front"></div><div class="face back"></div>
<div class="face right"></div><div class="face left"></div>
<div class="face top"></div><div class="face bottom"></div>
</div>
<!-- Left -->
<div class="gold-cube left-arm">
<div class="face front"></div><div class="face back"></div>
<div class="face right"></div><div class="face left"></div>
<div class="face top"></div><div class="face bottom"></div>
</div>
<!-- Right -->
<div class="gold-cube right-arm">
<div class="face front"></div><div class="face back"></div>
<div class="face right"></div><div class="face left"></div>
<div class="face top"></div><div class="face bottom"></div>
</div>
</div>
`;
} else if (visualClass === 'paradox-chunk') {
// Memory Paradox: Unbounded Expansion
// Create a center source and multiple debris cubes that fly out
let debrisHTML = '';
for (let i = 0; i < 12; i++) {
debrisHTML += `
<div class="debris-cube d-${i}">
<div class="face front"></div><div class="face back"></div>
<div class="face right"></div><div class="face left"></div>
<div class="face top"></div><div class="face bottom"></div>
</div>`;
}
artCanvas.innerHTML = `
<div class="chaos-wrapper">
<div class="central-source">
<div class="face front"></div><div class="face back"></div>
<div class="face right"></div><div class="face left"></div>
<div class="face top"></div><div class="face bottom"></div>
</div>
${debrisHTML}
</div>
`;
} else if (visualClass === 'point-field') {
// CSS-only implementation used before, but we need to ensure it still works
// The CSS implementation used ::before/::after on .art-canvas.point-field
// Since we cleared innerHTML, that's fine.
}
// Animate in
resultValue.style.opacity = 1;
descriptionText.style.opacity = 1;
artCanvas.style.opacity = 1;
intermediateText.style.opacity = 1;
if (assumptionsText) assumptionsText.style.opacity = 1;
}, 200);
}
switches.forEach(sw => {
sw.addEventListener('change', () => {
// Optional: Pre-calculate but don't show yet?
// calculateOutcome(); // We might want to wait for button press
});
});
// Transition Function
function switchSlide(fromSlide, toSlide) {
const wrapper = document.querySelector('.slides-wrapper');
// 1. Collapse
wrapper.classList.add('crt-collapse');
// Wait for collapse animation (200ms)
setTimeout(() => {
// 2. Swap Content
fromSlide.style.display = 'none';
toSlide.style.display = 'flex';
// 3. Expand
wrapper.classList.remove('crt-collapse');
wrapper.classList.add('crt-expand');
// Cleanup after expand (200ms)
setTimeout(() => {
wrapper.classList.remove('crt-expand');
}, 200);
}, 200);
}
simulateBtn.addEventListener('click', () => {
calculateOutcome();
switchSlide(slide1, slide2);
});
backBtn.addEventListener('click', () => {
switchSlide(slide2, slide1);
});
if (initBtn && splashSlide) {
// Initial state: Show Splash, Hide Slide 1 (which might be default flex in CSS if not handled)
slide1.style.display = 'none';
slide2.style.display = 'none';
splashSlide.style.display = 'flex';
initBtn.addEventListener('click', () => {
switchSlide(splashSlide, slide1);
});
}
// Initial calculation
// Initial calculation (optional to run it secretly)
// calculateOutcome();
});
+341
View File
@@ -0,0 +1,341 @@
# 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<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; }
```
---
## 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`
+677
View File
@@ -0,0 +1,677 @@
import { useEffect, useRef, useState, type CSSProperties } from 'react';
import frameImage from '../technoborder.png';
import { SOUND_IDS, safeEmit, safePlaySound, safeStopSound } from './hostBridge';
import type { GlitchComponentProps } from './types';
type Screen = 'splash' | 'controls' | 'processing' | 'result';
type ProcessingLine = {
label: string;
value: string;
};
type TransitionState = '' | 'crt-collapse' | 'crt-expand';
type VisualClass =
| 'paradox-point'
| 'stable-chunk'
| 'paradox-chunk'
| 'non-standard-particle-point'
| 'non-standard-particle-chunk'
| 'standard-particle-point'
| 'standard-particle-chunk';
type OutcomeTone = 'stable' | 'warning' | 'paradox' | 'uncertain';
type ToggleState = {
continuousUniverse: boolean;
infinitePrecision: boolean;
chunkParticles: boolean;
};
type OutcomeDefinition = {
outcome: string;
description: string;
intermediate: string;
visualClass: VisualClass;
tone: OutcomeTone;
};
const DEFAULT_STATE: ToggleState = {
continuousUniverse: true,
infinitePrecision: true,
chunkParticles: false
};
const OUTCOMES: Record<string, OutcomeDefinition> = {
'000': {
outcome: 'Paradoxical',
intermediate: 'Very very much information.',
description: 'Space is supposed to be discrete, but each cell must contain ar arbitrary amount of information.',
visualClass: 'paradox-point',
tone: 'paradox'
},
'001': {
outcome: 'Digital Physics',
intermediate: 'Instruction programs remain finite.',
description: 'Stable but requires continuous rotational symmetry to be emergent',
visualClass: 'stable-chunk',
tone: 'stable'
},
'010': {
outcome: '#1 Memory Paradox',
intermediate: 'Move sets can scale to infinite length',
description: 'Infinte memory per cell',
visualClass: 'paradox-point',
tone: 'paradox'
},
'011': {
outcome: '#2 Memory Paradox',
intermediate: 'Move sets can scale to infinite length',
description: 'Chunky particles must grow without bound to hold infinite move set',
visualClass: 'paradox-chunk',
tone: 'paradox'
},
'100': {
outcome: 'Paradoxical',
intermediate: 'Chunky geometry implies discrete space',
description: 'A continuous universe with chunky particles spanning fundamental cells is a constadiction.',
visualClass: 'non-standard-particle-point',
tone: 'warning'
},
'101': {
outcome: 'Paradoxical',
intermediate: 'The geometry breaks isotropy.',
description: 'Continuous universe plus chunk particles that imply discrete space is a constradiction.',
visualClass: 'non-standard-particle-chunk',
tone: 'warning'
},
'110': {
outcome: 'Standard Physics',
intermediate: 'The model remains isotropic.',
description: 'Continuous space with infinite precision preserves the canonical picture.',
visualClass: 'standard-particle-point',
tone: 'stable'
},
'111': {
outcome: 'Contradictory',
intermediate: 'Continuous universe cannot be combined with chunk particles.',
description: 'Chunky cannot be not chunky',
visualClass: 'standard-particle-chunk',
tone: 'uncertain'
}
};
const PROCESSING_LOGS: Record<string, ProcessingLine[]> = {
"000": [
{ label: "Universe model", value: "Discrete" },
{ label: "Precision model", value: "Finite (Rejects CRS!)" },
{ label: "Requirement", value: "Isotropy is emergent" },
{ label: "Particle mode", value: "Point particle" },
{ label: "Consistency check", value: "Point state exceeds single-cell information budget" },
{ label: "Verdict trace", value: "Single cell must have arbitrary information capacity" }
],
"001": [
{ label: "Universe model", value: "Discrete" },
{ label: "Precision model", value: "Finite (Rejects CRS!)" },
{ label: "Requirement", value: "Isotropy is emergent" },
{ label: "Particle mode", value: "Chunk particle geometry engaged" },
{ label: "Consistency check", value: "State remains compressible under finite instruction length" },
{ label: "Verdict trace", value: "Digital physics interpretation remains internally stable" }
],
"010": [
{ label: "Universe model", value: "Discrete" },
{ label: "Precision model", value: "Infinite (CRS)" },
{ label: "Particle mode", value: "Point particle" },
{ label: "Consistency check", value: "Each lattice cell requires unbounded memory" },
{ label: "Verdict trace", value: "Inconsistent with isotropy" }
],
"011": [
{ label: "Universe model", value: "Discrete" },
{ label: "Precision model", value: "Infinite (CRS)" },
{ label: "Particle mode", value: "Chunk particle" },
{ label: "Consistency check", value: "Particles grow to express information" },
{ label: "Verdict trace", value: "Chunk particles grow to infinite size" }
],
"100": [
{ label: "Universe model", value: "Continuous manifold" },
{ label: "Precision model", value: "Finite (Rejects CRS!)" },
{ label: "Requirement", value: "Isotropy is emergent" },
{ label: "Particle mode", value: "Point particle" },
{ label: "Consistency check", value: "Severely demotivated" },
{ label: "Verdict trace", value: "No explanation for rejecting CRS. Smell detected." }
],
"101": [
{ label: "Universe model", value: "Continuous manifold" },
{ label: "Precision model", value: "Finite (Rejects CRS!)" },
{ label: "Requirement", value: "Isotropy is emergent" },
{ label: "Particle mode", value: "Chunk particle geometry engaged" },
{ label: "Consistency check", value: "Space is quantized but cells infinitely small" },
{ label: "Verdict trace", value: "Non-standard hybrid geometry. Smell detected" }
],
"110": [
{ label: "Universe model", value: "Continuous manifold" },
{ label: "Precision model", value: "Infinite (CRS)" },
{ label: "Particle mode", value: "Point particle" },
{ label: "Consistency check", value: "Canonical isotropic field picture remains intact" },
{ label: "Verdict trace", value: "Standard physics baseline recovered successfully" }
],
"111": [
{ label: "Universe model", value: "Continuous manifold" },
{ label: "Precision model", value: "Infinite (CRS)" },
{ label: "Particle mode", value: "Chunk particle geometry engaged" },
{ label: "Consistency check", value: "Inconsistent." },
{ label: "Verdict trace", value: "Chunk particle cannot be not chunky" }
]
};
function coerceBoolean(value: unknown, fallback: boolean) {
if (typeof value === 'boolean') return value;
return fallback;
}
function readToggleState(params: Record<string, unknown> | undefined): ToggleState {
return {
continuousUniverse: coerceBoolean(params?.continuousUniverse, DEFAULT_STATE.continuousUniverse),
infinitePrecision: coerceBoolean(params?.infinitePrecision, DEFAULT_STATE.infinitePrecision),
chunkParticles: coerceBoolean(params?.chunkParticles, DEFAULT_STATE.chunkParticles)
};
}
function buildStateKey(state: ToggleState) {
return [
state.continuousUniverse ? '1' : '0',
state.infinitePrecision ? '1' : '0',
state.chunkParticles ? '1' : '0'
].join('');
}
function buildAssumptionsText(state: ToggleState) {
const universe = state.continuousUniverse ? 'Continuous universe' : 'Discrete universe';
const precision = state.infinitePrecision ? 'Infinite precision' : 'Finite precision';
const particles = state.chunkParticles ? 'Chunk particles' : 'Point particles';
return `${universe} + ${precision} + ${particles}`;
}
function getScore(outcome: OutcomeDefinition) {
if (outcome.tone === 'stable') return 100;
if (outcome.tone === 'uncertain') return 60;
if (outcome.tone === 'warning') return 40;
return 20;
}
function Visualization({ visualClass }: { visualClass: VisualClass }) {
if (visualClass === 'paradox-point') {
return (
<div className="cube-wrapper">
<div className="cube">
<div className="face front" />
<div className="face back" />
<div className="face right" />
<div className="face left" />
<div className="face top" />
<div className="face bottom" />
<div className="particle-point" />
</div>
<div className="info-arrow">
<div className="arrow-line" />
<div className="arrow-head" />
<span className="infinite-symbol"></span>
</div>
</div>
);
}
if (visualClass === 'stable-chunk') {
return (
<div className="digital-lattice">
<div className="digital-grid">
{Array.from({ length: 9 }, (_, index) => (
<span
key={index}
className={`digital-cell digital-cell-${index}${index === 4 ? ' digital-cell-core' : ''}`}
/>
))}
</div>
<div className="digital-core">
<span className="digital-core-dot" />
</div>
<div className="digital-scan digital-scan-horizontal" />
<div className="digital-scan digital-scan-vertical" />
<div className="digital-aura" />
</div>
);
}
if (visualClass === 'paradox-chunk') {
return (
<div className="chaos-wrapper">
<div className="central-source">
<div className="face front" />
<div className="face back" />
<div className="face right" />
<div className="face left" />
<div className="face top" />
<div className="face bottom" />
</div>
{Array.from({ length: 10 }, (_, index) => (
<div key={index} className={`debris-cube d-${index}`}>
<div className="face front" />
<div className="face back" />
<div className="face right" />
<div className="face left" />
<div className="face top" />
<div className="face bottom" />
</div>
))}
</div>
);
}
if (visualClass === 'non-standard-particle-point') {
return (
<div className="signal-orbit signal-orbit-point">
<div className="signal-ring" />
<div className="signal-dot" />
<div className="signal-axis" />
</div>
);
}
if (visualClass === 'non-standard-particle-chunk') {
return (
<div className="signal-orbit signal-orbit-chunk">
<div className="signal-ring" />
<div className="signal-cube" />
<div className="signal-axis" />
</div>
);
}
if (visualClass === 'standard-particle-point') {
return (
<div className="physics-observatory">
<div className="physics-grid" />
<div className="physics-halo physics-halo-outer" />
<div className="physics-halo physics-halo-inner" />
<div className="physics-core-shell">
<div className="physics-core-point" />
</div>
<span className="physics-orbit physics-orbit-1" />
<span className="physics-orbit physics-orbit-2" />
<span className="physics-orbit physics-orbit-3" />
</div>
);
}
return (
<div className="physics-observatory physics-observatory-chunk">
<div className="physics-grid" />
<div className="physics-halo physics-halo-outer" />
<div className="physics-halo physics-halo-inner" />
<div className="physics-core-shell physics-core-shell-chunk">
<div className="physics-core-point physics-core-chunk" />
</div>
<span className="physics-orbit physics-orbit-1" />
<span className="physics-orbit physics-orbit-2" />
<span className="physics-orbit physics-orbit-3" />
<div className="physics-question-ring">
<span className="physics-question">?</span>
</div>
</div>
);
}
export default function AssumptionToggle({
config,
onComplete,
onProgress,
theme,
className,
host
}: GlitchComponentProps) {
const [toggleState, setToggleState] = useState<ToggleState>(() => readToggleState(config.params));
const [screen, setScreen] = useState<Screen>('splash');
const [transitionState, setTransitionState] = useState<TransitionState>('');
const [result, setResult] = useState(() => OUTCOMES[buildStateKey(readToggleState(config.params))]);
const [processingKey, setProcessingKey] = useState(() => buildStateKey(readToggleState(config.params)));
const [processingAssumptions, setProcessingAssumptions] = useState(() =>
buildAssumptionsText(readToggleState(config.params))
);
const [completedLines, setCompletedLines] = useState<string[]>([]);
const [activeLine, setActiveLine] = useState('');
const timeoutsRef = useRef<number[]>([]);
const hostRef = useRef(host);
const onCompleteRef = useRef(onComplete);
const onProgressRef = useRef(onProgress);
useEffect(() => {
setToggleState(readToggleState(config.params));
}, [config.params]);
useEffect(() => {
hostRef.current = host;
onCompleteRef.current = onComplete;
onProgressRef.current = onProgress;
}, [host, onComplete, onProgress]);
useEffect(() => {
if (screen !== 'processing') return;
const lines = PROCESSING_LOGS[processingKey] ?? [];
let cancelled = false;
setCompletedLines([]);
setActiveLine('');
const typeLine = (lineIndex: number, charIndex: number) => {
if (cancelled) return;
if (lineIndex >= lines.length) {
queueTimeout(() => {
if (cancelled) return;
const finalResult = OUTCOMES[processingKey];
safePlaySound(hostRef.current, SOUND_IDS.computeDone, { state: processingKey });
runTransition('result', () => {
onProgressRef.current?.(100);
safePlaySound(
hostRef.current,
finalResult.tone === 'stable' ? SOUND_IDS.verdictStable : SOUND_IDS.verdictAlert,
{ state: processingKey, outcome: finalResult.outcome }
);
safeEmit(hostRef.current, 'verdict-ready', {
state: processingKey,
outcome: finalResult.outcome,
tone: finalResult.tone
});
onCompleteRef.current({
success: true,
score: getScore(finalResult),
data: {
assumptions: processingAssumptions,
state: processingKey,
outcome: finalResult.outcome,
intermediate: finalResult.intermediate,
description: finalResult.description,
processingTrace: PROCESSING_LOGS[processingKey].map((entry) => `${entry.label}: ${entry.value}`)
}
});
});
}, 520);
return;
}
const fullLine = `${lines[lineIndex].label}: ${lines[lineIndex].value}`;
if (charIndex <= fullLine.length) {
if (charIndex === 0) {
safePlaySound(hostRef.current, SOUND_IDS.computeStep, { state: processingKey, lineIndex });
}
setActiveLine(fullLine.slice(0, charIndex));
queueTimeout(() => typeLine(lineIndex, charIndex + 1), 24);
return;
}
setCompletedLines((current) => [...current, fullLine]);
setActiveLine('');
if (lineIndex === lines.length - 1) {
safeStopSound(hostRef.current, SOUND_IDS.computeStep, { state: processingKey, lineIndex });
}
queueTimeout(() => typeLine(lineIndex + 1, 0), 170);
};
queueTimeout(() => typeLine(0, 0), 180);
return () => {
cancelled = true;
safeStopSound(hostRef.current, SOUND_IDS.computeStep, { state: processingKey });
};
}, [processingAssumptions, processingKey, screen]);
useEffect(() => {
return () => {
clearQueuedTimeouts();
};
}, []);
function clearQueuedTimeouts() {
for (const timeout of timeoutsRef.current) {
window.clearTimeout(timeout);
}
timeoutsRef.current = [];
}
function queueTimeout(callback: () => void, delay: number) {
const timeout = window.setTimeout(callback, delay);
timeoutsRef.current.push(timeout);
}
function runTransition(nextScreen: Screen, onSwapped?: () => void) {
clearQueuedTimeouts();
setTransitionState('crt-collapse');
queueTimeout(() => {
setScreen(nextScreen);
onSwapped?.();
setTransitionState('crt-expand');
queueTimeout(() => {
setTransitionState('');
}, 110);
}, 110);
}
function setToggle<K extends keyof ToggleState>(key: K, value: ToggleState[K]) {
setToggleState(current => ({ ...current, [key]: value }));
safePlaySound(host, SOUND_IDS.click, { control: key, value });
}
function handleUiHover(target: string) {
safePlaySound(host, SOUND_IDS.hover, { target });
}
function handleInitialize() {
safeEmit(host, 'session-started', { component: config.name, id: config.id });
onProgress?.(10);
runTransition('controls');
}
function handleSimulate() {
const nextKey = buildStateKey(toggleState);
const nextResult = OUTCOMES[nextKey];
const nextAssumptions = buildAssumptionsText(toggleState);
setProcessingKey(nextKey);
setProcessingAssumptions(nextAssumptions);
setResult(nextResult);
safePlaySound(host, SOUND_IDS.computeStart, { state: nextKey });
safeEmit(host, 'processing-started', {
state: nextKey,
assumptions: nextAssumptions
});
onProgress?.(45);
runTransition('processing', () => {
onProgress?.(70);
});
}
function handleBack() {
clearQueuedTimeouts();
safeStopSound(host, SOUND_IDS.computeStep, { state: processingKey });
safeEmit(host, 'returned-to-controls', { state: processingKey });
onProgress?.(20);
runTransition('controls');
}
const assumptionsText = buildAssumptionsText(toggleState);
const showParadoxScreen = screen === 'result' && result.visualClass.includes('paradox');
const rootStyle = {
'--gc-primary': theme?.primary || '#248a00',
'--gc-accent': theme?.accent || '#1c6a00',
'--gc-bg': theme?.bg || '#ffffff',
'--gc-bg-secondary': theme?.bgSecondary || '#616161',
'--gc-text': theme?.text || '#0f172a',
'--gc-text-muted': theme?.textMuted || '#eaffd8',
'--gc-border': theme?.border || 'rgba(255,255,255,0.12)',
'--assumption-frame-image': `url(${frameImage})`
} as CSSProperties;
return (
<div className={`gc-assumption-toggle ${className || ''}`.trim()} style={rootStyle}>
<div className="component-shell">
<div className="main-wrapper">
<div className={`crt-screen ${showParadoxScreen ? 'paradox-errors' : ''}`}>
<div className={`slides-wrapper ${transitionState}`}>
{screen === 'splash' && (
<div className="slide slide-splash">
<div className="splash-content">
<h1>Foundational Assumption Recombinator Tool</h1>
<h2>v1.0</h2>
<button
type="button"
className="action-button"
onClick={handleInitialize}
onMouseEnter={() => handleUiHover('initialize')}
onFocus={() => handleUiHover('initialize')}
>
Initialize
</button>
</div>
</div>
)}
{screen === 'controls' && (
<div className="slide slide-controls">
<div className="widget-header">
<h1>Settings</h1>
</div>
<div className="switch-group">
<div className="switch-item">
<input
id={`${config.id}-toggle-universe`}
className="switch-input"
type="checkbox"
checked={toggleState.continuousUniverse}
onChange={(event) => setToggle('continuousUniverse', event.target.checked)}
/>
<label
className="toggle-track"
htmlFor={`${config.id}-toggle-universe`}
onMouseEnter={() => handleUiHover('toggle-universe')}
>
<span className="option-left">Discrete Universe</span>
<span className="option-right">Continuous Universe</span>
</label>
</div>
<div className="switch-item">
<input
id={`${config.id}-toggle-precision`}
className="switch-input"
type="checkbox"
checked={toggleState.infinitePrecision}
onChange={(event) => setToggle('infinitePrecision', event.target.checked)}
/>
<label
className="toggle-track"
htmlFor={`${config.id}-toggle-precision`}
onMouseEnter={() => handleUiHover('toggle-precision')}
>
<span className="option-left">Finite Precision</span>
<span className="option-right">Infinite Precision</span>
</label>
</div>
<div className="switch-item">
<input
id={`${config.id}-toggle-particles`}
className="switch-input"
type="checkbox"
checked={toggleState.chunkParticles}
onChange={(event) => setToggle('chunkParticles', event.target.checked)}
/>
<label
className="toggle-track"
htmlFor={`${config.id}-toggle-particles`}
onMouseEnter={() => handleUiHover('toggle-particles')}
>
<span className="option-left">Point Particles</span>
<span className="option-right">Chunk Particles</span>
</label>
</div>
</div>
<button
type="button"
className="action-button"
onClick={handleSimulate}
onMouseEnter={() => handleUiHover('simulate')}
onFocus={() => handleUiHover('simulate')}
>
Simulate
</button>
</div>
)}
{screen === 'result' && (
<div className="slide slide-result">
<div className="bottom-container">
<div className="visualization-container">
<div className={`art-canvas ${result.visualClass}`}>
<Visualization visualClass={result.visualClass} />
</div>
</div>
<div className="result-container">
<div className="summary-box">
<span className={`result-value result-${result.tone}`}>{result.outcome}</span>
</div>
<div className="summary-box">
<div className="intermediate-result">{result.intermediate}</div>
</div>
<span className="result-description">{result.description}</span>
</div>
</div>
<button
type="button"
className="action-button secondary"
onClick={handleBack}
onMouseEnter={() => handleUiHover('back')}
onFocus={() => handleUiHover('back')}
>
Back
</button>
</div>
)}
{screen === 'processing' && (
<div className="slide slide-processing">
<div className="processing-shell">
<div className="processing-header">
<span className="processing-led" />
<h2>Computing Assumption Matrix</h2>
</div>
<div className="processing-subtitle">Applying Constructor Theoretic Constraints</div>
<div className="processing-terminal">
{completedLines.map((line, index) => (
<div key={`${line}-${index}`} className="processing-line">
<span className="processing-prompt">&gt;</span>
<span>{line}</span>
</div>
))}
<div className="processing-line processing-line-active">
<span className="processing-prompt">&gt;</span>
<span>{activeLine}</span>
<span className="processing-cursor" />
</div>
</div>
</div>
</div>
)}
</div>
</div>
</div>
</div>
</div>
);
}
+129
View File
@@ -0,0 +1,129 @@
import React, { useEffect, useMemo, useState } from 'react';
import ReactDOM from 'react-dom/client';
import Component from './Component';
import { attachWindowSoundBridge, createDevHostBridge } from './devHostBridge';
import { metadata } from './index';
const mockConfig = {
id: 'assumption-toggle-dev',
name: metadata.name,
version: metadata.version,
params: metadata.defaultParams
};
const mockTheme = {
primary: '#248a00',
accent: '#1c6a00',
bg: '#060807',
bgSecondary: '#616161',
text: '#f4f5ee',
textMuted: '#b8c2b0',
border: 'rgba(255,255,255,0.12)'
};
function DevHarness() {
const [muted, setMuted] = useState(false);
const [logEntries, setLogEntries] = useState<Array<{ id: string; detail: string }>>([]);
const appendLog = (id: string, detail: string) => {
setLogEntries((current) => [{ id, detail }, ...current].slice(0, 8));
};
const devHost = useMemo(() => createDevHostBridge({
isMuted: () => muted,
onSound: (id, payload) => {
appendLog(id, payload ? JSON.stringify(payload) : 'play');
},
onEmit: (type, payload) => {
appendLog(`event:${type}`, payload ? JSON.stringify(payload) : 'emit');
}
}), [muted]);
useEffect(() => {
return attachWindowSoundBridge(devHost);
}, [devHost]);
return (
<div style={{ minHeight: '100vh', padding: '24px', position: 'relative' }}>
<aside
style={{
position: 'fixed',
top: 16,
right: 16,
width: 280,
padding: 14,
borderRadius: 14,
background: 'rgba(6, 16, 11, 0.92)',
border: '1px solid rgba(120,255,154,0.18)',
color: '#dfffe7',
fontFamily: 'Iceland, sans-serif',
zIndex: 10,
boxShadow: '0 12px 28px rgba(0,0,0,0.22)'
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
<strong style={{ fontFamily: 'Russo One, sans-serif', fontWeight: 400, letterSpacing: '0.06em' }}>
Sound Bridge
</strong>
<button
type="button"
onClick={() => setMuted((current) => !current)}
style={{
border: '1px solid rgba(120,255,154,0.28)',
background: muted ? 'rgba(96,20,20,0.92)' : 'rgba(14,40,20,0.92)',
color: '#ecfff2',
borderRadius: 999,
padding: '6px 10px',
cursor: 'pointer',
fontFamily: 'Russo One, sans-serif',
fontSize: 11,
letterSpacing: '0.05em'
}}
>
{muted ? 'Muted' : 'Live'}
</button>
</div>
<div style={{ opacity: 0.78, marginBottom: 10, fontSize: 18 }}>
Web Audio dev host listening for bridge calls
</div>
<div style={{ display: 'grid', gap: 6, fontSize: 18, lineHeight: 1.1 }}>
{logEntries.length === 0 ? (
<div style={{ opacity: 0.56 }}>No sound events yet.</div>
) : (
logEntries.map((entry, index) => (
<div
key={`${entry.id}-${index}`}
style={{
padding: '6px 8px',
borderRadius: 10,
background: 'rgba(255,255,255,0.04)',
border: '1px solid rgba(255,255,255,0.04)'
}}
>
<div style={{ fontFamily: 'Russo One, sans-serif', fontSize: 11, letterSpacing: '0.05em' }}>{entry.id}</div>
<div style={{ opacity: 0.78 }}>{entry.detail}</div>
</div>
))
)}
</div>
</aside>
<Component
config={mockConfig}
theme={mockTheme}
host={devHost}
onComplete={(result) => {
console.log('Assumption Toggle completed:', result);
}}
onProgress={(percent) => {
console.log('Assumption Toggle progress:', percent);
}}
/>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<DevHarness />
</React.StrictMode>
);
+192
View File
@@ -0,0 +1,192 @@
import type { GlitchHostBridge } from './types';
import { HOST_SOUND_EVENT, HOST_STOP_SOUND_EVENT, type SoundId } from './hostBridge';
type OscillatorShape = OscillatorType;
type SoundStep = {
frequency: number;
durationMs: number;
gain?: number;
type?: OscillatorShape;
delayMs?: number;
};
const COMPUTE_STEP_AUDIO_PATH = '/sounds/blblblbl.mp3';
const SOUND_PATTERNS: Record<string, SoundStep[]> = {
'ui.button_click': [
{ frequency: 640, durationMs: 55, gain: 0.025, type: 'square' },
{ frequency: 880, durationMs: 40, gain: 0.018, type: 'triangle', delayMs: 36 }
],
'ui.button_hover': [
{ frequency: 540, durationMs: 40, gain: 0.012, type: 'sine' }
],
'machine.compute_start': [
{ frequency: 240, durationMs: 90, gain: 0.02, type: 'sawtooth' },
{ frequency: 320, durationMs: 90, gain: 0.018, type: 'sawtooth', delayMs: 82 },
{ frequency: 430, durationMs: 90, gain: 0.016, type: 'triangle', delayMs: 164 }
],
'machine.compute_step': [
{ frequency: 780, durationMs: 26, gain: 0.009, type: 'square' }
],
'machine.compute_done': [
{ frequency: 520, durationMs: 80, gain: 0.018, type: 'triangle' },
{ frequency: 780, durationMs: 110, gain: 0.018, type: 'triangle', delayMs: 70 }
],
'machine.verdict_stable': [
{ frequency: 440, durationMs: 90, gain: 0.016, type: 'triangle' },
{ frequency: 554, durationMs: 90, gain: 0.016, type: 'triangle', delayMs: 65 },
{ frequency: 659, durationMs: 140, gain: 0.015, type: 'triangle', delayMs: 130 }
],
'machine.verdict_alert': [
{ frequency: 180, durationMs: 120, gain: 0.022, type: 'sawtooth' },
{ frequency: 140, durationMs: 140, gain: 0.02, type: 'sawtooth', delayMs: 100 }
]
};
let sharedAudioContext: AudioContext | null = null;
let computeStepAudio: HTMLAudioElement | null = null;
let computeStepAudioFailed = false;
let computeStepAudioErrorLogged = false;
export type DevHostBridgeOptions = {
isMuted?: () => boolean;
onSound?: (id: string, payload?: Record<string, unknown>) => void;
onEmit?: (type: string, payload?: unknown) => void;
};
function getAudioContext() {
if (typeof window === 'undefined') return null;
const AudioContextCtor = window.AudioContext || (window as typeof window & {
webkitAudioContext?: typeof AudioContext;
}).webkitAudioContext;
if (!AudioContextCtor) return null;
if (!sharedAudioContext) {
sharedAudioContext = new AudioContextCtor();
}
if (sharedAudioContext.state === 'suspended') {
void sharedAudioContext.resume();
}
return sharedAudioContext;
}
function scheduleTone(context: AudioContext, step: SoundStep) {
const startAt = context.currentTime + (step.delayMs ?? 0) / 1000;
const stopAt = startAt + step.durationMs / 1000;
const oscillator = context.createOscillator();
const gainNode = context.createGain();
oscillator.type = step.type ?? 'triangle';
oscillator.frequency.setValueAtTime(step.frequency, startAt);
gainNode.gain.setValueAtTime(0.0001, startAt);
gainNode.gain.exponentialRampToValueAtTime(step.gain ?? 0.02, startAt + 0.008);
gainNode.gain.exponentialRampToValueAtTime(0.0001, stopAt);
oscillator.connect(gainNode);
gainNode.connect(context.destination);
oscillator.start(startAt);
oscillator.stop(stopAt + 0.02);
}
function playSynthPattern(id: string) {
const context = getAudioContext();
if (!context) return;
const steps = SOUND_PATTERNS[id] ?? SOUND_PATTERNS['ui.button_click'];
for (const step of steps) {
scheduleTone(context, step);
}
}
function playComputeStepAudio() {
if (typeof window === 'undefined') return false;
if (computeStepAudioFailed) return false;
if (!computeStepAudio) {
computeStepAudio = new Audio(COMPUTE_STEP_AUDIO_PATH);
computeStepAudio.preload = 'auto';
computeStepAudio.addEventListener('error', () => {
computeStepAudioFailed = true;
if (computeStepAudioErrorLogged) return;
computeStepAudioErrorLogged = true;
console.warn(`[glitch-host:sound] Failed to load ${COMPUTE_STEP_AUDIO_PATH}; using synth fallback.`);
});
}
computeStepAudio.pause();
computeStepAudio.currentTime = 0;
const playAttempt = computeStepAudio.play();
if (!playAttempt) return true;
void playAttempt.catch((error) => {
computeStepAudioFailed = true;
playSynthPattern('machine.compute_step');
if (computeStepAudioErrorLogged) return;
computeStepAudioErrorLogged = true;
console.warn(`[glitch-host:sound] Failed to play ${COMPUTE_STEP_AUDIO_PATH}; using synth fallback.`, error);
});
return true;
}
function stopComputeStepAudio() {
if (!computeStepAudio) return;
computeStepAudio.pause();
computeStepAudio.currentTime = 0;
}
function playPattern(id: string) {
if (id === 'machine.compute_step' && playComputeStepAudio()) {
return;
}
playSynthPattern(id);
}
export function createDevHostBridge(options: DevHostBridgeOptions = {}): GlitchHostBridge {
return {
playSound(id, payload) {
options.onSound?.(id, payload);
if (options.isMuted?.()) return;
playPattern(id);
},
stopSound(id, payload) {
options.onSound?.(`stop:${id}`, payload);
if (id === 'machine.compute_step') {
stopComputeStepAudio();
}
},
emit(type, payload) {
options.onEmit?.(type, payload);
console.log('[glitch-host:event]', type, payload);
}
};
}
export function attachWindowSoundBridge(host: GlitchHostBridge) {
if (typeof window === 'undefined') return () => undefined;
const onPlaySound = (event: Event) => {
const customEvent = event as CustomEvent<{ id: SoundId; payload?: Record<string, unknown> }>;
host.playSound?.(customEvent.detail.id, customEvent.detail.payload);
};
const onStopSound = (event: Event) => {
const customEvent = event as CustomEvent<{ id: SoundId; payload?: Record<string, unknown> }>;
host.stopSound?.(customEvent.detail.id, customEvent.detail.payload);
};
window.addEventListener(HOST_SOUND_EVENT, onPlaySound as EventListener);
window.addEventListener(HOST_STOP_SOUND_EVENT, onStopSound as EventListener);
return () => {
window.removeEventListener(HOST_SOUND_EVENT, onPlaySound as EventListener);
window.removeEventListener(HOST_STOP_SOUND_EVENT, onStopSound as EventListener);
};
}
+69
View File
@@ -0,0 +1,69 @@
import type { GlitchHostBridge } from './types';
export const HOST_SOUND_EVENT = 'glitch:play-sound';
export const HOST_STOP_SOUND_EVENT = 'glitch:stop-sound';
export const HOST_EMIT_PREFIX = 'glitch:host:';
export const SOUND_IDS = {
click: 'ui.button_click',
hover: 'ui.button_hover',
computeStart: 'machine.compute_start',
computeStep: 'machine.compute_step',
computeDone: 'machine.compute_done',
verdictStable: 'machine.verdict_stable',
verdictAlert: 'machine.verdict_alert'
} as const;
export type SoundId = (typeof SOUND_IDS)[keyof typeof SOUND_IDS];
function dispatchWindowEvent<T>(type: string, detail: T) {
if (typeof window === 'undefined') return;
window.dispatchEvent(new CustomEvent(type, { detail }));
}
export function safePlaySound(
host: GlitchHostBridge | undefined,
id: string,
payload?: Record<string, unknown>
) {
try {
if (host?.playSound) {
host.playSound(id, payload);
return;
}
} catch {
// Host bridge errors should not break the component.
}
dispatchWindowEvent(HOST_SOUND_EVENT, { id, payload });
}
export function safeStopSound(
host: GlitchHostBridge | undefined,
id: string,
payload?: Record<string, unknown>
) {
try {
if (host?.stopSound) {
host.stopSound(id, payload);
return;
}
} catch {
// Host bridge errors should not break the component.
}
dispatchWindowEvent(HOST_STOP_SOUND_EVENT, { id, payload });
}
export function safeEmit(host: GlitchHostBridge | undefined, type: string, payload?: unknown) {
try {
if (host?.emit) {
host.emit(type, payload);
return;
}
} catch {
// Host bridge errors should not break the component.
}
dispatchWindowEvent(`${HOST_EMIT_PREFIX}${type}`, payload);
}
+42
View File
@@ -0,0 +1,42 @@
import './styles.css';
import Component from './Component';
import type { GlitchComponentMetadata } from './types';
export default Component;
export const metadata: GlitchComponentMetadata = {
name: 'assumption-toggle',
displayName: 'Assumption Toggle',
version: '1.0.0',
paramSchema: {
continuousUniverse: {
type: 'boolean',
label: 'Continuous Universe',
description: 'Toggle between a discrete and continuous universe model.',
default: true
},
infinitePrecision: {
type: 'boolean',
label: 'Infinite Precision',
description: 'Toggle between finite and infinite precision.',
default: true
},
chunkParticles: {
type: 'boolean',
label: 'Chunk Particles',
description: 'Toggle between point particles and chunk particles.',
default: false
}
},
defaultParams: {
continuousUniverse: true,
infinitePrecision: true,
chunkParticles: false
}
};
export type {
GlitchComponentProps,
GlitchComponentConfig,
GlitchComponentResult
} from './types';
+1227
View File
File diff suppressed because it is too large Load Diff
+59
View File
@@ -0,0 +1,59 @@
export interface GlitchComponentConfig {
id: string;
name: string;
version: string;
params: Record<string, unknown>;
}
export interface GlitchHostBridge {
playSound?: (id: string, payload?: Record<string, unknown>) => void;
stopSound?: (id: string, payload?: Record<string, unknown>) => void;
emit?: (type: string, payload?: unknown) => void;
}
export interface GlitchComponentProps {
config: GlitchComponentConfig;
onComplete: (result: GlitchComponentResult) => void;
onProgress?: (percent: number) => void;
theme?: GlitchTheme;
className?: string;
host?: GlitchHostBridge;
}
export interface GlitchComponentResult {
success: boolean;
score?: number;
data?: unknown;
error?: string;
}
export interface GlitchTheme {
primary: string;
accent: string;
bg: string;
bgSecondary: string;
text: string;
textMuted: string;
border: string;
}
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;
};
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+1316
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 826 KiB

Executable
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env bash
# to_mp3.sh — Convert .m4a and .wav files to .mp3
#
# Usage:
# ./to_mp3.sh <file_or_folder> [options]
#
# Options:
# --quality N VBR quality 0-9 (default: 2 ≈ 190kbps; lower = better)
# --bitrate N CBR bitrate e.g. 192k (overrides --quality)
# --replace Delete originals after successful conversion
# --dry-run Show what would be converted without doing anything
#
# Examples:
# ./to_mp3.sh recordings/
# ./to_mp3.sh interview.m4a
# ./to_mp3.sh recordings/ --replace
# ./to_mp3.sh recordings/ --bitrate 128k
set -euo pipefail
QUALITY=2
BITRATE=""
REPLACE=false
DRY_RUN=false
TARGET=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--quality) QUALITY="$2"; shift 2 ;;
--bitrate) BITRATE="$2"; shift 2 ;;
--replace) REPLACE=true; shift ;;
--dry-run) DRY_RUN=true; shift ;;
-*) echo "Unknown option: $1" >&2; exit 1 ;;
*) TARGET="$1"; shift ;;
esac
done
if [[ -z "$TARGET" ]]; then
echo "Usage: $(basename "$0") <file_or_folder> [--quality N] [--bitrate N] [--replace] [--dry-run]"
exit 1
fi
if ! command -v ffmpeg &>/dev/null; then
echo "Error: ffmpeg not found." >&2
exit 1
fi
# Collect files
files=()
if [[ -f "$TARGET" ]]; then
files=("$TARGET")
elif [[ -d "$TARGET" ]]; then
while IFS= read -r -d '' f; do
files+=("$f")
done < <(find "$TARGET" -maxdepth 1 -type f \( -iname "*.m4a" -o -iname "*.wav" \) -print0 | sort -z)
else
echo "Error: '$TARGET' is not a file or directory." >&2
exit 1
fi
if [[ ${#files[@]} -eq 0 ]]; then
echo "No .m4a or .wav files found in: $TARGET"
exit 0
fi
# Build audio quality flags
if [[ -n "$BITRATE" ]]; then
audio_flags=(-b:a "$BITRATE")
quality_desc="CBR ${BITRATE}"
else
audio_flags=(-q:a "$QUALITY")
quality_desc="VBR quality ${QUALITY}"
fi
echo "Converting ${#files[@]} file(s) to MP3 (${quality_desc})"
[[ "$REPLACE" == true ]] && echo " Originals will be deleted after conversion."
[[ "$DRY_RUN" == true ]] && echo " Dry-run mode — no files will be written."
echo ""
converted=0
skipped=0
errors=0
for src in "${files[@]}"; do
out="${src%.*}.mp3"
if [[ -f "$out" && "$DRY_RUN" == false ]]; then
echo " $(basename "$src"): output already exists, skipping"
((skipped++)) || true
continue
fi
size_mb=$(( $(stat -f%z "$src" 2>/dev/null || stat -c%s "$src") / 1048576 ))
printf " %-40s (%d MB)" "$(basename "$src")" "$size_mb"
if [[ "$DRY_RUN" == true ]]; then
echo " [dry-run] → $(basename "$out")"
continue
fi
if ffmpeg -i "$src" -vn "${audio_flags[@]}" -y "$out" -loglevel error; then
out_kb=$(( $(stat -f%z "$out" 2>/dev/null || stat -c%s "$out") / 1024 ))
echo "$(basename "$out") (${out_kb} KB)"
((converted++)) || true
if [[ "$REPLACE" == true ]]; then
rm "$src"
fi
else
echo " ERROR"
((errors++)) || true
[[ -f "$out" ]] && rm "$out"
fi
done
echo ""
echo "Done: ${converted} converted, ${skipped} skipped, ${errors} errors."
+20
View File
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "Bundler",
"allowImportingTsExtensions": false,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmitOnError": true,
"jsx": "react-jsx",
"strict": true,
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "dist"
},
"include": ["src"]
}
+35
View File
@@ -0,0 +1,35 @@
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: {
assetsInlineLimit: 1_000_000,
cssCodeSplit: false,
lib: {
entry: resolve(__dirname, 'src/index.tsx'),
name: 'AssumptionToggle',
fileName: 'assumption-toggle',
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
}
}));