@lemoncat7/dsh-theme-xiaohei 0.3.0-alpha.23 → 0.3.2
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/README.md +39 -17
- package/THIRD_PARTY_NOTICES.md +41 -0
- package/lib/appearance.js +26 -10
- package/lib/blink.js +3 -3
- package/lib/bloub-heixiu/ambient.d.ts +3 -0
- package/lib/bloub-heixiu/ambient.js +12 -0
- package/lib/bloub-heixiu/character.d.ts +33 -0
- package/lib/bloub-heixiu/character.js +124 -0
- package/lib/bloub-heixiu/effects.d.ts +4 -0
- package/lib/bloub-heixiu/effects.js +91 -0
- package/lib/bloub-heixiu/flight.d.ts +15 -0
- package/lib/bloub-heixiu/flight.js +66 -0
- package/lib/bloub-heixiu/follow.d.ts +13 -0
- package/lib/bloub-heixiu/follow.js +23 -0
- package/lib/bloub-heixiu/layout.d.ts +18 -0
- package/lib/bloub-heixiu/layout.js +12 -0
- package/lib/bloub-heixiu/renderer.d.ts +16 -0
- package/lib/bloub-heixiu/renderer.js +94 -0
- package/lib/bloub-heixiu/runtime.d.ts +6 -0
- package/lib/bloub-heixiu/runtime.js +395 -0
- package/lib/bloub-heixiu/signals.d.ts +17 -0
- package/lib/bloub-heixiu/signals.js +53 -0
- package/lib/bloub-heixiu/styles.d.ts +2 -0
- package/lib/bloub-heixiu/styles.js +98 -0
- package/lib/chrome/accessibility.d.ts +1 -1
- package/lib/chrome/accessibility.js +5 -13
- package/lib/chrome/composer-avatar.d.ts +10 -0
- package/lib/chrome/composer-avatar.js +599 -0
- package/lib/chrome/composer-send-heixiu.d.ts +3 -0
- package/lib/chrome/composer-send-heixiu.js +143 -0
- package/lib/chrome/composer.d.ts +2 -2
- package/lib/chrome/composer.js +84 -95
- package/lib/chrome/controls.d.ts +8 -0
- package/lib/chrome/controls.js +348 -0
- package/lib/chrome/conversation-messages.d.ts +1 -1
- package/lib/chrome/conversation-messages.js +207 -9
- package/lib/chrome/conversation.d.ts +1 -1
- package/lib/chrome/conversation.js +6 -0
- package/lib/chrome/identity.js +4 -18
- package/lib/chrome/portal.d.ts +1 -1
- package/lib/chrome/portal.js +1 -1
- package/lib/chrome/sidebar.d.ts +1 -1
- package/lib/chrome/sidebar.js +134 -153
- package/lib/chrome/tokens.d.ts +1 -1
- package/lib/chrome/tokens.js +121 -68
- package/lib/chrome/workspace.d.ts +2 -2
- package/lib/chrome/workspace.js +35 -254
- package/lib/chrome.js +4 -0
- package/lib/client.d.ts +5 -12
- package/lib/client.js +6262 -4455
- package/lib/composer-avatar.d.ts +18 -0
- package/lib/composer-avatar.js +441 -0
- package/lib/composer-send-heixiu.d.ts +8 -0
- package/lib/composer-send-heixiu.js +133 -0
- package/lib/gaze.js +3 -3
- package/lib/generated-backgrounds.d.ts +14 -0
- package/lib/generated-backgrounds.js +14 -0
- package/lib/generated-character-motion.d.ts +4 -0
- package/lib/generated-character-motion.js +2 -0
- package/lib/generated-identity.d.ts +1 -0
- package/lib/generated-identity.js +1 -0
- package/lib/generated-keyart.d.ts +18 -3
- package/lib/generated-keyart.js +18 -3
- package/lib/host-contract.d.ts +9 -0
- package/lib/host-contract.js +13 -0
- package/lib/host-dom.d.ts +11 -0
- package/lib/host-dom.js +60 -0
- package/lib/loading-heixiu.js +3 -3
- package/lib/metallic-brand-mark.d.ts +10 -0
- package/lib/metallic-brand-mark.js +323 -0
- package/lib/plugin.d.ts +1 -1
- package/lib/plugin.js +31 -16
- package/lib/portal.js +11 -10
- package/lib/reactions.js +3 -3
- package/lib/scene/avatar-model.d.ts +6 -0
- package/lib/scene/avatar-model.js +646 -0
- package/lib/scene/avatar-motion.d.ts +3 -0
- package/lib/scene/avatar-motion.js +799 -0
- package/lib/scene/character-idle.d.ts +17 -0
- package/lib/scene/character-idle.js +65 -0
- package/lib/scene/character-layout.d.ts +24 -0
- package/lib/scene/character-layout.js +51 -0
- package/lib/scene/character-motion-clock.d.ts +18 -0
- package/lib/scene/character-motion-clock.js +57 -0
- package/lib/scene/character-motion-types.d.ts +12 -0
- package/lib/scene/character-motion-types.js +2 -0
- package/lib/scene/character-motion.d.ts +6 -0
- package/lib/scene/character-motion.js +126 -0
- package/lib/scene/character-poses.d.ts +26 -0
- package/lib/scene/character-poses.js +11 -0
- package/lib/scene/character-rig-renderer.d.ts +4 -0
- package/lib/scene/character-rig-renderer.js +85 -0
- package/lib/scene/character-rig.d.ts +20 -0
- package/lib/scene/character-rig.js +53 -0
- package/lib/scene/heixiu-feedback.d.ts +1 -1
- package/lib/scene/heixiu-feedback.js +0 -17
- package/lib/scene/pointer-bridge.d.ts +21 -0
- package/lib/scene/pointer-bridge.js +155 -0
- package/lib/scene/runtime.d.ts +12 -0
- package/lib/scene/runtime.js +87 -0
- package/lib/scene/styles.d.ts +7 -0
- package/lib/scene/styles.js +75 -0
- package/lib/scene/sylva-performance.d.ts +11 -0
- package/lib/scene/sylva-performance.js +58 -0
- package/lib/scene/wallpaper-character-styles.d.ts +3 -0
- package/lib/scene/wallpaper-character-styles.js +53 -0
- package/lib/scene/wallpaper-character.d.ts +4 -0
- package/lib/scene/wallpaper-character.js +213 -0
- package/lib/scene.d.ts +7 -13
- package/lib/scene.js +7 -1023
- package/lib/sidebar-glass.js +99 -36
- package/lib/sidebar-heixiu-roaming.js +5 -5
- package/lib/sidebar-reveal.d.ts +8 -0
- package/lib/sidebar-reveal.js +51 -0
- package/lib/state.d.ts +14 -2
- package/lib/state.js +12 -4
- package/lib/theme.js +2 -2
- package/lib/vendor/bloub/decor.d.ts +114 -0
- package/lib/vendor/bloub/decor.js +198 -0
- package/lib/vendor/bloub/engine.d.ts +213 -0
- package/lib/vendor/bloub/engine.js +452 -0
- package/lib/vendor/bloub/expressions.d.ts +35 -0
- package/lib/vendor/bloub/expressions.js +143 -0
- package/lib/vendor/bloub/eyefit.d.ts +75 -0
- package/lib/vendor/bloub/eyefit.js +393 -0
- package/lib/vendor/bloub/face.d.ts +63 -0
- package/lib/vendor/bloub/face.js +106 -0
- package/lib/vendor/bloub/math.d.ts +22 -0
- package/lib/vendor/bloub/math.js +36 -0
- package/lib/vendor/bloub/profiles.d.ts +8 -0
- package/lib/vendor/bloub/profiles.js +20 -0
- package/lib/vendor/bloub/repere.d.ts +30 -0
- package/lib/vendor/bloub/repere.js +32 -0
- package/lib/vendor/bloub/shape.d.ts +80 -0
- package/lib/vendor/bloub/shape.js +268 -0
- package/lib/vendor/bloub/skins.d.ts +36 -0
- package/lib/vendor/bloub/skins.js +74 -0
- package/lib/vendor/bloub/states.d.ts +87 -0
- package/lib/vendor/bloub/states.js +468 -0
- package/package.json +23 -6
- package/lib/appearance.d.ts.map +0 -1
- package/lib/appearance.js.map +0 -1
- package/lib/blink.d.ts.map +0 -1
- package/lib/blink.js.map +0 -1
- package/lib/boot-appearance.d.ts.map +0 -1
- package/lib/boot-appearance.js.map +0 -1
- package/lib/boot-loader.d.ts.map +0 -1
- package/lib/boot-loader.js.map +0 -1
- package/lib/chrome/accessibility.d.ts.map +0 -1
- package/lib/chrome/accessibility.js.map +0 -1
- package/lib/chrome/blink.d.ts.map +0 -1
- package/lib/chrome/blink.js.map +0 -1
- package/lib/chrome/composer.d.ts.map +0 -1
- package/lib/chrome/composer.js.map +0 -1
- package/lib/chrome/conversation-messages.d.ts.map +0 -1
- package/lib/chrome/conversation-messages.js.map +0 -1
- package/lib/chrome/conversation.d.ts.map +0 -1
- package/lib/chrome/conversation.js.map +0 -1
- package/lib/chrome/frames.d.ts.map +0 -1
- package/lib/chrome/frames.js.map +0 -1
- package/lib/chrome/gaze.d.ts.map +0 -1
- package/lib/chrome/gaze.js.map +0 -1
- package/lib/chrome/identity.d.ts.map +0 -1
- package/lib/chrome/identity.js.map +0 -1
- package/lib/chrome/overlays.d.ts.map +0 -1
- package/lib/chrome/overlays.js.map +0 -1
- package/lib/chrome/portal.d.ts.map +0 -1
- package/lib/chrome/portal.js.map +0 -1
- package/lib/chrome/reactions.d.ts.map +0 -1
- package/lib/chrome/reactions.js.map +0 -1
- package/lib/chrome/sidebar.d.ts.map +0 -1
- package/lib/chrome/sidebar.js.map +0 -1
- package/lib/chrome/tokens.d.ts.map +0 -1
- package/lib/chrome/tokens.js.map +0 -1
- package/lib/chrome/workspace.d.ts.map +0 -1
- package/lib/chrome/workspace.js.map +0 -1
- package/lib/chrome.d.ts.map +0 -1
- package/lib/chrome.js.map +0 -1
- package/lib/client.d.ts.map +0 -1
- package/lib/client.js.map +0 -7
- package/lib/gaze.d.ts.map +0 -1
- package/lib/gaze.js.map +0 -1
- package/lib/generated-identity.d.ts.map +0 -1
- package/lib/generated-identity.js.map +0 -1
- package/lib/generated-keyart.d.ts.map +0 -1
- package/lib/generated-keyart.js.map +0 -1
- package/lib/generated-loading-assets.d.ts.map +0 -1
- package/lib/generated-loading-assets.js.map +0 -1
- package/lib/generated-sidebar-assets.d.ts +0 -4
- package/lib/generated-sidebar-assets.d.ts.map +0 -1
- package/lib/generated-sidebar-assets.js +0 -4
- package/lib/generated-sidebar-assets.js.map +0 -1
- package/lib/heixiu-interactions.d.ts.map +0 -1
- package/lib/heixiu-interactions.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/loading-heixiu.d.ts.map +0 -1
- package/lib/loading-heixiu.js.map +0 -1
- package/lib/plugin.d.ts.map +0 -1
- package/lib/plugin.js.map +0 -1
- package/lib/portal.d.ts.map +0 -1
- package/lib/portal.js.map +0 -1
- package/lib/reactions.d.ts.map +0 -1
- package/lib/reactions.js.map +0 -1
- package/lib/scene/heixiu-feedback.d.ts.map +0 -1
- package/lib/scene/heixiu-feedback.js.map +0 -1
- package/lib/scene/heixiu-interactions.d.ts.map +0 -1
- package/lib/scene/heixiu-interactions.js.map +0 -1
- package/lib/scene/state-transitions.d.ts.map +0 -1
- package/lib/scene/state-transitions.js.map +0 -1
- package/lib/scene.d.ts.map +0 -1
- package/lib/scene.js.map +0 -1
- package/lib/sidebar-glass.d.ts.map +0 -1
- package/lib/sidebar-glass.js.map +0 -1
- package/lib/sidebar-heixiu-escape.d.ts.map +0 -1
- package/lib/sidebar-heixiu-escape.js.map +0 -1
- package/lib/sidebar-heixiu-roaming.d.ts.map +0 -1
- package/lib/sidebar-heixiu-roaming.js.map +0 -1
- package/lib/state.d.ts.map +0 -1
- package/lib/state.js.map +0 -1
- package/lib/theme.d.ts.map +0 -1
- package/lib/theme.js.map +0 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Adapted from bloub, Copyright (c) 2026 Jérémy Perret (MIT).
|
|
2
|
+
// See THIRD_PARTY_NOTICES.md for source revision and local changes.
|
|
3
|
+
export const TAU = Math.PI * 2;
|
|
4
|
+
export const clamp = (v, lo = 0, hi = 1) => (v < lo ? lo : v > hi ? hi : v);
|
|
5
|
+
export const lerp = (a, b, t) => a + (b - a) * t;
|
|
6
|
+
/**
|
|
7
|
+
* Mesure sur la video : les transitions sont des ease-out exponentiels, sans
|
|
8
|
+
* depassement du corps. Les seuls effets de ressort sont locaux (le pop de la
|
|
9
|
+
* pastille de notification, l'ouverture des yeux) et sont ecrits directement
|
|
10
|
+
* dans l'etat concerne.
|
|
11
|
+
*/
|
|
12
|
+
export const easings = {
|
|
13
|
+
easeOutCubic: (t) => 1 - (1 - t) ** 3,
|
|
14
|
+
easeInOutCubic: (t) => (t < 0.5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2),
|
|
15
|
+
easeOutQuint: (t) => 1 - (1 - t) ** 5
|
|
16
|
+
};
|
|
17
|
+
/** Bruit 1D periodique : boucle sans couture sur `period`, utile pour la derive du regard. */
|
|
18
|
+
export function loopNoise(t, period, seed = 0) {
|
|
19
|
+
const p = (t / period) * TAU;
|
|
20
|
+
return (0.55 * Math.sin(p + seed) +
|
|
21
|
+
0.3 * Math.sin(2 * p + seed * 1.7 + 1.1) +
|
|
22
|
+
0.15 * Math.sin(3 * p + seed * 2.3 + 2.4));
|
|
23
|
+
}
|
|
24
|
+
/** PRNG deterministe (mulberry32) : meme sequence a chaque lecture. */
|
|
25
|
+
export function createRng(seed) {
|
|
26
|
+
let a = seed >>> 0;
|
|
27
|
+
return () => {
|
|
28
|
+
a = (a + 0x6d2b79f5) >>> 0;
|
|
29
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
30
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
31
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Arrondi court : divise par ~2 le poids des chaines de path generees a 60 fps. */
|
|
35
|
+
export const r2 = (v) => Math.round(v * 100) / 100;
|
|
36
|
+
//# sourceMappingURL=math.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const PROFILE_SAMPLES = 64;
|
|
2
|
+
export declare const PROFILES: {
|
|
3
|
+
readonly egg: readonly [0.8369, 0.8424, 0.8497, 0.8585, 0.8674, 0.8775, 0.8878, 0.8983, 0.9089, 0.9185, 0.9288, 0.9374, 0.9445, 0.9504, 0.9543, 0.9559, 0.9555, 0.9519, 0.9466, 0.9389, 0.9302, 0.9193, 0.9085, 0.8969, 0.8852, 0.8734, 0.8625, 0.8513, 0.8411, 0.8325, 0.8243, 0.8179, 0.8137, 0.8112, 0.8102, 0.8128, 0.8178, 0.8262, 0.8374, 0.8518, 0.8702, 0.8922, 0.9169, 0.9446, 0.9741, 1.0023, 1.0267, 1.0433, 1.0481, 1.0393, 1.0216, 0.997, 0.9697, 0.9418, 0.9169, 0.8949, 0.876, 0.8604, 0.849, 0.8394, 0.8337, 0.8314, 0.8305, 0.8326];
|
|
4
|
+
readonly hexagon: readonly [0.921, 0.9282, 0.9441, 0.9706, 0.9984, 1.0059, 0.9896, 0.9562, 0.929, 0.9124, 0.9047, 0.9058, 0.9157, 0.9349, 0.9642, 0.9873, 0.9882, 0.9665, 0.9336, 0.9105, 0.8968, 0.8918, 0.8955, 0.908, 0.9293, 0.9611, 0.982, 0.9812, 0.959, 0.9282, 0.9089, 0.8978, 0.8964, 0.9026, 0.9189, 0.9439, 0.9778, 0.999, 0.9964, 0.9713, 0.9439, 0.9274, 0.9196, 0.9206, 0.9308, 0.9502, 0.9799, 1.0121, 1.0226, 1.0071, 0.9752, 0.951, 0.9366, 0.9316, 0.9351, 0.9485, 0.9711, 1.0026, 1.0213, 1.0155, 0.9863, 0.9547, 0.9347, 0.9232];
|
|
5
|
+
readonly triangle: readonly [0.7819, 0.8211, 0.8747, 0.944, 1.0223, 1.096, 1.1401, 1.134, 1.0808, 1.0047, 0.9265, 0.8603, 0.8104, 0.773, 0.745, 0.7273, 0.7151, 0.7118, 0.7148, 0.7245, 0.7427, 0.768, 0.8037, 0.8518, 0.9148, 0.9876, 1.0583, 1.1073, 1.1109, 1.0667, 0.994, 0.9164, 0.8482, 0.7948, 0.7555, 0.7261, 0.7056, 0.6925, 0.6859, 0.6869, 0.6938, 0.7084, 0.7305, 0.7615, 0.804, 0.8595, 0.9311, 1.0092, 1.0791, 1.1171, 1.1054, 1.0501, 0.9779, 0.905, 0.845, 0.799, 0.7656, 0.7413, 0.7258, 0.716, 0.7146, 0.7204, 0.733, 0.7528];
|
|
6
|
+
};
|
|
7
|
+
export type ProfileName = keyof typeof PROFILES;
|
|
8
|
+
//# sourceMappingURL=profiles.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Adapted from bloub, Copyright (c) 2026 Jérémy Perret (MIT).
|
|
2
|
+
// See THIRD_PARTY_NOTICES.md for source revision and local changes.
|
|
3
|
+
// Profils radiaux r(theta) releves au pixel sur la video de reference.
|
|
4
|
+
// theta = 0 pointe vers la droite et croit dans le sens horaire (y vers le bas).
|
|
5
|
+
// Unite : rayon de la boule au repos = 1.
|
|
6
|
+
//
|
|
7
|
+
// Genere par tools/extract-profiles.py — ne pas editer a la main.
|
|
8
|
+
export const PROFILE_SAMPLES = 64;
|
|
9
|
+
export const PROFILES = {
|
|
10
|
+
// oeuf : meme hauteur que la boule, retreci en largeur
|
|
11
|
+
// image 164, empreinte mesuree 1.647 x 2.000
|
|
12
|
+
egg: [0.8369, 0.8424, 0.8497, 0.8585, 0.8674, 0.8775, 0.8878, 0.8983, 0.9089, 0.9185, 0.9288, 0.9374, 0.9445, 0.9504, 0.9543, 0.9559, 0.9555, 0.9519, 0.9466, 0.9389, 0.9302, 0.9193, 0.9085, 0.8969, 0.8852, 0.8734, 0.8625, 0.8513, 0.8411, 0.8325, 0.8243, 0.8179, 0.8137, 0.8112, 0.8102, 0.8128, 0.8178, 0.8262, 0.8374, 0.8518, 0.8702, 0.8922, 0.9169, 0.9446, 0.9741, 1.0023, 1.0267, 1.0433, 1.0481, 1.0393, 1.0216, 0.9970, 0.9697, 0.9418, 0.9169, 0.8949, 0.8760, 0.8604, 0.8490, 0.8394, 0.8337, 0.8314, 0.8305, 0.8326],
|
|
13
|
+
// hexagone pointe en haut, coins tres arrondis
|
|
14
|
+
// image 174, empreinte mesuree 1.826 x 2.011
|
|
15
|
+
hexagon: [0.9210, 0.9282, 0.9441, 0.9706, 0.9984, 1.0059, 0.9896, 0.9562, 0.9290, 0.9124, 0.9047, 0.9058, 0.9157, 0.9349, 0.9642, 0.9873, 0.9882, 0.9665, 0.9336, 0.9105, 0.8968, 0.8918, 0.8955, 0.9080, 0.9293, 0.9611, 0.9820, 0.9812, 0.9590, 0.9282, 0.9089, 0.8978, 0.8964, 0.9026, 0.9189, 0.9439, 0.9778, 0.9990, 0.9964, 0.9713, 0.9439, 0.9274, 0.9196, 0.9206, 0.9308, 0.9502, 0.9799, 1.0121, 1.0226, 1.0071, 0.9752, 0.9510, 0.9366, 0.9316, 0.9351, 0.9485, 0.9711, 1.0026, 1.0213, 1.0155, 0.9863, 0.9547, 0.9347, 0.9232],
|
|
16
|
+
// triangle pointe en haut, coins tres arrondis
|
|
17
|
+
// image 190, empreinte mesuree 1.995 x 1.884
|
|
18
|
+
triangle: [0.7819, 0.8211, 0.8747, 0.9440, 1.0223, 1.0960, 1.1401, 1.1340, 1.0808, 1.0047, 0.9265, 0.8603, 0.8104, 0.7730, 0.7450, 0.7273, 0.7151, 0.7118, 0.7148, 0.7245, 0.7427, 0.7680, 0.8037, 0.8518, 0.9148, 0.9876, 1.0583, 1.1073, 1.1109, 1.0667, 0.9940, 0.9164, 0.8482, 0.7948, 0.7555, 0.7261, 0.7056, 0.6925, 0.6859, 0.6869, 0.6938, 0.7084, 0.7305, 0.7615, 0.8040, 0.8595, 0.9311, 1.0092, 1.0791, 1.1171, 1.1054, 1.0501, 0.9779, 0.9050, 0.8450, 0.7990, 0.7656, 0.7413, 0.7258, 0.7160, 0.7146, 0.7204, 0.7330, 0.7528],
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=profiles.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Le repere de tout ce que le moteur rend.
|
|
3
|
+
*
|
|
4
|
+
* `engine.sample()` sort des coordonnees en unites de viewBox, et ces deux nombres en sont
|
|
5
|
+
* la definition : sans eux, une sortie du moteur ne veut rien dire. Ils vivaient dans
|
|
6
|
+
* `BloubBot.vue`, donc hors d'atteinte — un `<script setup>` n'exporte rien — et
|
|
7
|
+
* `export.ts` en redisait un a la main avec le commentaire qui nommait le probleme.
|
|
8
|
+
*
|
|
9
|
+
* Ils sont ici parce que `src/bot/` est ce qui se lit et se consomme du dehors : le
|
|
10
|
+
* composant Vue est UN client du moteur, pas sa definition.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Rayon de la boule au repos, en unites de viewBox. C'est le `scale` que le composant
|
|
14
|
+
* passe a `BotEngine`.
|
|
15
|
+
*
|
|
16
|
+
* Choisi et non mesure : c'est l'unite de travail. Tout le reste du dossier s'exprime en
|
|
17
|
+
* fractions de ce rayon, ce qui rend les mesures relevees sur la video independantes de la
|
|
18
|
+
* taille d'affichage.
|
|
19
|
+
*/
|
|
20
|
+
export declare const RAYON = 100;
|
|
21
|
+
/**
|
|
22
|
+
* Demi-cote du viewBox affiche. La marge au-dela du rayon loge les anneaux.
|
|
23
|
+
*
|
|
24
|
+
* Ce n'est pas une valeur libre : les anneaux de l'orbite et le swoosh de la comete montent
|
|
25
|
+
* a 1,4 fois le rayon, soit 140. Rien ne les borne au runtime — c'est le reglage a la main
|
|
26
|
+
* des tableaux `RINGS` et `SWOOSH` (`decor.ts`) qui les tient sous 158, et un test le
|
|
27
|
+
* verrouille.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEMI_VIEWBOX = 158;
|
|
30
|
+
//# sourceMappingURL=repere.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Adapted from bloub, Copyright (c) 2026 Jérémy Perret (MIT).
|
|
2
|
+
// See THIRD_PARTY_NOTICES.md for source revision and local changes.
|
|
3
|
+
/**
|
|
4
|
+
* Le repere de tout ce que le moteur rend.
|
|
5
|
+
*
|
|
6
|
+
* `engine.sample()` sort des coordonnees en unites de viewBox, et ces deux nombres en sont
|
|
7
|
+
* la definition : sans eux, une sortie du moteur ne veut rien dire. Ils vivaient dans
|
|
8
|
+
* `BloubBot.vue`, donc hors d'atteinte — un `<script setup>` n'exporte rien — et
|
|
9
|
+
* `export.ts` en redisait un a la main avec le commentaire qui nommait le probleme.
|
|
10
|
+
*
|
|
11
|
+
* Ils sont ici parce que `src/bot/` est ce qui se lit et se consomme du dehors : le
|
|
12
|
+
* composant Vue est UN client du moteur, pas sa definition.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Rayon de la boule au repos, en unites de viewBox. C'est le `scale` que le composant
|
|
16
|
+
* passe a `BotEngine`.
|
|
17
|
+
*
|
|
18
|
+
* Choisi et non mesure : c'est l'unite de travail. Tout le reste du dossier s'exprime en
|
|
19
|
+
* fractions de ce rayon, ce qui rend les mesures relevees sur la video independantes de la
|
|
20
|
+
* taille d'affichage.
|
|
21
|
+
*/
|
|
22
|
+
export const RAYON = 100;
|
|
23
|
+
/**
|
|
24
|
+
* Demi-cote du viewBox affiche. La marge au-dela du rayon loge les anneaux.
|
|
25
|
+
*
|
|
26
|
+
* Ce n'est pas une valeur libre : les anneaux de l'orbite et le swoosh de la comete montent
|
|
27
|
+
* a 1,4 fois le rayon, soit 140. Rien ne les borne au runtime — c'est le reglage a la main
|
|
28
|
+
* des tableaux `RINGS` et `SWOOSH` (`decor.ts`) qui les tient sous 158, et un test le
|
|
29
|
+
* verrouille.
|
|
30
|
+
*/
|
|
31
|
+
export const DEMI_VIEWBOX = 158;
|
|
32
|
+
//# sourceMappingURL=repere.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type ProfileName } from './profiles.js';
|
|
2
|
+
export interface Point {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Une silhouette = un profil radial r(theta) plus une pose.
|
|
8
|
+
*
|
|
9
|
+
* Tout passe par des profils echantillonnes au MEME nombre d'angles : deux
|
|
10
|
+
* formes quelconques ont donc des points qui se correspondent un a un, et le
|
|
11
|
+
* morphing se reduit a une interpolation lineaire des rayons. C'est ce qui
|
|
12
|
+
* rend les transitions propres sans librairie de morphing de path.
|
|
13
|
+
*/
|
|
14
|
+
export interface Silhouette {
|
|
15
|
+
radii: number[];
|
|
16
|
+
/** rotation du profil, en radians */
|
|
17
|
+
rot: number;
|
|
18
|
+
/** decalage du centre, en unites de rayon de boule */
|
|
19
|
+
cx: number;
|
|
20
|
+
cy: number;
|
|
21
|
+
/** squash & stretch, applique en repere ecran (apres rotation) */
|
|
22
|
+
sx: number;
|
|
23
|
+
sy: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function silhouette(name: ProfileName, pose?: Partial<Silhouette>): Silhouette;
|
|
26
|
+
/** Cercle parfait : sert de base neutre (point, bulle, cible de fondu). */
|
|
27
|
+
export declare function circle(radius: number, pose?: Partial<Silhouette>): Silhouette;
|
|
28
|
+
/** Interpolation de deux silhouettes. `out` est reutilise pour eviter d'allouer a 60 fps. */
|
|
29
|
+
export declare function blend(a: Silhouette, b: Silhouette, t: number, out?: Silhouette): Silhouette;
|
|
30
|
+
/** Projette la silhouette en points ecran. `scale` = rayon de la boule en unites de viewBox. */
|
|
31
|
+
export declare function toPoints(s: Silhouette, scale: number, out?: Point[]): Point[];
|
|
32
|
+
/**
|
|
33
|
+
* Polyligne fermee -> cubiques Catmull-Rom.
|
|
34
|
+
*
|
|
35
|
+
* Avec 64 points les tangentes centrees suffisent largement : le contour est
|
|
36
|
+
* lisse au pixel pres meme affiche en 600 px, et la chaine reste courte.
|
|
37
|
+
*/
|
|
38
|
+
export declare function closedPath(pts: Point[], tension?: number): string;
|
|
39
|
+
/**
|
|
40
|
+
* Polygone quelconque -> profil radial, par lancer de rayon depuis `center`.
|
|
41
|
+
*
|
|
42
|
+
* Sert a fabriquer les formes qui ne s'expriment pas naturellement en r(theta)
|
|
43
|
+
* (la barre tronconique du "!"). Calcule une seule fois au chargement, jamais
|
|
44
|
+
* dans la boucle de rendu.
|
|
45
|
+
*/
|
|
46
|
+
export declare function profileFromPolygon(poly: Point[], cx: number, cy: number): number[];
|
|
47
|
+
/** Enveloppe convexe de deux cercles : la barre tronconique du "!" vertical. */
|
|
48
|
+
export declare function hullOfCircles(x1: number, y1: number, r1: number, x2: number, y2: number, r2v: number, steps?: number): Point[];
|
|
49
|
+
/**
|
|
50
|
+
* Rayon du profil dans une direction quelconque, par interpolation entre les
|
|
51
|
+
* deux echantillons voisins.
|
|
52
|
+
*
|
|
53
|
+
* Sert a recaler ce qui est pose "sur" le corps (les yeux, la pastille de
|
|
54
|
+
* notification) quand la silhouette n'est plus un cercle : sans ca, un oeil
|
|
55
|
+
* place a 0.62 rayon sort d'une forme dont le bord est a 0.55 dans cette
|
|
56
|
+
* direction, et le masque le rogne.
|
|
57
|
+
*/
|
|
58
|
+
export declare function radiusAtAngle(radii: number[], angle: number): number;
|
|
59
|
+
/**
|
|
60
|
+
* Superellipse : |x/sx|^n + |y/sy|^n = 1.
|
|
61
|
+
* n = 2 donne une ellipse, n ~ 4 le squircle du personnalisateur.
|
|
62
|
+
*/
|
|
63
|
+
export declare function superellipseProfile(n: number, sx?: number, sy?: number): number[];
|
|
64
|
+
/**
|
|
65
|
+
* Profil radial de l'UNION de disques : r(theta) = la plus lointaine des
|
|
66
|
+
* intersections rayon/cercle. Exact tant que l'origine est dans l'union — c'est
|
|
67
|
+
* ce qui donne les bosses du nuage sans booleen de path.
|
|
68
|
+
*/
|
|
69
|
+
export declare function unionOfCirclesProfile(circles: Array<{
|
|
70
|
+
x: number;
|
|
71
|
+
y: number;
|
|
72
|
+
r: number;
|
|
73
|
+
}>): number[];
|
|
74
|
+
/** Polygone regulier a coins arrondis, inscrit dans `radius`. */
|
|
75
|
+
export declare function regularPolygonProfile(sides: number, radius: number, rc: number, rotationDeg?: number): number[];
|
|
76
|
+
/** Polyligne fermee exacte : garde les segments droits (contrairement a closedPath). */
|
|
77
|
+
export declare function polyPath(pts: Point[], scale?: number): string;
|
|
78
|
+
/** Capsule (stade) centree sur l'origine : la forme exacte des yeux du bot. */
|
|
79
|
+
export declare function capsulePath(w: number, h: number): string;
|
|
80
|
+
//# sourceMappingURL=shape.d.ts.map
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// Adapted from bloub, Copyright (c) 2026 Jérémy Perret (MIT).
|
|
2
|
+
// See THIRD_PARTY_NOTICES.md for source revision and local changes.
|
|
3
|
+
import { TAU, lerp, r2 } from './math.js';
|
|
4
|
+
import { PROFILES, PROFILE_SAMPLES } from './profiles.js';
|
|
5
|
+
const ANGLES = Array.from({ length: PROFILE_SAMPLES }, (_, i) => (i / PROFILE_SAMPLES) * TAU);
|
|
6
|
+
const COS = ANGLES.map(Math.cos);
|
|
7
|
+
const SIN = ANGLES.map(Math.sin);
|
|
8
|
+
export function silhouette(name, pose = {}) {
|
|
9
|
+
return {
|
|
10
|
+
radii: [...PROFILES[name]],
|
|
11
|
+
rot: 0,
|
|
12
|
+
cx: 0,
|
|
13
|
+
cy: 0,
|
|
14
|
+
sx: 1,
|
|
15
|
+
sy: 1,
|
|
16
|
+
...pose
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Cercle parfait : sert de base neutre (point, bulle, cible de fondu). */
|
|
20
|
+
export function circle(radius, pose = {}) {
|
|
21
|
+
return {
|
|
22
|
+
radii: new Array(PROFILE_SAMPLES).fill(radius),
|
|
23
|
+
rot: 0,
|
|
24
|
+
cx: 0,
|
|
25
|
+
cy: 0,
|
|
26
|
+
sx: 1,
|
|
27
|
+
sy: 1,
|
|
28
|
+
...pose
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Interpolation de deux silhouettes. `out` est reutilise pour eviter d'allouer a 60 fps. */
|
|
32
|
+
export function blend(a, b, t, out) {
|
|
33
|
+
const dst = out ?? { radii: new Array(PROFILE_SAMPLES), rot: 0, cx: 0, cy: 0, sx: 1, sy: 1 };
|
|
34
|
+
for (let i = 0; i < PROFILE_SAMPLES; i++) {
|
|
35
|
+
dst.radii[i] = lerp(a.radii[i] ?? 1, b.radii[i] ?? 1, t);
|
|
36
|
+
}
|
|
37
|
+
// Rotation par le chemin le plus court : evite de faire un tour complet
|
|
38
|
+
// quand on passe par exemple de +170deg a -170deg.
|
|
39
|
+
let dRot = b.rot - a.rot;
|
|
40
|
+
while (dRot > Math.PI)
|
|
41
|
+
dRot -= TAU;
|
|
42
|
+
while (dRot < -Math.PI)
|
|
43
|
+
dRot += TAU;
|
|
44
|
+
dst.rot = a.rot + dRot * t;
|
|
45
|
+
dst.cx = lerp(a.cx, b.cx, t);
|
|
46
|
+
dst.cy = lerp(a.cy, b.cy, t);
|
|
47
|
+
dst.sx = lerp(a.sx, b.sx, t);
|
|
48
|
+
dst.sy = lerp(a.sy, b.sy, t);
|
|
49
|
+
return dst;
|
|
50
|
+
}
|
|
51
|
+
/** Projette la silhouette en points ecran. `scale` = rayon de la boule en unites de viewBox. */
|
|
52
|
+
export function toPoints(s, scale, out = []) {
|
|
53
|
+
const cr = Math.cos(s.rot);
|
|
54
|
+
const sr = Math.sin(s.rot);
|
|
55
|
+
for (let i = 0; i < PROFILE_SAMPLES; i++) {
|
|
56
|
+
const r = s.radii[i] ?? 1;
|
|
57
|
+
const x = r * (COS[i] ?? 0);
|
|
58
|
+
const y = r * (SIN[i] ?? 0);
|
|
59
|
+
// rotation puis squash en repere ecran, puis translation
|
|
60
|
+
const rx = x * cr - y * sr;
|
|
61
|
+
const ry = x * sr + y * cr;
|
|
62
|
+
const p = out[i] ?? { x: 0, y: 0 };
|
|
63
|
+
p.x = (rx * s.sx + s.cx) * scale;
|
|
64
|
+
p.y = (ry * s.sy + s.cy) * scale;
|
|
65
|
+
out[i] = p;
|
|
66
|
+
}
|
|
67
|
+
out.length = PROFILE_SAMPLES;
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Polyligne fermee -> cubiques Catmull-Rom.
|
|
72
|
+
*
|
|
73
|
+
* Avec 64 points les tangentes centrees suffisent largement : le contour est
|
|
74
|
+
* lisse au pixel pres meme affiche en 600 px, et la chaine reste courte.
|
|
75
|
+
*/
|
|
76
|
+
export function closedPath(pts, tension = 1 / 6) {
|
|
77
|
+
const n = pts.length;
|
|
78
|
+
if (n < 3)
|
|
79
|
+
return '';
|
|
80
|
+
const first = pts[0];
|
|
81
|
+
let d = `M${r2(first.x)} ${r2(first.y)}`;
|
|
82
|
+
for (let i = 0; i < n; i++) {
|
|
83
|
+
const p0 = pts[(i - 1 + n) % n];
|
|
84
|
+
const p1 = pts[i];
|
|
85
|
+
const p2 = pts[(i + 1) % n];
|
|
86
|
+
const p3 = pts[(i + 2) % n];
|
|
87
|
+
const c1x = p1.x + (p2.x - p0.x) * tension;
|
|
88
|
+
const c1y = p1.y + (p2.y - p0.y) * tension;
|
|
89
|
+
const c2x = p2.x - (p3.x - p1.x) * tension;
|
|
90
|
+
const c2y = p2.y - (p3.y - p1.y) * tension;
|
|
91
|
+
d += `C${r2(c1x)} ${r2(c1y)} ${r2(c2x)} ${r2(c2y)} ${r2(p2.x)} ${r2(p2.y)}`;
|
|
92
|
+
}
|
|
93
|
+
return `${d}Z`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Polygone quelconque -> profil radial, par lancer de rayon depuis `center`.
|
|
97
|
+
*
|
|
98
|
+
* Sert a fabriquer les formes qui ne s'expriment pas naturellement en r(theta)
|
|
99
|
+
* (la barre tronconique du "!"). Calcule une seule fois au chargement, jamais
|
|
100
|
+
* dans la boucle de rendu.
|
|
101
|
+
*/
|
|
102
|
+
export function profileFromPolygon(poly, cx, cy) {
|
|
103
|
+
const radii = new Array(PROFILE_SAMPLES).fill(0);
|
|
104
|
+
const n = poly.length;
|
|
105
|
+
for (let k = 0; k < PROFILE_SAMPLES; k++) {
|
|
106
|
+
const dx = COS[k] ?? 0;
|
|
107
|
+
const dy = SIN[k] ?? 0;
|
|
108
|
+
let best = 0;
|
|
109
|
+
for (let i = 0; i < n; i++) {
|
|
110
|
+
const a = poly[i];
|
|
111
|
+
const b = poly[(i + 1) % n];
|
|
112
|
+
const ex = b.x - a.x;
|
|
113
|
+
const ey = b.y - a.y;
|
|
114
|
+
const den = dx * ey - dy * ex;
|
|
115
|
+
if (Math.abs(den) < 1e-9)
|
|
116
|
+
continue;
|
|
117
|
+
const px = a.x - cx;
|
|
118
|
+
const py = a.y - cy;
|
|
119
|
+
const t = (px * ey - py * ex) / den; // distance le long du rayon
|
|
120
|
+
const u = (px * dy - py * dx) / den; // position sur le segment
|
|
121
|
+
if (t > best && u >= 0 && u <= 1)
|
|
122
|
+
best = t;
|
|
123
|
+
}
|
|
124
|
+
radii[k] = best;
|
|
125
|
+
}
|
|
126
|
+
return radii;
|
|
127
|
+
}
|
|
128
|
+
/** Enveloppe convexe de deux cercles : la barre tronconique du "!" vertical. */
|
|
129
|
+
export function hullOfCircles(x1, y1, r1, x2, y2, r2v, steps = 96) {
|
|
130
|
+
const dx = x2 - x1;
|
|
131
|
+
const dy = y2 - y1;
|
|
132
|
+
const dist = Math.hypot(dx, dy) || 1e-6;
|
|
133
|
+
// angle des tangentes externes communes
|
|
134
|
+
const base = Math.atan2(dy, dx);
|
|
135
|
+
const spread = Math.acos(Math.max(-1, Math.min(1, (r1 - r2v) / dist)));
|
|
136
|
+
const pts = [];
|
|
137
|
+
// arc du grand cercle
|
|
138
|
+
for (let i = 0; i <= steps / 2; i++) {
|
|
139
|
+
const a = base + spread + ((TAU - 2 * spread) * i) / (steps / 2);
|
|
140
|
+
pts.push({ x: x1 + Math.cos(a) * r1, y: y1 + Math.sin(a) * r1 });
|
|
141
|
+
}
|
|
142
|
+
// arc du petit cercle
|
|
143
|
+
for (let i = 0; i <= steps / 2; i++) {
|
|
144
|
+
const a = base - spread + ((2 * spread) * i) / (steps / 2);
|
|
145
|
+
pts.push({ x: x2 + Math.cos(a) * r2v, y: y2 + Math.sin(a) * r2v });
|
|
146
|
+
}
|
|
147
|
+
return pts;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Rayon du profil dans une direction quelconque, par interpolation entre les
|
|
151
|
+
* deux echantillons voisins.
|
|
152
|
+
*
|
|
153
|
+
* Sert a recaler ce qui est pose "sur" le corps (les yeux, la pastille de
|
|
154
|
+
* notification) quand la silhouette n'est plus un cercle : sans ca, un oeil
|
|
155
|
+
* place a 0.62 rayon sort d'une forme dont le bord est a 0.55 dans cette
|
|
156
|
+
* direction, et le masque le rogne.
|
|
157
|
+
*/
|
|
158
|
+
export function radiusAtAngle(radii, angle) {
|
|
159
|
+
const n = radii.length;
|
|
160
|
+
const t = ((((angle / TAU) % 1) + 1) % 1) * n;
|
|
161
|
+
const i = Math.floor(t);
|
|
162
|
+
return lerp(radii[i % n] ?? 1, radii[(i + 1) % n] ?? 1, t - i);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Superellipse : |x/sx|^n + |y/sy|^n = 1.
|
|
166
|
+
* n = 2 donne une ellipse, n ~ 4 le squircle du personnalisateur.
|
|
167
|
+
*/
|
|
168
|
+
export function superellipseProfile(n, sx = 1, sy = 1) {
|
|
169
|
+
return ANGLES.map((_, i) => {
|
|
170
|
+
const c = Math.abs((COS[i] ?? 0) / sx) ** n;
|
|
171
|
+
const s = Math.abs((SIN[i] ?? 0) / sy) ** n;
|
|
172
|
+
return (c + s) ** (-1 / n);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Profil radial de l'UNION de disques : r(theta) = la plus lointaine des
|
|
177
|
+
* intersections rayon/cercle. Exact tant que l'origine est dans l'union — c'est
|
|
178
|
+
* ce qui donne les bosses du nuage sans booleen de path.
|
|
179
|
+
*/
|
|
180
|
+
export function unionOfCirclesProfile(circles) {
|
|
181
|
+
const out = new Array(PROFILE_SAMPLES).fill(0);
|
|
182
|
+
for (let i = 0; i < PROFILE_SAMPLES; i++) {
|
|
183
|
+
const dx = COS[i] ?? 0;
|
|
184
|
+
const dy = SIN[i] ?? 0;
|
|
185
|
+
let best = 0;
|
|
186
|
+
for (const c of circles) {
|
|
187
|
+
const b = dx * c.x + dy * c.y;
|
|
188
|
+
const disc = b * b - (c.x * c.x + c.y * c.y - c.r * c.r);
|
|
189
|
+
if (disc < 0)
|
|
190
|
+
continue;
|
|
191
|
+
const t = b + Math.sqrt(disc);
|
|
192
|
+
if (t > best)
|
|
193
|
+
best = t;
|
|
194
|
+
}
|
|
195
|
+
out[i] = best;
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Polygone a coins arrondis, par somme de Minkowski avec un disque : chaque
|
|
201
|
+
* arete est poussee de `rc` vers l'exterieur, chaque sommet devient un arc de
|
|
202
|
+
* rayon `rc`. Les sommets sont donc a poser au rayon voulu MOINS rc.
|
|
203
|
+
* Attend un polygone en sens horaire (repere ecran, y vers le bas).
|
|
204
|
+
*/
|
|
205
|
+
function roundedPolygon(verts, rc, arcSteps = 10) {
|
|
206
|
+
const n = verts.length;
|
|
207
|
+
const out = [];
|
|
208
|
+
const normal = (a, b) => {
|
|
209
|
+
const dx = b.x - a.x;
|
|
210
|
+
const dy = b.y - a.y;
|
|
211
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
212
|
+
// sens horaire + y vers le bas : la normale sortante est (dy, -dx)
|
|
213
|
+
return Math.atan2(-dx / len, dy / len);
|
|
214
|
+
};
|
|
215
|
+
for (let i = 0; i < n; i++) {
|
|
216
|
+
const prev = verts[(i - 1 + n) % n];
|
|
217
|
+
const cur = verts[i];
|
|
218
|
+
const next = verts[(i + 1) % n];
|
|
219
|
+
const a0 = normal(prev, cur);
|
|
220
|
+
const a1 = normal(cur, next);
|
|
221
|
+
let d = a1 - a0;
|
|
222
|
+
while (d > Math.PI)
|
|
223
|
+
d -= TAU;
|
|
224
|
+
while (d < -Math.PI)
|
|
225
|
+
d += TAU;
|
|
226
|
+
for (let k = 0; k <= arcSteps; k++) {
|
|
227
|
+
const a = a0 + (d * k) / arcSteps;
|
|
228
|
+
out.push({ x: cur.x + Math.cos(a) * rc, y: cur.y + Math.sin(a) * rc });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
/** Polygone regulier a coins arrondis, inscrit dans `radius`. */
|
|
234
|
+
export function regularPolygonProfile(sides, radius, rc, rotationDeg = 0) {
|
|
235
|
+
const rot = (rotationDeg * Math.PI) / 180;
|
|
236
|
+
const verts = Array.from({ length: sides }, (_, i) => {
|
|
237
|
+
// sens horaire a l'ecran : theta croissant avec y vers le bas
|
|
238
|
+
const a = rot + (i / sides) * TAU;
|
|
239
|
+
return { x: Math.cos(a) * (radius - rc), y: Math.sin(a) * (radius - rc) };
|
|
240
|
+
});
|
|
241
|
+
return profileFromPolygon(roundedPolygon(verts, rc), 0, 0);
|
|
242
|
+
}
|
|
243
|
+
/** Polyligne fermee exacte : garde les segments droits (contrairement a closedPath). */
|
|
244
|
+
export function polyPath(pts, scale = 1) {
|
|
245
|
+
if (pts.length < 3)
|
|
246
|
+
return '';
|
|
247
|
+
let d = '';
|
|
248
|
+
for (let i = 0; i < pts.length; i++) {
|
|
249
|
+
const p = pts[i];
|
|
250
|
+
d += `${i === 0 ? 'M' : 'L'}${r2(p.x * scale)} ${r2(p.y * scale)}`;
|
|
251
|
+
}
|
|
252
|
+
return `${d}Z`;
|
|
253
|
+
}
|
|
254
|
+
/** Capsule (stade) centree sur l'origine : la forme exacte des yeux du bot. */
|
|
255
|
+
export function capsulePath(w, h) {
|
|
256
|
+
const hw = Math.max(w, 0.01) / 2;
|
|
257
|
+
const hh = Math.max(h, 0.01) / 2;
|
|
258
|
+
const r = Math.min(hw, hh);
|
|
259
|
+
return (`M${r2(-hw)} ${r2(-hh + r)}` +
|
|
260
|
+
`A${r2(r)} ${r2(r)} 0 0 1 ${r2(-hw + r)} ${r2(-hh)}` +
|
|
261
|
+
`L${r2(hw - r)} ${r2(-hh)}` +
|
|
262
|
+
`A${r2(r)} ${r2(r)} 0 0 1 ${r2(hw)} ${r2(-hh + r)}` +
|
|
263
|
+
`L${r2(hw)} ${r2(hh - r)}` +
|
|
264
|
+
`A${r2(r)} ${r2(r)} 0 0 1 ${r2(hw - r)} ${r2(hh)}` +
|
|
265
|
+
`L${r2(-hw + r)} ${r2(hh)}` +
|
|
266
|
+
`A${r2(r)} ${r2(r)} 0 0 1 ${r2(-hw)} ${r2(hh - r)}Z`);
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=shape.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formes et couleurs proposees par le personnalisateur du bot.
|
|
3
|
+
*
|
|
4
|
+
* A la difference des silhouettes d'animation (`profiles.ts`), celles-ci ne sont
|
|
5
|
+
* PAS relevees sur la video : elles sont construites analytiquement d'apres la
|
|
6
|
+
* grille du personnalisateur d'origine. Deux sources distinctes, donc, et c'est
|
|
7
|
+
* volontaire — les etats animes doivent rester fideles a la video, les formes de
|
|
8
|
+
* base sont un choix d'utilisateur.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Les identifiants sont enumeres plutot que deduits du tableau : c'est ce qui
|
|
12
|
+
* permet a la couche i18n de verifier A LA COMPILATION que chaque forme a bien
|
|
13
|
+
* sa traduction dans les trois langues (`t(\`shapes.${id}\`)` ne compile que si
|
|
14
|
+
* la cle existe). Un `as const` sur le tableau aurait le meme effet mais
|
|
15
|
+
* rendrait `radii` en lecture seule, alors que le moteur le passe tel quel.
|
|
16
|
+
*/
|
|
17
|
+
export type ShapeId = 'cercle' | 'galet' | 'squircle' | 'capsule' | 'triangle' | 'hexagone' | 'nuage' | 'goutte';
|
|
18
|
+
export interface BotShape {
|
|
19
|
+
id: ShapeId;
|
|
20
|
+
radii: number[];
|
|
21
|
+
}
|
|
22
|
+
export declare const SHAPES: BotShape[];
|
|
23
|
+
export declare const SHAPE_BY_ID: Map<string, BotShape>;
|
|
24
|
+
export declare const DEFAULT_SHAPE = "cercle";
|
|
25
|
+
export type ColorId = 'encre' | 'creme' | 'brun' | 'rouge' | 'orange' | 'ambre' | 'vert' | 'turquoise' | 'bleu' | 'violet' | 'rose' | 'gris';
|
|
26
|
+
export interface BotColor {
|
|
27
|
+
id: ColorId;
|
|
28
|
+
hex: string;
|
|
29
|
+
}
|
|
30
|
+
/** Palette du personnalisateur d'origine. */
|
|
31
|
+
export declare const COLORS: BotColor[];
|
|
32
|
+
export declare const COLOR_BY_ID: Map<string, BotColor>;
|
|
33
|
+
export declare const DEFAULT_COLOR = "encre";
|
|
34
|
+
/** Melange deux couleurs hex. Sert a la brume de profondeur des particules. */
|
|
35
|
+
export declare function mixHex(from: string, to: string, t: number): string;
|
|
36
|
+
//# sourceMappingURL=skins.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Adapted from bloub, Copyright (c) 2026 Jérémy Perret (MIT).
|
|
2
|
+
// See THIRD_PARTY_NOTICES.md for source revision and local changes.
|
|
3
|
+
import { PROFILE_SAMPLES } from './profiles.js';
|
|
4
|
+
import { hullOfCircles, profileFromPolygon, regularPolygonProfile, superellipseProfile, unionOfCirclesProfile } from './shape.js';
|
|
5
|
+
/** Ramene le rayon maximal a `max` pour que toutes les formes pesent pareil a l'oeil. */
|
|
6
|
+
function normalize(radii, max = 1) {
|
|
7
|
+
const peak = Math.max(...radii);
|
|
8
|
+
if (peak <= 0)
|
|
9
|
+
return radii;
|
|
10
|
+
const k = max / peak;
|
|
11
|
+
return radii.map((r) => r * k);
|
|
12
|
+
}
|
|
13
|
+
const ANGLES = Array.from({ length: PROFILE_SAMPLES }, (_, i) => (i / PROFILE_SAMPLES) * Math.PI * 2);
|
|
14
|
+
/** Galet : cercle deforme par deux harmoniques basses, donc irregulier mais lisse. */
|
|
15
|
+
const pebble = normalize(ANGLES.map((a) => 1 + 0.075 * Math.cos(2 * a + 0.5) + 0.035 * Math.cos(3 * a + 2.1)), 1.02);
|
|
16
|
+
/** Nuage : union de bosses, large en bas, deux lobes en haut. */
|
|
17
|
+
const cloud = normalize(unionOfCirclesProfile([
|
|
18
|
+
{ x: -0.44, y: 0.2, r: 0.54 },
|
|
19
|
+
{ x: 0.46, y: 0.2, r: 0.5 },
|
|
20
|
+
{ x: 0.02, y: 0.3, r: 0.6 },
|
|
21
|
+
{ x: -0.24, y: -0.3, r: 0.48 },
|
|
22
|
+
{ x: 0.3, y: -0.24, r: 0.44 }
|
|
23
|
+
]), 1.02);
|
|
24
|
+
/** Goutte : gros disque en bas, pointe effilee en haut. */
|
|
25
|
+
const droplet = normalize(profileFromPolygon(hullOfCircles(0, 0.28, 0.66, 0, -0.96, 0.05), 0, 0), 1.04);
|
|
26
|
+
/** Capsule couchee : enveloppe de deux disques cote a cote. */
|
|
27
|
+
const capsule = profileFromPolygon(hullOfCircles(-0.42, 0, 0.62, 0.42, 0, 0.62), 0, 0);
|
|
28
|
+
export const SHAPES = [
|
|
29
|
+
{ id: 'cercle', radii: new Array(PROFILE_SAMPLES).fill(1) },
|
|
30
|
+
{ id: 'galet', radii: pebble },
|
|
31
|
+
// 1.15 et pas 1.02 : sur une superellipse le rayon maximal est la diagonale,
|
|
32
|
+
// donc normaliser dessus donne une forme qui parait plus petite que le cercle.
|
|
33
|
+
{ id: 'squircle', radii: normalize(superellipseProfile(4.2), 1.15) },
|
|
34
|
+
{ id: 'capsule', radii: capsule },
|
|
35
|
+
// -90deg : un sommet vers le haut de l'ecran (y est oriente vers le bas)
|
|
36
|
+
{ id: 'triangle', radii: regularPolygonProfile(3, 1.12, 0.34, -90) },
|
|
37
|
+
// 0deg : sommets a gauche et a droite, donc aretes du haut et du bas plates
|
|
38
|
+
{ id: 'hexagone', radii: regularPolygonProfile(6, 1.04, 0.26, 0) },
|
|
39
|
+
{ id: 'nuage', radii: cloud },
|
|
40
|
+
{ id: 'goutte', radii: droplet }
|
|
41
|
+
];
|
|
42
|
+
// Map indexee par `string` et non par `ShapeId` : les appelants interrogent avec
|
|
43
|
+
// une valeur relue du localStorage ou d'une prop, donc non validee.
|
|
44
|
+
export const SHAPE_BY_ID = new Map(SHAPES.map((s) => [s.id, s]));
|
|
45
|
+
export const DEFAULT_SHAPE = 'cercle';
|
|
46
|
+
/** Palette du personnalisateur d'origine. */
|
|
47
|
+
export const COLORS = [
|
|
48
|
+
{ id: 'encre', hex: '#0a0a0c' },
|
|
49
|
+
{ id: 'brun', hex: '#8b5e3c' },
|
|
50
|
+
{ id: 'rouge', hex: '#e8483f' },
|
|
51
|
+
{ id: 'orange', hex: '#f08a24' },
|
|
52
|
+
{ id: 'ambre', hex: '#f0b429' },
|
|
53
|
+
{ id: 'vert', hex: '#3ecf8e' },
|
|
54
|
+
{ id: 'turquoise', hex: '#2fbfa0' },
|
|
55
|
+
{ id: 'bleu', hex: '#3b93f0' },
|
|
56
|
+
{ id: 'violet', hex: '#8b5cf6' },
|
|
57
|
+
{ id: 'rose', hex: '#e152b0' },
|
|
58
|
+
{ id: 'gris', hex: '#a3a3a3' },
|
|
59
|
+
{ id: 'creme', hex: '#f1efe9' }
|
|
60
|
+
];
|
|
61
|
+
export const COLOR_BY_ID = new Map(COLORS.map((c) => [c.id, c]));
|
|
62
|
+
export const DEFAULT_COLOR = 'encre';
|
|
63
|
+
/** Melange deux couleurs hex. Sert a la brume de profondeur des particules. */
|
|
64
|
+
export function mixHex(from, to, t) {
|
|
65
|
+
const parse = (h) => {
|
|
66
|
+
const v = parseInt(h.slice(1), 16);
|
|
67
|
+
return [(v >> 16) & 255, (v >> 8) & 255, v & 255];
|
|
68
|
+
};
|
|
69
|
+
const a = parse(from);
|
|
70
|
+
const b = parse(to);
|
|
71
|
+
const c = a.map((x, i) => Math.round(x + (b[i] - x) * t));
|
|
72
|
+
return `#${c.map((x) => x.toString(16).padStart(2, '0')).join('')}`;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=skins.js.map
|