lively-mascot 0.2.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/CHANGELOG.md +37 -0
- package/CHANGELOG.zh-CN.md +37 -0
- package/LICENSE +21 -0
- package/README.md +357 -0
- package/README.zh-CN.md +357 -0
- package/dist/lively-mascot.cjs +1 -0
- package/dist/lively-mascot.min.css +3115 -0
- package/dist/lively-mascot.min.js +8 -0
- package/dist/lively-mascot.mjs +10 -0
- package/package.json +72 -0
- package/src/characters/cat.css +331 -0
- package/src/characters/cat.js +130 -0
- package/src/characters/ghost.css +77 -0
- package/src/characters/ghost.js +68 -0
- package/src/characters/jelly.css +54 -0
- package/src/characters/jelly.js +55 -0
- package/src/characters/robot.css +83 -0
- package/src/characters/robot.js +82 -0
- package/src/characters/sprout.css +69 -0
- package/src/characters/sprout.js +87 -0
- package/src/core/emotions.js +206 -0
- package/src/core/rig.js +282 -0
- package/src/lively-mascot.css +2501 -0
- package/src/lively-mascot.js +330 -0
- package/types/index.d.ts +66 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
* lively-mascot · Ghost Character
|
|
3
|
+
* Floating, semi-transparent spirit. No feet — the foot channel
|
|
4
|
+
* becomes a wavy hem. Simplest character in the set.
|
|
5
|
+
* ============================================================ */
|
|
6
|
+
|
|
7
|
+
.lively-mascot--ghost {
|
|
8
|
+
/* a little extra room for the floating motion + hem */
|
|
9
|
+
overflow: visible;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* --- Body (soft transparent blob) ---
|
|
13
|
+
Domed top with straight-ish sides so the body flows into the wavy hem
|
|
14
|
+
below — a classic ghost silhouette rather than a ball with hanging legs. */
|
|
15
|
+
.lively-mascot--ghost .lively-body {
|
|
16
|
+
border-radius: 50% 50% 0 0 / 62% 62% 0 0;
|
|
17
|
+
background: color-mix(in srgb, var(--lively-body) 72%, white);
|
|
18
|
+
border-color: var(--lively-outline);
|
|
19
|
+
border-bottom-width: 0;
|
|
20
|
+
box-shadow: 0 8px 18px rgba(0,0,0,0.12);
|
|
21
|
+
/* One shallow three-lobed silhouette in both outline states. */
|
|
22
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpath fill='white' d='M0 0H100V94C88.9 100 77.8 100 66.7 94C55.6 100 44.4 100 33.3 94C22.2 100 11.1 100 0 94Z'/%3E%3C/svg%3E");
|
|
23
|
+
-webkit-mask-repeat: no-repeat;
|
|
24
|
+
-webkit-mask-size: 100% 100%;
|
|
25
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpath fill='white' d='M0 0H100V94C88.9 100 77.8 100 66.7 94C55.6 100 44.4 100 33.3 94C22.2 100 11.1 100 0 94Z'/%3E%3C/svg%3E");
|
|
26
|
+
mask-repeat: no-repeat;
|
|
27
|
+
mask-size: 100% 100%;
|
|
28
|
+
/* float instead of the default sway */
|
|
29
|
+
animation: lively-ghost-float 3.4s ease-in-out infinite;
|
|
30
|
+
}
|
|
31
|
+
.lively-mascot--ghost.is-happy .lively-body {
|
|
32
|
+
animation: lively-ghost-float 3.4s ease-in-out infinite, lively-squish 0.6s cubic-bezier(.3,1.6,.4,1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@keyframes lively-ghost-float {
|
|
36
|
+
0%, 100% { transform: translateY(0) rotate(0); }
|
|
37
|
+
50% { transform: translateY(-7px) rotate(-2deg); }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* no hard hop for a ghost */
|
|
41
|
+
.lively-mascot--ghost.is-hopping .lively-mascot__rig { animation: none; }
|
|
42
|
+
|
|
43
|
+
/* The SVG only supplies the optional bottom ink. The body itself owns the
|
|
44
|
+
filled wavy silhouette, so there is never a second colour layer to join. */
|
|
45
|
+
.lively__ghost-hem {
|
|
46
|
+
position: absolute;
|
|
47
|
+
left: 0;
|
|
48
|
+
bottom: 0;
|
|
49
|
+
width: 100%;
|
|
50
|
+
height: 6cqw;
|
|
51
|
+
line-height: 0;
|
|
52
|
+
z-index: 1;
|
|
53
|
+
pointer-events: none;
|
|
54
|
+
}
|
|
55
|
+
.lively__ghost-hem svg {
|
|
56
|
+
display: block;
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: 100%;
|
|
59
|
+
overflow: visible;
|
|
60
|
+
}
|
|
61
|
+
.lively-ghost__hem-stroke {
|
|
62
|
+
fill: none;
|
|
63
|
+
stroke: var(--lively-outline);
|
|
64
|
+
stroke-width: 2.4;
|
|
65
|
+
stroke-linejoin: round;
|
|
66
|
+
stroke-linecap: round;
|
|
67
|
+
vector-effect: non-scaling-stroke;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.lively-mascot--ghost.lively-mascot--outline-hidden .lively__ghost-hem {
|
|
71
|
+
display: none;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* The shared rectangular inner rim cannot follow the custom lower contour. */
|
|
75
|
+
.lively-mascot--ghost .lively-body::after {
|
|
76
|
+
display: none;
|
|
77
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lively-mascot · Ghost Character
|
|
3
|
+
*
|
|
4
|
+
* A floating, semi-transparent spirit. No feet — instead the foot
|
|
5
|
+
* channel is repurposed into a wavy tail/hem at the bottom.
|
|
6
|
+
* Structurally the simplest character.
|
|
7
|
+
*
|
|
8
|
+
* Requires: lively-mascot.js (loaded before this script)
|
|
9
|
+
*/
|
|
10
|
+
(function () {
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
14
|
+
|
|
15
|
+
function svg(tag, attrs, children) {
|
|
16
|
+
var el = document.createElementNS(SVG_NS, tag);
|
|
17
|
+
if (attrs) for (var k in attrs) if (Object.prototype.hasOwnProperty.call(attrs, k)) el.setAttribute(k, String(attrs[k]));
|
|
18
|
+
if (children) for (var i = 0; i < children.length; i++) el.appendChild(children[i]);
|
|
19
|
+
return el;
|
|
20
|
+
}
|
|
21
|
+
function hEl(tag, attrs, children) {
|
|
22
|
+
var node = document.createElement(tag);
|
|
23
|
+
if (attrs) for (var k in attrs) {
|
|
24
|
+
if (Object.prototype.hasOwnProperty.call(attrs, k)) {
|
|
25
|
+
var v = attrs[k];
|
|
26
|
+
if (v === undefined || v === null || v === "") continue;
|
|
27
|
+
if (k === "class") node.className = String(v);
|
|
28
|
+
else if (k === "text") node.textContent = String(v);
|
|
29
|
+
else node.setAttribute(k, String(v));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (children) for (var i = 0; i < children.length; i++) node.appendChild(children[i]);
|
|
33
|
+
return node;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderGhost(rig, gazeEl) {
|
|
37
|
+
// Body (floating blob, no hard shadow)
|
|
38
|
+
var body = hEl("div", { class: "lively-body lively-body--ghost" });
|
|
39
|
+
rig.registerBody(body);
|
|
40
|
+
|
|
41
|
+
// Shared face
|
|
42
|
+
var face = LivelyMascot.buildFaceSvg(rig);
|
|
43
|
+
body.appendChild(face.wrap);
|
|
44
|
+
|
|
45
|
+
// The body CSS owns the filled wavy silhouette. This SVG adds only the
|
|
46
|
+
// optional ink line for outline mode, so the lower edge stays seamless.
|
|
47
|
+
var hem = hEl("div", { class: "lively__ghost-hem" });
|
|
48
|
+
hem.appendChild(svg("svg", { viewBox: "0 0 100 6", preserveAspectRatio: "none" }, [
|
|
49
|
+
svg("path", {
|
|
50
|
+
class: "lively-ghost__hem-stroke",
|
|
51
|
+
d: "M0 0 C11.1 6 22.2 6 33.3 0 C44.4 6 55.6 6 66.7 0 C77.8 6 88.9 6 100 0"
|
|
52
|
+
})
|
|
53
|
+
]));
|
|
54
|
+
body.appendChild(hem);
|
|
55
|
+
|
|
56
|
+
gazeEl.appendChild(body);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof LivelyMascot !== "undefined") {
|
|
60
|
+
LivelyMascot.registerCharacter("ghost", renderGhost, "Ghost", "0 0 100 100");
|
|
61
|
+
} else {
|
|
62
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
63
|
+
if (typeof LivelyMascot !== "undefined") {
|
|
64
|
+
LivelyMascot.registerCharacter("ghost", renderGhost, "Ghost", "0 0 100 100");
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
* lively-mascot · Jelly Character
|
|
3
|
+
* Pure jelly blob: no leaf, no feet. Q-bouncy squash-stretch.
|
|
4
|
+
* ============================================================ */
|
|
5
|
+
|
|
6
|
+
.lively-mascot--jelly {
|
|
7
|
+
overflow: visible;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* --- Body (translucent jelly) --- */
|
|
11
|
+
.lively-mascot--jelly .lively-body {
|
|
12
|
+
border-radius: 46% 46% 48% 48% / 52% 52% 50% 50%;
|
|
13
|
+
background:
|
|
14
|
+
radial-gradient(ellipse at 38% 30%, rgba(255,255,255,0.55), rgba(255,255,255,0) 45%),
|
|
15
|
+
color-mix(in srgb, var(--lively-body) 68%, white);
|
|
16
|
+
border-color: var(--lively-outline);
|
|
17
|
+
box-shadow: 0 8px 16px rgba(0,0,0,0.10), inset 0 -8px 14px rgba(0,0,0,0.06);
|
|
18
|
+
/* jiggly idle squash */
|
|
19
|
+
animation: lively-jelly-idle 2.8s ease-in-out infinite;
|
|
20
|
+
}
|
|
21
|
+
.lively-mascot--jelly.is-happy .lively-body {
|
|
22
|
+
animation: lively-jelly-idle 2.8s ease-in-out infinite, lively-squish 0.6s cubic-bezier(.3,1.6,.4,1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@keyframes lively-jelly-idle {
|
|
26
|
+
0%, 100% { transform: scale(1, 1); border-radius: 46% 46% 48% 48% / 52% 52% 50% 50%; }
|
|
27
|
+
25% { transform: scale(1.06, 0.94); border-radius: 44% 44% 50% 50% / 50% 50% 52% 52%; }
|
|
28
|
+
50% { transform: scale(0.95, 1.06); border-radius: 50% 50% 44% 44% / 54% 54% 46% 46%; }
|
|
29
|
+
75% { transform: scale(1.03, 0.97); border-radius: 44% 44% 50% 50% / 50% 50% 52% 52%; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* jelly never hops (no feet) */
|
|
33
|
+
.lively-mascot--jelly.is-hopping .lively-mascot__rig { animation: none; }
|
|
34
|
+
|
|
35
|
+
/* a couple of emotion-driven jelly reactions on top of face changes */
|
|
36
|
+
.lively-mascot--jelly.is-emotion-10 .lively-body,
|
|
37
|
+
.lively-mascot--jelly.is-emotion-30 .lively-body,
|
|
38
|
+
.lively-mascot--jelly.is-emotion-38 .lively-body {
|
|
39
|
+
animation: lively-jelly-bounce 0.7s cubic-bezier(.3,1.5,.5,1);
|
|
40
|
+
}
|
|
41
|
+
.lively-mascot--jelly.is-emotion-12 .lively-body,
|
|
42
|
+
.lively-mascot--jelly.is-emotion-35 .lively-body {
|
|
43
|
+
animation: lively-jelly-sad 3s ease-in-out infinite;
|
|
44
|
+
}
|
|
45
|
+
@keyframes lively-jelly-bounce {
|
|
46
|
+
0% { transform: scale(1,1); }
|
|
47
|
+
30% { transform: translateY(-10px) scale(1.08, 0.9); }
|
|
48
|
+
60% { transform: translateY(0) scale(0.94, 1.08); }
|
|
49
|
+
100% { transform: scale(1,1); }
|
|
50
|
+
}
|
|
51
|
+
@keyframes lively-jelly-sad {
|
|
52
|
+
0%, 100% { transform: scale(1, 1) translateY(2px); }
|
|
53
|
+
50% { transform: scale(1.04, 0.96) translateY(4px); }
|
|
54
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lively-mascot · Jelly Character
|
|
3
|
+
*
|
|
4
|
+
* A pure jelly blob — no leaf, no feet. Only a translucent body
|
|
5
|
+
* with a jiggly squash-stretch idle animation.
|
|
6
|
+
*
|
|
7
|
+
* Requires: lively-mascot.js (loaded before this script)
|
|
8
|
+
*/
|
|
9
|
+
(function () {
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
13
|
+
|
|
14
|
+
function svg(tag, attrs, children) {
|
|
15
|
+
var el = document.createElementNS(SVG_NS, tag);
|
|
16
|
+
if (attrs) for (var k in attrs) if (Object.prototype.hasOwnProperty.call(attrs, k)) el.setAttribute(k, String(attrs[k]));
|
|
17
|
+
if (children) for (var i = 0; i < children.length; i++) el.appendChild(children[i]);
|
|
18
|
+
return el;
|
|
19
|
+
}
|
|
20
|
+
function hEl(tag, attrs, children) {
|
|
21
|
+
var node = document.createElement(tag);
|
|
22
|
+
if (attrs) for (var k in attrs) {
|
|
23
|
+
if (Object.prototype.hasOwnProperty.call(attrs, k)) {
|
|
24
|
+
var v = attrs[k];
|
|
25
|
+
if (v === undefined || v === null || v === "") continue;
|
|
26
|
+
if (k === "class") node.className = String(v);
|
|
27
|
+
else if (k === "text") node.textContent = String(v);
|
|
28
|
+
else node.setAttribute(k, String(v));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (children) for (var i = 0; i < children.length; i++) node.appendChild(children[i]);
|
|
32
|
+
return node;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function renderJelly(rig, rigEl) {
|
|
36
|
+
// Body (pure jelly blob)
|
|
37
|
+
var body = hEl("div", { class: "lively-body lively-body--jelly" });
|
|
38
|
+
rig.registerBody(body);
|
|
39
|
+
|
|
40
|
+
// Shared face
|
|
41
|
+
var face = LivelyMascot.buildFaceSvg(rig);
|
|
42
|
+
body.appendChild(face.wrap);
|
|
43
|
+
rigEl.appendChild(body);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof LivelyMascot !== "undefined") {
|
|
47
|
+
LivelyMascot.registerCharacter("jelly", renderJelly, "Jelly", "0 0 100 100");
|
|
48
|
+
} else {
|
|
49
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
50
|
+
if (typeof LivelyMascot !== "undefined") {
|
|
51
|
+
LivelyMascot.registerCharacter("jelly", renderJelly, "Jelly", "0 0 100 100");
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
})();
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
* lively-mascot · Robot Character
|
|
3
|
+
* Square head + antenna (tech / mechanical vibe)
|
|
4
|
+
* ============================================================ */
|
|
5
|
+
|
|
6
|
+
/* --- Body (square head) --- */
|
|
7
|
+
.lively-mascot--robot .lively-body {
|
|
8
|
+
border-radius: 26%;
|
|
9
|
+
background:
|
|
10
|
+
linear-gradient(150deg, rgba(255,255,255,0.28), rgba(255,255,255,0) 55%),
|
|
11
|
+
var(--lively-body);
|
|
12
|
+
box-shadow: 3px 3px 0 color-mix(in srgb, var(--lively-outline) 70%, transparent), inset 0 0 0 3px rgba(255,255,255,0.10);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* subtle screen seam so it reads as a robot face panel */
|
|
16
|
+
.lively-mascot--robot .lively-face-wrap {
|
|
17
|
+
border-radius: 18%;
|
|
18
|
+
box-shadow: inset 0 0 0 2px rgba(0,0,0,0.07), inset 0 4px 10px rgba(0,0,0,0.07);
|
|
19
|
+
background: rgba(0,0,0,0.04);
|
|
20
|
+
}
|
|
21
|
+
.lively-mascot--robot .lively-face-wrap svg {
|
|
22
|
+
filter: drop-shadow(0 0 1px rgba(0,0,0,0.05));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Keep robot cheeks warmer and quieter than the cyan antenna accent. */
|
|
26
|
+
.lively-mascot--robot .lively-face__blush {
|
|
27
|
+
fill: #e7ad76;
|
|
28
|
+
fill-opacity: .58;
|
|
29
|
+
filter: drop-shadow(0 1px 1px rgba(0,0,0,.12));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* --- Antenna (leaf channel, CSS-driven) --- */
|
|
33
|
+
.lively__antenna {
|
|
34
|
+
position: absolute;
|
|
35
|
+
top: -22cqw;
|
|
36
|
+
left: 50%;
|
|
37
|
+
width: 32cqw;
|
|
38
|
+
height: 29cqw;
|
|
39
|
+
margin-left: -16cqw;
|
|
40
|
+
z-index: 3;
|
|
41
|
+
transform-origin: 50% 100%;
|
|
42
|
+
animation: lively-robot-antenna 2.6s ease-in-out infinite;
|
|
43
|
+
}
|
|
44
|
+
.lively__antenna svg { display: block; width: 100%; height: 100%; overflow: visible; }
|
|
45
|
+
.lively-robot__antenna-stalk {
|
|
46
|
+
fill: none;
|
|
47
|
+
stroke: var(--lively-outline);
|
|
48
|
+
stroke-width: 3;
|
|
49
|
+
stroke-linecap: round;
|
|
50
|
+
}
|
|
51
|
+
.lively-robot__antenna-ball {
|
|
52
|
+
fill: var(--lively-accent);
|
|
53
|
+
stroke: var(--lively-outline);
|
|
54
|
+
stroke-width: 2;
|
|
55
|
+
animation: lively-robot-blink 1.8s ease-in-out infinite;
|
|
56
|
+
}
|
|
57
|
+
@keyframes lively-robot-antenna {
|
|
58
|
+
0%, 100% { transform: rotate(-4deg); }
|
|
59
|
+
50% { transform: rotate(4deg); }
|
|
60
|
+
}
|
|
61
|
+
@keyframes lively-robot-blink {
|
|
62
|
+
0%, 100% { fill: var(--lively-accent); }
|
|
63
|
+
50% { fill: #fff; }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* --- Feet (mechanical blocks) --- */
|
|
67
|
+
.lively__feet--robot .lively__foot svg { overflow: visible; }
|
|
68
|
+
.lively-robot__foot {
|
|
69
|
+
fill: var(--lively-outline);
|
|
70
|
+
stroke: var(--lively-outline);
|
|
71
|
+
stroke-width: 1;
|
|
72
|
+
}
|
|
73
|
+
.lively-robot__foot-line { fill: rgba(255,255,255,0.5); }
|
|
74
|
+
|
|
75
|
+
/* Antenna emotion reactions (top decoration reacts to state) */
|
|
76
|
+
.lively-mascot.is-emotion-13 .lively__antenna { animation: lively-robot-antenna-fast 0.3s ease-in-out infinite; }
|
|
77
|
+
.lively-mascot.is-emotion-13 .lively-robot__antenna-ball { fill: #ff6b6b; }
|
|
78
|
+
.lively-mascot.is-emotion-31 .lively-robot__antenna-ball { fill: #ff6b6b; }
|
|
79
|
+
.lively-mascot.is-emotion-38 .lively-robot__antenna-ball { fill: #ffd23f; }
|
|
80
|
+
@keyframes lively-robot-antenna-fast {
|
|
81
|
+
0%, 100% { transform: rotate(-3deg); }
|
|
82
|
+
50% { transform: rotate(-14deg); }
|
|
83
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lively-mascot · Robot Character
|
|
3
|
+
*
|
|
4
|
+
* A square-headed robot with an antenna and a glowing tip.
|
|
5
|
+
* Tech vibe: hard corners, segmented body, mechanical feet.
|
|
6
|
+
*
|
|
7
|
+
* Requires: lively-mascot.js (loaded before this script)
|
|
8
|
+
*/
|
|
9
|
+
(function () {
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
13
|
+
|
|
14
|
+
function svg(tag, attrs, children) {
|
|
15
|
+
var el = document.createElementNS(SVG_NS, tag);
|
|
16
|
+
if (attrs) for (var k in attrs) if (Object.prototype.hasOwnProperty.call(attrs, k)) el.setAttribute(k, String(attrs[k]));
|
|
17
|
+
if (children) for (var i = 0; i < children.length; i++) el.appendChild(children[i]);
|
|
18
|
+
return el;
|
|
19
|
+
}
|
|
20
|
+
function hEl(tag, attrs, children) {
|
|
21
|
+
var node = document.createElement(tag);
|
|
22
|
+
if (attrs) for (var k in attrs) {
|
|
23
|
+
if (Object.prototype.hasOwnProperty.call(attrs, k)) {
|
|
24
|
+
var v = attrs[k];
|
|
25
|
+
if (v === undefined || v === null || v === "") continue;
|
|
26
|
+
if (k === "class") node.className = String(v);
|
|
27
|
+
else if (k === "text") node.textContent = String(v);
|
|
28
|
+
else node.setAttribute(k, String(v));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (children) for (var i = 0; i < children.length; i++) node.appendChild(children[i]);
|
|
32
|
+
return node;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function renderRobot(rig, rigEl) {
|
|
36
|
+
// Antenna: reuse the "leaf" channel so it follows the body posture.
|
|
37
|
+
// CSS drives the bob + tip glow (useLeafAnim:false).
|
|
38
|
+
var antenna = hEl("span", { class: "lively__antenna", "aria-hidden": "true" });
|
|
39
|
+
rig.registerLeaf(antenna, { useLeafAnim: false });
|
|
40
|
+
antenna.appendChild(svg("svg", { viewBox: "0 0 40 36" }, [
|
|
41
|
+
svg("line", { class: "lively-robot__antenna-stalk", x1: 20, y1: 34, x2: 20, y2: 8 }),
|
|
42
|
+
svg("circle", { class: "lively-robot__antenna-ball", cx: 20, cy: 5, r: 5 })
|
|
43
|
+
]));
|
|
44
|
+
|
|
45
|
+
// Body (square head)
|
|
46
|
+
var body = hEl("div", { class: "lively-body lively-body--robot" });
|
|
47
|
+
rig.registerBody(body);
|
|
48
|
+
|
|
49
|
+
// Shared face
|
|
50
|
+
var face = LivelyMascot.buildFaceSvg(rig);
|
|
51
|
+
body.appendChild(antenna);
|
|
52
|
+
body.appendChild(face.wrap);
|
|
53
|
+
rigEl.appendChild(body);
|
|
54
|
+
|
|
55
|
+
// Feet (mechanical blocks)
|
|
56
|
+
var feet = hEl("div", { class: "lively__feet lively__feet--robot" });
|
|
57
|
+
rig.registerFeet(feet);
|
|
58
|
+
var footL = hEl("span", { class: "lively__foot lively__foot--l" });
|
|
59
|
+
footL.appendChild(svg("svg", { viewBox: "0 0 22 16" }, [
|
|
60
|
+
svg("rect", { class: "lively-robot__foot", x: 1, y: 3, width: 20, height: 12, rx: 3 }),
|
|
61
|
+
svg("rect", { class: "lively-robot__foot-line", x: 4, y: 7, width: 14, height: 2, rx: 1 })
|
|
62
|
+
]));
|
|
63
|
+
var footR = hEl("span", { class: "lively__foot lively__foot--r" });
|
|
64
|
+
footR.appendChild(svg("svg", { viewBox: "0 0 22 16" }, [
|
|
65
|
+
svg("rect", { class: "lively-robot__foot", x: 1, y: 3, width: 20, height: 12, rx: 3 }),
|
|
66
|
+
svg("rect", { class: "lively-robot__foot-line", x: 4, y: 7, width: 14, height: 2, rx: 1 })
|
|
67
|
+
]));
|
|
68
|
+
feet.appendChild(footL);
|
|
69
|
+
feet.appendChild(footR);
|
|
70
|
+
rigEl.appendChild(feet);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof LivelyMascot !== "undefined") {
|
|
74
|
+
LivelyMascot.registerCharacter("robot", renderRobot, "Robot", "0 0 100 100");
|
|
75
|
+
} else {
|
|
76
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
77
|
+
if (typeof LivelyMascot !== "undefined") {
|
|
78
|
+
LivelyMascot.registerCharacter("robot", renderRobot, "Robot", "0 0 100 100");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
})();
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
* lively-mascot · Sprout Character Styles
|
|
3
|
+
*
|
|
4
|
+
* The leafy sprout character: round body with a small plant
|
|
5
|
+
* growing from the top of its head.
|
|
6
|
+
* ============================================================ */
|
|
7
|
+
|
|
8
|
+
/* --- Body shape override --- */
|
|
9
|
+
.lively-mascot--sprout .lively-body {
|
|
10
|
+
border-radius: 50% 50% 46% 49%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* --- Leaf: sits on top of the head --- */
|
|
14
|
+
.lively__leaf {
|
|
15
|
+
position: absolute;
|
|
16
|
+
top: -34cqw;
|
|
17
|
+
left: 50%;
|
|
18
|
+
width: 37cqw;
|
|
19
|
+
height: 40cqw;
|
|
20
|
+
margin-left: -18.5cqw;
|
|
21
|
+
transform-origin: 50% 100%;
|
|
22
|
+
animation: lively-leaf-sway 2.6s ease-in-out infinite;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.lively__leaf svg {
|
|
26
|
+
display: block;
|
|
27
|
+
width: 100%;
|
|
28
|
+
height: 100%;
|
|
29
|
+
overflow: visible;
|
|
30
|
+
filter: drop-shadow(1.2cqw 1.4cqw 0 color-mix(in srgb, var(--lively-outline) 55%, transparent));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.lively-sprout__stem {
|
|
34
|
+
fill: none;
|
|
35
|
+
stroke: var(--lively-outline);
|
|
36
|
+
stroke-width: 3;
|
|
37
|
+
stroke-linecap: round;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.lively-sprout__leaf {
|
|
41
|
+
fill: var(--lively-body);
|
|
42
|
+
stroke: var(--lively-outline);
|
|
43
|
+
stroke-width: 2;
|
|
44
|
+
stroke-linejoin: round;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.lively-sprout__vein {
|
|
48
|
+
fill: none;
|
|
49
|
+
stroke: var(--lively-outline);
|
|
50
|
+
stroke-width: 1.6;
|
|
51
|
+
stroke-linecap: round;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.lively-sprout__shine {
|
|
55
|
+
fill: none;
|
|
56
|
+
stroke: #fff;
|
|
57
|
+
stroke-width: 2.2;
|
|
58
|
+
stroke-linecap: round;
|
|
59
|
+
opacity: 0.9;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Happy click: leaf spins */
|
|
63
|
+
.lively-mascot.is-happy .lively__leaf {
|
|
64
|
+
animation: lively-leaf-spin 0.7s cubic-bezier(.3, 1.4, .4, 1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* --- Foot positioning for sprout --- */
|
|
68
|
+
.lively-mascot--sprout .lively__foot--l { left: 18cqw; }
|
|
69
|
+
.lively-mascot--sprout .lively__foot--r { left: 60cqw; animation-delay: 0.8s; }
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lively-mascot · Sprout Character
|
|
3
|
+
*
|
|
4
|
+
* The classic leafy sprout: round body with a small plant
|
|
5
|
+
* growing from the top of its head.
|
|
6
|
+
*
|
|
7
|
+
* Requires: lively-mascot.js (loaded before this script)
|
|
8
|
+
*/
|
|
9
|
+
(function () {
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
13
|
+
|
|
14
|
+
function svg(tag, attrs, children) {
|
|
15
|
+
var el = document.createElementNS(SVG_NS, tag);
|
|
16
|
+
if (attrs) for (var k in attrs) if (Object.prototype.hasOwnProperty.call(attrs, k)) el.setAttribute(k, String(attrs[k]));
|
|
17
|
+
if (children) for (var i = 0; i < children.length; i++) el.appendChild(children[i]);
|
|
18
|
+
return el;
|
|
19
|
+
}
|
|
20
|
+
function hEl(tag, attrs, children) {
|
|
21
|
+
var node = document.createElement(tag);
|
|
22
|
+
if (attrs) for (var k in attrs) {
|
|
23
|
+
if (Object.prototype.hasOwnProperty.call(attrs, k)) {
|
|
24
|
+
var v = attrs[k];
|
|
25
|
+
if (v === undefined || v === null || v === "") continue;
|
|
26
|
+
if (k === "class") node.className = String(v);
|
|
27
|
+
else if (k === "text") node.textContent = String(v);
|
|
28
|
+
else node.setAttribute(k, String(v));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (children) for (var i = 0; i < children.length; i++) node.appendChild(children[i]);
|
|
32
|
+
return node;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function renderSprout(rig, rigEl) {
|
|
36
|
+
// Leaf (top decoration, data-driven by emotion leafAnim)
|
|
37
|
+
var leaf = hEl("span", { class: "lively__leaf", "aria-hidden": "true" });
|
|
38
|
+
rig.registerLeaf(leaf);
|
|
39
|
+
leaf.appendChild(svg("svg", { viewBox: "0 0 52 56" }, [
|
|
40
|
+
svg("path", { class: "lively-sprout__stem", d: "M26 52 C26 42, 26 34, 26 27" }),
|
|
41
|
+
svg("path", { class: "lively-sprout__leaf", d: "M26 28 C22 15, 14 9, 6 12 C5 20, 12 29, 26 28 Z" }),
|
|
42
|
+
svg("path", { class: "lively-sprout__vein", d: "M23 26 C18 21, 12 16, 8 14" }),
|
|
43
|
+
svg("path", { class: "lively-sprout__shine", d: "M22 26 C16 16, 11 12, 7 14" }),
|
|
44
|
+
svg("path", { class: "lively-sprout__leaf", d: "M26 28 C30 15, 38 9, 46 12 C47 20, 40 29, 26 28 Z" }),
|
|
45
|
+
svg("path", { class: "lively-sprout__vein", d: "M29 26 C34 21, 40 16, 44 14" }),
|
|
46
|
+
svg("path", { class: "lively-sprout__shine", d: "M30 26 C36 16, 41 12, 45 14" })
|
|
47
|
+
]));
|
|
48
|
+
|
|
49
|
+
// Body
|
|
50
|
+
var body = hEl("div", { class: "lively-body" });
|
|
51
|
+
rig.registerBody(body);
|
|
52
|
+
|
|
53
|
+
// Shared face
|
|
54
|
+
var face = LivelyMascot.buildFaceSvg(rig);
|
|
55
|
+
body.appendChild(leaf);
|
|
56
|
+
body.appendChild(face.wrap);
|
|
57
|
+
rigEl.appendChild(body);
|
|
58
|
+
|
|
59
|
+
// Feet
|
|
60
|
+
var feet = hEl("div", { class: "lively__feet" });
|
|
61
|
+
rig.registerFeet(feet);
|
|
62
|
+
var footL = hEl("span", { class: "lively__foot lively__foot--l" });
|
|
63
|
+
footL.appendChild(svg("svg", { viewBox: "0 0 22 16" }, [
|
|
64
|
+
svg("ellipse", { class: "lively-foot__body", cx: 11, cy: 10, rx: 10, ry: 6 }),
|
|
65
|
+
svg("ellipse", { class: "lively-foot__shine", cx: 8, cy: 7, rx: 3, ry: 1.8 })
|
|
66
|
+
]));
|
|
67
|
+
var footR = hEl("span", { class: "lively__foot lively__foot--r" });
|
|
68
|
+
footR.appendChild(svg("svg", { viewBox: "0 0 22 16" }, [
|
|
69
|
+
svg("ellipse", { class: "lively-foot__body", cx: 11, cy: 10, rx: 10, ry: 6 }),
|
|
70
|
+
svg("ellipse", { class: "lively-foot__shine", cx: 8, cy: 7, rx: 3, ry: 1.8 })
|
|
71
|
+
]));
|
|
72
|
+
feet.appendChild(footL);
|
|
73
|
+
feet.appendChild(footR);
|
|
74
|
+
rigEl.appendChild(feet);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Register sprout character when SDK is available
|
|
78
|
+
if (typeof LivelyMascot !== "undefined") {
|
|
79
|
+
LivelyMascot.registerCharacter("sprout", renderSprout, "Sprout", "0 0 100 100");
|
|
80
|
+
} else {
|
|
81
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
82
|
+
if (typeof LivelyMascot !== "undefined") {
|
|
83
|
+
LivelyMascot.registerCharacter("sprout", renderSprout, "Sprout", "0 0 100 100");
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
})();
|