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) => ({ '&': '&', '<': '<', '>': '>' }[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.