eklavya 1.62.1 → 1.63.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/dist/assets/dashboard.html +42 -38
- package/dist/assets/mascot/expressions/celebrate-body.svg +69 -0
- package/dist/assets/mascot/expressions/celebrate-face.svg +43 -0
- package/dist/assets/mascot/expressions/confused-body.svg +69 -0
- package/dist/assets/mascot/expressions/confused-face.svg +43 -0
- package/dist/assets/mascot/expressions/curious-body.svg +69 -0
- package/dist/assets/mascot/expressions/curious-face.svg +43 -0
- package/dist/assets/mascot/expressions/excited-body.svg +69 -0
- package/dist/assets/mascot/expressions/excited-face.svg +43 -0
- package/dist/assets/mascot/expressions/focused-body.svg +69 -0
- package/dist/assets/mascot/expressions/focused-face.svg +43 -0
- package/dist/assets/mascot/expressions/happy-body.svg +69 -0
- package/dist/assets/mascot/expressions/happy-face.svg +43 -0
- package/dist/assets/mascot/expressions/neutral-body.svg +69 -0
- package/dist/assets/mascot/expressions/neutral-face.svg +43 -0
- package/dist/assets/mascot/expressions/sad-body.svg +69 -0
- package/dist/assets/mascot/expressions/sad-face.svg +43 -0
- package/dist/assets/mascot/expressions/sleepy-body.svg +69 -0
- package/dist/assets/mascot/expressions/sleepy-face.svg +43 -0
- package/dist/assets/mascot/expressions/surprised-body.svg +69 -0
- package/dist/assets/mascot/expressions/surprised-face.svg +43 -0
- package/dist/assets/mascot/expressions/thinking-body.svg +69 -0
- package/dist/assets/mascot/expressions/thinking-face.svg +43 -0
- package/dist/assets/mascot/expressions/wink-body.svg +69 -0
- package/dist/assets/mascot/expressions/wink-face.svg +43 -0
- package/dist/assets/mascot/expressions/worried-body.svg +69 -0
- package/dist/assets/mascot/expressions/worried-face.svg +43 -0
- package/dist/assets/mascot/loading.svg +69 -0
- package/dist/assets/mascot/mascot.css +23 -0
- package/dist/assets/mascot/mascot.js +89 -0
- package/dist/assets/mascot.html +43 -0
- package/dist/assets/tokens.css +17 -0
- package/dist/dashboard.js +10 -0
- package/dist/dashboard.js.map +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="30 12 126 128" width="200" height="200" class="ek-mascot" data-expression="worried" data-variant="face" role="img"><style>:root {/* ---------- MASCOT IDENTITY ----------
|
|
2
|
+
Archer Cloak artwork keeps its approved identity across both grounds.
|
|
3
|
+
UI chrome continues to use the surface, text and accent roles below. */
|
|
4
|
+
--mascot-coral: #ef9875;
|
|
5
|
+
--mascot-coral-dark: #c96d51;
|
|
6
|
+
--mascot-highlight: #ffc8a6;
|
|
7
|
+
--mascot-cloak: #0E6E66;
|
|
8
|
+
--mascot-cloak-dark: #07403C;
|
|
9
|
+
--mascot-thread: #C4EDE4;
|
|
10
|
+
--mascot-ink: #302c2a;
|
|
11
|
+
--mascot-metal: #5b7c73;
|
|
12
|
+
--mascot-wood: #bc9569;
|
|
13
|
+
--mascot-wood-dark: #74583f;
|
|
14
|
+
--mascot-string: #EAE7E1;
|
|
15
|
+
--mascot-shadow: #26262a;
|
|
16
|
+
/* ---------- END MASCOT IDENTITY ---------- */}/* Identity colours and UI surfaces use Eklavya's semantic tokens. */
|
|
17
|
+
eklavya-mascot { display:inline-flex; flex-shrink:0; vertical-align:middle; line-height:0; }
|
|
18
|
+
.ek-mascot { display:block; overflow:visible; flex-shrink:0; }
|
|
19
|
+
.ek-mascot[data-variant="face"] { overflow:hidden; }
|
|
20
|
+
.ek-mascot__face path:not([fill]) { fill:none; }
|
|
21
|
+
.ek-mascot--loading .ek-mascot__character { animation:ek-breathe 2.8s ease-in-out infinite; transform-origin:95px 185px; }
|
|
22
|
+
.ek-mascot--loading .ek-mascot__arrow { animation:ek-release 2.8s cubic-bezier(.3,0,.3,1) infinite; }
|
|
23
|
+
.ek-mascot--loading .ek-mascot__string { animation:ek-draw 2.8s ease-in-out infinite; transform-origin:157px 125px; }
|
|
24
|
+
.ek-mascot--loading .ek-mascot__ripple { animation:ek-hit 2.8s ease-out infinite; transform-origin:196px 128px; opacity:0; }
|
|
25
|
+
@keyframes ek-breathe { 0%,100% {transform:translateY(0)} 35%,55% {transform:translateY(-3px) rotate(-1deg)} }
|
|
26
|
+
@keyframes ek-release { 0%,12% {transform:translateX(0);opacity:1} 40%,52% {transform:translateX(-10px);opacity:1} 68% {transform:translateX(22px);opacity:0} 69%,85% {transform:translateX(0);opacity:0} 100% {transform:translateX(0);opacity:1} }
|
|
27
|
+
@keyframes ek-draw { 0%,100% {transform:scaleX(1)} 40%,52% {transform:scaleX(1.11)} 60% {transform:scaleX(.08)} 68% {transform:scaleX(1)} }
|
|
28
|
+
@keyframes ek-hit { 0%,60%,100% {opacity:0;transform:scale(.4)} 68% {opacity:.8;transform:scale(.7)} 88% {opacity:0;transform:scale(1.7)} }
|
|
29
|
+
.ek-companion { display:flex;align-items:center;gap:12px;color:var(--dim);font:13px/1.5 var(--font-body); }
|
|
30
|
+
.ek-companion--hero { margin-bottom:16px; }
|
|
31
|
+
.ek-companion--hero b { display:block;color:var(--ink);font-weight:600; }
|
|
32
|
+
.ek-mascot-message { display:flex;align-items:center;gap:16px;padding:16px 0; }
|
|
33
|
+
.ek-mascot-message p {margin:0}
|
|
34
|
+
@media (prefers-reduced-motion:reduce) { .ek-mascot * {animation:none !important;transition:none !important} }
|
|
35
|
+
|
|
36
|
+
.ek-companion--rail { padding:12px 20px; text-decoration:none; }
|
|
37
|
+
.ek-mascot-message > :last-child { min-width:0; overflow-wrap:break-word; }
|
|
38
|
+
.ek-mascot-status { display:inline-flex; align-items:center; gap:6px; }
|
|
39
|
+
</style><title>Eklavya, concerned</title><path d="M48 109q44-19 88 0l-8 30-33-15-31 15Z" fill="var(--mascot-cloak)"/><g class="ek-mascot__head">
|
|
40
|
+
<path d="M46 43h18V29h19V18h23v12h20v14h17v19h8v29h-8v16q0 12-16 15H62q-18-3-18-17V91h-9V63h11Z" fill="var(--mascot-coral)" stroke="var(--mascot-coral-dark)" stroke-width="2" stroke-linejoin="round"/>
|
|
41
|
+
<path d="M48 45h18V32h18V21h19" fill="none" stroke="var(--mascot-highlight)" stroke-width="3" stroke-linejoin="round"/>
|
|
42
|
+
<g class="ek-mascot__face" fill="var(--mascot-ink)" stroke="var(--mascot-ink)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><rect x="64" y="64" width="10" height="19" rx="5"/><rect x="110" y="64" width="10" height="19" rx="5"/><path d="M83 95q9-9 18 0"/><path d="M62 59l14-6m32 0 14 6" fill="none"/></g>
|
|
43
|
+
</g><circle cx="95" cy="126" r="6" fill="var(--mascot-metal)"/></svg>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210 200" width="200" height="200" class="ek-mascot ek-mascot--loading" data-expression="excited" data-variant="loading" role="img"><style>:root {/* ---------- MASCOT IDENTITY ----------
|
|
2
|
+
Archer Cloak artwork keeps its approved identity across both grounds.
|
|
3
|
+
UI chrome continues to use the surface, text and accent roles below. */
|
|
4
|
+
--mascot-coral: #ef9875;
|
|
5
|
+
--mascot-coral-dark: #c96d51;
|
|
6
|
+
--mascot-highlight: #ffc8a6;
|
|
7
|
+
--mascot-cloak: #0E6E66;
|
|
8
|
+
--mascot-cloak-dark: #07403C;
|
|
9
|
+
--mascot-thread: #C4EDE4;
|
|
10
|
+
--mascot-ink: #302c2a;
|
|
11
|
+
--mascot-metal: #5b7c73;
|
|
12
|
+
--mascot-wood: #bc9569;
|
|
13
|
+
--mascot-wood-dark: #74583f;
|
|
14
|
+
--mascot-string: #EAE7E1;
|
|
15
|
+
--mascot-shadow: #26262a;
|
|
16
|
+
/* ---------- END MASCOT IDENTITY ---------- */}/* Identity colours and UI surfaces use Eklavya's semantic tokens. */
|
|
17
|
+
eklavya-mascot { display:inline-flex; flex-shrink:0; vertical-align:middle; line-height:0; }
|
|
18
|
+
.ek-mascot { display:block; overflow:visible; flex-shrink:0; }
|
|
19
|
+
.ek-mascot[data-variant="face"] { overflow:hidden; }
|
|
20
|
+
.ek-mascot__face path:not([fill]) { fill:none; }
|
|
21
|
+
.ek-mascot--loading .ek-mascot__character { animation:ek-breathe 2.8s ease-in-out infinite; transform-origin:95px 185px; }
|
|
22
|
+
.ek-mascot--loading .ek-mascot__arrow { animation:ek-release 2.8s cubic-bezier(.3,0,.3,1) infinite; }
|
|
23
|
+
.ek-mascot--loading .ek-mascot__string { animation:ek-draw 2.8s ease-in-out infinite; transform-origin:157px 125px; }
|
|
24
|
+
.ek-mascot--loading .ek-mascot__ripple { animation:ek-hit 2.8s ease-out infinite; transform-origin:196px 128px; opacity:0; }
|
|
25
|
+
@keyframes ek-breathe { 0%,100% {transform:translateY(0)} 35%,55% {transform:translateY(-3px) rotate(-1deg)} }
|
|
26
|
+
@keyframes ek-release { 0%,12% {transform:translateX(0);opacity:1} 40%,52% {transform:translateX(-10px);opacity:1} 68% {transform:translateX(22px);opacity:0} 69%,85% {transform:translateX(0);opacity:0} 100% {transform:translateX(0);opacity:1} }
|
|
27
|
+
@keyframes ek-draw { 0%,100% {transform:scaleX(1)} 40%,52% {transform:scaleX(1.11)} 60% {transform:scaleX(.08)} 68% {transform:scaleX(1)} }
|
|
28
|
+
@keyframes ek-hit { 0%,60%,100% {opacity:0;transform:scale(.4)} 68% {opacity:.8;transform:scale(.7)} 88% {opacity:0;transform:scale(1.7)} }
|
|
29
|
+
.ek-companion { display:flex;align-items:center;gap:12px;color:var(--dim);font:13px/1.5 var(--font-body); }
|
|
30
|
+
.ek-companion--hero { margin-bottom:16px; }
|
|
31
|
+
.ek-companion--hero b { display:block;color:var(--ink);font-weight:600; }
|
|
32
|
+
.ek-mascot-message { display:flex;align-items:center;gap:16px;padding:16px 0; }
|
|
33
|
+
.ek-mascot-message p {margin:0}
|
|
34
|
+
@media (prefers-reduced-motion:reduce) { .ek-mascot * {animation:none !important;transition:none !important} }
|
|
35
|
+
|
|
36
|
+
.ek-companion--rail { padding:12px 20px; text-decoration:none; }
|
|
37
|
+
.ek-mascot-message > :last-child { min-width:0; overflow-wrap:break-word; }
|
|
38
|
+
.ek-mascot-status { display:inline-flex; align-items:center; gap:6px; }
|
|
39
|
+
</style><title>Eklavya drawing a bow</title><g class="ek-mascot__character">
|
|
40
|
+
<ellipse cx="94" cy="185" rx="49" ry="5" fill="var(--mascot-shadow)"/>
|
|
41
|
+
<path d="M51 124Q29 137 34 168l23-8 17-27m59-9q26 8 21 39l-24-7-17-22" fill="var(--mascot-cloak-dark)"/>
|
|
42
|
+
<path d="M65 157v23q0 8 16 6l4-28m21 0 1 23q2 8 17 4v-29" fill="var(--mascot-coral)"/>
|
|
43
|
+
<path d="M58 122q35-16 72 0l8 46q-19 9-42 1-23 7-43-1Z" fill="var(--mascot-cloak)"/>
|
|
44
|
+
<path d="M96 127v40m-31-22q29 9 64 0" fill="none" stroke="var(--mascot-cloak-dark)" stroke-width="3"/>
|
|
45
|
+
<path d="M52 109q42-22 86 0l-9 29-32-14-32 14Z" fill="var(--mascot-cloak)" stroke="var(--mascot-cloak-dark)" stroke-width="3" stroke-linejoin="round"/>
|
|
46
|
+
<path d="M57 114l37 10m42-10-39 10" fill="none" stroke="var(--mascot-thread)" stroke-width="2"/>
|
|
47
|
+
<path d="M54 148h80v11H54Z" fill="var(--mascot-ink)"/>
|
|
48
|
+
<rect x="87" y="146" width="17" height="15" rx="3" fill="var(--mascot-metal)"/><rect x="92" y="150" width="7" height="7" rx="1" fill="var(--mascot-ink)"/>
|
|
49
|
+
<g class="ek-mascot__head">
|
|
50
|
+
<path d="M46 43h18V29h19V18h23v12h20v14h17v19h8v29h-8v16q0 12-16 15H62q-18-3-18-17V91h-9V63h11Z" fill="var(--mascot-coral)" stroke="var(--mascot-coral-dark)" stroke-width="2" stroke-linejoin="round"/>
|
|
51
|
+
<path d="M48 45h18V32h18V21h19" fill="none" stroke="var(--mascot-highlight)" stroke-width="3" stroke-linejoin="round"/>
|
|
52
|
+
<g class="ek-mascot__face" fill="var(--mascot-ink)" stroke="var(--mascot-ink)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><rect x="64" y="64" width="10" height="19" rx="5"/><rect x="110" y="64" width="10" height="19" rx="5"/><path d="M81 87q11 22 22 0Z" fill="var(--mascot-ink)"/><path d="M87 97q5-3 10 0" stroke="var(--mascot-coral)"/></g>
|
|
53
|
+
</g>
|
|
54
|
+
<circle cx="96" cy="125" r="7" fill="var(--mascot-metal)"/><circle cx="94" cy="123" r="2" fill="var(--mascot-thread)"/>
|
|
55
|
+
<g class="ek-mascot__bow" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
56
|
+
<path d="M157 78q36 47 0 94" stroke="var(--mascot-wood-dark)" stroke-width="8"/>
|
|
57
|
+
<path d="M158 78q32 47 0 94" stroke="var(--mascot-wood)" stroke-width="4"/>
|
|
58
|
+
<path class="ek-mascot__string" d="M157 78l-88 50 88 44" stroke="var(--mascot-string)" stroke-width="1.8"/>
|
|
59
|
+
</g>
|
|
60
|
+
<path d="M130 127l16-8q10-1 13 5 2 8-6 11l-15 4" fill="var(--mascot-cloak)"/>
|
|
61
|
+
<rect x="147" y="117" width="15" height="18" rx="7" fill="var(--mascot-coral)"/>
|
|
62
|
+
<g class="ek-mascot__arrow" stroke-linecap="round" stroke-linejoin="round">
|
|
63
|
+
<path d="M69 128h114" stroke="var(--mascot-wood-dark)" stroke-width="3"/>
|
|
64
|
+
<path d="M179 120l13 8-13 8Z" fill="var(--mascot-thread)" stroke="var(--mascot-wood)" stroke-width="1.5"/>
|
|
65
|
+
<path d="M71 128l-9-9 10 1 8 8-8 8-10 1Z" fill="var(--mascot-thread)"/>
|
|
66
|
+
</g>
|
|
67
|
+
<path d="M58 128q-11-6-14 5-2 9 8 13l17-8" fill="var(--mascot-cloak)"/>
|
|
68
|
+
<rect x="62" y="121" width="18" height="16" rx="7" fill="var(--mascot-coral)"/>
|
|
69
|
+
</g><circle class="ek-mascot__ripple" cx="196" cy="128" r="10" fill="none" stroke="var(--mascot-thread)" stroke-width="2"/></svg>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* Identity colours and UI surfaces use Eklavya's semantic tokens. */
|
|
2
|
+
eklavya-mascot { display:inline-flex; flex-shrink:0; vertical-align:middle; line-height:0; }
|
|
3
|
+
.ek-mascot { display:block; overflow:visible; flex-shrink:0; }
|
|
4
|
+
.ek-mascot[data-variant="face"] { overflow:hidden; }
|
|
5
|
+
.ek-mascot__face path:not([fill]) { fill:none; }
|
|
6
|
+
.ek-mascot--loading .ek-mascot__character { animation:ek-breathe 2.8s ease-in-out infinite; transform-origin:95px 185px; }
|
|
7
|
+
.ek-mascot--loading .ek-mascot__arrow { animation:ek-release 2.8s cubic-bezier(.3,0,.3,1) infinite; }
|
|
8
|
+
.ek-mascot--loading .ek-mascot__string { animation:ek-draw 2.8s ease-in-out infinite; transform-origin:157px 125px; }
|
|
9
|
+
.ek-mascot--loading .ek-mascot__ripple { animation:ek-hit 2.8s ease-out infinite; transform-origin:196px 128px; opacity:0; }
|
|
10
|
+
@keyframes ek-breathe { 0%,100% {transform:translateY(0)} 35%,55% {transform:translateY(-3px) rotate(-1deg)} }
|
|
11
|
+
@keyframes ek-release { 0%,12% {transform:translateX(0);opacity:1} 40%,52% {transform:translateX(-10px);opacity:1} 68% {transform:translateX(22px);opacity:0} 69%,85% {transform:translateX(0);opacity:0} 100% {transform:translateX(0);opacity:1} }
|
|
12
|
+
@keyframes ek-draw { 0%,100% {transform:scaleX(1)} 40%,52% {transform:scaleX(1.11)} 60% {transform:scaleX(.08)} 68% {transform:scaleX(1)} }
|
|
13
|
+
@keyframes ek-hit { 0%,60%,100% {opacity:0;transform:scale(.4)} 68% {opacity:.8;transform:scale(.7)} 88% {opacity:0;transform:scale(1.7)} }
|
|
14
|
+
.ek-companion { display:flex;align-items:center;gap:12px;color:var(--dim);font:13px/1.5 var(--font-body); }
|
|
15
|
+
.ek-companion--hero { margin-bottom:16px; }
|
|
16
|
+
.ek-companion--hero b { display:block;color:var(--ink);font-weight:600; }
|
|
17
|
+
.ek-mascot-message { display:flex;align-items:center;gap:16px;padding:16px 0; }
|
|
18
|
+
.ek-mascot-message p {margin:0}
|
|
19
|
+
@media (prefers-reduced-motion:reduce) { .ek-mascot * {animation:none !important;transition:none !important} }
|
|
20
|
+
|
|
21
|
+
.ek-companion--rail { padding:12px 20px; text-decoration:none; }
|
|
22
|
+
.ek-mascot-message > :last-child { min-width:0; overflow-wrap:break-word; }
|
|
23
|
+
.ek-mascot-status { display:inline-flex; align-items:center; gap:6px; }
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/* Archer Cloak — shared, local-only artwork for the site and npm dashboard. */
|
|
2
|
+
(function (root) {
|
|
3
|
+
'use strict';
|
|
4
|
+
const expressions = Object.freeze({
|
|
5
|
+
neutral: 'Calm and attentive', happy: 'Happy', excited: 'Excited', celebrate: 'Celebrating',
|
|
6
|
+
curious: 'Curious', thinking: 'Thinking', focused: 'Focused', wink: 'Offering a tip',
|
|
7
|
+
surprised: 'Surprised', confused: 'Seeking clarity', worried: 'Concerned', sad: 'Sad', sleepy: 'Resting'
|
|
8
|
+
});
|
|
9
|
+
const states = Object.freeze({ idle: 'sleepy', info: 'neutral', success: 'happy', complete: 'celebrate',
|
|
10
|
+
milestone: 'celebrate', empty: 'curious', notFound: 'curious', loading: 'excited',
|
|
11
|
+
review: 'focused', tip: 'wink', new: 'surprised', validation: 'confused', warning: 'worried', error: 'sad' });
|
|
12
|
+
const own = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
13
|
+
const escape = (text) => String(text).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
14
|
+
function expressionForState(state) { return own(states, state) ? states[state] : 'neutral'; }
|
|
15
|
+
function dimension(value) { const n = Number(value); return Number.isFinite(n) && n > 0 ? Math.min(512, Math.max(16, n)) : 64; }
|
|
16
|
+
function face(expression) {
|
|
17
|
+
const eyes = '<rect x="64" y="64" width="10" height="19" rx="5"/><rect x="110" y="64" width="10" height="19" rx="5"/>';
|
|
18
|
+
const smile = '<path d="M83 88q9 10 18 0"/>';
|
|
19
|
+
const closed = '<path d="M64 74q5-7 10 0m36 0q5-7 10 0"/>';
|
|
20
|
+
const brows = { focused: 'M62 57l14 4m32 0 14-4', thinking: 'M62 58h14m33-2 13 3',
|
|
21
|
+
worried:'M62 59l14-6m32 0 14 6', sad:'M62 60l14-5m32 0 14 5',
|
|
22
|
+
confused:'M62 58l14-5m32 4h14', curious:'M62 59h14m32-6 14 5' };
|
|
23
|
+
const faces = {
|
|
24
|
+
neutral: eyes + '<path d="M84 89q8 6 16 0"/>', happy: closed + smile,
|
|
25
|
+
celebrate: closed + '<path d="M82 87q10 22 20 0Z" fill="var(--mascot-ink)"/><path d="M87 98h10" stroke="var(--mascot-coral)"/>',
|
|
26
|
+
excited: eyes + '<path d="M81 87q11 22 22 0Z" fill="var(--mascot-ink)"/><path d="M87 97q5-3 10 0" stroke="var(--mascot-coral)"/>',
|
|
27
|
+
curious: eyes + '<path d="M86 91q6-4 12 0"/>', thinking: eyes + '<path d="M86 91h10"/>',
|
|
28
|
+
focused: eyes + '<path d="M85 91h14"/>', wink: '<rect x="64" y="64" width="10" height="19" rx="5"/><path d="M110 74q5-7 10 0"/>' + smile,
|
|
29
|
+
surprised: eyes + '<ellipse cx="92" cy="92" rx="5" ry="7" fill="var(--mascot-ink)"/>',
|
|
30
|
+
confused: eyes + '<path d="M83 93q5-7 10-2t9-2"/>', worried: eyes + '<path d="M83 95q9-9 18 0"/>',
|
|
31
|
+
sad: eyes + '<path d="M83 96q9-9 18 0"/>', sleepy: '<path d="M64 75q5 5 10 0m36 0q5 5 10 0m-30 16h10"/>'
|
|
32
|
+
};
|
|
33
|
+
return `<g class="ek-mascot__face" fill="var(--mascot-ink)" stroke="var(--mascot-ink)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round">${faces[expression]}${brows[expression] ? `<path d="${brows[expression]}" fill="none"/>` : ''}</g>`;
|
|
34
|
+
}
|
|
35
|
+
function svg({ expression = 'neutral', state, variant = 'body', size = 64, label = null } = {}) {
|
|
36
|
+
expression = state ? expressionForState(state) : own(expressions, expression) ? expression : 'neutral';
|
|
37
|
+
variant = ['body','face','loading'].includes(variant) ? variant : 'body';
|
|
38
|
+
const loading = variant === 'loading';
|
|
39
|
+
const px = dimension(size);
|
|
40
|
+
const head = `<g class="ek-mascot__head">
|
|
41
|
+
<path d="M46 43h18V29h19V18h23v12h20v14h17v19h8v29h-8v16q0 12-16 15H62q-18-3-18-17V91h-9V63h11Z" fill="var(--mascot-coral)" stroke="var(--mascot-coral-dark)" stroke-width="2" stroke-linejoin="round"/>
|
|
42
|
+
<path d="M48 45h18V32h18V21h19" fill="none" stroke="var(--mascot-highlight)" stroke-width="3" stroke-linejoin="round"/>
|
|
43
|
+
${face(expression)}
|
|
44
|
+
</g>`;
|
|
45
|
+
const body = `<g class="ek-mascot__character">
|
|
46
|
+
<ellipse cx="94" cy="185" rx="49" ry="5" fill="var(--mascot-shadow)"/>
|
|
47
|
+
<path d="M51 124Q29 137 34 168l23-8 17-27m59-9q26 8 21 39l-24-7-17-22" fill="var(--mascot-cloak-dark)"/>
|
|
48
|
+
<path d="M65 157v23q0 8 16 6l4-28m21 0 1 23q2 8 17 4v-29" fill="var(--mascot-coral)"/>
|
|
49
|
+
<path d="M58 122q35-16 72 0l8 46q-19 9-42 1-23 7-43-1Z" fill="var(--mascot-cloak)"/>
|
|
50
|
+
<path d="M96 127v40m-31-22q29 9 64 0" fill="none" stroke="var(--mascot-cloak-dark)" stroke-width="3"/>
|
|
51
|
+
<path d="M52 109q42-22 86 0l-9 29-32-14-32 14Z" fill="var(--mascot-cloak)" stroke="var(--mascot-cloak-dark)" stroke-width="3" stroke-linejoin="round"/>
|
|
52
|
+
<path d="M57 114l37 10m42-10-39 10" fill="none" stroke="var(--mascot-thread)" stroke-width="2"/>
|
|
53
|
+
<path d="M54 148h80v11H54Z" fill="var(--mascot-ink)"/>
|
|
54
|
+
<rect x="87" y="146" width="17" height="15" rx="3" fill="var(--mascot-metal)"/><rect x="92" y="150" width="7" height="7" rx="1" fill="var(--mascot-ink)"/>
|
|
55
|
+
${head}
|
|
56
|
+
<circle cx="96" cy="125" r="7" fill="var(--mascot-metal)"/><circle cx="94" cy="123" r="2" fill="var(--mascot-thread)"/>
|
|
57
|
+
<g class="ek-mascot__bow" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
58
|
+
<path d="M157 78q36 47 0 94" stroke="var(--mascot-wood-dark)" stroke-width="8"/>
|
|
59
|
+
<path d="M158 78q32 47 0 94" stroke="var(--mascot-wood)" stroke-width="4"/>
|
|
60
|
+
<path class="ek-mascot__string" d="M157 78l-88 50 88 44" stroke="var(--mascot-string)" stroke-width="1.8"/>
|
|
61
|
+
</g>
|
|
62
|
+
<path d="M130 127l16-8q10-1 13 5 2 8-6 11l-15 4" fill="var(--mascot-cloak)"/>
|
|
63
|
+
<rect x="147" y="117" width="15" height="18" rx="7" fill="var(--mascot-coral)"/>
|
|
64
|
+
<g class="ek-mascot__arrow" stroke-linecap="round" stroke-linejoin="round">
|
|
65
|
+
<path d="M69 128h114" stroke="var(--mascot-wood-dark)" stroke-width="3"/>
|
|
66
|
+
<path d="M179 120l13 8-13 8Z" fill="var(--mascot-thread)" stroke="var(--mascot-wood)" stroke-width="1.5"/>
|
|
67
|
+
<path d="M71 128l-9-9 10 1 8 8-8 8-10 1Z" fill="var(--mascot-thread)"/>
|
|
68
|
+
</g>
|
|
69
|
+
<path d="M58 128q-11-6-14 5-2 9 8 13l17-8" fill="var(--mascot-cloak)"/>
|
|
70
|
+
<rect x="62" y="121" width="18" height="16" rx="7" fill="var(--mascot-coral)"/>
|
|
71
|
+
</g>`;
|
|
72
|
+
const accents = expression === 'celebrate' ? '<g fill="var(--mascot-thread)"><path d="m20 47 3-7 3 7 7 3-7 3-3 7-3-7-7-3Z"/><path d="m166 31 2-5 2 5 5 2-5 2-2 5-2-5-5-2Z"/></g>' : '';
|
|
73
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${variant === 'face' ? '30 12 126 128' : '0 0 210 200'}" width="${px}" height="${px}" class="ek-mascot${loading ? ' ek-mascot--loading' : ''}" data-expression="${expression}" data-variant="${variant}" ${label == null ? 'aria-hidden="true"' : 'role="img"'}>${label == null ? '' : `<title>${escape(label)}</title>`}${accents}${variant === 'face' ? `<path d="M48 109q44-19 88 0l-8 30-33-15-31 15Z" fill="var(--mascot-cloak)"/>${head}<circle cx="95" cy="126" r="6" fill="var(--mascot-metal)"/>` : body}${loading ? '<circle class="ek-mascot__ripple" cx="196" cy="128" r="10" fill="none" stroke="var(--mascot-thread)" stroke-width="2"/>' : ''}</svg>`;
|
|
74
|
+
}
|
|
75
|
+
root.EklavyaMascot = Object.freeze({ expressions, states, expressionForState, svg });
|
|
76
|
+
if (root.customElements && !root.customElements.get('eklavya-mascot')) {
|
|
77
|
+
class Mascot extends HTMLElement {
|
|
78
|
+
static get observedAttributes() { return ['state','expression','size','variant','label']; }
|
|
79
|
+
connectedCallback() { this.render(); }
|
|
80
|
+
attributeChangedCallback() { if (this.isConnected) this.render(); }
|
|
81
|
+
render() {
|
|
82
|
+
// Decorative by default: the adjoining text owns the status message.
|
|
83
|
+
this.innerHTML = svg({ state: this.getAttribute('state'), expression: this.getAttribute('expression') || 'neutral',
|
|
84
|
+
variant: this.getAttribute('variant') || 'body', size: this.getAttribute('size') || 64, label: this.getAttribute('label') });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
root.customElements.define('eklavya-mascot', Mascot);
|
|
88
|
+
}
|
|
89
|
+
})(globalThis);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" data-mode="ink">
|
|
3
|
+
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
4
|
+
<title>Eklavya — Mascot studio</title><meta name="robots" content="noindex, follow"><meta name="description" content="The Eklavya archer companion: expressions, motion, and reusable design-system components.">
|
|
5
|
+
<script>try{document.documentElement.dataset.mode=localStorage.getItem('eklavya-ground')==='paper'?'paper':'ink'}catch(e){}</script>
|
|
6
|
+
<link rel="stylesheet" href="/tokens.css?v=3"><link rel="stylesheet" href="/brand/mascot/mascot.css?v=1"><script src="/brand/mascot/mascot.js?v=1" defer></script>
|
|
7
|
+
<style>
|
|
8
|
+
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.6 var(--font-body)}
|
|
9
|
+
a{color:var(--spot)}button{font:inherit;color:var(--ink);cursor:pointer;background:var(--panel);border:1px solid var(--line-2);border-radius:0;padding:9px 15px}button:hover,button[aria-pressed=true]{border-color:var(--spot);background:var(--spot-soft)}:focus-visible{outline:2px solid var(--spot);outline-offset:4px}
|
|
10
|
+
.studio{max-width:1120px;margin:auto;padding:24px clamp(20px,4vw,48px) 72px}.top{display:flex;justify-content:space-between;align-items:center;gap:20px}.wordmark{font:800 20px var(--font-disp);color:var(--ink);text-decoration:none}.eyebrow{color:var(--spot);font:12px var(--font-mono);text-transform:uppercase;letter-spacing:.16em}h1{font:800 clamp(38px,6vw,72px)/1.08 var(--font-disp);letter-spacing:-.045em;margin:16px 0}h2{font:600 26px var(--font-disp);letter-spacing:-.02em}p{color:var(--dim)}.intro{max-width:630px}.hero{display:grid;grid-template-columns:1.1fr 1fr;gap:40px;align-items:center;margin:64px 0}.stage{position:relative;background:radial-gradient(ellipse at center,var(--spot-soft),transparent 70%);min-height:320px;display:grid;place-items:center}.stage::before{content:'';position:absolute;width:270px;height:270px;border:1px solid var(--line-2);border-radius:50%}.stage eklavya-mascot{position:relative}.section{border-top:1px solid var(--line-2);padding-top:24px;margin-top:40px}.section-head{display:flex;justify-content:space-between;gap:20px;align-items:baseline}.grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:12px}.expression{display:flex;flex-direction:column;align-items:center;gap:8px;padding:20px 8px 14px;min-width:0}.expression span{font-size:12px}.expression small{color:var(--faint);font-size:10px}.panels{display:grid;grid-template-columns:1fr 1fr;gap:20px}.panel{background:var(--panel);border:1px solid var(--line-2);border-radius:0;padding:24px}.motion{display:flex;align-items:center;gap:20px;min-height:140px}.sizes{display:flex;flex-wrap:wrap;gap:24px;align-items:end}.size{display:flex;flex-direction:column;align-items:center;gap:12px;font:11px var(--font-mono);color:var(--dim)}code,pre{font:12px/1.7 var(--font-mono)}pre{padding:20px;background:var(--panel);border:1px solid var(--line-2);border-radius:0;overflow:auto;color:var(--spot)}.state-list{display:flex;flex-wrap:wrap;gap:8px}.caption{font-size:12px;color:var(--faint)}.controls{display:flex;gap:8px;flex-wrap:wrap}.paused .ek-mascot *{animation-play-state:paused!important}.notice{color:var(--ink)}
|
|
11
|
+
@media(max-width:760px){.hero{grid-template-columns:1fr;margin:40px 0;gap:0}.stage{min-height:280px}.grid{grid-template-columns:repeat(3,minmax(0,1fr))}.panels{grid-template-columns:1fr}.section-head{display:block}}
|
|
12
|
+
</style></head><body><main class="studio">
|
|
13
|
+
<nav class="top" aria-label="Studio navigation"><a class="wordmark" href="/">Eklavya <span class="eyebrow">/ mascot studio</span></a><button id="ground" aria-label="Switch to paper theme">Paper ground</button></nav>
|
|
14
|
+
<section class="hero"><div class="intro"><span class="eyebrow">Meet your learning companion</span><h1>A steady aim.<br>A curious mind.</h1><p>The Archer Cloak companion. A warm coral character, a verdigris cloak, and a little encouragement as you learn.</p><p class="notice" id="selected" aria-live="polite">Calm and attentive</p><div class="controls"><button id="pose" aria-pressed="false">Show face only</button><button id="motion" aria-pressed="false">Play loading loop</button></div></div><div class="stage"><eklavya-mascot id="preview" expression="neutral" size="280" label="Eklavya, calm and attentive"></eklavya-mascot></div></section>
|
|
15
|
+
<section class="section"><div class="section-head"><h2>A face for the moment</h2><p>Choose an expression to preview it.</p></div><div class="grid" id="expressions" role="group" aria-label="Mascot expressions"></div></section>
|
|
16
|
+
<section class="section"><h2>Practice in motion</h2><p>Draw, release, reset. A quiet, repeating rhythm for real loading states.</p><div class="panels"><div class="panel"><div class="motion" role="status"><eklavya-mascot state="loading" variant="loading" size="112"></eklavya-mascot><div><strong>Loading your history…</strong><p class="caption">Page loading · 112 px</p></div></div></div><div class="panel"><div class="motion" role="status"><eklavya-mascot state="loading" variant="loading" size="56"></eklavya-mascot><div><strong>Finding the next step…</strong><p class="caption">Inline loading · 56 px</p></div></div></div></div><button id="pause" aria-pressed="false">Pause animations</button><p class="caption">Reduced-motion preferences are respected automatically. Status text remains readable when motion is off.</p></section>
|
|
17
|
+
<section class="section"><h2>Small enough to belong</h2><p>Use the face at compact sizes and the full archer when there is room to breathe.</p><div class="sizes" id="sizes"></div></section>
|
|
18
|
+
<section class="section"><h2>React to the state</h2><p>Choose a UI state. One shared mapping keeps the companion consistent everywhere.</p><div class="state-list" id="states"></div><div class="ek-mascot-message" style="margin-top:16px"><eklavya-mascot id="state-preview" state="empty" size="80"></eklavya-mascot><div><strong id="state-name">empty → curious</strong><p id="state-copy">Nothing here yet. Your next session is a good place to start.</p></div></div></section>
|
|
19
|
+
<section class="section"><h2>Made to be reused</h2><p>Same artwork on the website and the local dashboard. Use a state for product moments, or an expression for editorial illustrations.</p><pre><link rel="stylesheet" href="/tokens.css?v=3">
|
|
20
|
+
<link rel="stylesheet" href="/brand/mascot/mascot.css?v=1">
|
|
21
|
+
<script src="/brand/mascot/mascot.js?v=1" defer></script>
|
|
22
|
+
|
|
23
|
+
<eklavya-mascot state="empty" size="80"></eklavya-mascot>
|
|
24
|
+
<eklavya-mascot expression="wink" variant="face" size="40"></eklavya-mascot>
|
|
25
|
+
|
|
26
|
+
<div role="status">
|
|
27
|
+
<eklavya-mascot state="loading" variant="loading" size="56"></eklavya-mascot>
|
|
28
|
+
Loading your history…
|
|
29
|
+
</div></pre><p class="caption">Decorative by default. Add a label when the artwork communicates something on its own. Keep errors and guidance in visible text.</p></section>
|
|
30
|
+
</main><script>
|
|
31
|
+
window.addEventListener('DOMContentLoaded',()=>{
|
|
32
|
+
const M=EklavyaMascot, $=id=>document.getElementById(id), preview=$('preview');let expression='neutral',face=false,loading=false;
|
|
33
|
+
function update(){preview.removeAttribute('state');preview.setAttribute('expression',expression);preview.setAttribute('variant',loading?'loading':face?'face':'body');preview.setAttribute('label','Eklavya, '+M.expressions[expression].toLowerCase());$('selected').textContent=M.expressions[expression];$('pose').setAttribute('aria-pressed',String(face));$('motion').setAttribute('aria-pressed',String(loading));document.querySelectorAll('.expression').forEach(b=>b.setAttribute('aria-pressed',String(b.dataset.expression===expression)))}
|
|
34
|
+
for(const [key,label] of Object.entries(M.expressions)){const b=document.createElement('button');b.className='expression';b.dataset.expression=key;b.setAttribute('aria-pressed',String(key==='neutral'));b.innerHTML=M.svg({expression:key,variant:'face',size:72})+'<span>'+label+'</span><small>'+key+'</small>';b.addEventListener('click',()=>{expression=key;loading=false;update()});$('expressions').append(b)}
|
|
35
|
+
$('pose').onclick=()=>{face=!face;loading=false;update()};$('motion').onclick=()=>{loading=!loading;if(loading)expression='excited';update()};
|
|
36
|
+
$('pause').onclick=()=>{const paused=document.body.classList.toggle('paused');$('pause').setAttribute('aria-pressed',String(paused));$('pause').textContent=paused?'Resume animations':'Pause animations'};
|
|
37
|
+
const copy={empty:'Nothing here yet. Your next session is a good place to start.',loading:'Loading your history…',success:'Saved. Keep going.',complete:'Practice complete. Nice work.',error:'This could not load. Try again.',warning:'Review this step before continuing.',validation:'Check the highlighted field.',review:'Take a moment to review.',tip:'A useful detail to consider.',idle:'Ready when you are.'};
|
|
38
|
+
for(const [state,mood] of Object.entries(M.states)){const b=document.createElement('button');b.textContent=state;b.setAttribute('aria-pressed',String(state==='empty'));b.onclick=()=>{$('state-preview').setAttribute('state',state);$('state-preview').setAttribute('variant',state==='loading'?'loading':'body');$('state-name').textContent=state+' → '+mood;$('state-copy').textContent=copy[state]||M.expressions[mood];$('states').querySelectorAll('button').forEach(x=>x.setAttribute('aria-pressed',String(x===b)))};$('states').append(b)}
|
|
39
|
+
for(const size of [24,32,40,64,96,128]){const node=document.createElement('div');node.className='size';node.innerHTML=M.svg({expression:'neutral',variant:size<64?'face':'body',size})+'<span>'+size+' px</span>';$('sizes').append(node)}
|
|
40
|
+
function ground(){const paper=document.documentElement.dataset.mode==='paper';$('ground').textContent=paper?'Ink ground':'Paper ground';$('ground').setAttribute('aria-label',paper?'Switch to ink theme':'Switch to paper theme')}
|
|
41
|
+
$('ground').onclick=()=>{document.documentElement.dataset.mode=document.documentElement.dataset.mode==='paper'?'ink':'paper';try{localStorage.setItem('eklavya-ground',document.documentElement.dataset.mode)}catch(e){}ground()};ground();
|
|
42
|
+
});
|
|
43
|
+
</script></body></html>
|
package/dist/assets/tokens.css
CHANGED
|
@@ -39,6 +39,23 @@
|
|
|
39
39
|
--vd-800: #07403C;
|
|
40
40
|
--vd-900: #052E2B;
|
|
41
41
|
|
|
42
|
+
/* ---------- MASCOT IDENTITY ----------
|
|
43
|
+
Archer Cloak artwork keeps its approved identity across both grounds.
|
|
44
|
+
UI chrome continues to use the surface, text and accent roles below. */
|
|
45
|
+
--mascot-coral: #ef9875;
|
|
46
|
+
--mascot-coral-dark: #c96d51;
|
|
47
|
+
--mascot-highlight: #ffc8a6;
|
|
48
|
+
--mascot-cloak: #0E6E66;
|
|
49
|
+
--mascot-cloak-dark: #07403C;
|
|
50
|
+
--mascot-thread: #C4EDE4;
|
|
51
|
+
--mascot-ink: #302c2a;
|
|
52
|
+
--mascot-metal: #5b7c73;
|
|
53
|
+
--mascot-wood: #bc9569;
|
|
54
|
+
--mascot-wood-dark: #74583f;
|
|
55
|
+
--mascot-string: #EAE7E1;
|
|
56
|
+
--mascot-shadow: #26262a;
|
|
57
|
+
/* ---------- END MASCOT IDENTITY ---------- */
|
|
58
|
+
|
|
42
59
|
/* ---------- SEMANTIC (real errors and warnings only) ----------
|
|
43
60
|
Kept off the accent hue so state never reads as branding. --warning and
|
|
44
61
|
--error are redefined per ground below: an orange that carries on ink is
|
package/dist/dashboard.js
CHANGED
|
@@ -1391,6 +1391,12 @@ const MANIFEST = JSON.stringify({
|
|
|
1391
1391
|
});
|
|
1392
1392
|
/** Served from the bundled assets as-is; copied in from `web/public` at build. */
|
|
1393
1393
|
const APP_ICONS = new Set(['/icon-192.png', '/icon-512.png', '/apple-touch-icon.png']);
|
|
1394
|
+
/** Explicit mascot allowlist: no user-controlled filesystem paths. */
|
|
1395
|
+
const MASCOT_ASSETS = new Map([
|
|
1396
|
+
['/brand/mascot/mascot.js', ['mascot/mascot.js', 'text/javascript; charset=utf-8']],
|
|
1397
|
+
['/brand/mascot/mascot.css', ['mascot/mascot.css', 'text/css; charset=utf-8']],
|
|
1398
|
+
['/mascot.html', ['mascot.html', 'text/html; charset=utf-8']],
|
|
1399
|
+
]);
|
|
1394
1400
|
/** The tips library, copied from node_modules at build by `copy-assets.mjs`. */
|
|
1395
1401
|
const VENDOR = { '/vendor/driver-hints.js': 'text/javascript', '/vendor/driver-hints.css': 'text/css' };
|
|
1396
1402
|
function send(res, status, type, body, extra = {}) {
|
|
@@ -1626,6 +1632,10 @@ export function startDashboard(db, opts = {}) {
|
|
|
1626
1632
|
'cache-control': 'max-age=3600',
|
|
1627
1633
|
});
|
|
1628
1634
|
}
|
|
1635
|
+
const mascotAsset = MASCOT_ASSETS.get(url.pathname);
|
|
1636
|
+
if (mascotAsset) {
|
|
1637
|
+
return send(res, 200, mascotAsset[1], fs.readFileSync(path.join(assets, mascotAsset[0])));
|
|
1638
|
+
}
|
|
1629
1639
|
if (url.pathname === '/tokens.css') {
|
|
1630
1640
|
return send(res, 200, 'text/css', localTokens(fs.readFileSync(path.join(assets, 'tokens.css'), 'utf8')));
|
|
1631
1641
|
}
|