diff --git a/dashboard-mockup.html b/dashboard-mockup.html index eeeca43..1b3e070 100644 --- a/dashboard-mockup.html +++ b/dashboard-mockup.html @@ -67,7 +67,7 @@ body { /* PAGE HEADER */ .page-header { max-width: 1280px; margin: 0 auto; - padding: 32px 32px 0; + padding: 40px 32px 0; } .page-title { font-size: 28px; font-weight: 700; @@ -79,12 +79,13 @@ body { /* TABS */ .tabs-section { max-width: 1280px; margin: 0 auto; - padding: 24px 32px 0; + padding: 32px 32px 0; } .tabs { display: flex; align-items: center; gap: 0; border-bottom: 1px solid var(--border-strong); overflow-x: auto; scrollbar-width: none; + margin-bottom: 28px; } .tabs::-webkit-scrollbar { display: none; } .tab-btn { @@ -108,50 +109,59 @@ body { /* KPI GRID — Bloomberg ticker style */ .kpi-grid { display: grid; grid-template-columns: repeat(6, 1fr); - gap: 10px; padding: 0; + gap: 14px; padding: 0; max-width: 1280px; margin: 0 auto; } .kpi { - background: #121212; - border: 1px solid #2a2a2a; - border-radius: 6px; - padding: 14px 16px; + background: #0e0e0e; + border: 1px solid #1e1e1e; + border-radius: 8px; + padding: 16px 18px 14px; position: relative; - transition: all 0.2s var(--ease); + display: flex; flex-direction: column; + gap: 6px; + transition: all 0.25s var(--ease); opacity: 0; transform: translateY(8px); } .kpi.visible { opacity: 1; transform: translateY(0); } .kpi:hover { - border-color: #3a3a3a; - background: #181818; - transform: translateY(-1px); + border-color: #2e2e2e; + background: #131313; + transform: translateY(-2px); + box-shadow: 0 4px 20px rgba(0,0,0,0.3); } -.kpi-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; } +.kpi-top { display: flex; align-items: center; justify-content: space-between; } .kpi-name { font-size: 10px; font-weight: 600; - text-transform: uppercase; letter-spacing: 0.08em; - color: #71717a; + text-transform: uppercase; letter-spacing: 0.1em; + color: #666; } .kpi-badge { font-family: var(--mono); font-size: 10px; font-weight: 600; - padding: 2px 6px; border-radius: 3px; + padding: 2px 7px; border-radius: 4px; display: inline-flex; align-items: center; gap: 2px; + letter-spacing: 0; } -.kpi-badge.up { color: #22c55e; background: rgba(34,197,94,0.1); } -.kpi-badge.down { color: #ef4444; background: rgba(239,68,68,0.1); } -.kpi-badge.neutral { color: #3b82f6; background: rgba(59,130,246,0.1); } +.kpi-badge.up { color: #22c55e; background: rgba(34,197,94,0.08); } +.kpi-badge.down { color: #ef4444; background: rgba(239,68,68,0.08); } +.kpi-badge.neutral { color: #3b82f6; background: rgba(59,130,246,0.08); } .kpi-value { font-family: var(--mono); - font-size: 22px; font-weight: 700; + font-size: 24px; font-weight: 700; letter-spacing: -0.5px; line-height: 1.1; - color: #f4f4f4; margin-bottom: 4px; + color: #f4f4f4; } -.kpi-context { font-size: 10px; color: #52525b; } +.sparkline-wrap { + width: 100%; + height: 40px; + flex-shrink: 0; +} +.kpi-context { font-size: 10px; color: #4a4a4a; line-height: 1.4; } /* CHARTS WRAPPER */ .charts-wrap { max-width: 1280px; margin: 0 auto; - padding: 0 32px 32px; + padding: 28px 32px 32px; } .charts-grid { display: grid; gap: 1px; @@ -240,12 +250,12 @@ body { const D = { ecom: { kpis: [ - { n:'Выручка', v:'4.2М ₽', d:'+12%', t:'up', p:'vs прошл. мес' }, - { n:'AOV', v:'4 820 ₽', d:'+3%', t:'up', p:'ср. чек' }, - { n:'CVR', v:'3.8%', d:'+0.5%', t:'up', p:'клики→покупка' }, - { n:'CAC', v:'680 ₽', d:'−8%', t:'down', p:'стоим. клиента' }, - { n:'CLV', v:'12 400 ₽', d:'+18%', t:'up', p:'за 6 мес' }, - { n:'ROAS', v:'4.2×', d:'+15%', t:'up', p:'возврат рекламы' }, + { n:'Выручка', v:'4.2М ₽', d:'+12%', t:'up', p:'vs прошл. мес', spark:[2.8,3.1,2.9,3.4,3.6,3.2,3.8,4.0,3.9,4.2,3.7,4.2] }, + { n:'AOV', v:'4 820 ₽', d:'+3%', t:'up', p:'ср. чек', spark:[4200,4500,4350,4600,4700,4550,4800,4750,4650,4820,4700,4820] }, + { n:'CVR', v:'3.8%', d:'+0.5%', t:'up', p:'клики→покупка', spark:[3.2,3.3,3.1,3.4,3.5,3.3,3.6,3.7,3.5,3.8,3.6,3.8] }, + { n:'CAC', v:'680 ₽', d:'−8%', t:'down', p:'стоим. клиента', spark:[800,750,780,720,700,730,710,690,670,680,690,680] }, + { n:'CLV', v:'12 400 ₽', d:'+18%', t:'up', p:'за 6 мес', spark:[10000,10500,10200,11000,11200,10800,11500,11800,12000,12200,12100,12400] }, + { n:'ROAS', v:'4.2×', d:'+15%', t:'up', p:'возврат рекламы', spark:[3.5,3.6,3.7,3.8,3.9,3.7,4.0,4.1,4.0,4.2,4.1,4.2] }, ], mc: { title:'Выручка и расходы на рекламу', sub:'12 месяцев · выручка (зелёный) vs маркетинг-бюджет (пунктир)', type:'dual', labels:['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'], @@ -278,12 +288,12 @@ const D = { }, services: { kpis: [ - { n:'Пайплайн', v:'12.8М ₽', d:'+18%', t:'up', p:'открытых сделок' }, - { n:'Win Rate', v:'24%', d:'+3%', t:'up', p:'закрыто/всего' }, - { n:'Sales Cycle', v:'32 дн', d:'−5%', t:'down', p:'длительность' }, - { n:'Pipeline Cov.', v:'4.1×', d:'+0.6', t:'up', p:'покрытие квоты' }, - { n:'CPL', v:'2 400 ₽', d:'−12%', t:'down', p:'стоимость лида' }, - { n:'ROMI', v:'245%', d:'+35%', t:'up', p:'маркетинг ROI' }, + { n:'Пайплайн', v:'12.8М ₽', d:'+18%', t:'up', p:'открытых сделок', spark:[6.2,7.0,7.5,8.1,8.8,9.2,9.8,10.5,11.0,11.5,12.2,12.8] }, + { n:'Win Rate', v:'24%', d:'+3%', t:'up', p:'закрыто/всего', spark:[20,21,22,21,22,23,22,23,24,23,24,24] }, + { n:'Sales Cycle', v:'32 дн', d:'−5%', t:'down', p:'длительность', spark:[35,34,34,33,34,33,33,32,33,32,33,32] }, + { n:'Pipeline Cov.', v:'4.1×', d:'+0.6', t:'up', p:'покрытие квоты', spark:[3.2,3.4,3.5,3.6,3.7,3.8,3.9,3.9,4.0,4.0,4.1,4.1] }, + { n:'CPL', v:'2 400 ₽', d:'−12%', t:'down', p:'стоимость лида', spark:[3200,3000,3100,2900,2800,2900,2700,2600,2500,2400,2500,2400] }, + { n:'ROMI', v:'245%', d:'+35%', t:'up', p:'маркетинг ROI', spark:[200,210,220,225,230,228,235,240,238,245,242,245] }, ], mc: { title:'Пайплайн и закрытые сделки', sub:'12 месяцев · пайплайн (синий) vs закрытие (зелёный)', type:'dual', labels:['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'], @@ -316,12 +326,12 @@ const D = { }, production: { kpis: [ - { n:'Выпуск', v:'15 420 шт', d:'+8%', t:'up', p:'к плану' }, - { n:'OEE', v:'78%', d:'+4%', t:'up', p:'загрузка мощностей' }, - { n:'Брак', v:'2.1%', d:'−0.5%', t:'down', p:'дефекты' }, - { n:'Себест.', v:'3 200 ₽', d:'−3%', t:'down', p:'за единицу' }, - { n:'Оборачив.', v:'5.2×', d:'+0.3', t:'up', p:'запасов в год' }, - { n:'ROMI рекламы', v:'310%', d:'+45%', t:'up', p:'маркетинг ROI' }, + { n:'Выпуск', v:'15 420 шт', d:'+8%', t:'up', p:'к плану', spark:[11500,12800,12200,13800,14200,12500,14800,15200,14700,15000,13800,15420] }, + { n:'OEE', v:'78%', d:'+4%', t:'up', p:'загрузка мощностей', spark:[72,73,74,73,74,75,76,76,77,77,78,78] }, + { n:'Брак', v:'2.1%', d:'−0.5%', t:'down', p:'дефекты', spark:[2.8,2.7,2.6,2.5,2.5,2.6,2.4,2.3,2.3,2.2,2.2,2.1] }, + { n:'Себест.', v:'3 200 ₽', d:'−3%', t:'down', p:'за единицу', spark:[3500,3450,3400,3400,3350,3380,3320,3280,3250,3220,3250,3200] }, + { n:'Оборачив.', v:'5.2×', d:'+0.3', t:'up', p:'запасов в год', spark:[4.5,4.6,4.7,4.8,4.9,4.9,5.0,5.1,5.1,5.2,5.1,5.2] }, + { n:'ROMI рекламы', v:'310%', d:'+45%', t:'up', p:'маркетинг ROI', spark:[250,260,265,270,275,280,285,290,295,300,305,310] }, ], mc: { title:'План / факт выпуска', sub:'12 месяцев · план (пунктир) vs фактический выпуск (сплошная)', type:'bar-cmp', labels:['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'], @@ -372,11 +382,40 @@ function seg(key) { ${arrow} ${d.d}
${d.v}
+
${d.p}
`; }).join(''); requestAnimationFrame(() => g.querySelectorAll('.kpi').forEach(c => c.classList.add('visible'))); + // Sparkline charts + const sparkColors = []; + s.kpis.forEach(d => { sparkColors.push(d.t === 'up' ? '#22c55e' : d.t === 'down' ? '#ef4444' : '#3b82f6'); }); + s.kpis.forEach((d, i) => { + if (d.spark) { + const sparkEl = document.getElementById(`spark-${i}`); + if (!sparkEl) return; + const sparkChart = echarts.init(sparkEl, null, { renderer: 'canvas' }); + const isDownTrend = d.t === 'down' && d.p.includes('стоим') || d.p.includes('дефект') || d.p.includes('за единиц') || d.p.includes('стоим'); + sparkChart.setOption({ + grid: { left: 0, right: 0, top: 4, bottom: 4 }, + xAxis: { type: 'category', show: false, data: d.spark }, + yAxis: { type: 'value', show: false }, + series: [{ + type: 'line', data: d.spark, smooth: 0.4, symbol: 'none', + lineStyle: { width: 2, color: sparkColors[i] }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: sparkColors[i] + '30' }, + { offset: 1, color: sparkColors[i] + '05' } + ]) + }, + }], + animationDuration: 800, + }); + } + }); + // Main chart document.getElementById('mc-title').textContent = s.mc.title; document.getElementById('mc-sub').textContent = s.mc.sub;