@jonathankablan/noraivoire-fx-text 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +64 -0
- package/README.md +15 -2
- package/package.json +30 -4
- package/src/index.js +122 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
NORAIVOIRE — LICENCE PROPRIÉTAIRE
|
|
2
|
+
Copyright (c) 2026 Jonathan KABLAN. Tous droits réservés.
|
|
3
|
+
|
|
4
|
+
Le présent logiciel, incluant sans limitation son code source, ses spécifications
|
|
5
|
+
(fichiers .nora.json), son compilateur, son moteur d'effets, ses adaptateurs, ses
|
|
6
|
+
modèles (templates), sa documentation, ses jeux de tests et ses éléments graphiques
|
|
7
|
+
(ci-après « le Logiciel »), est la propriété exclusive de Jonathan KABLAN
|
|
8
|
+
(ci-après « le Titulaire »).
|
|
9
|
+
|
|
10
|
+
1. ABSENCE DE LICENCE OPEN SOURCE
|
|
11
|
+
Le Logiciel n'est pas distribué sous une licence open source. Aucun droit
|
|
12
|
+
n'est concédé implicitement, par estoppel ou autrement. La simple mise à
|
|
13
|
+
disposition du Logiciel, y compris dans un dépôt privé ou un registre de
|
|
14
|
+
paquets privé, ne vaut pas concession de licence.
|
|
15
|
+
|
|
16
|
+
2. USAGE AUTORISÉ
|
|
17
|
+
Tout usage du Logiciel est subordonné à un contrat de licence écrit et signé
|
|
18
|
+
par le Titulaire (ci-après « Contrat de Licence »). En l'absence d'un tel
|
|
19
|
+
contrat, seul le Titulaire est autorisé à utiliser le Logiciel.
|
|
20
|
+
|
|
21
|
+
3. INTERDICTIONS
|
|
22
|
+
Sauf autorisation écrite et préalable du Titulaire, il est interdit de :
|
|
23
|
+
a) copier, reproduire, modifier, adapter, traduire ou créer des œuvres
|
|
24
|
+
dérivées du Logiciel, en tout ou partie ;
|
|
25
|
+
b) distribuer, publier, sous-licencier, louer, prêter, vendre ou mettre à
|
|
26
|
+
disposition le Logiciel à des tiers, y compris sous forme de service
|
|
27
|
+
(SaaS) ou intégré dans un autre produit distribué ;
|
|
28
|
+
c) décompiler, désassembler ou procéder à de l'ingénierie inverse du
|
|
29
|
+
Logiciel, sauf dans les limites strictement prévues par l'article
|
|
30
|
+
L.122-6-1 du Code de la propriété intellectuelle ;
|
|
31
|
+
d) supprimer ou altérer les mentions de propriété, de copyright ou la
|
|
32
|
+
présente licence ;
|
|
33
|
+
e) utiliser le Logiciel, ses spécifications ou sa documentation pour
|
|
34
|
+
entraîner, affiner ou évaluer un modèle d'intelligence artificielle
|
|
35
|
+
destiné à des tiers, ou pour concevoir un produit concurrent.
|
|
36
|
+
|
|
37
|
+
4. PRODUITS GÉNÉRÉS
|
|
38
|
+
Les fichiers produits par le compilateur NoraIvoire pour le compte d'un
|
|
39
|
+
licencié (CSS, Dart, TypeScript générés) peuvent être intégrés dans les
|
|
40
|
+
applications de ce licencié dans les conditions fixées par son Contrat de
|
|
41
|
+
Licence. Ils ne peuvent pas être redistribués en tant que bibliothèque,
|
|
42
|
+
framework ou kit de design autonome.
|
|
43
|
+
|
|
44
|
+
5. CONFIDENTIALITÉ
|
|
45
|
+
Le Logiciel contient des informations confidentielles et des secrets
|
|
46
|
+
d'affaires du Titulaire. Toute personne y ayant accès s'engage à en
|
|
47
|
+
préserver la confidentialité.
|
|
48
|
+
|
|
49
|
+
6. ABSENCE DE GARANTIE
|
|
50
|
+
Sauf stipulation contraire du Contrat de Licence, le Logiciel est fourni
|
|
51
|
+
« EN L'ÉTAT », sans garantie d'aucune sorte, expresse ou implicite. Dans
|
|
52
|
+
les limites autorisées par la loi, le Titulaire ne saurait être tenu
|
|
53
|
+
responsable d'aucun dommage résultant de l'utilisation du Logiciel.
|
|
54
|
+
|
|
55
|
+
7. COMPOSANTS TIERS
|
|
56
|
+
Le Logiciel peut référencer des composants tiers soumis à leurs propres
|
|
57
|
+
licences, listés dans THIRD_PARTY_NOTICES.md. La présente licence ne
|
|
58
|
+
modifie pas ces licences.
|
|
59
|
+
|
|
60
|
+
8. DROIT APPLICABLE
|
|
61
|
+
La présente licence est régie par le droit français. Tout litige relève de
|
|
62
|
+
la compétence des tribunaux du ressort de la Cour d'appel de Paris.
|
|
63
|
+
|
|
64
|
+
Contact licences : jonathan.kablan@gmail.com
|
package/README.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @jonathankablan/noraivoire-fx-text
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Effets de texte optionnels de NoraIvoire 0.1.0 : `split`, `scramble`. Chargés à la demande, texte complet lu une seule fois par les lecteurs d'écran, DOM restauré au démontage, mouvement réduit respecté.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<link rel="stylesheet" href="node_modules/@jonathankablan/noraivoire-core/dist/fx/fx-text.css">
|
|
7
|
+
<script type="module">
|
|
8
|
+
import * as moteur from '@jonathankablan/noraivoire-core/engine';
|
|
9
|
+
import { install } from '@jonathankablan/noraivoire-fx-text';
|
|
10
|
+
install(moteur);
|
|
11
|
+
moteur.start();
|
|
12
|
+
</script>
|
|
13
|
+
<h1 data-fx="split" data-fx-split="words:true;stagger:60">Bonjour Abidjan</h1>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Logiciel propriétaire © 2026 Jonathan KABLAN, tous droits réservés (voir LICENSE). La mise à disposition sur npm ne vaut pas concession de licence : tout usage est subordonné à un contrat de licence écrit.
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jonathankablan/noraivoire-fx-text",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "UNLICENSED",
|
|
5
|
+
"description": "Effets de texte optionnels NoraIvoire (split, scramble), chargés à la demande. Propriétaire.",
|
|
6
|
+
"author": "Jonathan KABLAN",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "src/index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.js"
|
|
11
|
+
},
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"noraivoire",
|
|
19
|
+
"text",
|
|
20
|
+
"animation",
|
|
21
|
+
"effects"
|
|
22
|
+
],
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"registry": "https://registry.npmjs.org/"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// NoraIvoire — Effets de texte optionnels : split, scramble.
|
|
2
|
+
// © 2026 Jonathan KABLAN — Propriétaire, tous droits réservés.
|
|
3
|
+
//
|
|
4
|
+
// Arbitrage documenté (docs/SECURITY.md §moteur) : découper ou brouiller un texte
|
|
5
|
+
// impose de créer des nœuds. Ce paquet étend donc, et SEULEMENT lui, le contrat
|
|
6
|
+
// du moteur, de façon contrôlée et vérifiée par `nora audit` :
|
|
7
|
+
// - création de <span> par document.createElement + textContent (jamais de HTML) ;
|
|
8
|
+
// - attributs posés limités à aria-hidden et data-fx-* ;
|
|
9
|
+
// - styles limités aux variables --fx-* ;
|
|
10
|
+
// - les nœuds d'origine sont conservés et remis en place au démontage
|
|
11
|
+
// (mêmes objets : un framework qui les référence les retrouve).
|
|
12
|
+
// N'agit que sur un élément qui ne contient QUE du texte (≤ 500 caractères).
|
|
13
|
+
// Le texte complet reste lisible une seule fois par les lecteurs d'écran
|
|
14
|
+
// (copie visuellement masquée data-fx-sr), la version animée est aria-hidden.
|
|
15
|
+
|
|
16
|
+
const MAX_TEXT = 500;
|
|
17
|
+
const GLYPHS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&*+=<>';
|
|
18
|
+
|
|
19
|
+
/** Découpe pure : graphèmes (ou mots) ; les espaces restent des segments à part. */
|
|
20
|
+
export function segments(text, words = false) {
|
|
21
|
+
if (words) return text.split(/(\s+)/).filter(Boolean);
|
|
22
|
+
if (typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function') {
|
|
23
|
+
return [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(text)].map((s) => s.segment);
|
|
24
|
+
}
|
|
25
|
+
return Array.from(text);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Texte brouillé : les `progress` premiers pourcents sont révélés, espaces conservés. */
|
|
29
|
+
export function scrambleText(text, progress, rand = Math.random) {
|
|
30
|
+
const chars = Array.from(text);
|
|
31
|
+
const shown = Math.floor(chars.length * Math.min(1, Math.max(0, progress)));
|
|
32
|
+
return chars.map((c, i) => (i < shown || /\s/.test(c) ? c : GLYPHS[Math.floor(rand() * GLYPHS.length)])).join('');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Élément texte seul, non vide et borné ; sinon l'effet ne touche à rien. */
|
|
36
|
+
function plainText(el) {
|
|
37
|
+
for (const n of el.childNodes) if (n.nodeType !== 3) return null;
|
|
38
|
+
const text = el.textContent ?? '';
|
|
39
|
+
return text.trim() && text.length <= MAX_TEXT ? text : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function span(attr, text) {
|
|
43
|
+
const s = document.createElement('span');
|
|
44
|
+
s.setAttribute(attr, '');
|
|
45
|
+
if (text !== undefined) s.textContent = text;
|
|
46
|
+
return s;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Remplace le contenu par [copie lue | version animée aria-hidden] ; renvoie la restauration. */
|
|
50
|
+
function stage(el, text) {
|
|
51
|
+
const original = [...el.childNodes];
|
|
52
|
+
const visual = span('data-fx-visual');
|
|
53
|
+
visual.setAttribute('aria-hidden', 'true');
|
|
54
|
+
el.replaceChildren(span('data-fx-sr', text), visual);
|
|
55
|
+
return { visual, restore: () => el.replaceChildren(...original) };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const effects = Object.freeze({
|
|
59
|
+
split: {
|
|
60
|
+
options: {
|
|
61
|
+
words: { type: 'boolean', default: false },
|
|
62
|
+
stagger: { type: 'number', default: 30, min: 0, max: 200 },
|
|
63
|
+
},
|
|
64
|
+
mount(el, opts, ctx) {
|
|
65
|
+
const text = plainText(el);
|
|
66
|
+
if (ctx.reducedMotion || text === null) return;
|
|
67
|
+
const { visual, restore } = stage(el, text);
|
|
68
|
+
let i = 0;
|
|
69
|
+
for (const seg of segments(text, opts.words)) {
|
|
70
|
+
if (/^\s+$/.test(seg)) { visual.append(seg); continue; }
|
|
71
|
+
const piece = span('data-fx-piece', seg);
|
|
72
|
+
piece.style.setProperty('--fx-i', String(i++));
|
|
73
|
+
visual.append(piece);
|
|
74
|
+
}
|
|
75
|
+
ctx.set('--fx-stagger', `${opts.stagger}ms`);
|
|
76
|
+
const show = () => el.setAttribute('data-fx-visible', '');
|
|
77
|
+
if (typeof IntersectionObserver === 'undefined') show();
|
|
78
|
+
else ctx.onVisible((v) => { if (v) show(); });
|
|
79
|
+
return () => {
|
|
80
|
+
restore();
|
|
81
|
+
el.style.removeProperty('--fx-stagger');
|
|
82
|
+
el.removeAttribute('data-fx-visible');
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
scramble: {
|
|
88
|
+
options: { duration: { type: 'number', default: 800, min: 0, max: 5000 } },
|
|
89
|
+
mount(el, opts, ctx) {
|
|
90
|
+
const text = plainText(el);
|
|
91
|
+
if (ctx.reducedMotion || text === null || opts.duration === 0) return;
|
|
92
|
+
let staged = null;
|
|
93
|
+
let played = false;
|
|
94
|
+
const finish = () => { if (staged) { staged.restore(); staged = null; } };
|
|
95
|
+
const play = () => {
|
|
96
|
+
if (played) return;
|
|
97
|
+
played = true;
|
|
98
|
+
staged = stage(el, text);
|
|
99
|
+
const t0 = performance.now();
|
|
100
|
+
const tick = () => {
|
|
101
|
+
if (!staged) return;
|
|
102
|
+
const p = (performance.now() - t0) / opts.duration;
|
|
103
|
+
if (p >= 1) { finish(); return; }
|
|
104
|
+
staged.visual.textContent = scrambleText(text, p);
|
|
105
|
+
ctx.frame(tick);
|
|
106
|
+
};
|
|
107
|
+
tick();
|
|
108
|
+
};
|
|
109
|
+
if (typeof IntersectionObserver === 'undefined') play();
|
|
110
|
+
else ctx.onVisible((v) => { if (v) play(); });
|
|
111
|
+
return finish;
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/** Enregistre split et scramble dans le moteur fourni. Idempotent. */
|
|
117
|
+
export function install(engine) {
|
|
118
|
+
if (!engine || typeof engine.defineEffect !== 'function') throw new TypeError('NoraIvoire : install() attend le module du moteur');
|
|
119
|
+
for (const [name, def] of Object.entries(effects)) {
|
|
120
|
+
if (!engine.hasEffect?.(name)) engine.defineEffect(name, def);
|
|
121
|
+
}
|
|
122
|
+
}
|