发布
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 28s
Docker Build & Deploy / Deploy to Production (push) Successful in 11s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 2s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 28s
Docker Build & Deploy / Deploy to Production (push) Successful in 11s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 2s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
This commit is contained in:
514
Web/src/components/Budget/BudgetChartAnalysis.vue
Normal file
514
Web/src/components/Budget/BudgetChartAnalysis.vue
Normal file
@@ -0,0 +1,514 @@
|
||||
<template>
|
||||
<div class="chart-analysis-container">
|
||||
<!-- 仪表盘:整体健康度 -->
|
||||
<div class="gauges-row">
|
||||
<!-- 月度仪表盘 -->
|
||||
<div class="chart-card gauge-card">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">
|
||||
月度健康度
|
||||
</div>
|
||||
<div class="chart-subtitle">
|
||||
本月预算
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref="monthGaugeRef"
|
||||
class="chart-body gauge-chart"
|
||||
/>
|
||||
<div class="gauge-footer compact">
|
||||
<div class="gauge-item">
|
||||
<span class="label">已用</span>
|
||||
<span class="value expense">¥{{ formatMoney(overallStats.month.current) }}</span>
|
||||
</div>
|
||||
<div class="gauge-item">
|
||||
<span class="label">预算</span>
|
||||
<span class="value">¥{{ formatMoney(overallStats.month.limit) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 年度仪表盘 -->
|
||||
<div class="chart-card gauge-card">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">
|
||||
年度健康度
|
||||
</div>
|
||||
<div class="chart-subtitle">
|
||||
本年预算
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref="yearGaugeRef"
|
||||
class="chart-body gauge-chart"
|
||||
/>
|
||||
<div class="gauge-footer compact">
|
||||
<div class="gauge-item">
|
||||
<span class="label">已用</span>
|
||||
<span class="value expense">¥{{ formatMoney(overallStats.year.current) }}</span>
|
||||
</div>
|
||||
<div class="gauge-item">
|
||||
<span class="label">预算</span>
|
||||
<span class="value">¥{{ formatMoney(overallStats.year.limit) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 月度预算进度 -->
|
||||
<div
|
||||
v-if="budgets.length > 0"
|
||||
class="chart-card"
|
||||
>
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">
|
||||
预算进度(月度)
|
||||
</div>
|
||||
<div class="chart-subtitle">
|
||||
本月各预算执行情况
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref="monthBarChartRef"
|
||||
class="chart-body bar-chart"
|
||||
:style="{ height: calculateChartHeight() + 'px' }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 年度预算进度 -->
|
||||
<div
|
||||
v-if="budgets.length > 0"
|
||||
class="chart-card"
|
||||
style="margin-top: 12px"
|
||||
>
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">
|
||||
预算进度(年度)
|
||||
</div>
|
||||
<div class="chart-subtitle">
|
||||
本年各预算执行情况
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref="yearBarChartRef"
|
||||
class="chart-body bar-chart"
|
||||
:style="{ height: calculateChartHeight() + 'px' }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 空状态占位 -->
|
||||
<div
|
||||
v-else
|
||||
class="chart-card empty-card"
|
||||
>
|
||||
<van-empty
|
||||
description="暂无预算数据"
|
||||
image="search"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, nextTick, onUnmounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { BudgetCategory } from '@/constants/enums'
|
||||
import { getCssVar } from '@/utils/theme'
|
||||
|
||||
const props = defineProps({
|
||||
overallStats: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
budgets: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
activeTab: {
|
||||
type: [Number, String],
|
||||
default: BudgetCategory.Expense
|
||||
}
|
||||
})
|
||||
|
||||
const monthGaugeRef = ref(null)
|
||||
const yearGaugeRef = ref(null)
|
||||
const monthBarChartRef = ref(null)
|
||||
const yearBarChartRef = ref(null)
|
||||
let monthGaugeChart = null
|
||||
let yearGaugeChart = null
|
||||
let monthBarChart = null
|
||||
let yearBarChart = null
|
||||
|
||||
const formatMoney = (val) => {
|
||||
if (val >= 10000) {
|
||||
return (val / 10000).toFixed(1) + 'w'
|
||||
}
|
||||
return parseFloat(val || 0).toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
})
|
||||
}
|
||||
|
||||
const initGaugeChart = (chartInstance, dom, data, isExpense) => {
|
||||
if (!dom) { return null }
|
||||
|
||||
const chart = echarts.init(dom)
|
||||
updateSingleGauge(chart, data, isExpense)
|
||||
return chart
|
||||
}
|
||||
|
||||
const updateSingleGauge = (chart, data, isExpense) => {
|
||||
if (!chart) { return }
|
||||
|
||||
const rate = parseFloat(data.rate || 0)
|
||||
// 颜色逻辑
|
||||
let color = getCssVar('--chart-success') // 绿色
|
||||
if (isExpense) {
|
||||
if (rate >= 100) { color = getCssVar('--chart-danger') } // 红色
|
||||
else if (rate >= 80) { color = getCssVar('--chart-warning') } // 橙色
|
||||
} else {
|
||||
if (rate >= 100) { color = getCssVar('--chart-success') } // 绿色
|
||||
else if (rate >= 80) { color = getCssVar('--chart-warning') } // 橙色
|
||||
else { color = getCssVar('--chart-danger') } // 红色
|
||||
}
|
||||
|
||||
const option = {
|
||||
series: [
|
||||
{
|
||||
type: 'gauge',
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
min: 0,
|
||||
max: Math.max(100, rate * 1.2),
|
||||
splitNumber: 5,
|
||||
radius: '110%', // 放大一点以适应小卡片
|
||||
center: ['50%', '75%'],
|
||||
itemStyle: {
|
||||
color: color,
|
||||
shadowColor: getCssVar('--chart-shadow'),
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 2,
|
||||
shadowOffsetY: 2
|
||||
},
|
||||
progress: {
|
||||
show: true,
|
||||
roundCap: true,
|
||||
width: 12 // 变细一点
|
||||
},
|
||||
pointer: { show: false },
|
||||
axisLine: {
|
||||
roundCap: true,
|
||||
lineStyle: {
|
||||
width: 12,
|
||||
color: [[1, getCssVar('--chart-axis')]]
|
||||
}
|
||||
},
|
||||
axisTick: { show: false },
|
||||
splitLine: { show: false },
|
||||
axisLabel: { show: false },
|
||||
detail: {
|
||||
valueAnimation: true,
|
||||
fontSize: 24, // 字体调小
|
||||
offsetCenter: [0, -5],
|
||||
color: 'var(--van-text-color)',
|
||||
formatter: '{value}%',
|
||||
fontWeight: 'bold',
|
||||
fontFamily: 'DIN Alternate, system-ui'
|
||||
},
|
||||
data: [{ value: rate }]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
}
|
||||
|
||||
const updateCharts = () => {
|
||||
const isExpense = props.activeTab === BudgetCategory.Expense
|
||||
updateSingleGauge(monthGaugeChart, props.overallStats.month, isExpense)
|
||||
updateSingleGauge(yearGaugeChart, props.overallStats.year, isExpense)
|
||||
|
||||
// 确保 barChart 已初始化,如果还未初始化则先初始化
|
||||
if (props.budgets.length > 0) {
|
||||
if (!monthBarChart && monthBarChartRef.value) {
|
||||
monthBarChart = echarts.init(monthBarChartRef.value)
|
||||
}
|
||||
if (!yearBarChart && yearBarChartRef.value) {
|
||||
yearBarChart = echarts.init(yearBarChartRef.value)
|
||||
}
|
||||
updateBarChart()
|
||||
}
|
||||
}
|
||||
|
||||
const updateBarChart = () => {
|
||||
if (!monthBarChart || !yearBarChart) { return }
|
||||
|
||||
const sortedBudgets = [...props.budgets].sort((a, b) => b.current - a.current).slice(0, 10)
|
||||
const categories = sortedBudgets.map(b => b.name)
|
||||
// 月度数据
|
||||
const monthLimits = sortedBudgets.map(b => b.monthLimit || b.limit)
|
||||
const monthCurrents = sortedBudgets.map(b => b.monthCurrent || b.current)
|
||||
// 年度数据
|
||||
const yearLimits = sortedBudgets.map(b => b.yearLimit || b.limit)
|
||||
const yearCurrents = sortedBudgets.map(b => b.yearCurrent || b.current)
|
||||
|
||||
const getColors = (data, limits) => {
|
||||
return data.map((current, idx) => {
|
||||
const limit = limits[idx]
|
||||
const rate = limit > 0 ? (current / limit) : 0
|
||||
if (props.activeTab === BudgetCategory.Expense) {
|
||||
if (rate >= 1) { return getCssVar('--chart-danger') }
|
||||
if (rate >= 0.8) { return getCssVar('--chart-warning') }
|
||||
return getCssVar('--chart-primary')
|
||||
} else {
|
||||
if (rate >= 1) { return getCssVar('--chart-success') }
|
||||
return getCssVar('--chart-primary')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const monthColors = getColors(monthCurrents, monthLimits)
|
||||
const yearColors = getColors(yearCurrents, yearLimits)
|
||||
|
||||
// 获取当前主题下的颜色值
|
||||
const textColor = getCssVar('--van-text-color')
|
||||
const textColor2 = getCssVar('--van-text-color-2')
|
||||
const bgColor3 = getCssVar('--van-background-3')
|
||||
const splitLineColor = getCssVar('--chart-split')
|
||||
const axisLabelColor = getCssVar('--chart-text-muted')
|
||||
|
||||
const createOption = (limits, currents, colors) => {
|
||||
return {
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '8%',
|
||||
bottom: '3%',
|
||||
top: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: splitLineColor
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: axisLabelColor,
|
||||
formatter: (value) => {
|
||||
if (value >= 10000) { return (value / 10000).toFixed(0) + 'w' }
|
||||
return value
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: categories,
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: textColor,
|
||||
width: 50,
|
||||
overflow: 'truncate',
|
||||
interval: 0
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '预算',
|
||||
type: 'bar',
|
||||
data: limits,
|
||||
barWidth: 10,
|
||||
itemStyle: {
|
||||
color: bgColor3,
|
||||
borderRadius: 5
|
||||
},
|
||||
z: 1,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
formatter: (params) => {
|
||||
const val = params.value
|
||||
return val >= 10000 ? (val / 10000).toFixed(1) + 'w' : val
|
||||
},
|
||||
color: textColor2,
|
||||
fontSize: 10
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '实际',
|
||||
type: 'bar',
|
||||
data: currents,
|
||||
barGap: '-100%',
|
||||
barWidth: 10,
|
||||
itemStyle: {
|
||||
color: (params) => colors[params.dataIndex],
|
||||
borderRadius: 5
|
||||
},
|
||||
z: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
monthBarChart.setOption(createOption(monthLimits, monthCurrents, monthColors))
|
||||
yearBarChart.setOption(createOption(yearLimits, yearCurrents, yearColors))
|
||||
}
|
||||
|
||||
const calculateChartHeight = () => {
|
||||
// 根据数据数量动态计算图表高度
|
||||
// 每个类别占用 60px,最少显示 200px,最多 400px
|
||||
const dataCount = Math.min(props.budgets.length, 10) // 最多显示10条
|
||||
const minHeight = 150
|
||||
const maxHeight = 400
|
||||
const heightPerItem = 60
|
||||
const calculatedHeight = Math.max(minHeight, dataCount * heightPerItem)
|
||||
return Math.min(calculatedHeight, maxHeight)
|
||||
}
|
||||
|
||||
watch(() => props.overallStats, () => nextTick(updateCharts), { deep: true })
|
||||
watch(() => props.budgets, () => {
|
||||
nextTick(() => {
|
||||
if (props.budgets.length > 0 && (!monthBarChart || !yearBarChart) && monthBarChartRef.value && yearBarChartRef.value) {
|
||||
// budgets 从空到有值,需要初始化图表
|
||||
monthBarChart = echarts.init(monthBarChartRef.value)
|
||||
yearBarChart = echarts.init(yearBarChartRef.value)
|
||||
updateBarChart()
|
||||
} else {
|
||||
updateCharts()
|
||||
}
|
||||
})
|
||||
}, { deep: true })
|
||||
watch(() => props.activeTab, () => nextTick(updateCharts))
|
||||
|
||||
const handleResize = () => {
|
||||
monthGaugeChart?.resize()
|
||||
yearGaugeChart?.resize()
|
||||
monthBarChart?.resize()
|
||||
yearBarChart?.resize()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const isExpense = props.activeTab === BudgetCategory.Expense
|
||||
monthGaugeChart = initGaugeChart(monthGaugeChart, monthGaugeRef.value, props.overallStats.month, isExpense)
|
||||
yearGaugeChart = initGaugeChart(yearGaugeChart, yearGaugeRef.value, props.overallStats.year, isExpense)
|
||||
// 只在有数据时初始化柱状图
|
||||
if (props.budgets.length > 0) {
|
||||
if (monthBarChartRef.value) {
|
||||
monthBarChart = echarts.init(monthBarChartRef.value)
|
||||
}
|
||||
if (yearBarChartRef.value) {
|
||||
yearBarChart = echarts.init(yearBarChartRef.value)
|
||||
}
|
||||
updateBarChart()
|
||||
}
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
monthGaugeChart?.dispose()
|
||||
yearGaugeChart?.dispose()
|
||||
monthBarChart?.dispose()
|
||||
yearBarChart?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chart-analysis-container {
|
||||
padding: 12px;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.gauges-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
background: var(--van-background-2);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.gauge-card {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
/* 防止 flex 子项溢出 */
|
||||
padding: 12px;
|
||||
/* 减小内边距 */
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 14px;
|
||||
/* 调小标题 */
|
||||
font-weight: 600;
|
||||
color: var(--van-text-color);
|
||||
margin-bottom: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chart-subtitle {
|
||||
font-size: 10px;
|
||||
color: var(--van-text-color-2);
|
||||
}
|
||||
|
||||
.chart-body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gauge-chart {
|
||||
height: 120px;
|
||||
/* 调小高度 */
|
||||
}
|
||||
|
||||
.bar-chart {
|
||||
min-height: 200px;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.gauge-footer {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
/* 分散对齐 */
|
||||
align-items: center;
|
||||
margin-top: -20px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.gauge-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.gauge-item .label {
|
||||
font-size: 10px;
|
||||
color: var(--van-text-color-2);
|
||||
transform: scale(0.9);
|
||||
/* 视觉上更小 */
|
||||
}
|
||||
|
||||
.gauge-item .value {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-family: DIN Alternate, system-ui;
|
||||
color: var(--van-text-color);
|
||||
}
|
||||
|
||||
.gauge-item .value.expense {
|
||||
color: var(--van-primary-color);
|
||||
}
|
||||
</style>
|
||||
@@ -3,13 +3,22 @@
|
||||
<div class="grid-row">
|
||||
<!-- Weekday Labels (Fixed Left) -->
|
||||
<div class="weekday-col-fixed">
|
||||
<div class="weekday-label">二</div>
|
||||
<div class="weekday-label">四</div>
|
||||
<div class="weekday-label">六</div>
|
||||
<div class="weekday-label">
|
||||
二
|
||||
</div>
|
||||
<div class="weekday-label">
|
||||
四
|
||||
</div>
|
||||
<div class="weekday-label">
|
||||
六
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable Heatmap Area -->
|
||||
<div ref="scrollContainer" class="heatmap-scroll-container">
|
||||
<div
|
||||
ref="scrollContainer"
|
||||
class="heatmap-scroll-container"
|
||||
>
|
||||
<div class="heatmap-content">
|
||||
<!-- Month Labels -->
|
||||
<div class="month-row">
|
||||
@@ -25,7 +34,11 @@
|
||||
|
||||
<!-- Heatmap Grid -->
|
||||
<div class="heatmap-grid">
|
||||
<div v-for="(week, wIndex) in weeks" :key="wIndex" class="heatmap-week">
|
||||
<div
|
||||
v-for="(week, wIndex) in weeks"
|
||||
:key="wIndex"
|
||||
class="heatmap-week"
|
||||
>
|
||||
<div
|
||||
v-for="(day, dIndex) in week"
|
||||
:key="dIndex"
|
||||
@@ -42,7 +55,12 @@
|
||||
</div>
|
||||
|
||||
<div class="heatmap-footer">
|
||||
<div v-if="totalCount > 0" class="summary-text">过去一年共 {{ totalCount }} 笔交易</div>
|
||||
<div
|
||||
v-if="totalCount > 0"
|
||||
class="summary-text"
|
||||
>
|
||||
过去一年共 {{ totalCount }} 笔交易
|
||||
</div>
|
||||
<div class="legend">
|
||||
<span>少</span>
|
||||
<div class="legend-item level-0" />
|
||||
@@ -110,7 +128,6 @@ const fetchData = async () => {
|
||||
}
|
||||
|
||||
const avg = last15DaysSum / 15
|
||||
console.log('avg', avg)
|
||||
// Step size calculation: ensure at least 1, roughly avg/2 to create spread
|
||||
// Level 1: 1 ~ step
|
||||
// Level 2: step+1 ~ step*2
|
||||
@@ -127,7 +144,6 @@ const fetchData = async () => {
|
||||
}
|
||||
|
||||
const generateHeatmapData = (startDate, endDate) => {
|
||||
const data = []
|
||||
const current = new Date(startDate)
|
||||
|
||||
const allDays = []
|
||||
@@ -143,7 +159,6 @@ const generateHeatmapData = (startDate, endDate) => {
|
||||
// Sunday (0) -> 6 days back
|
||||
// Tuesday (2) -> 1 day back
|
||||
|
||||
const daysToSubtract = (startDay + 6) % 7
|
||||
// We don't necessarily need to subtract from startDate for data fetching,
|
||||
// but for grid alignment we want the first column to start on Monday.
|
||||
|
||||
@@ -393,62 +408,19 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.level-0 {
|
||||
background-color: var(--van-gray-2);
|
||||
background-color: var(--heatmap-level-0);
|
||||
}
|
||||
/* Default (Light Mode) - Light to Deep Green */
|
||||
.level-1 {
|
||||
background-color: #9be9a8;
|
||||
background-color: var(--heatmap-level-1);
|
||||
}
|
||||
.level-2 {
|
||||
background-color: #40c463;
|
||||
background-color: var(--heatmap-level-2);
|
||||
}
|
||||
.level-3 {
|
||||
background-color: #30a14e;
|
||||
background-color: var(--heatmap-level-3);
|
||||
}
|
||||
.level-4 {
|
||||
background-color: #216e39;
|
||||
}
|
||||
|
||||
/* Dark Mode - Dark to Light/Bright Green (GitHub Dark Mode Style) */
|
||||
/* The user requested "From Light to Deep" (浅至深) which usually means standard heatmap logic (darker = more).
|
||||
HOWEVER, in dark interfaces, usually "Brighter = More".
|
||||
If the user explicitly says "colors are wrong, should be from light to deep", and they are referring to the visual gradient:
|
||||
|
||||
If they mean visual brightness:
|
||||
Light (Dim) -> Deep (Bright)
|
||||
|
||||
Let's stick to the GitHub Dark Mode palette which is scientifically designed for dark backgrounds:
|
||||
L1 (Less): Dark Green (#0e4429)
|
||||
L4 (More): Neon Green (#39d353)
|
||||
This is visually "Dim to Bright".
|
||||
|
||||
If the user meant "Light color to Dark color" literally (like white -> black green), that would look bad on dark mode.
|
||||
"浅至深" in color context usually implies saturation/intensity.
|
||||
|
||||
Let's restore the GitHub Dark Mode colors for dark mode, as my previous change might have inverted them incorrectly or caused confusion.
|
||||
|
||||
GitHub Dark Mode:
|
||||
L0: #161b22
|
||||
L1: #0e4429
|
||||
L2: #006d32
|
||||
L3: #26a641
|
||||
L4: #39d353
|
||||
|
||||
This goes from Dark Green -> Bright Green.
|
||||
*/
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.level-1 {
|
||||
background-color: #9be9a8;
|
||||
}
|
||||
.level-2 {
|
||||
background-color: #40c463;
|
||||
}
|
||||
.level-3 {
|
||||
background-color: #30a14e;
|
||||
}
|
||||
.level-4 {
|
||||
background-color: #216e39;
|
||||
}
|
||||
background-color: var(--heatmap-level-4);
|
||||
}
|
||||
|
||||
.heatmap-footer {
|
||||
|
||||
Reference in New Issue
Block a user