Update assumption toggle component

This commit is contained in:
2026-06-10 11:47:42 +02:00
parent 644599cc56
commit 79be15aad6
4 changed files with 343 additions and 332 deletions
+143 -127
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+163 -138
View File
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState, type CSSProperties } from 'react';
import { useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from 'react';
import frameImage from '../technoborder.png';
import { SOUND_IDS, safeEmit, safePlaySound, safeStopSound } from './hostBridge';
import type { GlitchComponentProps } from './types';
@@ -326,6 +326,7 @@ export default function AssumptionToggle({
className,
host
}: GlitchComponentProps) {
const containerRef = useRef<HTMLDivElement | null>(null);
const [toggleState, setToggleState] = useState<ToggleState>(() => readToggleState(config.params));
const [screen, setScreen] = useState<Screen>('splash');
const [transitionState, setTransitionState] = useState<TransitionState>('');
@@ -336,6 +337,7 @@ export default function AssumptionToggle({
);
const [completedLines, setCompletedLines] = useState<string[]>([]);
const [activeLine, setActiveLine] = useState('');
const [containerScale, setContainerScale] = useState(1);
const timeoutsRef = useRef<number[]>([]);
const hostRef = useRef(host);
const onCompleteRef = useRef(onComplete);
@@ -351,6 +353,29 @@ export default function AssumptionToggle({
onProgressRef.current = onProgress;
}, [host, onComplete, onProgress]);
useLayoutEffect(() => {
const node = containerRef.current;
if (!node) return;
const updateScale = (width: number, height: number) => {
const nextScale = Math.max(Math.min(width, height) / 1000, 0);
setContainerScale((current) => (Math.abs(current - nextScale) < 0.001 ? current : nextScale));
};
updateScale(node.clientWidth, node.clientHeight);
if (typeof ResizeObserver === 'undefined') return;
const observer = new ResizeObserver((entries) => {
const entry = entries[0];
if (!entry) return;
updateScale(entry.contentRect.width, entry.contentRect.height);
});
observer.observe(node);
return () => observer.disconnect();
}, []);
useEffect(() => {
if (screen !== 'processing') return;
@@ -506,168 +531,168 @@ export default function AssumptionToggle({
'--gc-text': theme?.text || '#0f172a',
'--gc-text-muted': theme?.textMuted || '#eaffd8',
'--gc-border': theme?.border || 'rgba(255,255,255,0.12)',
'--gc-scale': containerScale.toString(),
'--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>
<div ref={containerRef} className="square-container">
<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={handleInitialize}
onMouseEnter={() => handleUiHover('initialize')}
onFocus={() => handleUiHover('initialize')}
onClick={handleSimulate}
onMouseEnter={() => handleUiHover('simulate')}
onFocus={() => handleUiHover('simulate')}
>
Initialize
Simulate
</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>
{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="summary-box">
<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>
</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">
<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>{line}</span>
<span>{activeLine}</span>
<span className="processing-cursor" />
</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>
+35 -65
View File
@@ -10,8 +10,10 @@
--panel-white: rgba(255, 255, 255, 0.92);
--font-display: "Russo One", sans-serif;
--font-ui: "VT323", sans-serif;
--gc-scale: 1;
color: var(--text-primary);
width: 100%;
aspect-ratio: 1 / 1;
}
.gc-assumption-toggle *,
@@ -21,17 +23,27 @@
}
.gc-assumption-toggle .component-shell {
min-height: 100%;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 24px 0;
background: var(--shell-bg);
}
.gc-assumption-toggle .square-container {
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.gc-assumption-toggle .main-wrapper {
width: min(100%, 1000px);
aspect-ratio: 1000 / 678;
width: 1000px;
height: 678px;
flex: 0 0 auto;
background-image: var(--assumption-frame-image);
background-position: center;
background-repeat: no-repeat;
@@ -39,6 +51,8 @@
display: flex;
justify-content: center;
align-items: center;
transform: scale(var(--gc-scale));
transform-origin: center;
}
.gc-assumption-toggle .crt-screen {
@@ -97,7 +111,7 @@
.gc-assumption-toggle .splash-content h1 {
margin: 0 0 18px;
font-size: clamp(1.4rem, 2.7vw, 2.2rem);
font-size: 35px;
line-height: 1.3;
color: var(--text-primary);
font-family: var(--font-display);
@@ -109,7 +123,7 @@
.gc-assumption-toggle .splash-content h2 {
margin: 0 0 42px;
color: var(--text-secondary);
font-size: clamp(1rem, 1.5vw, 1.2rem);
font-size: 19px;
font-weight: 400;
letter-spacing: 0.08em;
font-family: var(--font-ui);
@@ -118,7 +132,7 @@
.gc-assumption-toggle .widget-header h1 {
margin: 0 0 18px;
color: var(--text-secondary);
font-size: clamp(1.4rem, 2.5vw, 1.8rem);
font-size: 29px;
font-weight: 400;
letter-spacing: 0.05em;
text-transform: uppercase;
@@ -187,7 +201,7 @@
text-align: center;
padding: 10px 8px;
color: rgba(255, 255, 255, 0.62);
font-size: clamp(0.78rem, 2.0vw, 1.4rem);
font-size: 20px;
font-weight: 500;
letter-spacing: 0.04em;
line-height: 1.15;
@@ -211,7 +225,7 @@
cursor: pointer;
text-transform: uppercase;
font-family: var(--font-display);
font-size: clamp(1rem, 2vw, 1.35rem);
font-size: 22px;
font-weight: 400;
letter-spacing: 0.06em;
box-shadow: 0 4px 0 rgba(0, 0, 0, 0.28);
@@ -228,7 +242,7 @@
}
.gc-assumption-toggle .action-button.secondary {
font-size: 1rem;
font-size: 16px;
padding: 7px 88px;
color: var(--text-secondary);
background: transparent;
@@ -283,7 +297,7 @@
margin: 0;
color: #dfffe7;
font-family: var(--font-display);
font-size: clamp(1.05rem, 2vw, 1.3rem);
font-size: 21px;
font-weight: 400;
letter-spacing: 0.06em;
text-transform: uppercase;
@@ -292,7 +306,7 @@
.gc-assumption-toggle .processing-subtitle {
color: rgba(182, 255, 198, 0.72);
font-family: var(--font-ui);
font-size: clamp(0.88rem, 1.6vw, 1rem);
font-size: 16px;
letter-spacing: 0.05em;
text-transform: uppercase;
}
@@ -322,7 +336,7 @@
min-height: 1.45em;
color: #b7ffca;
font-family: var(--font-ui);
font-size: clamp(1rem, 1.8vw, 1.18rem);
font-size: 19px;
letter-spacing: 0.04em;
line-height: 1.35;
word-break: break-word;
@@ -392,7 +406,7 @@
display: block;
margin-bottom: 8px;
color: var(--text-secondary);
font-size: 0.84rem;
font-size: 13px;
letter-spacing: 0.08em;
text-transform: uppercase;
font-family: var(--font-display);
@@ -401,7 +415,7 @@
.gc-assumption-toggle .summary-value {
display: block;
color: var(--text-primary);
font-size: clamp(0.9rem, 1.5vw, 1rem);
font-size: 16px;
letter-spacing: 0.04em;
line-height: 1.4;
font-family: var(--font-ui);
@@ -411,7 +425,7 @@
display: block;
width: 100%;
margin-top: 4px;
font-size: clamp(1.7rem, 3vw, 2rem);
font-size: 32px;
font-weight: 400;
letter-spacing: 0.05em;
background-clip: text;
@@ -441,7 +455,7 @@
width: 100%;
margin-top: 4px;
color: black;
font-size: clamp(0.96rem, 1.8vw, 1.08rem);
font-size: 17px;
letter-spacing: 0.03em;
line-height: 1.35;
font-family: var(--font-ui);
@@ -450,7 +464,7 @@
.gc-assumption-toggle .intermediate-result {
margin-top: 14px;
color: var(--text-secondary);
font-size: 0.76rem;
font-size: 12px;
font-weight: 400;
letter-spacing: 0.08em;
text-transform: uppercase;
@@ -558,7 +572,7 @@
top: -25px;
left: 8px;
color: #a10356;
font-size: 1.4rem;
font-size: 22px;
font-weight: 700;
text-shadow: 0 0 10px rgba(161, 3, 86, 0.45);
}
@@ -749,7 +763,7 @@
top: 12px;
right: 16px;
color: rgba(255, 255, 255, 0.85);
font-size: 1.4rem;
font-size: 22px;
font-weight: 700;
}
@@ -899,50 +913,6 @@
50% { opacity: 1; transform: scale(1.08); }
}
@media (max-width: 900px) {
.gc-assumption-toggle .component-shell {
padding: 12px 0;
}
.gc-assumption-toggle .switch-group {
gap: 12px;
padding: 0 14px;
}
.gc-assumption-toggle .toggle-track {
min-height: 50px;
}
.gc-assumption-toggle .action-button {
padding: 10px 22px;
}
.gc-assumption-toggle .action-button.secondary {
padding: 6px 36px;
}
.gc-assumption-toggle .result-container {
padding: 14px;
}
.gc-assumption-toggle .intermediate-result {
margin-top: 10px;
font-size: 0.68rem;
}
.gc-assumption-toggle .slide-processing {
padding: 18px 14px 14px;
}
.gc-assumption-toggle .processing-shell {
padding: 12px;
}
.gc-assumption-toggle .processing-terminal {
padding: 12px;
}
}
/* Result-screen overrides after interrupted migration */
.gc-assumption-toggle .visualization-container {
background:
@@ -1188,7 +1158,7 @@
.gc-assumption-toggle .physics-question {
color: rgba(255, 248, 210, 0.94);
font-size: 1.2rem;
font-size: 19px;
font-weight: 700;
text-shadow: 0 0 12px rgba(255, 226, 130, 0.35);
}