PageSourceSearch

https://alsimon.net/js/utils.js

js alsimon.net collected 2026-10-09 01:08:34 UTC 3,633 bytes, 79 lines download raw bytes

1// Fonctions utilitaires.
2
3function toMinutes(time) {
4  const [h, m] = time.split(':').map(Number);
5  return h * 60 + m;
6}
7
8function toTimeString(totalMinutes) {
9  const h = Math.floor(totalMinutes / 60);
10  const m = totalMinutes % 60;
11  return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
12}
13
14// Génère une grille régulière de points de passage entre deux bornes ("HH:MM"),
15// plutôt que la liste des seuls instants où quelque chose se passe dans le script —
16// voir GDD 2.2 ("points de passage horaires fixes, toutes les 15-30 min").
17export function generateCheckpoints(startTime, endTime, stepMinutes) {
18  const start = toMinutes(startTime);
19  const end = toMinutes(endTime);
20
21  if (end < start) {
22    throw new Error(`generateCheckpoints : l'heure de fin (${endTime}) précède l'heure de début (${startTime}).`);
23  }
24  if ((end - start) % stepMinutes !== 0) {
25    throw new Error(`generateCheckpoints : l'intervalle ${startTime}–${endTime} n'est pas un multiple de ${stepMinutes} minutes.`);
26  }
27
28  const checkpoints = [];
29  for (let t = start; t <= end; t += stepMinutes) {
30    checkpoints.push(toTimeString(t));
31  }
32  return checkpoints;
33}
34
35// Courbe d'accélération partagée par toutes les animations écrites à la main
36// (glissé de caméra dans map.js, disque de visibilité dans visibility.js) —
37// quadratique décélérée, jumelle du `cubic-bezier` des transitions CSS et de
38// l'`easeLinearity` passé à Leaflet : les trois couches bougent ensemble, elles
39// doivent suivre la même courbe (voir CLAUDE.md, mécanisme « instant »).
40export function easeOut(t) {
41  return 1 - (1 - t) * (1 - t);
42}
43
44// Parse un hex `#RGB` ou `#RRGGBB` (le `#` est optionnel) en `{r, g, b}`
45// (0-255 chacun). Partagé par `js/habitant-color.js` (couleur d'habitant libre,
46// saisie par l'autrice — d'où le support de la forme courte `#RGB`) et
47// `editor/js/recolor.js` (palette LPC interne, toujours `#RRGGBB` — voir son
48// propre petit wrapper qui reconvertit en tableau `[r,g,b]`, la forme dont il a
49// besoin pour l'indexation positionnelle de `matchRamp`, appelée par pixel).
50export function hexToRgb(hex) {
51  const clean = hex.replace('#', '');
52  const full = clean.length === 3 ? clean.split('').map((c) => c + c).join('') : clean;
53  const value = parseInt(full, 16);
54  return { r: (value >> 16) & 255, g: (value >> 8) & 255, b: value & 255 };
55}
56
57// Échappement minimal (`&`/`<`/`>`, jamais les guillemets — jamais utilisé pour
58// une valeur d'attribut) pour réinjecter du texte brut d'autrice dans du HTML
59// généré via `innerHTML` sans risque d'injection (un nom d'habitant, un libellé
60// de critère de mission, un fragment de fenêtre horaire…). Partagé par
61// js/dialogue-markup.js, js/mission.js et editor/js/bible-mode.js — trois
62// besoins voisins qui réimplémentaient la même chose séparément.
63export function escapeHtml(text) {
64  return text.replace(/[&<>]/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[char]));
65}
66
67// `true` si l'événement clavier vient d'un champ de saisie — les raccourcis à une
68// touche du jeu comme de l'éditeur doivent alors se taire, sous peine de faire
69// avancer le temps ou de changer de mode pendant qu'on écrit un dialogue.
70// `isContentEditable` et `<select>` comptent aussi : un select ouvert utilise les
71// flèches pour choisir son option.
72export function isTypingTarget(event) {
73  const target = event.target;
74  if (!(target instanceof HTMLElement)) return false;
75  return target.tagName === 'INPUT'
76    || target.tagName === 'TEXTAREA'
77    || target.tagName === 'SELECT'
78    || target.isContentEditable;
79}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.