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