feat(web): 因子详情页——表达式卡+6格指标+月度/累计IC双系列图+十分组柱状(echarts终端暗色)+排行榜页色值清理走token [nas]
This commit is contained in:
@@ -20,6 +20,9 @@
|
||||
--cyan-dim: #00b8cc; /* 暗青(hover/次要激活) */
|
||||
--cyan-soft: rgba(0, 229, 255, 0.14); /* 青色填充底 */
|
||||
|
||||
/* —— 次强调:紫(alpha158)—— */
|
||||
--purple-dim: #b48cff; /* alpha158 类别色 */
|
||||
|
||||
/* —— 数据高亮:琥珀 —— */
|
||||
--amber: #ffb000; /* 数据高亮/警告 */
|
||||
--warn: #ffb000; /* 语义别名 */
|
||||
|
||||
@@ -269,14 +269,14 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
color: #04141a;
|
||||
color: var(--text);
|
||||
background: var(--brand);
|
||||
border-color: var(--brand);
|
||||
box-shadow: var(--glow-cyan);
|
||||
}
|
||||
|
||||
.btn.primary:hover {
|
||||
background: #33ecff;
|
||||
background: var(--cyan-dim);
|
||||
}
|
||||
|
||||
.stats {
|
||||
@@ -572,7 +572,7 @@ td .fexpr {
|
||||
}
|
||||
|
||||
.cat-alpha158 {
|
||||
color: #b48cff;
|
||||
color: var(--purple-dim);
|
||||
background: rgba(180, 140, 255, 0.1);
|
||||
border-color: rgba(180, 140, 255, 0.3);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,313 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, nextTick, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { getEvalDetail, type EvalDetail } from '@/api/factor'
|
||||
import { useChart } from '@/composables/useChart'
|
||||
import { darkTitle, darkTooltip, darkGrid, darkAxis, BRAND } from '@/utils/echartsDark'
|
||||
import type { EChartsCoreOption } from 'echarts'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const factor = route.params.factor as string
|
||||
const runId = route.query.run as string | undefined
|
||||
const period = ref<'1' | '5' | '10'>('1')
|
||||
const detail = ref<EvalDetail | null>(null)
|
||||
const error = ref('')
|
||||
const expandedExpr = ref(false)
|
||||
|
||||
// Chart refs
|
||||
const monthlyChartEl = ref<HTMLDivElement>()
|
||||
const decileChartEl = ref<HTMLDivElement>()
|
||||
const { setOption: setMonthlyOption } = useChart(monthlyChartEl)
|
||||
const { setOption: setDecileOption } = useChart(decileChartEl)
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
error.value = ''
|
||||
detail.value = await getEvalDetail(String(route.params.factor), route.query.run ? String(route.query.run) : undefined)
|
||||
} catch (e) {
|
||||
error.value = '因子不存在或无评估数据'
|
||||
detail.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
router.push('/factor/leaderboard')
|
||||
router.back()
|
||||
}
|
||||
|
||||
function goToNewFactor() {
|
||||
router.push('/factor/new')
|
||||
}
|
||||
|
||||
const currentMetrics = computed(() => {
|
||||
if (!detail.value?.metrics) return null
|
||||
const metrics = detail.value.metrics as Record<string, unknown>
|
||||
return metrics[period.value] as Record<string, unknown> | null
|
||||
})
|
||||
|
||||
function renderMonthlyChart() {
|
||||
const metrics = currentMetrics.value
|
||||
if (!metrics || !monthlyChartEl.value) return
|
||||
|
||||
const monthlyIc = metrics.monthly_ic as Array<{ month: string; ic: number }> | null
|
||||
if (!monthlyIc || !monthlyIc.length) return
|
||||
|
||||
// Compute cumulative IC
|
||||
let cumulative = 0
|
||||
const cumulativeData = monthlyIc.map((p) => {
|
||||
cumulative += p.ic
|
||||
return cumulative
|
||||
})
|
||||
|
||||
const months = monthlyIc.map((p) => p.month)
|
||||
const icValues = monthlyIc.map((p) => p.ic)
|
||||
|
||||
const option: EChartsCoreOption = {
|
||||
title: darkTitle('月度 IC 与累计 IC'),
|
||||
tooltip: darkTooltip(),
|
||||
grid: darkGrid(),
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: months,
|
||||
...darkAxis(),
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: 'IC',
|
||||
...darkAxis(),
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
name: '月度 IC',
|
||||
data: icValues,
|
||||
itemStyle: {
|
||||
color: (params) => (params.data as number) >= 0 ? 'var(--up)' : 'var(--down)',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
name: '累计 IC',
|
||||
data: cumulativeData,
|
||||
showSymbol: false,
|
||||
lineStyle: { color: BRAND, width: 1.8 },
|
||||
itemStyle: { color: BRAND },
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
setMonthlyOption(option)
|
||||
}
|
||||
|
||||
function renderDecileChart() {
|
||||
const metrics = currentMetrics.value
|
||||
if (!metrics || !decileChartEl.value) return
|
||||
|
||||
const deciles = metrics.deciles as (number | null)[] | null
|
||||
if (!deciles || !deciles.length) return
|
||||
|
||||
const labels = ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9', 'D10']
|
||||
const colors = deciles.map((val) => {
|
||||
if (val === null) return 'var(--text-3)'
|
||||
// Create gradient from --down (negative) to --up (positive)
|
||||
// Normalize between -0.5% and 0.5% for color transition
|
||||
const normalized = Math.max(-1, Math.min(1, val / 0.5))
|
||||
if (normalized < 0) return 'var(--down)'
|
||||
return 'var(--up)'
|
||||
})
|
||||
|
||||
const option: EChartsCoreOption = {
|
||||
title: darkTitle('十分组年化收益'),
|
||||
tooltip: darkTooltip(),
|
||||
grid: darkGrid(),
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: labels,
|
||||
...darkAxis(),
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '年化收益',
|
||||
...darkAxis(),
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
name: '年化收益',
|
||||
data: deciles.map((val, i) => ({
|
||||
value: val,
|
||||
itemStyle: { color: colors[i] },
|
||||
})),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
setDecileOption(option)
|
||||
}
|
||||
|
||||
function renderCharts() {
|
||||
renderMonthlyChart()
|
||||
renderDecileChart()
|
||||
}
|
||||
|
||||
function toggleExpr() {
|
||||
expandedExpr.value = !expandedExpr.value
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
|
||||
watch(
|
||||
() => [detail.value, period.value],
|
||||
async () => {
|
||||
if (!detail.value) return
|
||||
await nextTick()
|
||||
renderCharts()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// Computed helpers
|
||||
const conclusionLabel = computed(() => {
|
||||
const map: Record<string, string> = {
|
||||
effective: '有效',
|
||||
watch: '观察',
|
||||
eliminated: '淘汰',
|
||||
}
|
||||
if (!currentMetrics.value) return ''
|
||||
const conclusion = (currentMetrics.value as Record<string, unknown>).conclusion as string | undefined
|
||||
return conclusion ? map[conclusion] || conclusion : ''
|
||||
})
|
||||
|
||||
const conclusionClass = computed(() => {
|
||||
if (!currentMetrics.value) return ''
|
||||
const conclusion = (currentMetrics.value as Record<string, unknown>).conclusion as string | undefined
|
||||
return conclusion ? `st-${conclusion}` : ''
|
||||
})
|
||||
|
||||
const conclusionColor = computed(() => {
|
||||
const map: Record<string, string> = {
|
||||
effective: 'var(--lamp-ok)',
|
||||
watch: 'var(--lamp-warn)',
|
||||
eliminated: 'var(--lamp-idle)',
|
||||
}
|
||||
if (!currentMetrics.value) return ''
|
||||
const conclusion = (currentMetrics.value as Record<string, unknown>).conclusion as string | undefined
|
||||
return conclusion ? map[conclusion] || '' : ''
|
||||
})
|
||||
|
||||
const categoryClass = computed(() => {
|
||||
if (!detail.value) return ''
|
||||
const map: Record<string, string> = {
|
||||
alpha101: 'cat-alpha101',
|
||||
alpha158: 'cat-alpha158',
|
||||
builtin: 'cat-builtin',
|
||||
}
|
||||
return map[detail.value.category] || 'cat-builtin'
|
||||
})
|
||||
|
||||
// Formatting helpers
|
||||
function num(val: number | null | undefined, digits = 3): string {
|
||||
if (val == null) return '—'
|
||||
return val.toFixed(digits)
|
||||
}
|
||||
|
||||
function pct(val: number | null | undefined): string {
|
||||
if (val == null) return '—'
|
||||
const sign = val >= 0 ? '+' : ''
|
||||
return `${sign}${(val * 100).toFixed(1)}%`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="detail-page">
|
||||
<a class="backlink" href="#" @click.prevent="goBack">← 返回排行榜</a>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1 class="page-title">因子详情 · {{ factor }}</h1>
|
||||
<p class="page-sub">批次 {{ runId || '最新' }}</p>
|
||||
|
||||
<div v-if="error" class="error-state">
|
||||
<p>{{ error }}</p>
|
||||
<a class="backlink" href="#" @click.prevent="goBack">返回排行榜</a>
|
||||
</div>
|
||||
|
||||
<template v-else-if="detail">
|
||||
<!-- Page Header -->
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1 class="page-title">因子详情 · {{ detail.factor }}</h1>
|
||||
<p class="page-sub">批次 {{ route.query.run || '最新' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="placeholder">
|
||||
<p>因子详情页待实现(Task 11)</p>
|
||||
</div>
|
||||
|
||||
<!-- Factor Card -->
|
||||
<div class="fcard">
|
||||
<div class="hd">
|
||||
<span class="name">{{ detail.factor }}</span>
|
||||
<span :class="['cat-chip', categoryClass]">{{ detail.category }}</span>
|
||||
<span v-if="conclusionLabel" :class="['st', conclusionClass]">
|
||||
<span class="lamp" :style="{ background: conclusionColor, boxShadow: conclusionColor !== 'var(--lamp-idle)' ? `0 0 6px ${conclusionColor}` : 'none' }"></span>
|
||||
{{ conclusionLabel }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="expr" :class="{ expanded: expandedExpr }">
|
||||
<span class="expr-text">{{ detail.expression }}</span>
|
||||
<button v-if="detail.expression && detail.expression.length > 100" class="expr-toggle" @click="toggleExpr">
|
||||
{{ expandedExpr ? '收起' : '展开全文' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Metrics Strip -->
|
||||
<div v-if="currentMetrics" class="metric-strip">
|
||||
<div class="metric">
|
||||
<div class="k">IC 均值 ({{ period }}D)</div>
|
||||
<div class="v" :class="(currentMetrics.ic_mean as number) >= 0 ? 'pos' : 'neg'">{{ num(currentMetrics.ic_mean as number) }}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="k">ICIR</div>
|
||||
<div class="v" :class="(currentMetrics.icir as number) >= 0 ? 'pos' : 'neg'">{{ num(currentMetrics.icir as number, 2) }}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="k">t 值</div>
|
||||
<div class="v" :class="(currentMetrics.t_stat as number) >= 0 ? 'pos' : 'neg'">{{ num(currentMetrics.t_stat as number, 1) }}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="k">IC 胜率</div>
|
||||
<div class="v" :class="(currentMetrics.win_rate as number) >= 0 ? 'pos' : 'neg'">{{ pct(currentMetrics.win_rate as number) }}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="k">多空年化</div>
|
||||
<div class="v" :class="(currentMetrics.ls_annual as number) >= 0 ? 'pos' : 'neg'">{{ pct(currentMetrics.ls_annual as number) }}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="k">日均换手</div>
|
||||
<div class="v" style="color: var(--text-2)">{{ pct(currentMetrics.turnover as number) }}</div>
|
||||
<div class="d">参评 {{ currentMetrics.count }} 日</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Period Switcher -->
|
||||
<div class="period-switch">
|
||||
<button :class="{ on: period === '1' }" @click="period = '1'">1D</button>
|
||||
<button :class="{ on: period === '5' }" @click="period = '5'">5D</button>
|
||||
<button :class="{ on: period === '10' }" @click="period = '10'">10D</button>
|
||||
</div>
|
||||
|
||||
<!-- Charts -->
|
||||
<div class="charts">
|
||||
<div class="chart-card">
|
||||
<h3>月度 IC 与累计 IC</h3>
|
||||
<div class="cs">柱=月度 RankIC · 线=累计 IC</div>
|
||||
<div ref="monthlyChartEl" class="chart-box"></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h3>十分组年化收益 ({{ period }}D)</h3>
|
||||
<div class="cs">Q1=因子值最低组 · 单调性=区分度</div>
|
||||
<div ref="decileChartEl" class="chart-box"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="actions">
|
||||
<button class="btn primary" @click="goToNewFactor">查看完整 tears 报告</button>
|
||||
<button class="btn">加入对比</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -47,6 +332,15 @@ function goBack() {
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.error-state {
|
||||
padding: 60px;
|
||||
text-align: center;
|
||||
color: var(--text-3);
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-lg);
|
||||
}
|
||||
|
||||
.page-head {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
@@ -71,12 +365,282 @@ function goBack() {
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
padding: 60px;
|
||||
text-align: center;
|
||||
color: var(--text-3);
|
||||
.fcard {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-lg);
|
||||
padding: 16px 18px;
|
||||
margin-bottom: var(--sp-3);
|
||||
}
|
||||
|
||||
.fcard .hd {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.fcard .name {
|
||||
font-family: var(--mono);
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.expr {
|
||||
font-family: var(--mono);
|
||||
font-size: 11.5px;
|
||||
line-height: 1.7;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border-2);
|
||||
border-left: 2px solid var(--cyan-dim);
|
||||
border-radius: var(--r-sm);
|
||||
padding: 11px 14px;
|
||||
color: var(--text-2);
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.expr:not(.expanded) {
|
||||
max-height: 80px;
|
||||
overflow: hidden;
|
||||
mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
|
||||
}
|
||||
|
||||
.expr.expanded {
|
||||
mask-image: none;
|
||||
-webkit-mask-image: none;
|
||||
}
|
||||
|
||||
.expr-toggle {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 12px;
|
||||
font-size: 10px;
|
||||
color: var(--brand);
|
||||
background: rgba(0, 229, 255, 0.1);
|
||||
border: 1px solid rgba(0, 229, 255, 0.3);
|
||||
border-radius: var(--r-sm);
|
||||
padding: 2px 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s var(--ease);
|
||||
}
|
||||
|
||||
.expr-toggle:hover {
|
||||
background: rgba(0, 229, 255, 0.2);
|
||||
}
|
||||
|
||||
.metric-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 1px;
|
||||
background: var(--border);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-lg);
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--sp-3);
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.metric-strip {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.metric {
|
||||
background: var(--bg-card);
|
||||
padding: 11px 13px;
|
||||
}
|
||||
|
||||
.metric .k {
|
||||
color: var(--text-3);
|
||||
font-family: var(--mono);
|
||||
font-size: 9.5px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.metric .v {
|
||||
font-family: var(--mono);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.metric .d {
|
||||
font-size: 10px;
|
||||
margin-top: 2px;
|
||||
color: var(--text-3);
|
||||
font-family: var(--mono);
|
||||
}
|
||||
|
||||
.period-switch {
|
||||
display: flex;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-sm);
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--sp-3);
|
||||
}
|
||||
|
||||
.period-switch button {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
padding: 4px 12px;
|
||||
color: var(--text-2);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.period-switch button.on {
|
||||
background: rgba(0, 229, 255, 0.12);
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.charts {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 2fr;
|
||||
gap: var(--sp-3);
|
||||
margin-bottom: var(--sp-3);
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.charts {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r-lg);
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.chart-card h3 {
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.chart-card .cs {
|
||||
color: var(--text-3);
|
||||
font-family: var(--mono);
|
||||
font-size: 10.5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.chart-box {
|
||||
width: 100%;
|
||||
height: 215px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
border-radius: var(--r-md);
|
||||
padding: 7px 16px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-2);
|
||||
background: var(--bg-card);
|
||||
transition: all 0.15s var(--ease);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
color: var(--text);
|
||||
border-color: var(--text-3);
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
color: var(--text);
|
||||
background: var(--brand);
|
||||
border-color: var(--brand);
|
||||
box-shadow: var(--glow-cyan);
|
||||
}
|
||||
|
||||
.btn.primary:hover {
|
||||
background: var(--cyan-dim);
|
||||
}
|
||||
|
||||
.cat-chip {
|
||||
display: inline-block;
|
||||
padding: 1px 8px;
|
||||
border-radius: var(--r-sm);
|
||||
font-family: var(--mono);
|
||||
font-size: 10.5px;
|
||||
font-weight: 600;
|
||||
line-height: 17px;
|
||||
letter-spacing: 0.3px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.cat-alpha101 {
|
||||
color: var(--brand);
|
||||
background: rgba(0, 229, 255, 0.1);
|
||||
border-color: rgba(0, 229, 255, 0.3);
|
||||
}
|
||||
|
||||
.cat-alpha158 {
|
||||
color: var(--purple-dim);
|
||||
background: rgba(180, 140, 255, 0.1);
|
||||
border-color: rgba(180, 140, 255, 0.3);
|
||||
}
|
||||
|
||||
.cat-builtin {
|
||||
color: var(--amber);
|
||||
background: rgba(255, 176, 0, 0.1);
|
||||
border-color: rgba(255, 176, 0, 0.3);
|
||||
}
|
||||
|
||||
.st {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-family: var(--mono);
|
||||
font-size: 10.5px;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.st-ok .lamp {
|
||||
background: var(--lamp-ok);
|
||||
box-shadow: 0 0 6px rgba(46, 230, 138, 0.7);
|
||||
}
|
||||
|
||||
.st-watch .lamp {
|
||||
background: var(--lamp-warn);
|
||||
box-shadow: 0 0 6px rgba(255, 176, 0, 0.7);
|
||||
}
|
||||
|
||||
.st-eliminated .lamp {
|
||||
background: var(--lamp-idle);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.lamp {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pos {
|
||||
color: var(--up);
|
||||
}
|
||||
|
||||
.neg {
|
||||
color: var(--down);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user