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
|
@@ -44,6 +44,8 @@
|
|
|
44
44
|
this page is fetched from anywhere but this loopback server. The font
|
|
45
45
|
stacks fall through to the system faces they already name. -->
|
|
46
46
|
<link rel="stylesheet" href="/tokens.css">
|
|
47
|
+
<link rel="stylesheet" href="/brand/mascot/mascot.css">
|
|
48
|
+
<script src="/brand/mascot/mascot.js" defer></script>
|
|
47
49
|
<!-- Feature tips: Driver.js's hints module, pinned and served by this server (see TIP). -->
|
|
48
50
|
<link rel="stylesheet" href="/vendor/driver-hints.css">
|
|
49
51
|
<script src="/vendor/driver-hints.js" defer></script>
|
|
@@ -154,23 +156,13 @@
|
|
|
154
156
|
animation:skel 1.4s var(--ease) infinite alternate; }
|
|
155
157
|
@keyframes skel { from { opacity:.55; } to { opacity:1; } }
|
|
156
158
|
|
|
157
|
-
/*
|
|
158
|
-
|
|
159
|
-
brand mark and never moves; a short --spot segment travels round a square
|
|
160
|
-
outline just outside its tile. It fades in after 150ms so a fast loopback
|
|
161
|
-
answer never flashes it; reduced motion stops the trace and keeps the label. */
|
|
159
|
+
/* One companion loader for every wait. A delayed reveal avoids flicker;
|
|
160
|
+
the shared mascot stylesheet respects reduced motion. */
|
|
162
161
|
.loader { display:flex; align-items:center; gap:var(--space-4); padding:var(--space-6) 0; color:var(--dim);
|
|
163
162
|
font-family:var(--font-mono); font-size:12px; letter-spacing:.04em;
|
|
164
163
|
animation:loader-in var(--duration-base) var(--ease) 150ms both; }
|
|
165
164
|
.loader--page { justify-content:center; flex-direction:column; min-height:40vh; gap:var(--space-5); }
|
|
166
|
-
.loader .mark { position:relative; overflow:visible; }
|
|
167
|
-
.loader__trace { position:absolute; inset:-5px; width:calc(100% + 10px); height:calc(100% + 10px); overflow:visible; }
|
|
168
|
-
.loader__trace rect { fill:none; stroke:var(--spot); stroke-width:1.5; stroke-dasharray:32 128;
|
|
169
|
-
animation:trace 1.6s linear infinite; }
|
|
170
|
-
.loader--page .mark { width:40px; height:40px; }
|
|
171
|
-
.loader--page .mark svg:first-child { width:24px; height:24px; }
|
|
172
165
|
@keyframes loader-in { from { opacity:0; } to { opacity:1; } }
|
|
173
|
-
@keyframes trace { to { stroke-dashoffset:-160; } }
|
|
174
166
|
|
|
175
167
|
/* ---------- the sidebar navigation ---------- */
|
|
176
168
|
nav.side__nav { display:flex; flex-direction:column; gap:var(--space-4); }
|
|
@@ -940,6 +932,7 @@
|
|
|
940
932
|
<span>dashboard</span>
|
|
941
933
|
</div>
|
|
942
934
|
</a>
|
|
935
|
+
<a href="/mascot.html" class="ek-companion ek-companion--rail"><eklavya-mascot state="info" variant="face" size="40"></eklavya-mascot><span>Meet your companion ↗</span></a>
|
|
943
936
|
|
|
944
937
|
<div class="wf" id="wf">
|
|
945
938
|
<a class="wf__main" id="wf-main" href="#/learning/dashboard"></a>
|
|
@@ -982,7 +975,7 @@
|
|
|
982
975
|
<p class="page__eyebrow crumb inner" id="crumb" aria-label="Workflow and scope"></p>
|
|
983
976
|
<div class="inner stack" id="view" aria-busy="true">
|
|
984
977
|
<!-- The same markup `loader()` writes; the script has not run yet. -->
|
|
985
|
-
<div class="loader loader--page" role="status" aria-live="polite"><
|
|
978
|
+
<div class="loader loader--page" role="status" aria-live="polite"><eklavya-mascot state="loading" variant="loading" size="96"></eklavya-mascot><span>Loading your history…</span></div>
|
|
986
979
|
</div>
|
|
987
980
|
<span class="sr" id="fb-live" role="status" aria-live="polite"></span>
|
|
988
981
|
<footer class="foot inner">
|
|
@@ -1362,8 +1355,16 @@ function copyCommand(btn) {
|
|
|
1362
1355
|
* One line per item: `{ t, s, v, go, cls }` — a title, a quiet mono second
|
|
1363
1356
|
* line, one value on the right, and where it opens. All HTML the caller escaped.
|
|
1364
1357
|
*/
|
|
1358
|
+
/** State art is decorative; the caller owns escaped content and visible copy. */
|
|
1359
|
+
function mascotMessage(content, state = 'empty') {
|
|
1360
|
+
return `<div class="ek-mascot-message"><eklavya-mascot state="${state}" size="56"></eklavya-mascot><div>${content}</div></div>`;
|
|
1361
|
+
}
|
|
1362
|
+
function mascotStatus(text, state) {
|
|
1363
|
+
return `<span class="ek-mascot-status"><eklavya-mascot state="${state}" variant="face" size="24"></eklavya-mascot>${esc(text)}</span>`;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1365
1366
|
function lines(items, empty = 'Nothing here yet.') {
|
|
1366
|
-
if (!items.length) return `<p class="empty">${empty}</p
|
|
1367
|
+
if (!items.length) return mascotMessage(`<p class="empty">${empty}</p>`);
|
|
1367
1368
|
return `<ul class="lines">` + items.map((x) =>
|
|
1368
1369
|
`<li${x.go ? ` data-go="${esc(x.go)}" role="link"` : ''}${x.cls ? ` class="${x.cls}"` : ''}>`
|
|
1369
1370
|
+ `<div class="ln__main"><span class="ln__t">${x.t}</span>${x.s ? `<span class="ln__s">${x.s}</span>` : ''}</div>`
|
|
@@ -1392,7 +1393,7 @@ function qaLine(a, lead, linkConcept = false) {
|
|
|
1392
1393
|
}
|
|
1393
1394
|
|
|
1394
1395
|
function table(cols, rows) {
|
|
1395
|
-
if (!rows.length) return '<p class="empty">Nothing here yet.</p>';
|
|
1396
|
+
if (!rows.length) return mascotMessage('<p class="empty">Nothing here yet.</p>');
|
|
1396
1397
|
return `<div class="scroll"><table><thead><tr>`
|
|
1397
1398
|
+ cols.map((c) => `<th${c.n ? ' class="num"' : ''}${c.w ? ` style="width:${c.w}"` : ''}>${esc(c.t)}</th>`).join('')
|
|
1398
1399
|
+ `</tr></thead><tbody>` + rows.join('') + `</tbody></table></div>`;
|
|
@@ -1696,13 +1697,13 @@ let GEN = 0;
|
|
|
1696
1697
|
* not exist until `render()` has written the view's `innerHTML`.
|
|
1697
1698
|
*/
|
|
1698
1699
|
/**
|
|
1699
|
-
* The one loading state: the
|
|
1700
|
+
* The one loading state: the companion drawing and releasing its arrow, and what is being waited
|
|
1700
1701
|
* for. `page: true` is the full-height version the first paint uses. Every
|
|
1701
1702
|
* placeholder a `fill()` target starts with is one of these, so a wait looks
|
|
1702
1703
|
* the same wherever it happens. `label` is plain text; it is escaped here.
|
|
1703
1704
|
*/
|
|
1704
1705
|
function loader(label, { page = false } = {}) {
|
|
1705
|
-
return `<div class="loader${page ? ' loader--page' : ''}" role="status" aria-live="polite"><
|
|
1706
|
+
return `<div class="loader${page ? ' loader--page' : ''}" role="status" aria-live="polite"><eklavya-mascot state="loading" variant="loading" size="${page ? 96 : 56}"></eklavya-mascot><span>${esc(label)}</span></div>`;
|
|
1706
1707
|
}
|
|
1707
1708
|
|
|
1708
1709
|
function fill(id, url, draw) {
|
|
@@ -1725,7 +1726,7 @@ function fill(id, url, draw) {
|
|
|
1725
1726
|
TIP.sync();
|
|
1726
1727
|
}).catch((e) => {
|
|
1727
1728
|
const el = $(id);
|
|
1728
|
-
if (el && gen === GEN) { el.innerHTML = `<p class="empty">Could not load: ${esc(e.message)}</p>`; el.removeAttribute('aria-busy'); }
|
|
1729
|
+
if (el && gen === GEN) { el.innerHTML = `<div class="ek-mascot-message"><eklavya-mascot state="error" size="56"></eklavya-mascot><p class="empty">Could not load: ${esc(e.message)}</p></div>`; el.removeAttribute('aria-busy'); }
|
|
1729
1730
|
});
|
|
1730
1731
|
});
|
|
1731
1732
|
}
|
|
@@ -1968,7 +1969,7 @@ function viewConcepts(param) {
|
|
|
1968
1969
|
|
|
1969
1970
|
function viewConcept(slug) {
|
|
1970
1971
|
const c = S.concepts.find((x) => x.slug === slug);
|
|
1971
|
-
if (!c) return `<p class="empty">No concept called <code>${esc(slug)}</code>. <a class="link" data-go="${esc(LH('concepts'))}">Back to concepts</a>.</p
|
|
1972
|
+
if (!c) return mascotMessage(`<p class="empty">No concept called <code>${esc(slug)}</code>. <a class="link" data-go="${esc(LH('concepts'))}">Back to concepts</a>.</p>`, 'notFound');
|
|
1972
1973
|
|
|
1973
1974
|
const atts = S.attempts.filter((a) => a.slug === slug);
|
|
1974
1975
|
// A logged row with no context line is a bookkeeping row, not a memory: it
|
|
@@ -2004,7 +2005,7 @@ function viewConcept(slug) {
|
|
|
2004
2005
|
|
|
2005
2006
|
card('Every question you were asked', atts.length ? `${plural(atts.length, 'attempt')}, newest first. Open one for the options and the feedback.` : '',
|
|
2006
2007
|
atts.length ? history + pager('concept', atts.length, p, PER_Q)
|
|
2007
|
-
: '<p class="empty">Not asked yet. It will come up when your work touches it.</p>'),
|
|
2008
|
+
: mascotMessage('<p class="empty">Not asked yet. It will come up when your work touches it.</p>')),
|
|
2008
2009
|
|
|
2009
2010
|
atts.length
|
|
2010
2011
|
? fold('concept:grades', 'How the grades went', plural(atts.length, 'grade'),
|
|
@@ -2019,7 +2020,7 @@ function viewConcept(slug) {
|
|
|
2019
2020
|
s: `${esc(ago(l.ts))} · ${esc(pname(l.repo, l.session_id))} · ${esc(l.origin ?? 'work')}`,
|
|
2020
2021
|
go: sessHref(l.session_id),
|
|
2021
2022
|
})))
|
|
2022
|
-
: '<p class="empty">No context recorded — this one came from the seed graph.</p>',
|
|
2023
|
+
: mascotMessage('<p class="empty">No context recorded — this one came from the seed graph.</p>'),
|
|
2023
2024
|
{ hint: 'What the agent said the concept was for, in the code it wrote. Open a line for that session.' }),
|
|
2024
2025
|
|
|
2025
2026
|
fold('concept:graph', 'Connected concepts',
|
|
@@ -2131,8 +2132,8 @@ function viewSessions() {
|
|
|
2131
2132
|
].join('');
|
|
2132
2133
|
}
|
|
2133
2134
|
|
|
2134
|
-
const noSession = (id) => `<p class="empty">No session <code>${esc(id)}</code> in this scope.
|
|
2135
|
-
<a class="link" data-go="${esc(href(CUR.wf, 'sessions'))}">Back to sessions</a>.</p
|
|
2135
|
+
const noSession = (id) => mascotMessage(`<p class="empty">No session <code>${esc(id)}</code> in this scope.
|
|
2136
|
+
<a class="link" data-go="${esc(href(CUR.wf, 'sessions'))}">Back to sessions</a>.</p>`, 'notFound');
|
|
2136
2137
|
|
|
2137
2138
|
/**
|
|
2138
2139
|
* One session, from either workflow. Shared rendering: the learning half and
|
|
@@ -2298,7 +2299,7 @@ function viewProjects() {
|
|
|
2298
2299
|
fourth Node service and their first Rust one. Everyone starts at easy (tiers 1–2) and climbs on
|
|
2299
2300
|
evidence — a promotion needs passing answers <em>and</em> accuracy <em>and</em> distinct concepts. A
|
|
2300
2301
|
project appears here as soon as its work logs a concept, before a single question is asked.`),
|
|
2301
|
-
folds.join('') || '<p class="empty">No project has logged or answered anything yet.</p>',
|
|
2302
|
+
folds.join('') || mascotMessage('<p class="empty">No project has logged or answered anything yet.</p>'),
|
|
2302
2303
|
other.length
|
|
2303
2304
|
? fold('proj:memory-only', 'Memory only', plural(other.length, 'project'),
|
|
2304
2305
|
lines(other.map((p) => ({
|
|
@@ -2348,8 +2349,8 @@ function viewDomains() {
|
|
|
2348
2349
|
|
|
2349
2350
|
function viewDomain(name) {
|
|
2350
2351
|
const all = conceptsInScope().filter((c) => c.domain === name);
|
|
2351
|
-
if (!all.length) return `<p class="empty">No concepts in <code>${esc(name)}</code> for this scope.
|
|
2352
|
-
<a class="link" data-go="${esc(LH('domains'))}">Back to domains</a>.</p
|
|
2352
|
+
if (!all.length) return mascotMessage(`<p class="empty">No concepts in <code>${esc(name)}</code> for this scope.
|
|
2353
|
+
<a class="link" data-go="${esc(LH('domains'))}">Back to domains</a>.</p>`);
|
|
2353
2354
|
const seen = all.filter((c) => c.seen);
|
|
2354
2355
|
const list = [...all].sort((a, b) => (a.seen === b.seen ? a.score - b.score : a.seen ? -1 : 1));
|
|
2355
2356
|
const p = page('domain', list.length);
|
|
@@ -2417,7 +2418,7 @@ function tlEntry(e) {
|
|
|
2417
2418
|
*/
|
|
2418
2419
|
function drawMemory(d) {
|
|
2419
2420
|
T.memory.page = d.page; // the server clamps a page number a filter shrank
|
|
2420
|
-
if (!d.rows.length) return '<p class="empty">Nothing in memory matches these filters.</p>';
|
|
2421
|
+
if (!d.rows.length) return mascotMessage('<p class="empty">Nothing in memory matches these filters.</p>');
|
|
2421
2422
|
const summaries = new Map(d.rows.filter((e) => e.kind === 'session_summary' && e.session_id).map((e) => [e.session_id, e]));
|
|
2422
2423
|
const kids = new Map();
|
|
2423
2424
|
for (const e of d.rows) {
|
|
@@ -2697,7 +2698,7 @@ function drawEntry(d) {
|
|
|
2697
2698
|
`<div class="list">${d.replaces.map((x) => `<div>${link(x)}</div>`).join('')}</div>`)
|
|
2698
2699
|
: '',
|
|
2699
2700
|
fold('entry:evidence', 'The raw evidence', plural(d.events.length, 'event'),
|
|
2700
|
-
evidence || '<p class="empty">No evidence is linked — this entry was written by hand or imported.</p>',
|
|
2701
|
+
evidence || mascotMessage('<p class="empty">No evidence is linked — this entry was written by hand or imported.</p>'),
|
|
2701
2702
|
{ hint: 'Every event this was distilled from, exactly as captured. Open one to read it.' }),
|
|
2702
2703
|
d.candidates.length
|
|
2703
2704
|
? fold('entry:cands', 'Concepts it proposed', plural(d.candidates.length, 'candidate'),
|
|
@@ -2723,7 +2724,7 @@ function drawEntry(d) {
|
|
|
2723
2724
|
|
|
2724
2725
|
function viewEntry(id) {
|
|
2725
2726
|
fill('entry-body', '/api/memory/entry?id=' + encodeURIComponent(id), (d) =>
|
|
2726
|
-
d && d.entry ? drawEntry(d) : `<p class="empty">No observation <code>${esc(id)}</code>.</p
|
|
2727
|
+
d && d.entry ? drawEntry(d) : mascotMessage(`<p class="empty">No observation <code>${esc(id)}</code>.</p>`, 'notFound'));
|
|
2727
2728
|
return `<div><a class="page__back" data-go="${esc(MH('timeline'))}">← Timeline</a></div>
|
|
2728
2729
|
<div class="stack" id="entry-body">${loader('Loading the observation…')}</div>`;
|
|
2729
2730
|
}
|
|
@@ -2926,7 +2927,7 @@ function viewArtifacts(param) {
|
|
|
2926
2927
|
const projects = new Set(all.map((a) => pid(a.project))).size;
|
|
2927
2928
|
const chip = (k, label) => `<button class="chip" data-go="${esc(AH('dashboard', k === 'all' ? null : k))}" aria-pressed="${kind === k}">${label} ${counts[k]}</button>`;
|
|
2928
2929
|
const body = !list.length
|
|
2929
|
-
? `<p class="empty">${all.length ? 'Nothing matches that search.' : 'No artifacts yet. Ask for an Eklavya artifact, or miss a question with explain_on_wrong on, and they land here.'}</p>`
|
|
2930
|
+
? mascotMessage(`<p class="empty">${all.length ? 'Nothing matches that search.' : 'No artifacts yet. Ask for an Eklavya artifact, or miss a question with explain_on_wrong on, and they land here.'}</p>`)
|
|
2930
2931
|
: [...groups].map(([id, rows]) =>
|
|
2931
2932
|
`<h3 class="slug" style="margin:var(--space-5) 0 var(--space-2)">${esc(projectById(id)?.name ?? (id === '~' ? 'no project' : base(id)))}</h3>`
|
|
2932
2933
|
+ `<div class="gallery">${rows.map(artifactCard).join('')}</div>`).join('') + pager('artifacts', list.length, p);
|
|
@@ -3033,7 +3034,7 @@ function shuffled(n) {
|
|
|
3033
3034
|
|
|
3034
3035
|
function fixSay(text, kind) {
|
|
3035
3036
|
const m = $('fix-msg');
|
|
3036
|
-
m.
|
|
3037
|
+
m.innerHTML = text ? mascotStatus(text, kind === 'ok' ? 'success' : kind === 'wrong' ? 'tip' : 'error') : '';
|
|
3037
3038
|
m.className = 'fix__msg' + (kind ? ' ' + kind : '');
|
|
3038
3039
|
}
|
|
3039
3040
|
|
|
@@ -3520,7 +3521,7 @@ function fieldError(key, text) {
|
|
|
3520
3521
|
const el = document.querySelector(`[data-set="${CSS.escape(key)}"]`);
|
|
3521
3522
|
const box = el && $(el.id + '-e');
|
|
3522
3523
|
if (!box) return;
|
|
3523
|
-
box.
|
|
3524
|
+
box.innerHTML = text ? mascotStatus(text, 'validation') : '';
|
|
3524
3525
|
box.hidden = !text;
|
|
3525
3526
|
if (text) el.setAttribute('aria-invalid', 'true'); else el.removeAttribute('aria-invalid');
|
|
3526
3527
|
}
|
|
@@ -3666,6 +3667,7 @@ const fbCmd = (text) => `<div class="cmd"><code>${esc(text)}</code><button type=
|
|
|
3666
3667
|
/** Why there is no item: the one reason, in plain words. */
|
|
3667
3668
|
function fbEmpty(F) {
|
|
3668
3669
|
let body;
|
|
3670
|
+
let state = 'empty';
|
|
3669
3671
|
if (!F.enabled) {
|
|
3670
3672
|
body = `<p>Prompt feedback is off. Turn on <code>feedback.enabled</code> in Settings to start.</p>
|
|
3671
3673
|
<a class="btn" data-go="${esc(href('settings', 'user'))}">Open Settings</a>`;
|
|
@@ -3675,11 +3677,12 @@ function fbEmpty(F) {
|
|
|
3675
3677
|
body = `<p>Prompt feedback needs an observer model. Run <code>eklavya config set providers.observer <model></code>.</p>${fbCmd(FB.observerCmd)}
|
|
3676
3678
|
<p class="cmd__note">Put the model's name in place of <model>, for example claude-haiku-4-5. Setting it lets Eklavya send your prompts to that model, through your Claude Code login.</p>`;
|
|
3677
3679
|
} else if (F.failed) {
|
|
3680
|
+
state = 'error';
|
|
3678
3681
|
body = `<p>Couldn't review the last session. It will try again at a later start.</p>`;
|
|
3679
3682
|
} else {
|
|
3680
3683
|
body = `<p>Nothing to review yet. When a session ends, the next one you start reviews one of its prompts.</p>`;
|
|
3681
3684
|
}
|
|
3682
|
-
return `<div class="card fb__why" id="fb-empty">${body}</div>`;
|
|
3685
|
+
return `<div class="card fb__why" id="fb-empty"><div class="ek-mascot-message"><eklavya-mascot state="${state}" size="72"></eklavya-mascot><div>${body}</div></div></div>`;
|
|
3683
3686
|
}
|
|
3684
3687
|
|
|
3685
3688
|
function viewFeedbackDashboard() {
|
|
@@ -3807,7 +3810,7 @@ function fbDone(text) {
|
|
|
3807
3810
|
|
|
3808
3811
|
function fbFail(e) {
|
|
3809
3812
|
const m = $('fb-msg');
|
|
3810
|
-
if (m) m.
|
|
3813
|
+
if (m) m.innerHTML = mascotStatus(`Couldn't do that: ${e.message}`, 'error');
|
|
3811
3814
|
}
|
|
3812
3815
|
|
|
3813
3816
|
function fbAcknowledge(btn) {
|
|
@@ -3820,7 +3823,7 @@ function fbAcknowledge(btn) {
|
|
|
3820
3823
|
|
|
3821
3824
|
function fbAskDelete(btn) {
|
|
3822
3825
|
const id = +btn.dataset.fbDel;
|
|
3823
|
-
$('fb-actions').innerHTML = `<span class="fb__ask" role="group" aria-label="Confirm delete"><span>Delete this feedback for good?</span>
|
|
3826
|
+
$('fb-actions').innerHTML = `<span class="fb__ask" role="group" aria-label="Confirm delete"><span class="ek-mascot-status"><eklavya-mascot state="warning" variant="face" size="24"></eklavya-mascot>Delete this feedback for good?</span>
|
|
3824
3827
|
<button type="button" class="btn" data-fb-yes="${id}">Delete</button>
|
|
3825
3828
|
<button type="button" class="set__btn" data-fb-no="${id}">Cancel</button></span><p class="fb__msg" id="fb-msg" role="alert"></p>`;
|
|
3826
3829
|
document.querySelector('[data-fb-no]').focus();
|
|
@@ -3869,7 +3872,7 @@ function postJson(path, body) {
|
|
|
3869
3872
|
function saveSetting(key, change) {
|
|
3870
3873
|
const scope = CUR.page === 'project' ? 'project' : 'user';
|
|
3871
3874
|
const msg = document.querySelector(`[data-msg="${CSS.escape(key)}"]`);
|
|
3872
|
-
const say = (text, warn) => { if (msg) { msg.
|
|
3875
|
+
const say = (text, warn) => { if (msg) { msg.innerHTML = text ? mascotStatus(text, warn ? 'error' : 'loading') : ''; msg.className = warn ? 'warn' : 'here'; } };
|
|
3873
3876
|
fieldError(key, null);
|
|
3874
3877
|
say('saving…');
|
|
3875
3878
|
const gen = GEN;
|
|
@@ -3884,7 +3887,7 @@ function saveSetting(key, change) {
|
|
|
3884
3887
|
const back = (focusId && $(focusId)) || document.querySelector(`[data-set="${CSS.escape(key)}"]`);
|
|
3885
3888
|
back?.focus({ preventScroll: true });
|
|
3886
3889
|
const m = document.querySelector(`[data-msg="${CSS.escape(key)}"]`);
|
|
3887
|
-
if (m) { m.
|
|
3890
|
+
if (m) { m.innerHTML = mascotStatus(change.unset ? 'inherited' : 'saved', 'success'); m.className = 'here'; }
|
|
3888
3891
|
})
|
|
3889
3892
|
.catch((e) => {
|
|
3890
3893
|
// A refused choice or switch is put back to what is on disk, so the
|
|
@@ -4586,6 +4589,7 @@ syncDrawer();
|
|
|
4586
4589
|
|
|
4587
4590
|
function viewNotFound(what, bad) {
|
|
4588
4591
|
return `<div>
|
|
4592
|
+
<eklavya-mascot state="${bad ? 'validation' : 'notFound'}" size="80"></eklavya-mascot>
|
|
4589
4593
|
<h1 class="page__title">${bad ? 'This link is malformed' : 'No page here'}</h1>
|
|
4590
4594
|
<p class="page__sub">${bad ? 'Part of' : 'Nothing answers to'} <code class="slug">${esc(what)}</code>${bad ? ' is not valid URL encoding, so it cannot be opened.' : '.'}
|
|
4591
4595
|
Open the <a class="link" data-go="${esc(LH('dashboard'))}">Learning dashboard</a> or the
|
|
@@ -4892,7 +4896,7 @@ Promise.all([json('/api/state'), json('/api/projects')]).then(([s, inv]) => {
|
|
|
4892
4896
|
}).catch((e) => {
|
|
4893
4897
|
$('app').classList.remove('booting');
|
|
4894
4898
|
$('view').removeAttribute('aria-busy');
|
|
4895
|
-
$('view').innerHTML = `<p class="empty">Could not read the database: ${esc(e.message)}</p>`;
|
|
4899
|
+
$('view').innerHTML = `<div class="ek-mascot-message"><eklavya-mascot state="error" size="72"></eklavya-mascot><p class="empty">Could not read the database: ${esc(e.message)}</p></div>`;
|
|
4896
4900
|
});
|
|
4897
4901
|
</script>
|
|
4898
4902
|
</body>
|
|
@@ -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" data-expression="celebrate" data-variant="body" 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, celebrating</title><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><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"><path d="M64 74q5-7 10 0m36 0q5-7 10 0"/><path d="M82 87q10 22 20 0Z" fill="var(--mascot-ink)"/><path d="M87 98h10" 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></svg>
|
|
@@ -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="celebrate" 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, celebrating</title><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><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"><path d="M64 74q5-7 10 0m36 0q5-7 10 0"/><path d="M82 87q10 22 20 0Z" fill="var(--mascot-ink)"/><path d="M87 98h10" stroke="var(--mascot-coral)"/></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" data-expression="confused" data-variant="body" 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, seeking clarity</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="M83 93q5-7 10-2t9-2"/><path d="M62 58l14-5m32 4h14" fill="none"/></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></svg>
|
|
@@ -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="confused" 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, seeking clarity</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 93q5-7 10-2t9-2"/><path d="M62 58l14-5m32 4h14" 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" data-expression="curious" data-variant="body" 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, curious</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="M86 91q6-4 12 0"/><path d="M62 59h14m32-6 14 5" fill="none"/></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></svg>
|