diff --git a/dashboard-mockup.html b/dashboard-mockup.html
index cc25cb8..61013c1 100644
--- a/dashboard-mockup.html
+++ b/dashboard-mockup.html
@@ -11,7 +11,6 @@
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
- /* IBM Carbon Design System — Dark 10 */
--bg: #0a0a0a;
--surface: #161616;
--surface-hover: #1f1f1f;
@@ -22,17 +21,14 @@
--text-tertiary: #6f6f6f;
--accent: #0f6fff;
--accent-hover: #3d8eff;
- --accent-dim: rgba(15,111,255,0.1);
--green: #16a34a;
--green-dim: rgba(22,163,74,0.12);
--red: #e21d1d;
--red-dim: rgba(226,29,29,0.12);
--yellow: #eac518;
- --yellow-dim: rgba(234,197,24,0.12);
--mono: 'IBM Plex Mono', monospace;
--sans: 'IBM Plex Sans', -apple-system, sans-serif;
- --ease-out: cubic-bezier(0.2, 0, 0, 1);
- --ease-standard: cubic-bezier(0.2, 0, 0, 1);
+ --ease: cubic-bezier(0.2, 0, 0, 1);
}
html { font-size: 14px; }
@@ -43,13 +39,10 @@ body {
color: var(--text-primary);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
}
-/* ============================================
- TOP BAR — IBM style
- ============================================ */
+/* TOP BAR */
.topbar {
display: flex; align-items: center; justify-content: space-between;
padding: 0 32px; height: 48px;
@@ -59,8 +52,7 @@ body {
}
.topbar-brand {
font-family: var(--mono); font-size: 13px; font-weight: 600;
- letter-spacing: 0.5px; color: var(--text-primary);
- display: flex; align-items: center; gap: 10px;
+ letter-spacing: 0.5px; display: flex; align-items: center; gap: 10px;
}
.topbar-brand .icon {
width: 20px; height: 20px; background: var(--accent);
@@ -69,33 +61,23 @@ body {
}
.topbar-right { display: flex; align-items: center; gap: 20px; font-size: 12px; }
.status { display: flex; align-items: center; gap: 6px; color: var(--green); font-weight: 500; }
-.status-dot { width: 6px; height: 6px; background: var(--green); border-radius: 0; animation: blink 2s ease-in-out infinite; }
+.status-dot { width: 6px; height: 6px; background: var(--green); animation: blink 2s ease-in-out infinite; }
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.4} }
.clock { font-family: var(--mono); color: var(--text-tertiary); font-size: 11px; letter-spacing: 0.3px; }
-/* ============================================
- PAGE HEADER — IBM heading style
- ============================================ */
+/* PAGE HEADER */
.page-header {
max-width: 1280px; margin: 0 auto;
padding: 32px 32px 0;
}
.page-title {
- font-family: var(--sans);
font-size: 28px; font-weight: 700;
- letter-spacing: -0.5px;
- line-height: 1.2;
- color: var(--text-primary);
- margin-bottom: 8px;
-}
-.page-desc {
- font-size: 14px; color: var(--text-secondary);
- font-weight: 400; line-height: 1.5;
+ letter-spacing: -0.5px; line-height: 1.2;
+ color: var(--text-primary); margin-bottom: 8px;
}
+.page-desc { font-size: 14px; color: var(--text-secondary); font-weight: 400; }
-/* ============================================
- IBM TABS — Horizontal navigation tabs
- ============================================ */
+/* TABS */
.tabs-section {
max-width: 1280px; margin: 0 auto;
padding: 24px 32px 0;
@@ -103,52 +85,31 @@ body {
.tabs {
display: flex; align-items: center; gap: 0;
border-bottom: 1px solid var(--border-strong);
- overflow-x: auto;
- scrollbar-width: none;
+ overflow-x: auto; scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab-btn {
- padding: 12px 20px;
- border: none; background: transparent;
- color: var(--text-secondary);
- font-family: var(--sans);
- font-size: 14px;
- font-weight: 600;
- cursor: pointer;
- position: relative;
- white-space: nowrap;
- transition: color 0.15s var(--ease-standard);
+ padding: 12px 20px; border: none; background: transparent;
+ color: var(--text-secondary); font-family: var(--sans);
+ font-size: 14px; font-weight: 600; cursor: pointer;
+ position: relative; white-space: nowrap;
+ transition: color 0.15s var(--ease);
}
.tab-btn:hover { color: var(--text-primary); }
.tab-btn::after {
- content: '';
- position: absolute;
- bottom: -1px; left: 0; right: 0;
- height: 2px;
- background: var(--accent);
- transform: scaleX(0);
- transform-origin: left;
- transition: transform 0.2s var(--ease-standard);
-}
-.tab-btn.active {
- color: var(--accent);
-}
-.tab-btn.active::after {
- transform: scaleX(1);
+ content: ''; position: absolute;
+ bottom: -1px; left: 0; right: 0; height: 2px;
+ background: var(--accent); transform: scaleX(0);
+ transform-origin: left; transition: transform 0.2s var(--ease);
}
+.tab-btn.active { color: var(--accent); }
+.tab-btn.active::after { transform: scaleX(1); }
.tab-label { display: flex; align-items: center; gap: 8px; }
-.tab-icon {
- width: 16px; height: 16px; border-radius: 0;
- display: inline-flex; align-items: center; justify-content: center;
- font-size: 13px;
-}
-/* ============================================
- KPI GRID — IBM cards, brutal
- ============================================ */
+/* KPI GRID — IBM Carbon style: single border around all */
.kpi-grid {
display: grid; grid-template-columns: repeat(6, 1fr);
- gap: 1px; padding: 24px 32px; max-width: 1280px; margin: 0 auto;
+ gap: 1px; padding: 0; max-width: 1280px; margin: 0 auto;
background: var(--border); border: 1px solid var(--border);
border-radius: 4px; overflow: hidden;
}
@@ -174,49 +135,37 @@ body {
}
.kpi-badge.up { color: var(--green); background: var(--green-dim); }
.kpi-badge.down { color: var(--red); background: var(--red-dim); }
-.kpi-badge.neutral { color: var(--accent); background: var(--accent-dim); }
+.kpi-badge.neutral { color: var(--accent); background: rgba(15,111,255,0.1); }
.kpi-value {
font-family: var(--mono);
font-size: 24px; font-weight: 600;
- letter-spacing: -0.5px;
- line-height: 1.1;
- color: var(--text-primary);
- margin-bottom: 8px;
+ letter-spacing: -0.5px; line-height: 1.1;
+ color: var(--text-primary); margin-bottom: 8px;
}
.kpi-context { font-size: 11px; color: var(--text-tertiary); }
-/* ============================================
- CHART PANELS — IBM structure
- ============================================ */
+/* CHARTS WRAPPER */
.charts-wrap {
max-width: 1280px; margin: 0 auto;
padding: 0 32px 32px;
+}
+.charts-grid {
display: grid; gap: 1px;
background: var(--border);
border: 1px solid var(--border);
border-radius: 4px; overflow: hidden;
}
-.charts-row-main { grid-template-columns: 2fr 1fr; }
-.charts-row-sub { grid-template-columns: 1fr 1fr 1fr; }
-
.chart-panel {
background: var(--surface);
padding: 24px;
- min-height: 280px;
}
.chart-header { margin-bottom: 20px; }
-.chart-title {
- font-size: 14px; font-weight: 600;
- color: var(--text-primary);
- margin-bottom: 4px;
-}
-.chart-subtitle {
- font-size: 12px; color: var(--text-tertiary);
-}
+.chart-title { font-size: 14px; font-weight: 600; color: var(--text-primary); margin-bottom: 4px; }
+.chart-subtitle { font-size: 12px; color: var(--text-tertiary); }
.chart-canvas { width: 100%; height: 260px; }
.chart-canvas.small { height: 200px; }
-/* Metric list inside chart panel */
+/* Metric list */
.metric-list { display: flex; flex-direction: column; gap: 0; margin-top: 8px; }
.metric-item {
display: flex; align-items: center;
@@ -224,75 +173,62 @@ body {
border-bottom: 1px solid var(--border);
}
.metric-item:last-child { border-bottom: none; }
-.metric-item:first-child { padding-top: 0; }
-.metric-item:last-child { padding-bottom: 0; }
-.m-label { font-size: 12px; color: var(--text-secondary); flex: 1; font-weight: 400; }
+.m-label { font-size: 12px; color: var(--text-secondary); flex: 1; }
.m-value { font-family: var(--mono); font-size: 13px; font-weight: 600; color: var(--text-primary); margin: 0 16px; min-width: 64px; text-align: right; }
-.m-bar { width: 80px; height: 4px; background: var(--border-strong); border-radius: 0; overflow: hidden; }
-.m-fill { height: 100%; width: 0; transition: width 0.8s var(--ease-standard); }
+.m-bar { width: 80px; height: 4px; background: var(--border-strong); overflow: hidden; }
+.m-fill { height: 100%; width: 0; transition: width 0.8s var(--ease); }
.m-fill.green { background: var(--green); }
.m-fill.red { background: var(--red); }
.m-fill.yellow { background: var(--yellow); }
.m-fill.blue { background: var(--accent); }
-/* ============================================
- RESPONSIVE
- ============================================ */
+/* RESPONSIVE */
@media(max-width: 1024px) {
.kpi-grid { grid-template-columns: repeat(3, 1fr); }
- .charts-row-main, .charts-row-sub { grid-template-columns: 1fr; }
- .page-title { font-size: 22px; }
}
@media(max-width: 640px) {
.kpi-grid { grid-template-columns: repeat(2, 1fr); }
.topbar { padding: 0 16px; }
- .page-header, .tabs-section, .kpi-grid, .charts-wrap { padding-left: 16px; padding-right: 16px; }
+ .page-header, .tabs-section, .charts-wrap { padding-left: 16px; padding-right: 16px; }
.tab-btn { padding: 12px 14px; font-size: 13px; }
}
-
${d.n}
- ${arrow} ${d.d}
+ ${arrow} ${d.d}
${d.v}
${d.p}
@@ -438,49 +373,32 @@ function seg(key) {
}).join('');
requestAnimationFrame(() => g.querySelectorAll('.kpi').forEach(c => c.classList.add('visible')));
- // Main chart area
- const mcDom = document.getElementById('charts-main');
- const mc = s.mc;
- if (!charts.mc) charts.mc = echarts.init(document.createElement('div'));
- mcDom.innerHTML = `
-
`;
-
// Main chart
+ document.getElementById('mc-title').textContent = s.mc.title;
+ document.getElementById('mc-sub').textContent = s.mc.sub;
const mcEl = document.getElementById('mc-canvas');
- charts.mc.dispose();
- charts.mc = echarts.init(mcEl);
+ if (!charts.mc) charts.mc = echarts.init(mcEl);
+ charts.mc.clear();
let mcOpt;
- if (mc.type === 'dual') {
+ if (s.mc.type === 'dual') {
mcOpt = {
- tooltip: { trigger: 'axis', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 12, fontFamily: 'IBM Plex Mono' } },
- legend: { data: ['Доход', 'Расход'], textStyle: { color: '#6f6f6f', fontSize: 11, fontFamily: 'IBM Plex Sans' }, top: 0, itemWidth: 12, itemHeight: 2 },
+ tooltip: { trigger: 'axis', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 12 } },
+ legend: { data: ['Доход', 'Расход'], textStyle: { color: '#6f6f6f', fontSize: 11 }, top: 0, itemWidth: 12, itemHeight: 2 },
grid: { left: 48, right: 20, top: 32, bottom: 28 },
xAxis: {
- type: 'category', data: mc.labels,
+ type: 'category', data: s.mc.labels,
axisLine: { lineStyle: { color: '#262626' } },
- axisLabel: { color: '#6f6f6f', fontSize: 11, fontFamily: 'IBM Plex Mono' },
+ axisLabel: { color: '#6f6f6f', fontSize: 11 },
axisTick: { show: false },
},
yAxis: {
type: 'value',
splitLine: { lineStyle: { color: '#1f1f1f' } },
- axisLabel: { color: '#6f6f6f', fontSize: 11, fontFamily: 'IBM Plex Mono', formatter: '{value}М' },
+ axisLabel: { color: '#6f6f6f', fontSize: 11, formatter: '{value}М' },
},
series: [
{
- name: 'Доход', type: 'line', data: mc.rev, smooth: 0.4, symbol: 'none',
+ name: 'Доход', type: 'line', data: s.mc.rev, smooth: 0.4, symbol: 'none',
lineStyle: { width: 2.5, color: '#16a34a' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
@@ -490,58 +408,54 @@ function seg(key) {
}
},
{
- name: 'Расход', type: 'line', data: mc.ads, smooth: 0.4, symbol: 'none',
+ name: 'Расход', type: 'line', data: s.mc.ads, smooth: 0.4, symbol: 'none',
lineStyle: { width: 1.5, color: '#0f6fff', type: 'dashed' },
},
],
animationDuration: 500,
- animationEasing: 'cubicOut',
};
} else {
mcOpt = {
- tooltip: { trigger: 'axis', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 12, fontFamily: 'IBM Plex Mono' } },
- legend: { data: ['План', 'Факт'], textStyle: { color: '#6f6f6f', fontSize: 11, fontFamily: 'IBM Plex Sans' }, top: 0, itemWidth: 12, itemHeight: 14 },
+ tooltip: { trigger: 'axis', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 12 } },
+ legend: { data: ['План', 'Факт'], textStyle: { color: '#6f6f6f', fontSize: 11 }, top: 0, itemWidth: 12, itemHeight: 14 },
grid: { left: 48, right: 20, top: 32, bottom: 28 },
xAxis: {
- type: 'category', data: mc.labels,
+ type: 'category', data: s.mc.labels,
axisLine: { lineStyle: { color: '#262626' } },
- axisLabel: { color: '#6f6f6f', fontSize: 11, fontFamily: 'IBM Plex Mono' },
+ axisLabel: { color: '#6f6f6f', fontSize: 11 },
},
yAxis: {
type: 'value',
splitLine: { lineStyle: { color: '#1f1f1f' } },
- axisLabel: { color: '#6f6f6f', fontSize: 11, fontFamily: 'IBM Plex Mono', formatter: '{value}К' },
+ axisLabel: { color: '#6f6f6f', fontSize: 11, formatter: '{value}К' },
},
series: [
- { name: 'План', type: 'bar', data: mc.plan, barWidth: '30%', itemStyle: { color: 'rgba(15,111,255,0.1)' } },
- { name: 'Факт', type: 'bar', data: mc.fact, barWidth: '30%', itemStyle: { color: '#0f6fff' } },
+ { name: 'План', type: 'bar', data: s.mc.plan, barWidth: '30%', itemStyle: { color: 'rgba(15,111,255,0.1)' } },
+ { name: 'Факт', type: 'bar', data: s.mc.fact, barWidth: '30%', itemStyle: { color: '#0f6fff' } },
],
animationDuration: 500,
- animationEasing: 'cubicOut',
};
}
- charts.mc.setOption(mcOpt, true);
+ charts.mc.setOption(mcOpt);
// Side chart
- const sc = s.sc;
- const scEl = document.getElementById('sc-canvas');
- if (!charts.sc) charts.sc = echarts.init(scEl);
- charts.sc.dispose();
- charts.sc = echarts.init(scEl);
- const scData = sc.data.reverse();
+ document.getElementById('sc-title').textContent = s.sc.title;
+ document.getElementById('sc-sub').textContent = s.sc.sub;
+ const scData = s.sc.data.slice().reverse();
const scColors = ['#0f6fff', '#3d8eff', '#16a34a', '#06b6d4', '#a855f7', '#eac518'];
- const scOpt = {
- tooltip: { trigger: 'axis', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 11 } },
+ if (!charts.sc) charts.sc = echarts.init(document.getElementById('sc-canvas'));
+ charts.sc.clear();
+ charts.sc.setOption({
grid: { left: 90, right: 28, top: 8, bottom: 8 },
xAxis: {
type: 'value',
splitLine: { lineStyle: { color: '#1f1f1f' } },
- axisLabel: { color: '#6f6f6f', fontSize: 10, fontFamily: 'IBM Plex Mono', formatter: '{value}%' },
+ axisLabel: { color: '#6f6f6f', fontSize: 10, formatter: '{value}%' },
},
yAxis: {
type: 'category', data: scData.map(d => d.n),
axisLine: { lineStyle: { color: '#262626' } },
- axisLabel: { color: '#a6a6a6', fontSize: 11, fontFamily: 'IBM Plex Sans' },
+ axisLabel: { color: '#a6a6a6', fontSize: 11 },
},
series: [{
type: 'bar', data: scData.map((d, i) => ({
@@ -556,120 +470,66 @@ function seg(key) {
barWidth: 28,
}],
animationDuration: 500,
- };
- charts.sc.setOption(scOpt, true);
-
- // Sub row charts
- const subEl = document.getElementById('charts-sub');
- const c1 = s.c1;
- const c2 = s.c2;
-
- // Funnel chart
- let funnelHtml = `
`;
-
- // Second chart
- let secondHtml;
- if (c2.labels) {
- const isPie = c2.data.length && c2.data.reduce((a, b) => a + b, 0) <= 100;
- const pc = ['#0f6fff', '#16a34a', '#eac518', '#a855f7', '#e21d1d'];
- if (isPie) {
- secondHtml = `
`;
- } else {
- secondHtml = `
`;
- }
- }
-
- // Metrics panel
- const metricsHtml = `
`;
-
- subEl.innerHTML = funnelHtml + secondHtml + metricsHtml;
+ });
// Funnel
+ document.getElementById('c1-title').textContent = s.c1.title;
+ document.getElementById('c1-sub').textContent = s.c1.sub;
const fc = ['#0f6fff', '#3d8eff', '#16a34a', '#e21d1d'];
- const c1Dom = document.getElementById('c1-canvas');
- if (!charts.c1) charts.c1 = echarts.init(c1Dom);
- charts.c1.dispose();
- charts.c1 = echarts.init(c1Dom);
+ if (!charts.c1) charts.c1 = echarts.init(document.getElementById('c1-canvas'));
+ charts.c1.clear();
charts.c1.setOption({
- tooltip: { trigger: 'item', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 11 } },
series: [{
- type: 'funnel', left: '8%', bottom: 5, width: '84%', min: 0, max: c1.data[0].v,
+ type: 'funnel', left: '8%', bottom: 5, width: '84%', min: 0, max: s.c1.data[0].v,
minSize: '0%', maxSize: '100%', sort: 'descending', gap: 2,
- label: { show: true, position: 'inside', color: '#f4f4f4', fontSize: 11, fontFamily: 'IBM Plex Mono', formatter: '{b}\n{d}%' },
+ label: { show: true, position: 'inside', color: '#f4f4f4', fontSize: 11, formatter: '{b}\n{d}%' },
labelLine: { show: false },
itemStyle: { borderColor: '#161616', borderWidth: 2 },
- data: c1.data.map((d, i) => ({ name: d.n, value: d.v, itemStyle: { color: fc[i % fc.length] } })),
+ data: s.c1.data.map((d, i) => ({ name: d.n, value: d.v, itemStyle: { color: fc[i % fc.length] } })),
}],
animationDuration: 500,
- }, true);
+ });
// Second chart
- const c2Dom = document.getElementById('c2-canvas');
- if (!charts.c2) charts.c2 = echarts.init(c2Dom);
- charts.c2.dispose();
- charts.c2 = echarts.init(c2Dom);
- if (c2.labels) {
- const isPie = c2.data.length && c2.data.reduce((a, b) => a + b, 0) <= 100;
- const pc = ['#0f6fff', '#16a34a', '#eac518', '#a855f7', '#e21d1d'];
- if (isPie) {
- charts.c2.setOption({
- tooltip: { trigger: 'item', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 11 } },
- series: [{
- type: 'pie', radius: ['40%', '65%'], center: ['50%', '55%'],
- itemStyle: { borderRadius: 0, borderColor: '#161616', borderWidth: 2 },
- label: { show: false }, labelLine: { show: false },
- data: c2.labels.map((l, i) => ({ name: l, value: c2.data[i], itemStyle: { color: pc[i % pc.length] } })),
- }],
- animationDuration: 500,
- }, true);
- } else {
- charts.c2.setOption({
- tooltip: { trigger: 'axis', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 11 } },
- grid: { left: 60, right: 16, top: 8, bottom: 28 },
- xAxis: {
- type: 'value',
- splitLine: { lineStyle: { color: '#1f1f1f' } },
- axisLabel: { color: '#6f6f6f', fontSize: 10, formatter: '{value}%' },
- },
- yAxis: {
- type: 'category', data: c2.labels,
- axisLine: { lineStyle: { color: '#262626' } },
- axisLabel: { color: '#a6a6a6', fontSize: 11, fontFamily: 'IBM Plex Sans' },
- },
- series: [{
- type: 'bar', data: c2.data.map(v => ({
- value: v,
- itemStyle: { color: v >= 100 ? '#16a34a' : '#eac518' }
- })),
- barWidth: 24,
- }],
- animationDuration: 500,
- }, true);
- }
+ document.getElementById('c2-title').textContent = s.c2.title;
+ document.getElementById('c2-sub').textContent = s.c2.sub;
+ const c2 = s.c2;
+ const isPie = c2.labels && c2.data.reduce((a, b) => a + b, 0) <= 100;
+ const pc = ['#0f6fff', '#16a34a', '#eac518', '#a855f7', '#e21d1d'];
+ if (!charts.c2) charts.c2 = echarts.init(document.getElementById('c2-canvas'));
+ charts.c2.clear();
+ if (isPie) {
+ charts.c2.setOption({
+ series: [{
+ type: 'pie', radius: ['40%', '65%'], center: ['50%', '55%'],
+ itemStyle: { borderRadius: 0, borderColor: '#161616', borderWidth: 2 },
+ label: { show: false }, labelLine: { show: false },
+ data: c2.labels.map((l, i) => ({ name: l, value: c2.data[i], itemStyle: { color: pc[i % pc.length] } })),
+ }],
+ animationDuration: 500,
+ });
+ } else {
+ charts.c2.setOption({
+ grid: { left: 60, right: 16, top: 8, bottom: 28 },
+ xAxis: {
+ type: 'value',
+ splitLine: { lineStyle: { color: '#1f1f1f' } },
+ axisLabel: { color: '#6f6f6f', fontSize: 10, formatter: '{value}%' },
+ },
+ yAxis: {
+ type: 'category', data: c2.labels,
+ axisLine: { lineStyle: { color: '#262626' } },
+ axisLabel: { color: '#a6a6a6', fontSize: 11 },
+ },
+ series: [{
+ type: 'bar', data: c2.data.map(v => ({
+ value: v,
+ itemStyle: { color: v >= 100 ? '#16a34a' : '#eac518' }
+ })),
+ barWidth: 24,
+ }],
+ animationDuration: 500,
+ });
}
// Metrics list
@@ -699,7 +559,7 @@ document.addEventListener('DOMContentLoaded', () => {
String(n.getHours()).padStart(2, '0') + ':' + String(n.getMinutes()).padStart(2, '0') + ':' + String(n.getSeconds()).padStart(2, '0');
}
tick(); setInterval(tick, 1000);
- new ResizeObserver(() => Object.values(charts).forEach(c => c.resize())).observe(document.getElementById('charts-wrap'));
+ new ResizeObserver(() => Object.values(charts).forEach(c => c.resize())).observe(document.querySelector('.charts-grid'));
});