src/solar-system/blocks/scale-simple-block.js
// src/solar-system/blocks/scale-simple-block.js
//
// Single-knob SCALE block for the élève panel — one slider that moves from
// "Artistique" (the pleasing default) to "Réel" (true scale), driving all
// four scale sub-axes (size / distance / exaggeration / lunarOrbit) along the
// curated, coherence-preserving path in scaleArtisticToReal (see
// ../state/scale.js). The dev panel keeps the full 4-slider scale-block.js.
//
// mountScaleSimpleBlock(target, ctx) → teardown
// - target: an HTMLElement that will receive a `.section-block` child
// - ctx: panelCtx (must expose `onScaleChange` and either
// `scaleSetters.setScale` or be permissive — falls back to the
// module-local setter)
// - returns a teardown function that removes subscribers and clears the target
//
// Mirrors scale-block.js's contract: own DOM/listeners/teardown, reads
// scaleState directly on mount, re-syncs on every onScaleChange so a voyage
// that drives setScale(...) keeps the slider in lock-step. On the curated
// line size == s, so the slider position is derived from scaleState.global.size.
import { t, onLangChange } from '../../i18n.js';
import { scaleState, setScale, onScaleChange, scaleArtisticToReal } from '../state/scale.js';
export function mountScaleSimpleBlock(target, ctx = {}) {
const setScaleFn =
ctx?.scaleSetters?.setScale ??
((delta, opts) => setScale(delta, { duration: 0, source: 'panel', ...opts }));
const subscribe = ctx?.onScaleChange ?? onScaleChange;
const root = document.createElement('div');
root.classList.add('section-block', 'scale-simple-block');
const header = document.createElement('h3');
header.classList.add('section-block__header');
header.setAttribute('data-i18n', 'solarSystem.blocks.scaleSimple.title');
header.textContent = t('solarSystem.blocks.scaleSimple.title') || 'ÉCHELLE';
const body = document.createElement('div');
body.classList.add('section-block__body', 'scale-simple-block__body');
const wrap = document.createElement('div');
wrap.classList.add('scale-block__slider');
const artisticLabel = document.createElement('span');
artisticLabel.classList.add('scale-block__label');
artisticLabel.setAttribute('data-i18n', 'solarSystem.blocks.scaleSimple.artistic');
artisticLabel.textContent = t('solarSystem.blocks.scaleSimple.artistic') || 'Artistique';
const input = document.createElement('input');
input.type = 'range';
input.min = '0';
input.max = '100';
input.step = '1';
input.value = String(Math.round((scaleState.global.size ?? 0) * 100));
const realLabel = document.createElement('span');
realLabel.classList.add('scale-block__label');
realLabel.setAttribute('data-i18n', 'solarSystem.blocks.scaleSimple.real');
realLabel.textContent = t('solarSystem.blocks.scaleSimple.real') || 'Réel';
input.addEventListener('input', () => {
const s = Number(input.value) / 100;
setScaleFn(scaleArtisticToReal(s), { duration: 0, source: 'panel' });
});
wrap.append(artisticLabel, input, realLabel);
body.appendChild(wrap);
root.append(header, body);
target.appendChild(root);
function refresh() {
const next = String(Math.round((scaleState.global.size ?? 0) * 100));
// Don't override the slider while the user is dragging it (mirrors
// scale-block.js's guard).
if (document.activeElement !== input && input.value !== next) {
input.value = next;
}
}
const offScale = subscribe(refresh);
const offLang = onLangChange(() => {
header.textContent = t('solarSystem.blocks.scaleSimple.title') || 'ÉCHELLE';
artisticLabel.textContent = t('solarSystem.blocks.scaleSimple.artistic') || 'Artistique';
realLabel.textContent = t('solarSystem.blocks.scaleSimple.real') || 'Réel';
});
return () => {
try {
offScale?.();
} catch {
// best-effort
}
try {
offLang?.();
} catch {
// best-effort
}
target.replaceChildren();
};
}
← Astrarium