feat: add character illustrations to dashboard

Adds four hand-drawn flat-style SVG mascots:

- recruiter.svg — woman at a laptop, paired with the workload block
- candidate.svg — smiling candidate with a resume, paired with the
  manual-overrides block
- thinker.svg — person thinking with a lightbulb thought bubble, paired
  with the simulator block
- team.svg — three teammates in a row, paired with the functions table

Adds a .block.with-mascot grid layout so each illustration sits beside
its block and collapses above the body on narrow screens. Hover scales
the figure slightly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Askarova A 2026-05-22 14:24:29 +05:00
parent ea42399925
commit 1244c52e41
6 changed files with 196 additions and 7 deletions

39
assets/candidate.svg Normal file
View File

@ -0,0 +1,39 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 220" width="100%" height="100%">
<defs>
<linearGradient id="cbg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#34d399" stop-opacity=".22"/>
<stop offset="100%" stop-color="#60a5fa" stop-opacity=".05"/>
</linearGradient>
</defs>
<circle cx="110" cy="110" r="100" fill="url(#cbg)"/>
<!-- resume sheet -->
<g transform="translate(140 100) rotate(10)">
<rect x="0" y="0" width="60" height="80" rx="4" fill="white" stroke="#cbd5e1" stroke-width="1.5"/>
<rect x="0" y="0" width="60" height="14" fill="#60a5fa" rx="4"/>
<circle cx="12" cy="22" r="4" fill="#60a5fa"/>
<line x1="20" y1="22" x2="50" y2="22" stroke="#cbd5e1" stroke-width="2" stroke-linecap="round"/>
<line x1="8" y1="34" x2="52" y2="34" stroke="#cbd5e1" stroke-width="2" stroke-linecap="round"/>
<line x1="8" y1="42" x2="46" y2="42" stroke="#cbd5e1" stroke-width="2" stroke-linecap="round"/>
<line x1="8" y1="50" x2="52" y2="50" stroke="#cbd5e1" stroke-width="2" stroke-linecap="round"/>
<line x1="8" y1="58" x2="40" y2="58" stroke="#cbd5e1" stroke-width="2" stroke-linecap="round"/>
<line x1="8" y1="66" x2="50" y2="66" stroke="#cbd5e1" stroke-width="2" stroke-linecap="round"/>
</g>
<!-- body -->
<path d="M55 200 Q55 130 95 130 Q135 130 135 200 Z" fill="#34d399"/>
<!-- tie -->
<polygon points="92,138 98,138 98,165 95,180 92,165" fill="#1e3a8a"/>
<!-- head -->
<circle cx="95" cy="90" r="30" fill="#fde68a"/>
<!-- hair -->
<path d="M65 88 Q65 56 95 56 Q125 56 125 88 Q120 68 95 68 Q70 68 65 88" fill="#451a03"/>
<!-- eyes -->
<circle cx="85" cy="92" r="2.5" fill="#1e293b"/>
<circle cx="105" cy="92" r="2.5" fill="#1e293b"/>
<!-- big smile -->
<path d="M82 104 Q95 116 108 104" stroke="#92400e" stroke-width="2.2" fill="none" stroke-linecap="round"/>
<!-- arm holding resume -->
<path d="M125 145 L150 130" stroke="#34d399" stroke-width="14" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

38
assets/recruiter.svg Normal file
View File

@ -0,0 +1,38 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" width="100%" height="100%">
<defs>
<linearGradient id="rbg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#a78bfa" stop-opacity=".25"/>
<stop offset="100%" stop-color="#60a5fa" stop-opacity=".05"/>
</linearGradient>
</defs>
<circle cx="120" cy="120" r="110" fill="url(#rbg)"/>
<!-- desk -->
<rect x="40" y="170" width="160" height="14" rx="2" fill="#94a3b8"/>
<rect x="50" y="184" width="6" height="38" fill="#64748b"/>
<rect x="184" y="184" width="6" height="38" fill="#64748b"/>
<!-- laptop -->
<rect x="80" y="140" width="80" height="50" rx="4" fill="#1e293b"/>
<rect x="86" y="146" width="68" height="38" rx="2" fill="#60a5fa"/>
<rect x="92" y="152" width="30" height="3" fill="white" opacity=".8"/>
<rect x="92" y="160" width="40" height="3" fill="white" opacity=".5"/>
<rect x="92" y="168" width="22" height="3" fill="white" opacity=".5"/>
<rect x="70" y="186" width="100" height="6" rx="2" fill="#334155"/>
<!-- person body -->
<path d="M85 150 Q85 110 120 110 Q155 110 155 150" fill="#8b5cf6"/>
<!-- head -->
<circle cx="120" cy="80" r="28" fill="#fde68a"/>
<!-- hair -->
<path d="M92 78 Q92 50 120 50 Q148 50 148 78 Q145 60 120 60 Q95 60 92 78" fill="#451a03"/>
<path d="M92 80 Q90 100 100 105 L100 85 Z" fill="#451a03"/>
<!-- eyes -->
<circle cx="110" cy="80" r="2.5" fill="#1e293b"/>
<circle cx="130" cy="80" r="2.5" fill="#1e293b"/>
<!-- smile -->
<path d="M112 92 Q120 98 128 92" stroke="#92400e" stroke-width="2" fill="none" stroke-linecap="round"/>
<!-- arm typing -->
<path d="M95 130 L80 155" stroke="#8b5cf6" stroke-width="12" stroke-linecap="round"/>
<path d="M145 130 L160 155" stroke="#8b5cf6" stroke-width="12" stroke-linecap="round"/>
<!-- speech bubble -->
<circle cx="190" cy="40" r="22" fill="white" stroke="#60a5fa" stroke-width="2"/>
<text x="190" y="46" text-anchor="middle" font-family="-apple-system, sans-serif" font-size="18" fill="#60a5fa">💼</text>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

47
assets/team.svg Normal file
View File

@ -0,0 +1,47 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 200" width="100%" height="100%">
<defs>
<linearGradient id="tbg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#34d399" stop-opacity=".15"/>
<stop offset="100%" stop-color="#60a5fa" stop-opacity=".05"/>
</linearGradient>
</defs>
<rect width="300" height="200" rx="20" fill="url(#tbg)"/>
<!-- person 1 -->
<g transform="translate(40 50)">
<circle cx="30" cy="30" r="22" fill="#fbbf24"/>
<path d="M14 28 Q14 8 30 8 Q46 8 46 28" fill="#1e293b"/>
<circle cx="24" cy="32" r="2" fill="#1e293b"/>
<circle cx="36" cy="32" r="2" fill="#1e293b"/>
<path d="M25 42 Q30 46 35 42" stroke="#92400e" stroke-width="1.5" fill="none" stroke-linecap="round"/>
<path d="M0 110 Q0 70 30 70 Q60 70 60 110 Z" fill="#60a5fa"/>
<rect x="5" y="105" width="50" height="35" fill="#60a5fa"/>
</g>
<!-- person 2 (taller) -->
<g transform="translate(110 30)">
<circle cx="30" cy="30" r="24" fill="#fde68a"/>
<path d="M12 28 Q12 4 30 4 Q48 4 48 28 Q46 14 30 14 Q14 14 12 28" fill="#451a03"/>
<circle cx="23" cy="32" r="2" fill="#1e293b"/>
<circle cx="37" cy="32" r="2" fill="#1e293b"/>
<path d="M24 44 Q30 48 36 44" stroke="#92400e" stroke-width="1.5" fill="none" stroke-linecap="round"/>
<path d="M-4 130 Q-4 80 30 80 Q64 80 64 130 Z" fill="#a78bfa"/>
<rect x="0" y="125" width="60" height="35" fill="#a78bfa"/>
<!-- folder -->
<rect x="48" y="92" width="22" height="28" rx="2" fill="#fbbf24" transform="rotate(8 60 106)"/>
</g>
<!-- person 3 -->
<g transform="translate(195 50)">
<circle cx="30" cy="30" r="22" fill="#fda4af"/>
<path d="M12 30 Q12 6 30 6 Q48 6 48 30 Q40 10 30 10 Q20 10 12 30" fill="#7c2d12"/>
<circle cx="24" cy="32" r="2" fill="#1e293b"/>
<circle cx="36" cy="32" r="2" fill="#1e293b"/>
<path d="M25 42 Q30 46 35 42" stroke="#92400e" stroke-width="1.5" fill="none" stroke-linecap="round"/>
<path d="M0 110 Q0 70 30 70 Q60 70 60 110 Z" fill="#34d399"/>
<rect x="5" y="105" width="50" height="35" fill="#34d399"/>
</g>
<!-- floor shadow -->
<ellipse cx="150" cy="186" rx="120" ry="6" fill="black" opacity=".08"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

41
assets/thinker.svg Normal file
View File

@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 220" width="100%" height="100%">
<defs>
<radialGradient id="thbg" cx="50%" cy="50%" r="60%">
<stop offset="0%" stop-color="#fbbf24" stop-opacity=".2"/>
<stop offset="100%" stop-color="#f87171" stop-opacity="0"/>
</radialGradient>
</defs>
<rect width="220" height="220" fill="url(#thbg)"/>
<!-- thought bubbles -->
<circle cx="155" cy="35" r="6" fill="white" stroke="#94a3b8" stroke-width="1.5"/>
<circle cx="170" cy="55" r="9" fill="white" stroke="#94a3b8" stroke-width="1.5"/>
<circle cx="195" cy="80" r="22" fill="white" stroke="#94a3b8" stroke-width="2"/>
<text x="195" y="88" text-anchor="middle" font-family="-apple-system, sans-serif" font-size="20">💡</text>
<!-- chair -->
<rect x="50" y="170" width="100" height="14" rx="2" fill="#64748b"/>
<rect x="55" y="184" width="6" height="30" fill="#64748b"/>
<rect x="139" y="184" width="6" height="30" fill="#64748b"/>
<!-- body -->
<path d="M65 170 Q65 110 105 110 Q145 110 145 170 Z" fill="#3b82f6"/>
<!-- legs -->
<rect x="78" y="160" width="14" height="40" rx="4" fill="#1e293b"/>
<rect x="118" y="160" width="14" height="40" rx="4" fill="#1e293b"/>
<!-- head -->
<circle cx="105" cy="85" r="28" fill="#fde68a"/>
<!-- hair -->
<path d="M77 85 Q77 55 105 55 Q133 55 133 85 Q128 65 105 65 Q82 65 77 85" fill="#1e293b"/>
<!-- eyes -->
<circle cx="95" cy="86" r="2.5" fill="#1e293b"/>
<circle cx="115" cy="86" r="2.5" fill="#1e293b"/>
<!-- thinking mouth -->
<path d="M97 100 L113 100" stroke="#92400e" stroke-width="2" stroke-linecap="round"/>
<!-- chin hand -->
<path d="M115 105 L130 95" stroke="#fde68a" stroke-width="10" stroke-linecap="round"/>
<circle cx="130" cy="95" r="7" fill="#fde68a"/>
<!-- arm -->
<path d="M132 110 L150 90" stroke="#3b82f6" stroke-width="14" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -51,12 +51,17 @@
<div id="strategic-kpi" class="kpi-grid"></div>
</section>
<section class="block">
<h2>👤 Нагрузка на сотрудника (дни и недели)</h2>
<div id="workload-kpi" class="kpi-grid"></div>
<section class="block with-mascot">
<img src="./assets/recruiter.svg" alt="" class="mascot" loading="lazy">
<div class="block-body">
<h2>👤 Нагрузка на сотрудника (дни и недели)</h2>
<div id="workload-kpi" class="kpi-grid"></div>
</div>
</section>
<section class="block">
<section class="block with-mascot mascot-right">
<img src="./assets/candidate.svg" alt="" class="mascot" loading="lazy">
<div class="block-body">
<h2>✏️ Ручные правки времени (без изменения xlsx)</h2>
<p class="hint">Заменить минуты конкретной функции по №. Влияет на все KPI и графики. Пусто = убрать правку.</p>
<div class="ovr-form">
@ -66,9 +71,12 @@
<button type="button" id="ovr-clear" class="secondary">Очистить все</button>
</div>
<div id="ovr-list" class="ovr-list"></div>
</div>
</section>
<section class="block">
<section class="block with-mascot">
<img src="./assets/thinker.svg" alt="" class="mascot" loading="lazy">
<div class="block-body">
<h2>🧪 Симулятор «что если»</h2>
<p class="hint">Не меняет исходные данные. Покажет, как изменятся KPI при заданных условиях.</p>
<label class="sim-toggle"><input type="checkbox" id="sim-toggle"> Включить симулятор</label>
@ -101,6 +109,7 @@
</div>
<div id="sim-output" hidden></div>
</div>
</section>
<section class="block">
@ -142,7 +151,9 @@
<div id="chart-treemap"></div>
</section>
<section class="block">
<section class="block with-mascot mascot-right mascot-wide">
<img src="./assets/team.svg" alt="" class="mascot" loading="lazy">
<div class="block-body">
<h2>Все функции <span class="hint">(клик по заголовку — сортировка)</span></h2>
<table id="table">
<thead><tr>
@ -155,6 +166,7 @@
</tr></thead>
<tbody></tbody>
</table>
</div>
</section>
</main>
@ -163,6 +175,6 @@
<p>Загрузка данных… Если дашборд не появился — нажмите «📁 Загрузить .xlsx».</p>
</div>
<script src="./app.js?v=10"></script>
<script src="./app.js?v=11"></script>
</body>
</html>

View File

@ -75,6 +75,18 @@ main { padding: 1.5rem 2rem; display: flex; flex-direction: column; gap: 1.25rem
transition: box-shadow .2s;
}
.block:hover { box-shadow: 0 4px 18px rgba(96,165,250,0.10); }
.block.with-mascot { display: grid; grid-template-columns: 130px 1fr; gap: 1.2rem; align-items: start; }
.block.with-mascot.mascot-right { grid-template-columns: 1fr 130px; }
.block.with-mascot.mascot-right .mascot { order: 2; }
.block.with-mascot.mascot-wide { grid-template-columns: 1fr 180px; }
.block .mascot { width: 100%; max-width: 150px; align-self: center; opacity: .95; transition: transform .25s ease; }
.block .mascot:hover { transform: scale(1.06) rotate(-2deg); }
.block .block-body { min-width: 0; }
@media (max-width: 760px) {
.block.with-mascot, .block.with-mascot.mascot-right, .block.with-mascot.mascot-wide { grid-template-columns: 1fr; }
.block.with-mascot .mascot { max-width: 110px; justify-self: end; order: 0 !important; }
}
.block h2 {
margin: 0 0 1rem;
font-size: 1.05rem; font-weight: 700;