Adding updates to fartmachine
@@ -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.
|
||||||
@@ -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;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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';
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 826 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
@@ -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`
|
||||||
@@ -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">></span>
|
||||||
|
<span>{line}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="processing-line processing-line-active">
|
||||||
|
<span className="processing-prompt">></span>
|
||||||
|
<span>{activeLine}</span>
|
||||||
|
<span className="processing-cursor" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
@@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
After Width: | Height: | Size: 826 KiB |
@@ -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."
|
||||||
@@ -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"]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}));
|
||||||