Spaces:
Sleeping
Sleeping
feat: O6 Mah feminine Byte
Browse files
dashboard/features/streamer/mah.svg.js
CHANGED
|
@@ -1,58 +1,46 @@
|
|
| 1 |
// dashboard/features/streamer/mah.svg.js
|
| 2 |
-
// O6 — Mah's avatar
|
| 3 |
-
//
|
| 4 |
-
//
|
| 5 |
-
//
|
| 6 |
-
// commissioned emote (that art is the artist's IP).
|
| 7 |
|
| 8 |
-
// Mouth shape per state (mirrors Byte's vocabulary
|
| 9 |
export const MOUTH_PATHS = Object.freeze({
|
| 10 |
-
unseen: "
|
| 11 |
-
idle: "
|
| 12 |
-
neutral: "
|
| 13 |
-
happy: "
|
| 14 |
-
hype: "
|
| 15 |
-
sad: "
|
| 16 |
-
talking: "
|
| 17 |
});
|
| 18 |
|
| 19 |
export const MAH_SVG = `
|
| 20 |
-
<svg class="mah-figure" viewBox="0 0 48
|
| 21 |
-
<!--
|
| 22 |
-
<
|
| 23 |
-
<
|
| 24 |
-
<
|
| 25 |
-
<
|
| 26 |
-
<
|
| 27 |
-
<
|
| 28 |
-
<
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
<g class="mah-hat">
|
| 32 |
-
<path class="mah-hat-brim" d="M7 19 Q24 13 41 19 Q41 23 24 23 Q7 23 7 19 Z" fill="#ffffff" stroke="#1e1430" stroke-width="0.8" />
|
| 33 |
-
<path class="mah-hat-top" d="M11 19 Q11 7 24 7 Q37 7 37 19 Q24 16 11 19 Z" fill="#ffffff" stroke="#1e1430" stroke-width="0.8" />
|
| 34 |
-
<ellipse class="mah-spot" cx="18" cy="13" rx="2.6" ry="2" fill="#1e1430" />
|
| 35 |
-
<ellipse class="mah-spot" cx="30" cy="12" rx="2.1" ry="1.7" fill="#1e1430" />
|
| 36 |
-
<ellipse class="mah-spot" cx="26" cy="17" rx="1.5" ry="1.2" fill="#1e1430" />
|
| 37 |
-
<!-- tiny cow ears -->
|
| 38 |
-
<ellipse cx="10" cy="14" rx="2.4" ry="1.6" fill="#f1c9a4" transform="rotate(-25 10 14)" />
|
| 39 |
-
<ellipse cx="38" cy="14" rx="2.4" ry="1.6" fill="#f1c9a4" transform="rotate(25 38 14)" />
|
| 40 |
-
<!-- little red bow on the hat (her accent) -->
|
| 41 |
-
<path class="mah-bow" d="M24 19 l-3.5 -2.2 v4.4 Z M24 19 l3.5 -2.2 v4.4 Z" fill="#e23b5a" />
|
| 42 |
-
<circle cx="24" cy="19" r="1.1" fill="#b5283f" />
|
| 43 |
</g>
|
| 44 |
-
<!--
|
| 45 |
-
<circle class="mah-cheek left" cx="16
|
| 46 |
-
<circle class="mah-cheek right" cx="
|
| 47 |
-
<!--
|
| 48 |
-
<g class="mah-
|
| 49 |
-
<
|
| 50 |
-
<
|
| 51 |
-
<line x1="
|
|
|
|
| 52 |
</g>
|
| 53 |
-
<!-- eyes (
|
| 54 |
-
<
|
| 55 |
-
<
|
| 56 |
<!-- mouth -->
|
| 57 |
-
<path class="mah-mouth" d="${MOUTH_PATHS.idle}" stroke="#
|
| 58 |
</svg>`;
|
|
|
|
| 1 |
// dashboard/features/streamer/mah.svg.js
|
| 2 |
+
// O6 — Mah's avatar: a FEMININE counterpart to Byte, same mascot language (no
|
| 3 |
+
// real-person traits). Same head/face/eyes so they read as siblings; feminine
|
| 4 |
+
// touches: a little bow, eyelashes, blush, a heart antenna, and her coral/red
|
| 5 |
+
// accent. Pure scalable SVG, same viewBox as Byte for the duo.
|
|
|
|
| 6 |
|
| 7 |
+
// Mouth shape per state (mirrors Byte's vocabulary).
|
| 8 |
export const MOUTH_PATHS = Object.freeze({
|
| 9 |
+
unseen: "M19 30 h10",
|
| 10 |
+
idle: "M19 30 h10",
|
| 11 |
+
neutral: "M19 30 h10",
|
| 12 |
+
happy: "M19 29 Q24 34 29 29",
|
| 13 |
+
hype: "M18 28 Q24 37 30 28 Z",
|
| 14 |
+
sad: "M19 32 Q24 27 29 32",
|
| 15 |
+
talking: "M20 29 q4 5 8 0 q-4 2 -8 0 Z",
|
| 16 |
});
|
| 17 |
|
| 18 |
export const MAH_SVG = `
|
| 19 |
+
<svg class="mah-figure" viewBox="0 0 48 50" role="img" aria-label="Mah">
|
| 20 |
+
<!-- antenna with a heart tip (feminine touch) -->
|
| 21 |
+
<line class="mah-antenna" x1="24" y1="10" x2="24" y2="5.5" stroke="#ff7aa8" stroke-width="2" stroke-linecap="round" />
|
| 22 |
+
<path class="mah-heart" d="M24 3.4 q1.6 -2 3 0 q1.2 1.7 -3 4 q-4.2 -2.3 -3 -4 q1.4 -2 3 0 Z" fill="#e23b5a" />
|
| 23 |
+
<!-- head -->
|
| 24 |
+
<rect class="mah-head" x="8" y="10" width="32" height="30" rx="9" fill="#ff7aa8" />
|
| 25 |
+
<rect class="mah-face" x="13" y="15" width="22" height="20" rx="6" fill="#1e1430" />
|
| 26 |
+
<!-- side bow (her signature accent) -->
|
| 27 |
+
<g class="mah-bow">
|
| 28 |
+
<path d="M9 16 l-4 -3 v6 Z M9 16 l4 -3 v6 Z" fill="#e23b5a" />
|
| 29 |
+
<circle cx="9" cy="16" r="1.3" fill="#b5283f" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
</g>
|
| 31 |
+
<!-- blush cheeks (revealed on happy/hype) -->
|
| 32 |
+
<circle class="mah-cheek left" cx="16" cy="29" r="2.3" fill="#ff5d86" />
|
| 33 |
+
<circle class="mah-cheek right" cx="32" cy="29" r="2.3" fill="#ff5d86" />
|
| 34 |
+
<!-- eyelashes (feminine) -->
|
| 35 |
+
<g class="mah-lashes" stroke="#8be9fd" stroke-width="1" stroke-linecap="round">
|
| 36 |
+
<line x1="17" y1="20.6" x2="15.4" y2="19.6" />
|
| 37 |
+
<line x1="17.6" y1="20" x2="16.6" y2="18.6" />
|
| 38 |
+
<line x1="31" y1="20.6" x2="32.6" y2="19.6" />
|
| 39 |
+
<line x1="30.4" y1="20" x2="31.4" y2="18.6" />
|
| 40 |
</g>
|
| 41 |
+
<!-- eyes (same as Byte — sibling resemblance) -->
|
| 42 |
+
<rect class="mah-eye left" x="17" y="21" width="3.6" height="6.2" rx="1.8" fill="#8be9fd" />
|
| 43 |
+
<rect class="mah-eye right" x="27.4" y="21" width="3.6" height="6.2" rx="1.8" fill="#8be9fd" />
|
| 44 |
<!-- mouth -->
|
| 45 |
+
<path class="mah-mouth" d="${MOUTH_PATHS.idle}" stroke="#ff9ab4" stroke-width="2" fill="#ff9ab4" fill-opacity="0" stroke-linecap="round" stroke-linejoin="round" />
|
| 46 |
</svg>`;
|
dashboard/styles/mascot.css
CHANGED
|
@@ -312,7 +312,7 @@
|
|
| 312 |
.mah-avatar {
|
| 313 |
--mah-size: 44px;
|
| 314 |
width: var(--mah-size);
|
| 315 |
-
height: calc(var(--mah-size) *
|
| 316 |
flex: 0 0 auto;
|
| 317 |
display: inline-block;
|
| 318 |
line-height: 0;
|
|
|
|
| 312 |
.mah-avatar {
|
| 313 |
--mah-size: 44px;
|
| 314 |
width: var(--mah-size);
|
| 315 |
+
height: calc(var(--mah-size) * 50 / 48);
|
| 316 |
flex: 0 0 auto;
|
| 317 |
display: inline-block;
|
| 318 |
line-height: 0;
|
dashboard/tests/mah_render.test.js
CHANGED
|
@@ -48,15 +48,18 @@ test("Prove that the context/size class survives re-render", () => {
|
|
| 48 |
});
|
| 49 |
|
| 50 |
test("Prove the SVG carries her signature motifs (homage, not copy)", () => {
|
|
|
|
| 51 |
for (const hook of [
|
| 52 |
"mah-figure",
|
| 53 |
-
"mah-
|
| 54 |
-
"mah-
|
| 55 |
-
"mah-
|
| 56 |
-
"mah-
|
|
|
|
|
|
|
| 57 |
"mah-cheek",
|
| 58 |
"mah-mouth",
|
| 59 |
-
'viewBox="0 0 48
|
| 60 |
]) {
|
| 61 |
assert.ok(MAH_SVG.includes(hook), `SVG must expose ${hook}`);
|
| 62 |
}
|
|
|
|
| 48 |
});
|
| 49 |
|
| 50 |
test("Prove the SVG carries her signature motifs (homage, not copy)", () => {
|
| 51 |
+
// Feminine counterpart to Byte: same mascot hooks + feminine touches.
|
| 52 |
for (const hook of [
|
| 53 |
"mah-figure",
|
| 54 |
+
"mah-head",
|
| 55 |
+
"mah-face",
|
| 56 |
+
"mah-eye", // same eyes as Byte (sibling)
|
| 57 |
+
"mah-lashes", // feminine
|
| 58 |
+
"mah-bow", // feminine signature
|
| 59 |
+
"mah-heart", // heart antenna tip
|
| 60 |
"mah-cheek",
|
| 61 |
"mah-mouth",
|
| 62 |
+
'viewBox="0 0 48 50"', // same scale as Byte for the duo
|
| 63 |
]) {
|
| 64 |
assert.ok(MAH_SVG.includes(hook), `SVG must expose ${hook}`);
|
| 65 |
}
|